From fedd74669aa8409d68ad0d2afb9ce133b8370e3a Mon Sep 17 00:00:00 2001 From: Alexander Markov Date: Sat, 20 Jul 2019 00:05:23 +0000 Subject: [PATCH] [vm/bytecode] Support multiple entry points when compiling from bytecode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DeltaBlueClosures +26.54% Closes https://github.com/dart-lang/sdk/issues/36889 Issue: https://github.com/dart-lang/sdk/issues/36429 Change-Id: I8920c7985366a2c955a06013aa9f58763fcbced9 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/109580 Reviewed-by: Régis Crelier Reviewed-by: Ryan Macnak Commit-Queue: Alexander Markov --- pkg/vm/lib/bytecode/assembler.dart | 4 + pkg/vm/lib/bytecode/dbc.dart | 8 +- pkg/vm/lib/bytecode/gen_bytecode.dart | 109 +++++++- pkg/vm/testcases/bytecode/async.dart.expect | 2 + .../testcases/bytecode/closures.dart.expect | 14 +- pkg/vm/testcases/bytecode/type_ops.dart | 13 + .../testcases/bytecode/type_ops.dart.expect | 256 ++++++++++++++++++ runtime/tests/vm/vm.status | 8 +- .../frontend/base_flow_graph_builder.cc | 81 ++++++ .../frontend/base_flow_graph_builder.h | 45 +++ .../frontend/bytecode_flow_graph_builder.cc | 179 +++++++++++- .../frontend/bytecode_flow_graph_builder.h | 8 +- .../frontend/kernel_binary_flowgraph.cc | 2 +- runtime/vm/compiler/frontend/kernel_to_il.cc | 85 +----- runtime/vm/compiler/frontend/kernel_to_il.h | 34 --- runtime/vm/constants_kbc.h | 18 +- runtime/vm/interpreter.cc | 7 + tools/bots/test_matrix.json | 3 + 18 files changed, 721 insertions(+), 155 deletions(-) diff --git a/pkg/vm/lib/bytecode/assembler.dart b/pkg/vm/lib/bytecode/assembler.dart index af0be29989c..2349b5ec8d5 100644 --- a/pkg/vm/lib/bytecode/assembler.dart +++ b/pkg/vm/lib/bytecode/assembler.dart @@ -325,6 +325,10 @@ class BytecodeAssembler { _emitJumpInstruction(Opcode.kJumpIfNotNull, label); } + void emitJumpIfUnchecked(Label label) { + _emitJumpInstruction(Opcode.kJumpIfUnchecked, label); + } + void emitReturnTOS() { emitSourcePosition(); _emitInstruction0(Opcode.kReturnTOS); diff --git a/pkg/vm/lib/bytecode/dbc.dart b/pkg/vm/lib/bytecode/dbc.dart index ad5e2b77389..5534e82b256 100644 --- a/pkg/vm/lib/bytecode/dbc.dart +++ b/pkg/vm/lib/bytecode/dbc.dart @@ -10,7 +10,7 @@ library vm.bytecode.dbc; /// Before bumping current bytecode version format, make sure that /// all users have switched to a VM which is able to consume new /// version of bytecode. -const int currentBytecodeFormatVersion = 15; +const int currentBytecodeFormatVersion = 16; enum Opcode { kUnusedOpcode000, @@ -116,8 +116,8 @@ enum Opcode { kCheckFunctionTypeArgs_Wide, kCheckStack, kDebugCheck, - kUnused02, // Reserved for CheckParameterTypes - kUnused03, // Reserved for CheckParameterTypes_Wide + kJumpIfUnchecked, + kJumpIfUnchecked_Wide, // Object allocation. kAllocate, @@ -434,6 +434,8 @@ const Map BytecodeFormats = const { Encoding.kT, const [Operand.tgt, Operand.none, Operand.none]), Opcode.kJumpIfNotNull: const Format( Encoding.kT, const [Operand.tgt, Operand.none, Operand.none]), + Opcode.kJumpIfUnchecked: const Format( + Encoding.kT, const [Operand.tgt, Operand.none, Operand.none]), Opcode.kInterfaceCall: const Format( Encoding.kDF, const [Operand.lit, Operand.imm, Operand.none]), Opcode.kDynamicCall: const Format( diff --git a/pkg/vm/lib/bytecode/gen_bytecode.dart b/pkg/vm/lib/bytecode/gen_bytecode.dart index f82eca7c0a3..d4924804a16 100644 --- a/pkg/vm/lib/bytecode/gen_bytecode.dart +++ b/pkg/vm/lib/bytecode/gen_bytecode.dart @@ -1827,29 +1827,116 @@ class BytecodeGenerator extends RecursiveVisitor { final forwardingParamTypes = _getForwardingParameterTypes( function, forwardingTarget, forwardingSubstitution); - for (var typeParam in function.typeParameters) { - _genTypeParameterBoundCheck(typeParam, forwardingBounds); + if (_hasSkippableTypeChecks( + function, forwardingBounds, forwardingParamTypes)) { + final Label skipChecks = new Label(); + asm.emitJumpIfUnchecked(skipChecks); + + // We can skip bounds checks of type parameter and type checks of + // non-covariant parameters if function is called via unchecked call. + + for (var typeParam in function.typeParameters) { + if (_typeParameterNeedsBoundCheck(typeParam, forwardingBounds)) { + _genTypeParameterBoundCheck(typeParam, forwardingBounds); + } + } + for (var param in function.positionalParameters) { + if (!param.isCovariant && + _parameterNeedsTypeCheck(param, forwardingParamTypes)) { + _genArgumentTypeCheck(param, forwardingParamTypes); + } + } + for (var param in locals.sortedNamedParameters) { + if (!param.isCovariant && + _parameterNeedsTypeCheck(param, forwardingParamTypes)) { + _genArgumentTypeCheck(param, forwardingParamTypes); + } + } + + asm.bind(skipChecks); } + + // Covariant parameters need to be checked even if function is called + // via unchecked call, so they are generated outside of JumpIfUnchecked. + for (var param in function.positionalParameters) { - _genArgumentTypeCheck(param, forwardingParamTypes); + if (param.isCovariant && + _parameterNeedsTypeCheck(param, forwardingParamTypes)) { + _genArgumentTypeCheck(param, forwardingParamTypes); + } } for (var param in locals.sortedNamedParameters) { - _genArgumentTypeCheck(param, forwardingParamTypes); + if (param.isCovariant && + _parameterNeedsTypeCheck(param, forwardingParamTypes)) { + _genArgumentTypeCheck(param, forwardingParamTypes); + } } } - void _genTypeParameterBoundCheck(TypeParameter typeParam, + /// Returns true if bound of [typeParam] should be checked. + bool _typeParameterNeedsBoundCheck(TypeParameter typeParam, Map forwardingTypeParameterBounds) { if (canSkipTypeChecksForNonCovariantArguments && !typeParam.isGenericCovariantImpl) { - return; + return false; } final DartType bound = (forwardingTypeParameterBounds != null) ? forwardingTypeParameterBounds[typeParam] : typeParam.bound; if (typeEnvironment.isTop(bound)) { - return; + return false; } + return true; + } + + /// Returns true if type of [param] should be checked. + bool _parameterNeedsTypeCheck(VariableDeclaration param, + Map forwardingParameterTypes) { + if (canSkipTypeChecksForNonCovariantArguments && + !param.isCovariant && + !param.isGenericCovariantImpl) { + return false; + } + final DartType type = (forwardingParameterTypes != null) + ? forwardingParameterTypes[param] + : param.type; + if (typeEnvironment.isTop(type)) { + return false; + } + return true; + } + + /// Returns true if there are parameter type/bound checks which can + /// be skipped on unchecked call. + bool _hasSkippableTypeChecks( + FunctionNode function, + Map forwardingBounds, + Map forwardingParamTypes) { + for (var typeParam in function.typeParameters) { + if (_typeParameterNeedsBoundCheck(typeParam, forwardingBounds)) { + return true; + } + } + for (var param in function.positionalParameters) { + if (!param.isCovariant && + _parameterNeedsTypeCheck(param, forwardingParamTypes)) { + return true; + } + } + for (var param in locals.sortedNamedParameters) { + if (!param.isCovariant && + _parameterNeedsTypeCheck(param, forwardingParamTypes)) { + return true; + } + } + return false; + } + + void _genTypeParameterBoundCheck(TypeParameter typeParam, + Map forwardingTypeParameterBounds) { + final DartType bound = (forwardingTypeParameterBounds != null) + ? forwardingTypeParameterBounds[typeParam] + : typeParam.bound; final DartType type = new TypeParameterType(typeParam); _genPushInstantiatorAndFunctionTypeArguments([type, bound]); asm.emitPushConstant(cp.addType(type)); @@ -1860,17 +1947,9 @@ class BytecodeGenerator extends RecursiveVisitor { void _genArgumentTypeCheck(VariableDeclaration variable, Map forwardingParameterTypes) { - if (canSkipTypeChecksForNonCovariantArguments && - !variable.isCovariant && - !variable.isGenericCovariantImpl) { - return; - } final DartType type = (forwardingParameterTypes != null) ? forwardingParameterTypes[variable] : variable.type; - if (typeEnvironment.isTop(type)) { - return; - } if (locals.isCaptured(variable)) { asm.emitPush(locals.getOriginalParamSlotIndex(variable)); } else { diff --git a/pkg/vm/testcases/bytecode/async.dart.expect b/pkg/vm/testcases/bytecode/async.dart.expect index e14292446b8..ea3f372ef98 100644 --- a/pkg/vm/testcases/bytecode/async.dart.expect +++ b/pkg/vm/testcases/bytecode/async.dart.expect @@ -87,6 +87,7 @@ ClosureCode { Push r0 Push FP[-5] StoreContextVar 0, 0 + JumpIfUnchecked L1 Push FP[-5] PushConstant CP#3 PushNull @@ -94,6 +95,7 @@ ClosureCode { PushConstant CP#4 AssertAssignable 0, CP#5 Drop1 +L1: Push r0 PushConstant CP#7 PushConstant CP#6 diff --git a/pkg/vm/testcases/bytecode/closures.dart.expect b/pkg/vm/testcases/bytecode/closures.dart.expect index c790306ed60..9c7943cabab 100644 --- a/pkg/vm/testcases/bytecode/closures.dart.expect +++ b/pkg/vm/testcases/bytecode/closures.dart.expect @@ -76,6 +76,7 @@ ClosureCode { Push FP[-6] LoadFieldTOS CP#1 PopLocal r0 + JumpIfUnchecked L1 Push FP[-5] PushConstant CP#3 PushNull @@ -83,6 +84,7 @@ ClosureCode { PushConstant CP#4 AssertAssignable 1, CP#5 Drop1 +L1: Push r0 Push r0 LoadContextVar 0, 0 @@ -337,6 +339,7 @@ L2: PushInt 1 DirectCall CP#8, 4 PopLocal r0 + JumpIfUnchecked L3 Push FP[-5] PushConstant CP#10 PushNull @@ -344,6 +347,7 @@ L2: PushConstant CP#11 AssertAssignable 0, CP#12 Drop1 +L3: PushNull ReturnTOS } @@ -1011,6 +1015,7 @@ ClosureCode { Push r0 Push FP[-5] StoreContextVar 1, 0 + JumpIfUnchecked L1 Push FP[-5] PushConstant CP#3 PushNull @@ -1018,6 +1023,7 @@ ClosureCode { PushConstant CP#4 AssertAssignable 1, CP#5 Drop1 +L1: Push r0 LoadContextParent Push r0 @@ -1030,7 +1036,7 @@ ClosureCode { LoadContextVar 0, 1 PushInt 5 CompareIntGt - JumpIfFalse L1 + JumpIfFalse L2 Push r0 PushInt 4 StoreContextVar 1, 1 @@ -1061,7 +1067,7 @@ ClosureCode { LoadContextVar 1, 1 DirectCall CP#20, 1 Drop1 -L1: +L2: PushNull ReturnTOS } @@ -1282,6 +1288,7 @@ ClosureCode { Push FP[-6] LoadFieldTOS CP#5 PopLocal r0 + JumpIfUnchecked L1 Push FP[-5] PushConstant CP#20 PushNull @@ -1289,6 +1296,7 @@ ClosureCode { PushConstant CP#21 AssertAssignable 1, CP#22 Drop1 +L1: Push r0 Push FP[-5] Push r0 @@ -1434,6 +1442,7 @@ Bytecode { Push r0 Push FP[-5] StoreContextVar 0, 0 + JumpIfUnchecked L1 Push FP[-5] PushConstant CP#0 Push FP[-6] @@ -1442,6 +1451,7 @@ Bytecode { PushConstant CP#2 AssertAssignable 0, CP#3 Drop1 +L1: AllocateClosure CP#4 StoreLocal r2 Push r2 diff --git a/pkg/vm/testcases/bytecode/type_ops.dart b/pkg/vm/testcases/bytecode/type_ops.dart index 687d58f2108..d9ff5ebf780 100644 --- a/pkg/vm/testcases/bytecode/type_ops.dart +++ b/pkg/vm/testcases/bytecode/type_ops.dart @@ -60,4 +60,17 @@ class E

