mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 00:39:49 +00:00
[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:
parent
9cd47ac2e6
commit
fedd74669a
|
@ -325,6 +325,10 @@ class BytecodeAssembler {
|
|||
_emitJumpInstruction(Opcode.kJumpIfNotNull, label);
|
||||
}
|
||||
|
||||
void emitJumpIfUnchecked(Label label) {
|
||||
_emitJumpInstruction(Opcode.kJumpIfUnchecked, label);
|
||||
}
|
||||
|
||||
void emitReturnTOS() {
|
||||
emitSourcePosition();
|
||||
_emitInstruction0(Opcode.kReturnTOS);
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -1827,29 +1827,116 @@ class BytecodeGenerator extends RecursiveVisitor<Null> {
|
|||
final forwardingParamTypes = _getForwardingParameterTypes(
|
||||
function, forwardingTarget, forwardingSubstitution);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void _genTypeParameterBoundCheck(TypeParameter typeParam,
|
||||
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) {
|
||||
if (param.isCovariant &&
|
||||
_parameterNeedsTypeCheck(param, forwardingParamTypes)) {
|
||||
_genArgumentTypeCheck(param, forwardingParamTypes);
|
||||
}
|
||||
}
|
||||
for (var param in locals.sortedNamedParameters) {
|
||||
if (param.isCovariant &&
|
||||
_parameterNeedsTypeCheck(param, forwardingParamTypes)) {
|
||||
_genArgumentTypeCheck(param, forwardingParamTypes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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_; }
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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).
|
||||
//
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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": {
|
||||
|
|
Loading…
Reference in a new issue