[vm/bytecode] Support multiple entry points when compiling from bytecode

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 <regis@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Alexander Markov 2019-07-20 00:05:23 +00:00 committed by commit-bot@chromium.org
parent 9cd47ac2e6
commit fedd74669a
18 changed files with 721 additions and 155 deletions

View file

@ -325,6 +325,10 @@ class BytecodeAssembler {
_emitJumpInstruction(Opcode.kJumpIfNotNull, label);
}
void emitJumpIfUnchecked(Label label) {
_emitJumpInstruction(Opcode.kJumpIfUnchecked, label);
}
void emitReturnTOS() {
emitSourcePosition();
_emitInstruction0(Opcode.kReturnTOS);

View file

@ -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<Opcode, Format> 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(

View file

@ -1827,29 +1827,116 @@ class BytecodeGenerator extends RecursiveVisitor<Null> {
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<TypeParameter, DartType> 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<VariableDeclaration, DartType> 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<TypeParameter, DartType> forwardingBounds,
Map<VariableDeclaration, DartType> 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<TypeParameter, DartType> 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<Null> {
void _genArgumentTypeCheck(VariableDeclaration variable,
Map<VariableDeclaration, DartType> 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 {

View file

@ -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

View file

@ -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

View file

@ -60,4 +60,17 @@ class E<P extends String> {
void foo6<T extends P, U extends List<T>>(Map<T, U> map) {}
}
abstract class F<T> {
void foo7<Q extends T>(Q a, covariant num b, T c);
void foo8<Q extends T>(Q a, covariant num b, T c);
}
class G<T> {
void foo7<Q extends T>(Q a, int b, T c) {}
}
class H<T> extends G<T> implements F<T> {
void foo8<Q extends T>(Q a, int b, T c) {}
}
main() {}

View file

@ -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 <dart:core::Object T> (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 <dart:core::Object T> (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 <dart:core::Object T> (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<generic-covariant-impl T extends #lib::E::P = #lib::E::P, U extends dart.core::List<#lib::E::foo6::T> = dart.core::List<#lib::E::P>>(dart.core::Map<#lib::E::foo6::T, #lib::E::foo6::U> map) → void {}
}
abstract class F<T extends dart.core::Object = dynamic> extends dart.core::Object {
synthetic constructor •() → #lib::F<#lib::F::T>
: super dart.core::Object::•()
;
abstract method foo7<generic-covariant-impl Q extends #lib::F::T = #lib::F::T>(#lib::F::foo7::Q a, covariant dart.core::num b, generic-covariant-impl #lib::F::T c) → void;
abstract method foo8<generic-covariant-impl Q extends #lib::F::T = #lib::F::T>(#lib::F::foo8::Q a, covariant dart.core::num b, generic-covariant-impl #lib::F::T c) → void;
}
class G<T extends dart.core::Object = dynamic> extends dart.core::Object {
synthetic constructor •() → #lib::G<#lib::G::T>
: super dart.core::Object::•()
;
method foo7<generic-covariant-impl Q extends #lib::G::T = #lib::G::T>(#lib::G::foo7::Q a, dart.core::int b, generic-covariant-impl #lib::G::T c) → void {}
}
class H<T extends dart.core::Object = dynamic> extends #lib::G<#lib::H::T> implements #lib::F<#lib::H::T> {
synthetic constructor •() → #lib::H<#lib::H::T>
: super #lib::G::•()
;
method foo8<generic-covariant-impl Q extends #lib::H::T = #lib::H::T>(#lib::H::foo8::Q a, dart.core::int b, generic-covariant-impl #lib::H::T c) → void {}
forwarding-stub method foo7<generic-covariant-impl Q extends #lib::H::T>(#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<dart.core::Iterable<dynamic>> globalVar;
static method foo1(dynamic x) → dynamic {
if(x is #lib::B) {

View file

@ -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.

View file

@ -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

View file

@ -108,6 +108,32 @@ class TestFragment {
typedef ZoneGrowableArray<PushArgumentInstr*>* 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_; }

View file

@ -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<intptr_t>(UncheckedEntryPointStyle::kNone);
const intptr_t kUncheckedEntry =
static_cast<intptr_t>(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<intptr_t>(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);

View file

@ -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<LocalVariable*> local_vars_;
ZoneGrowableArray<LocalVariable*> parameters_;
@ -200,6 +204,8 @@ class BytecodeFlowGraphBuilder {
IntMap<Value*> stack_states_;
PrologueInfo prologue_info_;
JoinEntryInstr* throw_no_such_method_;
GraphEntryInstr* graph_entry_ = nullptr;
UncheckedEntryPointStyle entry_point_style_ = UncheckedEntryPointStyle::kNone;
};
} // namespace kernel

View file

@ -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

View file

@ -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<Representation>& 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;

View file

@ -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<Representation>& 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).
//

View file

@ -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:

View file

@ -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;

View file

@ -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": {