{ void foo6>(Map map) {} } +abstract class F { + void foo7(Q a, covariant num b, T c); + void foo8(Q a, covariant num b, T c); +} + +class G { + void foo7(Q a, int b, T c) {} +} + +class H extends G implements F { + void foo8(Q a, int b, T c) {} +} + main() {} diff --git a/pkg/vm/testcases/bytecode/type_ops.dart.expect b/pkg/vm/testcases/bytecode/type_ops.dart.expect index 0e84cb2f457..3ff5f13222c 100644 --- a/pkg/vm/testcases/bytecode/type_ops.dart.expect +++ b/pkg/vm/testcases/bytecode/type_ops.dart.expect @@ -411,6 +411,7 @@ Bytecode { InstantiateTypeArgumentsTOS 0, CP#1 PopLocal r0 L1: + JumpIfUnchecked L2 Push FP[-6] LoadTypeArgumentsField CP#0 Push r0 @@ -418,6 +419,7 @@ L1: PushConstant CP#3 PushConstant CP#4 AssertSubtype +L2: PushNull ReturnTOS } @@ -430,6 +432,239 @@ ConstantPool { [4] = ObjectRef 'T' } +Class 'F', script = '#lib', abstract + type-params (args: 1) + extends dart:core::Object + + +Function '', constructor, reflectable + parameters [] (required: 0) + return-type #lib::F < #lib::F::TypeParam/0 > + +Bytecode { + Entry 0 + CheckStack 0 + Push FP[-5] + DirectCall CP#0, 1 + Drop1 + PushNull + ReturnTOS +} +ConstantPool { + [0] = DirectCall 'dart:core::Object:: (constructor)', ArgDesc num-args 1, num-type-args 0, names [] + [1] = Reserved +} + + +Function 'foo7', abstract, reflectable, debuggable + type-params <#lib::F::TypeParam/0 Q> + parameters [#lib::F::foo7::TypeParam/0 'a', dart:core::num 'b', #lib::F::TypeParam/0 'c'] (required: 3) + return-type void + +Function 'foo8', abstract, reflectable, debuggable + type-params <#lib::F::TypeParam/0 Q> + parameters [#lib::F::foo8::TypeParam/0 'a', dart:core::num 'b', #lib::F::TypeParam/0 'c'] (required: 3) + return-type void +Class 'G', script = '#lib' + type-params (args: 1) + extends dart:core::Object + + +Function '', constructor, reflectable + parameters [] (required: 0) + return-type #lib::G < #lib::G::TypeParam/0 > + +Bytecode { + Entry 0 + CheckStack 0 + Push FP[-5] + DirectCall CP#0, 1 + Drop1 + PushNull + ReturnTOS +} +ConstantPool { + [0] = DirectCall 'dart:core::Object:: (constructor)', ArgDesc num-args 1, num-type-args 0, names [] + [1] = Reserved +} + + +Function 'foo7', reflectable, debuggable + type-params <#lib::G::TypeParam/0 Q> + parameters [#lib::G::foo7::TypeParam/0 'a', dart:core::int 'b', #lib::G::TypeParam/0 'c'] (required: 3) + return-type void + +Bytecode { + Entry 1 + CheckStack 0 + CheckFunctionTypeArgs 1, r0 + JumpIfNotZeroTypeArgs L1 + Push FP[-8] + LoadTypeArgumentsField CP#0 + PopLocal r0 +L1: + JumpIfUnchecked L2 + Push FP[-8] + LoadTypeArgumentsField CP#0 + Push r0 + PushConstant CP#1 + PushConstant CP#2 + PushConstant CP#3 + AssertSubtype + Push FP[-5] + PushConstant CP#2 + Push FP[-8] + LoadTypeArgumentsField CP#0 + PushNull + PushConstant CP#4 + AssertAssignable 0, CP#5 + Drop1 +L2: + PushNull + ReturnTOS +} +Parameter flags: [0, 0, 2] +Default function type arguments: CP#6 +ConstantPool { + [0] = TypeArgumentsField #lib::G + [1] = Type #lib::G::foo7::TypeParam/0 + [2] = Type #lib::G::TypeParam/0 + [3] = ObjectRef 'Q' + [4] = ObjectRef 'c' + [5] = SubtypeTestCache + [6] = ObjectRef < #lib::G::TypeParam/0 > +} + +Class 'H', script = '#lib' + type-params (args: 1) + extends #lib::G < #lib::H::TypeParam/0 > + implements [#lib::F < #lib::H::TypeParam/0 >] + + +Function '', constructor, reflectable + parameters [] (required: 0) + return-type #lib::H < #lib::H::TypeParam/0 > + +Bytecode { + Entry 0 + CheckStack 0 + Push FP[-5] + DirectCall CP#0, 1 + Drop1 + PushNull + ReturnTOS +} +ConstantPool { + [0] = DirectCall '#lib::G:: (constructor)', ArgDesc num-args 1, num-type-args 0, names [] + [1] = Reserved +} + + +Function 'foo8', reflectable, debuggable + type-params <#lib::H::TypeParam/0 Q> + parameters [#lib::H::foo8::TypeParam/0 'a', dart:core::int 'b', #lib::H::TypeParam/0 'c'] (required: 3) + return-type void + +Bytecode { + Entry 1 + CheckStack 0 + CheckFunctionTypeArgs 1, r0 + JumpIfNotZeroTypeArgs L1 + Push FP[-8] + LoadTypeArgumentsField CP#0 + PopLocal r0 +L1: + JumpIfUnchecked L2 + Push FP[-8] + LoadTypeArgumentsField CP#0 + Push r0 + PushConstant CP#1 + PushConstant CP#2 + PushConstant CP#3 + AssertSubtype + Push FP[-5] + PushConstant CP#2 + Push FP[-8] + LoadTypeArgumentsField CP#0 + PushNull + PushConstant CP#4 + AssertAssignable 0, CP#5 + Drop1 +L2: + PushNull + ReturnTOS +} +Parameter flags: [0, 0, 2] +Default function type arguments: CP#6 +ConstantPool { + [0] = TypeArgumentsField #lib::H + [1] = Type #lib::H::foo8::TypeParam/0 + [2] = Type #lib::H::TypeParam/0 + [3] = ObjectRef 'Q' + [4] = ObjectRef 'c' + [5] = SubtypeTestCache + [6] = ObjectRef < #lib::H::TypeParam/0 > +} + + +Function 'foo7', reflectable, debuggable, forwarding-stub + type-params <#lib::H::TypeParam/0 Q> + parameters [#lib::H::foo7::TypeParam/0 'a', dart:core::num 'b', #lib::H::TypeParam/0 'c'] (required: 3) + return-type void + +Bytecode { + Entry 2 + CheckStack 0 + CheckFunctionTypeArgs 1, r0 + JumpIfUnchecked L1 + Push FP[-8] + LoadTypeArgumentsField CP#0 + Push r0 + PushConstant CP#1 + PushConstant CP#2 + PushConstant CP#3 + AssertSubtype + Push FP[-5] + PushConstant CP#2 + Push FP[-8] + LoadTypeArgumentsField CP#0 + PushNull + PushConstant CP#4 + AssertAssignable 0, CP#5 + Drop1 +L1: + Push FP[-6] + PushConstant CP#6 + PushNull + PushNull + PushConstant CP#7 + AssertAssignable 1, CP#8 + Drop1 + Push r0 + Push FP[-8] + Push FP[-7] + Push FP[-6] + Push FP[-5] + DirectCall CP#9, 5 + ReturnTOS +} +Parameter flags: [0, 1, 2] +Forwarding stub target: CP#11 +ConstantPool { + [0] = TypeArgumentsField #lib::H + [1] = Type #lib::H::foo7::TypeParam/0 + [2] = Type #lib::H::TypeParam/0 + [3] = ObjectRef 'Q' + [4] = ObjectRef 'c' + [5] = SubtypeTestCache + [6] = Type dart:core::int + [7] = ObjectRef 'b' + [8] = SubtypeTestCache + [9] = DirectCall '#lib::G::foo7', ArgDesc num-args 4, num-type-args 1, names [] + [10] = Reserved + [11] = ObjectRef #lib::G::foo7 +} + } ]library #lib from "#lib" as #lib { @@ -482,6 +717,27 @@ ConstantPool { return null; method foo6 = dart.core::List<#lib::E::P>>(dart.core::Map<#lib::E::foo6::T, #lib::E::foo6::U> map) → void {} } + abstract class F extends dart.core::Object { + synthetic constructor •() → #lib::F<#lib::F::T> + : super dart.core::Object::•() + ; + abstract method foo7(#lib::F::foo7::Q a, covariant dart.core::num b, generic-covariant-impl #lib::F::T c) → void; + abstract method foo8(#lib::F::foo8::Q a, covariant dart.core::num b, generic-covariant-impl #lib::F::T c) → void; + } + class G extends dart.core::Object { + synthetic constructor •() → #lib::G<#lib::G::T> + : super dart.core::Object::•() + ; + method foo7(#lib::G::foo7::Q a, dart.core::int b, generic-covariant-impl #lib::G::T c) → void {} + } + class H extends #lib::G<#lib::H::T> implements #lib::F<#lib::H::T> { + synthetic constructor •() → #lib::H<#lib::H::T> + : super #lib::G::•() + ; + method foo8(#lib::H::foo8::Q a, dart.core::int b, generic-covariant-impl #lib::H::T c) → void {} + forwarding-stub method foo7(#lib::H::foo7::Q a, covariant dart.core::num b, generic-covariant-impl #lib::H::T c) → void + return super.{#lib::G::foo7}<#lib::H::foo7::Q>(a, b, c); + } static field dart.core::List> globalVar; static method foo1(dynamic x) → dynamic { if(x is #lib::B) { diff --git a/runtime/tests/vm/vm.status b/runtime/tests/vm/vm.status index 70fbaa21fe7..d26ca4f45b9 100644 --- a/runtime/tests/vm/vm.status +++ b/runtime/tests/vm/vm.status @@ -30,9 +30,6 @@ dart/kernel_determinism_test: SkipSlow dart/redirection_type_shuffling_test/none: RuntimeError dart/snapshot_version_test: RuntimeError -[ $compiler != dartk ] -dart/entrypoints/jit/*: SkipByDesign # These tests should only run on JIT. - [ $compiler == dartkb ] cc/*: Skip # Too many timeouts and crashes for infrastructure to handle. @@ -107,6 +104,7 @@ cc/IsolateReload_KernelIncrementalCompileExpression: SkipByDesign cc/IsolateReload_KernelIncrementalCompileGenerics: SkipByDesign cc/Mixin_PrivateSuperResolution: Skip cc/Mixin_PrivateSuperResolutionCrossLibraryShouldFail: Skip +dart/entrypoints/jit/*: SkipByDesign # These tests should only run on JIT. [ $compiler != dartk && $compiler != dartkb && $compiler != none ] dart/appjit*: SkipByDesign # Test needs to run from source @@ -247,8 +245,8 @@ cc/RegenerateAllocStubs: SkipByDesign # This test is meaningless for DBC as allo [ $arch == simdbc || $arch == simdbc64 || $compiler == dartkb ] dart/generic_field_invocation_test: SkipByDesign # DBC and KBC interpreters do not support --no_lazy_dispatchers -[ $builder_tag == optimization_counter_threshold || $hot_reload || $hot_reload_rollback || $arch != arm && $arch != simarm && $arch != x64 || $compiler != dartk && $compiler != dartkp ] -dart/entrypoints/*: SkipByDesign # These tests are for compiler optimizations and very sensible to when functions are optimized, so they are disabled on hotreload and optcounter bots. +[ $builder_tag == bytecode_interpreter || $builder_tag == bytecode_mixed || $builder_tag == optimization_counter_threshold || $hot_reload || $hot_reload_rollback || $arch != arm && $arch != simarm && $arch != x64 || $compiler != dartk && $compiler != dartkb && $compiler != dartkp ] +dart/entrypoints/*: SkipByDesign # These tests are for compiler optimizations and very sensible to when functions are optimized, so they are disabled on hotreload, optcounter and bytecode interpreter bots. [ $compiler == dart2analyzer || $compiler == dart2js ] dart/data_uri*test: Skip # Data uri's not supported by dart2js or the analyzer. diff --git a/runtime/vm/compiler/frontend/base_flow_graph_builder.cc b/runtime/vm/compiler/frontend/base_flow_graph_builder.cc index b0f24a6d1c4..ea81fbe360d 100644 --- a/runtime/vm/compiler/frontend/base_flow_graph_builder.cc +++ b/runtime/vm/compiler/frontend/base_flow_graph_builder.cc @@ -904,6 +904,87 @@ Fragment BaseFlowGraphBuilder::CheckNull(TokenPosition position, return instructions; } +void BaseFlowGraphBuilder::RecordUncheckedEntryPoint( + GraphEntryInstr* graph_entry, + FunctionEntryInstr* unchecked_entry) { + // Closures always check all arguments on their checked entry-point, most + // call-sites are unchecked, and they're inlined less often, so it's very + // beneficial to build multiple entry-points for them. Regular methods however + // have fewer checks to begin with since they have dynamic invocation + // forwarders, so in AOT we implement a more conservative time-space tradeoff + // by only building the unchecked entry-point when inlining. We should + // reconsider this heuristic if we identify non-inlined type-checks in + // hotspots of new benchmarks. + if (!IsInlining() && (parsed_function_->function().IsClosureFunction() || + !FLAG_precompiled_mode)) { + graph_entry->set_unchecked_entry(unchecked_entry); + } else if (InliningUncheckedEntry()) { + graph_entry->set_normal_entry(unchecked_entry); + } +} + +Fragment BaseFlowGraphBuilder::BuildEntryPointsIntrospection() { + if (!FLAG_enable_testing_pragmas) return Drop(); + + auto& function = Function::Handle(Z, parsed_function_->function().raw()); + + if (function.IsImplicitClosureFunction()) { + const auto& parent = Function::Handle(Z, function.parent_function()); + const auto& func_name = String::Handle(Z, parent.name()); + const auto& owner = Class::Handle(Z, parent.Owner()); + function = owner.LookupFunction(func_name); + } + + Object& options = Object::Handle(Z); + if (!Library::FindPragma(thread_, /*only_core=*/false, function, + Symbols::vm_trace_entrypoints(), &options) || + options.IsNull() || !options.IsClosure()) { + return Drop(); + } + auto& closure = Closure::ZoneHandle(Z, Closure::Cast(options).raw()); + LocalVariable* entry_point_num = MakeTemporary(); + + auto& function_name = String::ZoneHandle( + Z, String::New(function.ToLibNamePrefixedQualifiedCString(), Heap::kOld)); + if (parsed_function_->function().IsImplicitClosureFunction()) { + function_name = String::Concat( + function_name, String::Handle(Z, String::New("#tearoff", Heap::kNew)), + Heap::kOld); + } + + Fragment call_hook; + call_hook += Constant(closure); + call_hook += PushArgument(); + call_hook += Constant(function_name); + call_hook += PushArgument(); + call_hook += LoadLocal(entry_point_num); + call_hook += PushArgument(); + call_hook += Constant(Function::ZoneHandle(Z, closure.function())); + call_hook += ClosureCall(TokenPosition::kNoSource, + /*type_args_len=*/0, /*argument_count=*/3, + /*argument_names=*/Array::ZoneHandle(Z)); + call_hook += Drop(); // result of closure call + call_hook += Drop(); // entrypoint number + return call_hook; +} + +Fragment BaseFlowGraphBuilder::ClosureCall(TokenPosition position, + intptr_t type_args_len, + intptr_t argument_count, + const Array& argument_names, + bool is_statically_checked) { + Value* function = Pop(); + const intptr_t total_count = argument_count + (type_args_len > 0 ? 1 : 0); + ArgumentArray arguments = GetArguments(total_count); + ClosureCallInstr* call = new (Z) + ClosureCallInstr(function, arguments, type_args_len, argument_names, + position, GetNextDeoptId(), + is_statically_checked ? Code::EntryKind::kUnchecked + : Code::EntryKind::kNormal); + Push(call); + return Fragment(call); +} + } // namespace kernel } // namespace dart diff --git a/runtime/vm/compiler/frontend/base_flow_graph_builder.h b/runtime/vm/compiler/frontend/base_flow_graph_builder.h index 342d4a0b5cd..61788be655e 100644 --- a/runtime/vm/compiler/frontend/base_flow_graph_builder.h +++ b/runtime/vm/compiler/frontend/base_flow_graph_builder.h @@ -108,6 +108,32 @@ class TestFragment { typedef ZoneGrowableArray* ArgumentArray; +// Indicates which form of the unchecked entrypoint we are compiling. +// +// kNone: +// +// There is no unchecked entrypoint: the unchecked entry is set to NULL in +// the 'GraphEntryInstr'. +// +// kSeparate: +// +// The normal and unchecked entrypoint each point to their own versions of +// the prologue, containing exactly those checks which need to be performed +// on either side. Both sides jump directly to the body after performing +// their prologue. +// +// kSharedWithVariable: +// +// A temporary variable is allocated and initialized to 0 on normal entry +// and 2 on unchecked entry. Code which should be ommitted on the unchecked +// entrypoint is made conditional on this variable being equal to 0. +// +enum class UncheckedEntryPointStyle { + kNone = 0, + kSeparate = 1, + kSharedWithVariable = 2, +}; + class BaseFlowGraphBuilder { public: BaseFlowGraphBuilder( @@ -312,6 +338,25 @@ class BaseFlowGraphBuilder { const String& function_name, bool clear_the_temp = true); + // Records extra unchecked entry point 'unchecked_entry' in 'graph_entry'. + void RecordUncheckedEntryPoint(GraphEntryInstr* graph_entry, + FunctionEntryInstr* unchecked_entry); + + // Pop the index of the current entry-point off the stack. If there is any + // entrypoint-tracing hook registered in a pragma for the function, it is + // called with the name of the current function and the current entry-point + // index. + Fragment BuildEntryPointsIntrospection(); + + // Builds closure call with given number of arguments. Target closure + // function is taken from top of the stack. + // PushArgument instructions should be already added for arguments. + Fragment ClosureCall(TokenPosition position, + intptr_t type_args_len, + intptr_t argument_count, + const Array& argument_names, + bool use_unchecked_entry = false); + protected: intptr_t AllocateBlockId() { return ++last_used_block_id_; } diff --git a/runtime/vm/compiler/frontend/bytecode_flow_graph_builder.cc b/runtime/vm/compiler/frontend/bytecode_flow_graph_builder.cc index f3da2a682c9..075a0715aaf 100644 --- a/runtime/vm/compiler/frontend/bytecode_flow_graph_builder.cc +++ b/runtime/vm/compiler/frontend/bytecode_flow_graph_builder.cc @@ -174,6 +174,9 @@ void BytecodeFlowGraphBuilder::AllocateLocalVariables( if (parsed_function()->has_arg_desc_var()) { ++num_locals; } + if (parsed_function()->has_entry_points_temp_var()) { + ++num_locals; + } if (num_locals == 0) { return; @@ -207,6 +210,11 @@ void BytecodeFlowGraphBuilder::AllocateLocalVariables( parsed_function()->arg_desc_var()->set_index(VariableIndex(-idx)); ++idx; } + if (parsed_function()->has_entry_points_temp_var()) { + parsed_function()->entry_points_temp_var()->set_index( + VariableIndex(-idx)); + ++idx; + } ASSERT(idx == num_locals); ASSERT(parsed_function()->scope() == nullptr); @@ -815,6 +823,10 @@ void BytecodeFlowGraphBuilder::BuildDirectCall() { Array::ZoneHandle(Z, arg_desc.GetArgumentNames()), arguments, *ic_data_array_, B->GetNextDeoptId(), ICData::kStatic); + if (target.MayHaveUncheckedEntryPoint(isolate())) { + call->set_entry_kind(Code::EntryKind::kUnchecked); + } + // TODO(alexmarkov): add type info // SetResultTypeForStaticCall(call, target, argument_count, result_type); @@ -1348,6 +1360,112 @@ void BytecodeFlowGraphBuilder::BuildJumpIfNotNull() { BuildJumpIfStrictCompare(Token::kNE); } +void BytecodeFlowGraphBuilder::BuildJumpIfUnchecked() { + if (is_generating_interpreter()) { + UNIMPLEMENTED(); // TODO(alexmarkov): interpreter + } + + ASSERT(IsStackEmpty()); + + const intptr_t target_pc = pc_ + DecodeOperandT().value(); + JoinEntryInstr* target = jump_targets_.Lookup(target_pc); + ASSERT(target != nullptr); + FunctionEntryInstr* unchecked_entry = nullptr; + const intptr_t kCheckedEntry = + static_cast(UncheckedEntryPointStyle::kNone); + const intptr_t kUncheckedEntry = + static_cast(UncheckedEntryPointStyle::kSharedWithVariable); + + switch (entry_point_style_) { + case UncheckedEntryPointStyle::kNone: { + JoinEntryInstr* do_checks = B->BuildJoinEntry(); + code_ += B->Goto(B->InliningUncheckedEntry() ? target : do_checks); + code_ = Fragment(do_checks); + } break; + + case UncheckedEntryPointStyle::kSeparate: { + // Route normal entry to checks. + if (FLAG_enable_testing_pragmas) { + code_ += B->IntConstant(kCheckedEntry); + code_ += B->BuildEntryPointsIntrospection(); + } + Fragment do_checks = code_; + + // Create a separate unchecked entry point. + unchecked_entry = B->BuildFunctionEntry(graph_entry_); + code_ = Fragment(unchecked_entry); + + // Re-build prologue for unchecked entry point. It can only contain + // Entry, CheckStack and DebugCheck instructions. + bytecode_instr_ = raw_bytecode_; + ASSERT(KernelBytecode::IsEntryOpcode(bytecode_instr_)); + bytecode_instr_ = KernelBytecode::Next(bytecode_instr_); + while (!KernelBytecode::IsJumpIfUncheckedOpcode(bytecode_instr_)) { + ASSERT(KernelBytecode::IsCheckStackOpcode(bytecode_instr_) || + KernelBytecode::IsDebugCheckOpcode(bytecode_instr_)); + ASSERT(jump_targets_.Lookup(bytecode_instr_ - raw_bytecode_) == + nullptr); + BuildInstruction(KernelBytecode::DecodeOpcode(bytecode_instr_)); + bytecode_instr_ = KernelBytecode::Next(bytecode_instr_); + } + ASSERT((bytecode_instr_ - raw_bytecode_) == pc_); + + if (FLAG_enable_testing_pragmas) { + code_ += B->IntConstant( + static_cast(UncheckedEntryPointStyle::kSeparate)); + code_ += B->BuildEntryPointsIntrospection(); + } + code_ += B->Goto(target); + + code_ = do_checks; + } break; + + case UncheckedEntryPointStyle::kSharedWithVariable: { + LocalVariable* ep_var = parsed_function()->entry_points_temp_var(); + + // Dispatch based on the value of entry_points_temp_var. + TargetEntryInstr *do_checks, *skip_checks; + if (FLAG_enable_testing_pragmas) { + code_ += B->LoadLocal(ep_var); + code_ += B->BuildEntryPointsIntrospection(); + } + code_ += B->LoadLocal(ep_var); + code_ += B->IntConstant(kUncheckedEntry); + code_ += B->BranchIfEqual(&skip_checks, &do_checks, /*negate=*/false); + + code_ = Fragment(skip_checks); + code_ += B->Goto(target); + + // Relink the body of the function from normal entry to 'prologue_join'. + JoinEntryInstr* prologue_join = B->BuildJoinEntry(); + FunctionEntryInstr* normal_entry = graph_entry_->normal_entry(); + if (normal_entry->next() != nullptr) { + prologue_join->LinkTo(normal_entry->next()); + normal_entry->set_next(nullptr); + } + + unchecked_entry = B->BuildFunctionEntry(graph_entry_); + code_ = Fragment(unchecked_entry); + code_ += B->IntConstant(kUncheckedEntry); + code_ += B->StoreLocal(TokenPosition::kNoSource, ep_var); + code_ += B->Drop(); + code_ += B->Goto(prologue_join); + + code_ = Fragment(normal_entry); + code_ += B->IntConstant(kCheckedEntry); + code_ += B->StoreLocal(TokenPosition::kNoSource, ep_var); + code_ += B->Drop(); + code_ += B->Goto(prologue_join); + + code_ = Fragment(do_checks); + } break; + } + + if (unchecked_entry != nullptr) { + B->RecordUncheckedEntryPoint(graph_entry_, unchecked_entry); + } +} + void BytecodeFlowGraphBuilder::BuildDrop1() { if (is_generating_interpreter()) { UNIMPLEMENTED(); // TODO(alexmarkov): interpreter @@ -1715,12 +1833,29 @@ void BytecodeFlowGraphBuilder::CollectControlFlow( const PcDescriptors& descriptors, const ExceptionHandlers& handlers, GraphEntryInstr* graph_entry) { + bool seen_jump_if_unchecked = false; for (intptr_t pc = 0; pc < bytecode_length_;) { const KBCInstr* instr = &(raw_bytecode_[pc]); if (KernelBytecode::IsJumpOpcode(instr)) { const intptr_t target = pc + KernelBytecode::DecodeT(instr); EnsureControlFlowJoin(descriptors, target); + + if (KernelBytecode::IsJumpIfUncheckedOpcode(instr)) { + if (seen_jump_if_unchecked) { + FATAL1( + "Multiple JumpIfUnchecked bytecode instructions are not allowed: " + "%s.", + function().ToFullyQualifiedCString()); + } + seen_jump_if_unchecked = true; + ASSERT(entry_point_style_ == UncheckedEntryPointStyle::kNone); + entry_point_style_ = ChooseEntryPointStyle(instr); + if (entry_point_style_ == + UncheckedEntryPointStyle::kSharedWithVariable) { + parsed_function_->EnsureEntryPointsTemp(); + } + } } else if (KernelBytecode::IsCheckStackOpcode(instr) && (KernelBytecode::DecodeA(instr) != 0)) { // (dartbug.com/36590) BlockEntryInstr::FindOsrEntryAndRelink assumes @@ -1789,6 +1924,37 @@ void BytecodeFlowGraphBuilder::CollectControlFlow( } } +UncheckedEntryPointStyle BytecodeFlowGraphBuilder::ChooseEntryPointStyle( + const KBCInstr* jump_if_unchecked) { + ASSERT(KernelBytecode::IsJumpIfUncheckedOpcode(jump_if_unchecked)); + + if (!function().MayHaveUncheckedEntryPoint(isolate())) { + return UncheckedEntryPointStyle::kNone; + } + + // Separate entry points are used if bytecode has the following pattern: + // Entry + // CheckStack (optional) + // DebugCheck (optional) + // JumpIfUnchecked + // + const KBCInstr* instr = raw_bytecode_; + if (!KernelBytecode::IsEntryOpcode(instr)) { + return UncheckedEntryPointStyle::kSharedWithVariable; + } + instr = KernelBytecode::Next(instr); + if (KernelBytecode::IsCheckStackOpcode(instr)) { + instr = KernelBytecode::Next(instr); + } + if (KernelBytecode::IsDebugCheckOpcode(instr)) { + instr = KernelBytecode::Next(instr); + } + if (instr != jump_if_unchecked) { + return UncheckedEntryPointStyle::kSharedWithVariable; + } + return UncheckedEntryPointStyle::kSeparate; +} + void BytecodeFlowGraphBuilder::CreateParameterVariables() { const Bytecode& bytecode = Bytecode::Handle(Z, function().bytecode()); object_pool_ = bytecode.object_pool(); @@ -1817,18 +1983,17 @@ FlowGraph* BytecodeFlowGraphBuilder::BuildGraph() { ProcessICDataInObjectPool(object_pool_); - GraphEntryInstr* graph_entry = - new (Z) GraphEntryInstr(*parsed_function_, B->osr_id_); + graph_entry_ = new (Z) GraphEntryInstr(*parsed_function_, B->osr_id_); - auto normal_entry = B->BuildFunctionEntry(graph_entry); - graph_entry->set_normal_entry(normal_entry); + auto normal_entry = B->BuildFunctionEntry(graph_entry_); + graph_entry_->set_normal_entry(normal_entry); const PcDescriptors& descriptors = PcDescriptors::Handle(Z, bytecode.pc_descriptors()); const ExceptionHandlers& handlers = ExceptionHandlers::Handle(Z, bytecode.exception_handlers()); - CollectControlFlow(descriptors, handlers, graph_entry); + CollectControlFlow(descriptors, handlers, graph_entry_); kernel::BytecodeSourcePositionsIterator source_pos_iter(Z, bytecode); bool update_position = source_pos_iter.MoveNext(); @@ -1877,11 +2042,11 @@ FlowGraph* BytecodeFlowGraphBuilder::BuildGraph() { // Catch entries are always considered reachable, even if they // become unreachable after OSR. if (B->IsCompiledForOsr()) { - graph_entry->RelinkToOsrEntry(Z, B->last_used_block_id_ + 1); + graph_entry_->RelinkToOsrEntry(Z, B->last_used_block_id_ + 1); } FlowGraph* flow_graph = new (Z) FlowGraph( - *parsed_function_, graph_entry, B->last_used_block_id_, prologue_info_); + *parsed_function_, graph_entry_, B->last_used_block_id_, prologue_info_); if (FLAG_print_flow_graph_from_bytecode) { FlowGraphPrinter::PrintGraph("Constructed from bytecode", flow_graph); diff --git a/runtime/vm/compiler/frontend/bytecode_flow_graph_builder.h b/runtime/vm/compiler/frontend/bytecode_flow_graph_builder.h index e03a5020024..7ee01a71b47 100644 --- a/runtime/vm/compiler/frontend/bytecode_flow_graph_builder.h +++ b/runtime/vm/compiler/frontend/bytecode_flow_graph_builder.h @@ -166,6 +166,10 @@ class BytecodeFlowGraphBuilder { const ExceptionHandlers& handlers, GraphEntryInstr* graph_entry); + // Figure out entry points style. + UncheckedEntryPointStyle ChooseEntryPointStyle( + const KBCInstr* jump_if_unchecked); + Thread* thread() const { return flow_graph_builder_->thread_; } Isolate* isolate() const { return thread()->isolate(); } @@ -189,7 +193,7 @@ class BytecodeFlowGraphBuilder { intptr_t pc_; intptr_t next_pc_ = -1; const KBCInstr* bytecode_instr_ = nullptr; - TokenPosition position_; // TODO(alexmarkov): Set/update. + TokenPosition position_; Fragment code_; ZoneGrowableArray local_vars_; ZoneGrowableArray parameters_; @@ -200,6 +204,8 @@ class BytecodeFlowGraphBuilder { IntMap stack_states_; PrologueInfo prologue_info_; JoinEntryInstr* throw_no_such_method_; + GraphEntryInstr* graph_entry_ = nullptr; + UncheckedEntryPointStyle entry_point_style_ = UncheckedEntryPointStyle::kNone; }; } // namespace kernel diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc index dbdc1f43006..08d36ddb5d7 100644 --- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc +++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc @@ -874,7 +874,7 @@ FlowGraph* StreamingFlowGraphBuilder::BuildGraphOfFunction( } } if (extra_entry != nullptr) { - B->RecordUncheckedEntryPoint(extra_entry); + B->RecordUncheckedEntryPoint(graph_entry, extra_entry); } } else { // If the function's body contains any yield points, build switch statement diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc index 60e150d38f5..088f81ee7ed 100644 --- a/runtime/vm/compiler/frontend/kernel_to_il.cc +++ b/runtime/vm/compiler/frontend/kernel_to_il.cc @@ -360,23 +360,6 @@ Fragment FlowGraphBuilder::InstanceCall( return Fragment(call); } -Fragment FlowGraphBuilder::ClosureCall(TokenPosition position, - intptr_t type_args_len, - intptr_t argument_count, - const Array& argument_names, - bool is_statically_checked) { - Value* function = Pop(); - const intptr_t total_count = argument_count + (type_args_len > 0 ? 1 : 0); - ArgumentArray arguments = GetArguments(total_count); - ClosureCallInstr* call = new (Z) - ClosureCallInstr(function, arguments, type_args_len, argument_names, - position, GetNextDeoptId(), - is_statically_checked ? Code::EntryKind::kUnchecked - : Code::EntryKind::kNormal); - Push(call); - return Fragment(call); -} - Fragment FlowGraphBuilder::FfiCall( const Function& signature, const ZoneGrowableArray& arg_reps, @@ -1992,54 +1975,6 @@ Fragment FlowGraphBuilder::BuildDefaultTypeHandling(const Function& function) { return Fragment(); } -// Pop the index of the current entry-point off the stack. If there is any -// entrypoint-tracing hook registered in a pragma for the function, it is called -// with the name of the current function and the current entry-point index. -Fragment FlowGraphBuilder::BuildEntryPointsIntrospection() { - if (!FLAG_enable_testing_pragmas) return Drop(); - - auto& function = Function::Handle(Z, parsed_function_->function().raw()); - - if (function.IsImplicitClosureFunction()) { - const auto& parent = Function::Handle(Z, function.parent_function()); - const auto& func_name = String::Handle(Z, parent.name()); - const auto& owner = Class::Handle(Z, parent.Owner()); - function = owner.LookupFunction(func_name); - } - - Object& options = Object::Handle(Z); - if (!Library::FindPragma(thread_, /*only_core=*/false, function, - Symbols::vm_trace_entrypoints(), &options) || - options.IsNull() || !options.IsClosure()) { - return Drop(); - } - auto& closure = Closure::ZoneHandle(Z, Closure::Cast(options).raw()); - LocalVariable* entry_point_num = MakeTemporary(); - - auto& function_name = String::ZoneHandle( - Z, String::New(function.ToLibNamePrefixedQualifiedCString(), Heap::kOld)); - if (parsed_function_->function().IsImplicitClosureFunction()) { - function_name = String::Concat( - function_name, String::Handle(Z, String::New("#tearoff", Heap::kNew)), - Heap::kOld); - } - - Fragment call_hook; - call_hook += Constant(closure); - call_hook += PushArgument(); - call_hook += Constant(function_name); - call_hook += PushArgument(); - call_hook += LoadLocal(entry_point_num); - call_hook += PushArgument(); - call_hook += Constant(Function::ZoneHandle(Z, closure.function())); - call_hook += ClosureCall(TokenPosition::kNoSource, - /*type_args_len=*/0, /*argument_count=*/3, - /*argument_names=*/Array::ZoneHandle(Z)); - call_hook += Drop(); // result of closure call - call_hook += Drop(); // entrypoint number - return call_hook; -} - FunctionEntryInstr* FlowGraphBuilder::BuildSharedUncheckedEntryPoint( Fragment shared_prologue_linked_in, Fragment skippable_checks, @@ -2121,24 +2056,6 @@ FunctionEntryInstr* FlowGraphBuilder::BuildSeparateUncheckedEntryPoint( return extra_entry; } -void FlowGraphBuilder::RecordUncheckedEntryPoint( - FunctionEntryInstr* extra_entry) { - // Closures always check all arguments on their checked entry-point, most - // call-sites are unchecked, and they're inlined less often, so it's very - // beneficial to build multiple entry-points for them. Regular methods however - // have fewer checks to begin with since they have dynamic invocation - // forwarders, so in AOT we implement a more conservative time-space tradeoff - // by only building the unchecked entry-point when inlining. We should - // reconsider this heuristic if we identify non-inlined type-checks in - // hotspots of new benchmarks. - if (!IsInlining() && (parsed_function_->function().IsClosureFunction() || - !FLAG_precompiled_mode)) { - graph_entry_->set_unchecked_entry(extra_entry); - } else if (InliningUncheckedEntry()) { - graph_entry_->set_normal_entry(extra_entry); - } -} - FlowGraph* FlowGraphBuilder::BuildGraphOfImplicitClosureFunction( const Function& function) { const Function& parent = Function::ZoneHandle(Z, function.parent_function()); @@ -2262,7 +2179,7 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfImplicitClosureFunction( /*redefinitions_if_skipped=*/Fragment(), /*body=*/body); } - RecordUncheckedEntryPoint(extra_entry); + RecordUncheckedEntryPoint(graph_entry_, extra_entry); } else { Fragment function(instruction_cursor); function += prologue; diff --git a/runtime/vm/compiler/frontend/kernel_to_il.h b/runtime/vm/compiler/frontend/kernel_to_il.h index c2185bd70c3..ac131f2ffae 100644 --- a/runtime/vm/compiler/frontend/kernel_to_il.h +++ b/runtime/vm/compiler/frontend/kernel_to_il.h @@ -47,32 +47,6 @@ enum class TypeChecksToBuild { kCheckCovariantTypeParameterBounds, }; -// Indicates which form of the unchecked entrypoint we are compiling. -// -// kNone: -// -// There is no unchecked entrypoint: the unchecked entry is set to NULL in -// the 'GraphEntryInstr'. -// -// kSeparate: -// -// The normal and unchecked entrypoint each point to their own versions of -// the prologue, containing exactly those checks which need to be performed -// on either side. Both sides jump directly to the body after performing -// their prologue. -// -// kSharedWithVariable: -// -// A temporary variable is allocated and initialized to 0 on normal entry -// and 2 on unchecked entry. Code which should be ommitted on the unchecked -// entrypoint is made conditional on this variable being equal to 0. -// -enum class UncheckedEntryPointStyle { - kNone = 0, - kSeparate = 1, - kSharedWithVariable = 2, -}; - class FlowGraphBuilder : public BaseFlowGraphBuilder { public: FlowGraphBuilder(ParsedFunction* parsed_function, @@ -148,12 +122,6 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder { bool use_unchecked_entry = false, const CallSiteAttributesMetadata* call_site_attrs = nullptr); - Fragment ClosureCall(TokenPosition position, - intptr_t type_args_len, - intptr_t argument_count, - const Array& argument_names, - bool use_unchecked_entry = false); - Fragment FfiCall( const Function& signature, const ZoneGrowableArray& arg_reps, @@ -292,7 +260,6 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder { // - function_type_arguments() Fragment BuildDefaultTypeHandling(const Function& function); - Fragment BuildEntryPointsIntrospection(); FunctionEntryInstr* BuildSharedUncheckedEntryPoint( Fragment prologue_from_normal_entry, Fragment skippable_checks, @@ -304,7 +271,6 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder { Fragment extra_prologue, Fragment shared_prologue, Fragment body); - void RecordUncheckedEntryPoint(FunctionEntryInstr* extra_entry); // Builds flow graph for implicit closure function (tear-off). // diff --git a/runtime/vm/constants_kbc.h b/runtime/vm/constants_kbc.h index c1d5a30a84c..e1942f849b1 100644 --- a/runtime/vm/constants_kbc.h +++ b/runtime/vm/constants_kbc.h @@ -571,8 +571,8 @@ namespace dart { V(CheckFunctionTypeArgs_Wide, A_E, WIDE, num, reg, ___) \ V(CheckStack, A, ORDN, num, ___, ___) \ V(DebugCheck, 0, ORDN, ___, ___, ___) \ - V(Unused02, 0, RESV, ___, ___, ___) \ - V(Unused03, 0, RESV, ___, ___, ___) \ + V(JumpIfUnchecked, T, ORDN, tgt, ___, ___) \ + V(JumpIfUnchecked_Wide, T, WIDE, tgt, ___, ___) \ V(Allocate, D, ORDN, lit, ___, ___) \ V(Allocate_Wide, D, WIDE, lit, ___, ___) \ V(AllocateT, 0, ORDN, ___, ___, ___) \ @@ -749,7 +749,7 @@ class KernelBytecode { // Maximum bytecode format version supported by VM. // The range of supported versions should include version produced by bytecode // generator (currentBytecodeFormatVersion in pkg/vm/lib/bytecode/dbc.dart). - static const intptr_t kMaxSupportedBytecodeFormatVersion = 15; + static const intptr_t kMaxSupportedBytecodeFormatVersion = 16; enum Opcode { #define DECLARE_BYTECODE(name, encoding, kind, op1, op2, op3) k##name, @@ -885,6 +885,8 @@ class KernelBytecode { case KernelBytecode::kJumpIfNull_Wide: case KernelBytecode::kJumpIfNotNull: case KernelBytecode::kJumpIfNotNull_Wide: + case KernelBytecode::kJumpIfUnchecked: + case KernelBytecode::kJumpIfUnchecked_Wide: return true; default: @@ -892,6 +894,16 @@ class KernelBytecode { } } + DART_FORCE_INLINE static bool IsJumpIfUncheckedOpcode(const KBCInstr* instr) { + switch (DecodeOpcode(instr)) { + case KernelBytecode::kJumpIfUnchecked: + case KernelBytecode::kJumpIfUnchecked_Wide: + return true; + default: + return false; + } + } + DART_FORCE_INLINE static bool IsLoadConstantOpcode(const KBCInstr* instr) { switch (DecodeOpcode(instr)) { case KernelBytecode::kLoadConstant: diff --git a/runtime/vm/interpreter.cc b/runtime/vm/interpreter.cc index f5cfb3167ab..be7fb2685ec 100644 --- a/runtime/vm/interpreter.cc +++ b/runtime/vm/interpreter.cc @@ -2745,6 +2745,13 @@ SwitchDispatch: DISPATCH(); } + { + BYTECODE(JumpIfUnchecked, T); + // Interpreter is not tracking unchecked calls, so fall through to + // parameter type checks. + DISPATCH(); + } + { BYTECODE(StoreIndexedTOS, 0); SP -= 3; diff --git a/tools/bots/test_matrix.json b/tools/bots/test_matrix.json index 58a2b50e242..88aab1f2776 100644 --- a/tools/bots/test_matrix.json +++ b/tools/bots/test_matrix.json @@ -391,14 +391,17 @@ "app_jitk-(linux|mac|win)-(debug|product|release)-(ia32|x64)": { }, "dartkb-interpret-(linux|mac|win)-(debug|product|release)-(ia32|x64|arm|arm64|simarm|simarm64)": { "options": { + "builder-tag": "bytecode_interpreter", "vm-options": ["--enable_interpreter", "--compilation-counter-threshold=-1"] }}, "dartkb-mixed-(linux|mac|win)-(debug|product|release)-(ia32|x64|arm|arm64|simarm|simarm64)": { "options": { + "builder-tag": "bytecode_mixed", "vm-options": ["--enable_interpreter"] }}, "dartkb-compile-(linux|mac|win)-(debug|product|release)-(ia32|x64|arm|arm64|simarm|simarm64)": { "options": { + "builder-tag": "bytecode_compiler", "vm-options": ["--use_bytecode_compiler"] }}, "(dartdevc|dartdevk)-checked-(linux|mac|win)-(debug|product|release)-chrome": {