diff --git a/pkg/vm/lib/bytecode/assembler.dart b/pkg/vm/lib/bytecode/assembler.dart index f80c544a375..d3840b5c81b 100644 --- a/pkg/vm/lib/bytecode/assembler.dart +++ b/pkg/vm/lib/bytecode/assembler.dart @@ -379,6 +379,11 @@ class BytecodeAssembler { _emitInstructionDF(Opcode.kInterfaceCall, rd, rf); } + void emitUncheckedClosureCall(int rd, int rf) { + emitSourcePosition(); + _emitInstructionDF(Opcode.kUncheckedClosureCall, rd, rf); + } + void emitUncheckedInterfaceCall(int rd, int rf) { emitSourcePosition(); _emitInstructionDF(Opcode.kUncheckedInterfaceCall, rd, rf); diff --git a/pkg/vm/lib/bytecode/dbc.dart b/pkg/vm/lib/bytecode/dbc.dart index 3d4c19350c8..ad5e2b77389 100644 --- a/pkg/vm/lib/bytecode/dbc.dart +++ b/pkg/vm/lib/bytecode/dbc.dart @@ -10,7 +10,7 @@ library vm.bytecode.dbc; /// Before bumping current bytecode version format, make sure that /// all users have switched to a VM which is able to consume new /// version of bytecode. -const int currentBytecodeFormatVersion = 14; +const int currentBytecodeFormatVersion = 15; enum Opcode { kUnusedOpcode000, @@ -218,8 +218,8 @@ enum Opcode { kUnused24, // Reserved for InterfaceCall1_Wide kUnused25, // Reserved for InterfaceCall2 kUnused26, // Reserved for InterfaceCall2_Wide - kUnused27, // Reserved for InterfaceCall3 - kUnused28, // Reserved for InterfaceCall3_Wide + kUncheckedClosureCall, + kUncheckedClosureCall_Wide, kUncheckedInterfaceCall, kUncheckedInterfaceCall_Wide, kDynamicCall, @@ -500,6 +500,8 @@ const Map BytecodeFormats = const { Encoding.kDF, const [Operand.lit, Operand.imm, Operand.none]), Opcode.kAllocateClosure: const Format( Encoding.kD, const [Operand.lit, Operand.none, Operand.none]), + Opcode.kUncheckedClosureCall: const Format( + Encoding.kDF, const [Operand.lit, Operand.imm, Operand.none]), Opcode.kUncheckedInterfaceCall: const Format( Encoding.kDF, const [Operand.lit, Operand.imm, Operand.none]), Opcode.kNegateDouble: const Format( @@ -603,6 +605,7 @@ bool isCall(Opcode opcode) { switch (opcode) { case Opcode.kDirectCall: case Opcode.kInterfaceCall: + case Opcode.kUncheckedClosureCall: case Opcode.kUncheckedInterfaceCall: case Opcode.kDynamicCall: case Opcode.kNativeCall: diff --git a/pkg/vm/lib/bytecode/gen_bytecode.dart b/pkg/vm/lib/bytecode/gen_bytecode.dart index 4d6eef744a6..ad3ec55f40f 100644 --- a/pkg/vm/lib/bytecode/gen_bytecode.dart +++ b/pkg/vm/lib/bytecode/gen_bytecode.dart @@ -42,7 +42,8 @@ import 'generics.dart' getInstantiatorTypeArguments, hasFreeTypeParameters, hasInstantiatorTypeArguments, - isUncheckedCall; + isUncheckedCall, + isUncheckedClosureCall; import 'local_variable_table.dart' show LocalVariableTable; import 'local_vars.dart' show LocalVariables; import 'nullability_detector.dart' show NullabilityDetector; @@ -928,11 +929,15 @@ class BytecodeGenerator extends RecursiveVisitor { initializedFields.add(field); } - void _genArguments(Expression receiver, Arguments arguments) { + void _genArguments(Expression receiver, Arguments arguments, + {int storeReceiverToLocal}) { if (arguments.types.isNotEmpty) { _genTypeArguments(arguments.types); } _generateNode(receiver); + if (storeReceiverToLocal != null) { + asm.emitStoreLocal(storeReceiverToLocal); + } _generateNodeList(arguments.positional); arguments.named.forEach((NamedExpression ne) => _generateNode(ne.value)); } @@ -1366,7 +1371,7 @@ class BytecodeGenerator extends RecursiveVisitor { savedMaxSourcePositions = []; maxSourcePosition = node.fileOffset; - locals = new LocalVariables(node, options); + locals = new LocalVariables(node, options, typeEnvironment); locals.enterScope(node); assert(!locals.isSyncYieldingFrame); @@ -2678,9 +2683,6 @@ class BytecodeGenerator extends RecursiveVisitor { void _genInstanceCall( int totalArgCount, int callCpIndex, bool isDynamic, bool isUnchecked, [TreeNode context]) { - if (totalArgCount >= argumentsLimit) { - throw new TooManyArgumentsException(context.fileOffset); - } if (isDynamic) { assert(!isUnchecked); asm.emitDynamicCall(callCpIndex, totalArgCount); @@ -2699,6 +2701,27 @@ class BytecodeGenerator extends RecursiveVisitor { return; } final args = node.arguments; + final totalArgCount = args.positional.length + + args.named.length + + 1 /* receiver */ + + (args.types.isNotEmpty ? 1 : 0) /* type arguments */; + if (totalArgCount >= argumentsLimit) { + throw new TooManyArgumentsException(node.fileOffset); + } + // Front-end guarantees that all calls with known function type + // do not need any argument type checks. + if (isUncheckedClosureCall(node, typeEnvironment)) { + final int receiverTemp = locals.tempIndexInFrame(node); + _genArguments(node.receiver, args, storeReceiverToLocal: receiverTemp); + // Duplicate receiver (closure) for UncheckedClosureCall. + asm.emitPush(receiverTemp); + final argDescCpIndex = cp.addArgDescByArguments(args, hasReceiver: true); + asm.emitUncheckedClosureCall(argDescCpIndex, totalArgCount); + return; + } + + _genArguments(node.receiver, args); + Member interfaceTarget = node.interfaceTarget; if (interfaceTarget is Field || interfaceTarget is Procedure && interfaceTarget.isGetter) { @@ -2709,15 +2732,10 @@ class BytecodeGenerator extends RecursiveVisitor { final isDynamic = interfaceTarget == null; final isUnchecked = isUncheckedCall(interfaceTarget, node.receiver, typeEnvironment); - _genArguments(node.receiver, args); final argDesc = objectTable.getArgDescHandleByArguments(args, hasReceiver: true); final callCpIndex = cp.addInstanceCall( InvocationKind.method, interfaceTarget, node.name, argDesc); - final totalArgCount = args.positional.length + - args.named.length + - 1 /* receiver */ + - (args.types.isNotEmpty ? 1 : 0) /* type arguments */; _genInstanceCall(totalArgCount, callCpIndex, isDynamic, isUnchecked, node); } diff --git a/pkg/vm/lib/bytecode/generics.dart b/pkg/vm/lib/bytecode/generics.dart index 91f8a70e245..d1ff3192ef5 100644 --- a/pkg/vm/lib/bytecode/generics.dart +++ b/pkg/vm/lib/bytecode/generics.dart @@ -176,9 +176,9 @@ bool isSealedType(DartType type, CoreTypes coreTypes) { return false; } -// Returns true if an instance call to [interfaceTarget] with given -// [receiver] can omit argument type checks needed due to generic-covariant -// parameters. +/// Returns true if an instance call to [interfaceTarget] with given +/// [receiver] can omit argument type checks needed due to generic-covariant +/// parameters. bool isUncheckedCall(Member interfaceTarget, Expression receiver, TypeEnvironment typeEnvironment) { if (interfaceTarget == null) { @@ -229,3 +229,10 @@ bool _hasGenericCovariantParameters(Member target) { throw 'Unexpected instance call target ${target.runtimeType} $target'; } } + +/// Returns true if invocation [node] is a closure call with statically known +/// function type. Such invocations can omit argument type checks. +bool isUncheckedClosureCall( + MethodInvocation node, TypeEnvironment typeEnvironment) => + node.name.name == 'call' && + getStaticType(node.receiver, typeEnvironment) is FunctionType; diff --git a/pkg/vm/lib/bytecode/local_vars.dart b/pkg/vm/lib/bytecode/local_vars.dart index 1f202426de2..d75f4a0b4d8 100644 --- a/pkg/vm/lib/bytecode/local_vars.dart +++ b/pkg/vm/lib/bytecode/local_vars.dart @@ -9,6 +9,8 @@ import 'dart:math' show max; import 'package:kernel/ast.dart'; import 'package:kernel/transformations/continuation.dart' show ContinuationVariables; +import 'package:kernel/type_environment.dart'; +import 'package:vm/bytecode/generics.dart'; import 'dbc.dart'; import 'options.dart' show BytecodeOptions; @@ -27,6 +29,7 @@ class LocalVariables { final Map _capturedIteratorVars = {}; final BytecodeOptions options; + final TypeEnvironment typeEnvironment; Scope _currentScope; Frame _currentFrame; @@ -185,7 +188,7 @@ class LocalVariables { List get sortedNamedParameters => _currentFrame.sortedNamedParameters; - LocalVariables(Member node, this.options) { + LocalVariables(Member node, this.options, this.typeEnvironment) { final scopeBuilder = new _ScopeBuilder(this); node.accept(scopeBuilder); @@ -1191,6 +1194,15 @@ class _Allocator extends RecursiveVisitor { _visit(node, temps: 1); } + @override + visitMethodInvocation(MethodInvocation node) { + int numTemps = 0; + if (isUncheckedClosureCall(node, locals.typeEnvironment)) { + numTemps = 1; + } + _visit(node, temps: numTemps); + } + @override visitPropertySet(PropertySet node) { _visit(node, temps: 1); diff --git a/pkg/vm/testcases/bytecode/asserts.dart.expect b/pkg/vm/testcases/bytecode/asserts.dart.expect index e1323cb11ac..1e5fb8130a0 100644 --- a/pkg/vm/testcases/bytecode/asserts.dart.expect +++ b/pkg/vm/testcases/bytecode/asserts.dart.expect @@ -41,18 +41,22 @@ Function 'test2', static, reflectable, debuggable return-type void Bytecode { - Entry 0 + Entry 1 CheckStack 0 JumpIfNoAsserts L1 Push FP[-6] - DynamicCall CP#1, 1 + StoreLocal r0 + Push r0 + UncheckedClosureCall CP#0, 1 AssertBoolean 0 JumpIfTrue L1 PushInt 0 PushInt 0 Push FP[-5] - DynamicCall CP#2, 1 - DirectCall CP#3, 3 + StoreLocal r0 + Push r0 + UncheckedClosureCall CP#0, 1 + DirectCall CP#1, 3 Drop1 L1: PushNull @@ -60,10 +64,8 @@ L1: } ConstantPool { [0] = ObjectRef ArgDesc num-args 1, num-type-args 0, names [] - [1] = ICData dynamic target-name 'call', arg-desc CP#0 - [2] = ICData dynamic target-name 'call', arg-desc CP#0 - [3] = DirectCall 'dart:core::_AssertionError::_throwNew', ArgDesc num-args 3, num-type-args 0, names [] - [4] = Reserved + [1] = DirectCall 'dart:core::_AssertionError::_throwNew', ArgDesc num-args 3, num-type-args 0, names [] + [2] = Reserved } diff --git a/pkg/vm/testcases/bytecode/closures.dart.expect b/pkg/vm/testcases/bytecode/closures.dart.expect index 2932382f81f..c790306ed60 100644 --- a/pkg/vm/testcases/bytecode/closures.dart.expect +++ b/pkg/vm/testcases/bytecode/closures.dart.expect @@ -41,8 +41,10 @@ Bytecode { StoreFieldTOS CP#1 PopLocal r2 Push r2 + StoreLocal r3 PushInt 3 - DynamicCall CP#17, 2 + Push r3 + UncheckedClosureCall CP#16, 2 Drop1 Push r0 LoadContextVar 0, 0 @@ -66,7 +68,6 @@ ConstantPool { [14] = InstanceField dart:core::_Closure::_function (field) [15] = Reserved [16] = ObjectRef ArgDesc num-args 2, num-type-args 0, names [] - [17] = ICData dynamic target-name 'call', arg-desc CP#16 } Closure #lib::simpleClosure::'' (dart:core::int y) -> dart:core::Null ClosureCode { @@ -594,13 +595,17 @@ Bytecode { Push r1 StoreFieldTOS CP#1 PopLocal r3 - PushConstant CP#43 + PushConstant CP#40 Push r3 - DynamicCall CP#44, 2 + StoreLocal r4 + Push r4 + UncheckedClosureCall CP#37, 2 Drop1 - PushConstant CP#45 + PushConstant CP#41 Push r3 - DynamicCall CP#46, 2 + StoreLocal r4 + Push r4 + UncheckedClosureCall CP#37, 2 Drop1 PushNull ReturnTOS @@ -641,18 +646,13 @@ ConstantPool { [32] = InstanceField dart:core::_Closure::_function (field) [33] = Reserved [34] = ObjectRef ArgDesc num-args 1, num-type-args 0, names [] - [35] = ICData dynamic target-name 'call', arg-desc CP#34 - [36] = EndClosureFunctionScope - [37] = ObjectRef < #lib::C7, #lib::C8 > - [38] = ObjectRef ArgDesc num-args 1, num-type-args 2, names [] - [39] = ICData dynamic target-name 'call', arg-desc CP#38 - [40] = ObjectRef < dart:core::List < #lib::C7 >, dart:core::List < #lib::C8 > > - [41] = ICData dynamic target-name 'call', arg-desc CP#38 - [42] = EndClosureFunctionScope - [43] = ObjectRef < #lib::C5, #lib::C6 > - [44] = ICData dynamic target-name 'call', arg-desc CP#38 - [45] = ObjectRef < dart:core::List < #lib::C5 >, dart:core::List < #lib::C6 > > - [46] = ICData dynamic target-name 'call', arg-desc CP#38 + [35] = EndClosureFunctionScope + [36] = ObjectRef < #lib::C7, #lib::C8 > + [37] = ObjectRef ArgDesc num-args 1, num-type-args 2, names [] + [38] = ObjectRef < dart:core::List < #lib::C7 >, dart:core::List < #lib::C8 > > + [39] = EndClosureFunctionScope + [40] = ObjectRef < #lib::C5, #lib::C6 > + [41] = ObjectRef < dart:core::List < #lib::C5 >, dart:core::List < #lib::C6 > > } Closure #lib::A::foo::'nested1' () -> void ClosureCode { @@ -698,13 +698,17 @@ L2: Push r1 StoreFieldTOS CP#1 PopLocal r3 - PushConstant CP#37 + PushConstant CP#36 Push r3 - DynamicCall CP#39, 2 + StoreLocal r4 + Push r4 + UncheckedClosureCall CP#37, 2 Drop1 - PushConstant CP#40 + PushConstant CP#38 Push r3 - DynamicCall CP#41, 2 + StoreLocal r4 + Push r4 + UncheckedClosureCall CP#37, 2 Drop1 PushNull ReturnTOS @@ -755,7 +759,9 @@ L2: StoreFieldTOS CP#1 PopLocal r3 Push r3 - DynamicCall CP#35, 1 + StoreLocal r4 + Push r4 + UncheckedClosureCall CP#34, 1 Drop1 PushNull ReturnTOS @@ -908,28 +914,32 @@ Bytecode { StoreFieldTOS CP#1 PopLocal r3 Push r3 + StoreLocal r4 PushInt 10 - DynamicCall CP#25, 2 + Push r4 + UncheckedClosureCall CP#23, 2 Drop1 Push r3 + StoreLocal r4 PushInt 11 - DynamicCall CP#26, 2 + Push r4 + UncheckedClosureCall CP#23, 2 Drop1 Push r2 - DirectCall CP#21, 1 + DirectCall CP#20, 1 Drop1 Push r0 LoadContextVar 0, 2 - DirectCall CP#21, 1 + DirectCall CP#20, 1 Drop1 Push r0 LoadContextVar 0, 1 - DirectCall CP#21, 1 + DirectCall CP#20, 1 Drop1 Push r0 PushInt 42 StoreContextVar 0, 3 - AllocateClosure CP#27 + AllocateClosure CP#24 StoreLocal r3 Push r3 PushNull @@ -941,14 +951,16 @@ Bytecode { PushConstant CP#14 StoreFieldTOS CP#15 Push r3 - PushConstant CP#27 + PushConstant CP#24 StoreFieldTOS CP#17 Push r3 Push r0 StoreFieldTOS CP#1 PopLocal r2 Push r2 - DynamicCall CP#31, 1 + StoreLocal r3 + Push r3 + UncheckedClosureCall CP#19, 1 Drop1 PushNull ReturnTOS @@ -974,18 +986,14 @@ ConstantPool { [17] = InstanceField dart:core::_Closure::_function (field) [18] = Reserved [19] = ObjectRef ArgDesc num-args 1, num-type-args 0, names [] - [20] = ICData dynamic target-name 'call', arg-desc CP#19 - [21] = DirectCall 'dart:core::print', ArgDesc num-args 1, num-type-args 0, names [] - [22] = Reserved - [23] = EndClosureFunctionScope - [24] = ObjectRef ArgDesc num-args 2, num-type-args 0, names [] - [25] = ICData dynamic target-name 'call', arg-desc CP#24 - [26] = ICData dynamic target-name 'call', arg-desc CP#24 - [27] = ClosureFunction 2 - [28] = InterfaceCall '#lib::B::set:foo', ArgDesc num-args 2, num-type-args 0, names [] - [29] = Reserved - [30] = EndClosureFunctionScope - [31] = ICData dynamic target-name 'call', arg-desc CP#19 + [20] = DirectCall 'dart:core::print', ArgDesc num-args 1, num-type-args 0, names [] + [21] = Reserved + [22] = EndClosureFunctionScope + [23] = ObjectRef ArgDesc num-args 2, num-type-args 0, names [] + [24] = ClosureFunction 2 + [25] = InterfaceCall '#lib::B::set:foo', ArgDesc num-args 2, num-type-args 0, names [] + [26] = Reserved + [27] = EndClosureFunctionScope } Closure #lib::B::topLevel::'' (dart:core::int y) -> dart:core::Null ClosureCode { @@ -1045,11 +1053,13 @@ ClosureCode { StoreFieldTOS CP#1 PopLocal r3 Push r3 - DynamicCall CP#20, 1 + StoreLocal r2 + Push r2 + UncheckedClosureCall CP#19, 1 Drop1 Push r0 LoadContextVar 1, 1 - DirectCall CP#21, 1 + DirectCall CP#20, 1 Drop1 L1: PushNull @@ -1095,7 +1105,7 @@ ClosureCode { LoadContextVar 0, 0 Push r0 LoadContextVar 0, 3 - InterfaceCall CP#28, 2 + InterfaceCall CP#25, 2 Drop1 PushNull ReturnTOS @@ -1331,11 +1341,13 @@ L2: StoreFieldTOS CP#7 PopLocal r3 Push r3 - DynamicCall CP#20, 1 + StoreLocal r4 + Push r4 + UncheckedClosureCall CP#19, 1 Drop1 Push r0 LoadContextVar 0, 0 - DirectCall CP#21, 1 + DirectCall CP#20, 1 Drop1 Push r0 LoadContextParent @@ -1366,9 +1378,8 @@ ConstantPool { [17] = InstanceField dart:core::_Closure::_function (field) [18] = Reserved [19] = ObjectRef ArgDesc num-args 1, num-type-args 0, names [] - [20] = ICData dynamic target-name 'call', arg-desc CP#19 - [21] = DirectCall 'dart:core::print', ArgDesc num-args 1, num-type-args 0, names [] - [22] = Reserved + [20] = DirectCall 'dart:core::print', ArgDesc num-args 1, num-type-args 0, names [] + [21] = Reserved } Closure #lib::C::testForInLoop::'' () -> dart:core::Null ClosureCode { @@ -1533,8 +1544,7 @@ ConstantPool { [13] = InstanceField dart:core::_Closure::_function (field) [14] = Reserved [15] = ObjectRef ArgDesc num-args 1, num-type-args 0, names [] - [16] = ICData dynamic target-name 'call', arg-desc CP#15 - [17] = EndClosureFunctionScope + [16] = EndClosureFunctionScope } Closure #lib::D::bar::'' () -> dart:core::Null ClosureCode { @@ -1564,7 +1574,9 @@ ClosureCode { StoreFieldTOS CP#1 PopLocal r2 Push r2 - DynamicCall CP#16, 1 + StoreLocal r3 + Push r3 + UncheckedClosureCall CP#15, 1 Drop1 PushNull ReturnTOS diff --git a/pkg/vm/testcases/bytecode/try_blocks.dart.expect b/pkg/vm/testcases/bytecode/try_blocks.dart.expect index f1ef3d47395..d06c91839b5 100644 --- a/pkg/vm/testcases/bytecode/try_blocks.dart.expect +++ b/pkg/vm/testcases/bytecode/try_blocks.dart.expect @@ -237,7 +237,9 @@ Try #0 start: StoreFieldTOS CP#1 PopLocal r4 Push r4 - DynamicCall CP#18, 1 + StoreLocal r5 + Push r5 + UncheckedClosureCall CP#17, 1 Drop1 Push r0 LoadContextVar 0, 1 @@ -262,7 +264,7 @@ Try #0 handler: StoreLocal r5 Push r5 PushInt 0 - PushConstant CP#19 + PushConstant CP#18 StoreIndexedTOS Push r5 PushInt 1 @@ -270,17 +272,17 @@ Try #0 handler: StoreIndexedTOS Push r5 PushInt 2 - PushConstant CP#20 + PushConstant CP#19 StoreIndexedTOS Push r5 PushInt 3 Push r0 LoadContextVar 0, 2 StoreIndexedTOS - DirectCall CP#21, 1 + DirectCall CP#20, 1 DirectCall CP#4, 1 Drop1 - AllocateClosure CP#23 + AllocateClosure CP#22 StoreLocal r5 Push r5 PushNull @@ -292,7 +294,7 @@ Try #0 handler: PushConstant CP#12 StoreFieldTOS CP#13 Push r5 - PushConstant CP#23 + PushConstant CP#22 StoreFieldTOS CP#15 Push r5 Push r0 @@ -308,7 +310,7 @@ L1: ReturnTOS } ExceptionsTable { - try-index 0, outer -1, start 20, end 80, handler 80, needs-stack-trace, types [CP#6] + try-index 0, outer -1, start 20, end 84, handler 84, needs-stack-trace, types [CP#6] } ConstantPool { [0] = ClosureFunction 0 @@ -329,19 +331,18 @@ ConstantPool { [15] = InstanceField dart:core::_Closure::_function (field) [16] = Reserved [17] = ObjectRef ArgDesc num-args 1, num-type-args 0, names [] - [18] = ICData dynamic target-name 'call', arg-desc CP#17 - [19] = ObjectRef 'caught ' - [20] = ObjectRef ' ' - [21] = DirectCall 'dart:core::_StringBase::_interpolate', ArgDesc num-args 1, num-type-args 0, names [] - [22] = Reserved - [23] = ClosureFunction 1 - [24] = ObjectRef 'danger bar' - [25] = Type dart:core::Error - [26] = InterfaceCall 'dart:core::Object::_simpleInstanceOf', ArgDesc num-args 2, num-type-args 0, names [] - [27] = Reserved - [28] = ObjectRef 'error ' - [29] = ObjectRef ', captured stack trace: ' - [30] = EndClosureFunctionScope + [18] = ObjectRef 'caught ' + [19] = ObjectRef ' ' + [20] = DirectCall 'dart:core::_StringBase::_interpolate', ArgDesc num-args 1, num-type-args 0, names [] + [21] = Reserved + [22] = ClosureFunction 1 + [23] = ObjectRef 'danger bar' + [24] = Type dart:core::Error + [25] = InterfaceCall 'dart:core::Object::_simpleInstanceOf', ArgDesc num-args 2, num-type-args 0, names [] + [26] = Reserved + [27] = ObjectRef 'error ' + [28] = ObjectRef ', captured stack trace: ' + [29] = EndClosureFunctionScope } Closure #lib::testTryCatch3::'foo' () -> void ClosureCode { @@ -389,7 +390,7 @@ ClosureCode { Push r0 PopLocal r2 Try #0 start: - PushConstant CP#24 + PushConstant CP#23 DirectCall CP#4, 1 Drop1 Jump L1 @@ -401,8 +402,8 @@ Try #0 handler: MoveSpecial exception, r2 MoveSpecial stackTrace, r3 Push r2 - PushConstant CP#25 - InterfaceCall CP#26, 2 + PushConstant CP#24 + InterfaceCall CP#25, 2 JumpIfFalse L2 Push r2 PopLocal r4 @@ -412,7 +413,7 @@ Try #0 handler: StoreLocal r5 Push r5 PushInt 0 - PushConstant CP#28 + PushConstant CP#27 StoreIndexedTOS Push r5 PushInt 1 @@ -420,14 +421,14 @@ Try #0 handler: StoreIndexedTOS Push r5 PushInt 2 - PushConstant CP#29 + PushConstant CP#28 StoreIndexedTOS Push r5 PushInt 3 Push r0 LoadContextVar 0, 2 StoreIndexedTOS - DirectCall CP#21, 1 + DirectCall CP#20, 1 DirectCall CP#4, 1 Drop1 Jump L1 @@ -646,7 +647,9 @@ Try #1 start: StoreFieldTOS CP#7 PopLocal r7 Push r7 - DynamicCall CP#20, 1 + StoreLocal r8 + Push r8 + UncheckedClosureCall CP#19, 1 Drop1 Jump L4 Try #1 end: @@ -656,7 +659,7 @@ Try #1 handler: PopLocal r0 MoveSpecial exception, r5 MoveSpecial stackTrace, r6 - PushConstant CP#22 + PushConstant CP#21 DirectCall CP#3, 1 Drop1 Push r5 @@ -665,7 +668,7 @@ Try #1 handler: L4: Push r5 PopLocal r0 - PushConstant CP#22 + PushConstant CP#21 DirectCall CP#3, 1 Drop1 Jump L5 @@ -676,7 +679,7 @@ Try #0 handler: PopLocal r0 MoveSpecial exception, r3 MoveSpecial stackTrace, r4 - PushConstant CP#24 + PushConstant CP#23 DirectCall CP#3, 1 Drop1 Push r3 @@ -685,12 +688,12 @@ Try #0 handler: L5: Push r3 PopLocal r0 - PushConstant CP#24 + PushConstant CP#23 DirectCall CP#3, 1 Drop1 Jump L2 L2: - PushConstant CP#25 + PushConstant CP#24 DirectCall CP#3, 1 Drop1 Jump L3 @@ -699,8 +702,8 @@ L3: ReturnTOS } ExceptionsTable { - try-index 0, outer -1, start 53, end 158, handler 158, needs-stack-trace, types [CP#21] - try-index 1, outer 0, start 70, end 120, handler 120, needs-stack-trace, types [CP#21] + try-index 0, outer -1, start 53, end 162, handler 162, needs-stack-trace, types [CP#20] + try-index 1, outer 0, start 70, end 124, handler 124, needs-stack-trace, types [CP#20] } ConstantPool { [0] = InterfaceCall 'dart:core::Object::==', ArgDesc num-args 2, num-type-args 0, names [] @@ -723,12 +726,11 @@ ConstantPool { [17] = InstanceField dart:core::_Closure::_function (field) [18] = Reserved [19] = ObjectRef ArgDesc num-args 1, num-type-args 0, names [] - [20] = ICData dynamic target-name 'call', arg-desc CP#19 - [21] = Type dynamic - [22] = ObjectRef 'finally 1' - [23] = ObjectRef 'after try 1' - [24] = ObjectRef 'finally 2' - [25] = ObjectRef 'case 2' + [20] = Type dynamic + [21] = ObjectRef 'finally 1' + [22] = ObjectRef 'after try 1' + [23] = ObjectRef 'finally 2' + [24] = ObjectRef 'case 2' } Closure #lib::testTryFinally2::'foo' () -> void ClosureCode { diff --git a/runtime/vm/compiler/assembler/disassembler_kbc.cc b/runtime/vm/compiler/assembler/disassembler_kbc.cc index 0a1046ae480..e6d80052414 100644 --- a/runtime/vm/compiler/assembler/disassembler_kbc.cc +++ b/runtime/vm/compiler/assembler/disassembler_kbc.cc @@ -259,6 +259,8 @@ static intptr_t GetConstantPoolIndex(const KBCInstr* instr) { case KernelBytecode::kDirectCall_Wide: case KernelBytecode::kInterfaceCall: case KernelBytecode::kInterfaceCall_Wide: + case KernelBytecode::kUncheckedClosureCall: + case KernelBytecode::kUncheckedClosureCall_Wide: case KernelBytecode::kUncheckedInterfaceCall: case KernelBytecode::kUncheckedInterfaceCall_Wide: case KernelBytecode::kDynamicCall: diff --git a/runtime/vm/compiler/frontend/base_flow_graph_builder.cc b/runtime/vm/compiler/frontend/base_flow_graph_builder.cc index 6e844d638ef..b0f24a6d1c4 100644 --- a/runtime/vm/compiler/frontend/base_flow_graph_builder.cc +++ b/runtime/vm/compiler/frontend/base_flow_graph_builder.cc @@ -882,6 +882,28 @@ Fragment BaseFlowGraphBuilder::DebugStepCheck(TokenPosition position) { #endif } +Fragment BaseFlowGraphBuilder::CheckNull(TokenPosition position, + LocalVariable* receiver, + const String& function_name, + bool clear_the_temp /* = true */) { + Fragment instructions = LoadLocal(receiver); + + CheckNullInstr* check_null = + new (Z) CheckNullInstr(Pop(), function_name, GetNextDeoptId(), position); + + instructions <<= check_null; + + if (clear_the_temp) { + // Null out receiver to make sure it is not saved into the frame before + // doing the call. + instructions += NullConstant(); + instructions += StoreLocal(TokenPosition::kNoSource, receiver); + instructions += Drop(); + } + + return instructions; +} + } // namespace kernel } // namespace dart diff --git a/runtime/vm/compiler/frontend/base_flow_graph_builder.h b/runtime/vm/compiler/frontend/base_flow_graph_builder.h index dd2298fc32f..342d4a0b5cd 100644 --- a/runtime/vm/compiler/frontend/base_flow_graph_builder.h +++ b/runtime/vm/compiler/frontend/base_flow_graph_builder.h @@ -303,6 +303,15 @@ class BaseFlowGraphBuilder { Fragment DebugStepCheck(TokenPosition position); + // Loads 'receiver' and checks it for null. Throws NoSuchMethod if it is null. + // 'function_name' is a selector which is being called (reported in + // NoSuchMethod message). + // Sets 'receiver' to 'null' after the check if 'clear_the_temp'. + Fragment CheckNull(TokenPosition position, + LocalVariable* receiver, + const String& function_name, + bool clear_the_temp = true); + protected: intptr_t AllocateBlockId() { return ++last_used_block_id_; } diff --git a/runtime/vm/compiler/frontend/bytecode_flow_graph_builder.cc b/runtime/vm/compiler/frontend/bytecode_flow_graph_builder.cc index 6a8553ca2e8..f3da2a682c9 100644 --- a/runtime/vm/compiler/frontend/bytecode_flow_graph_builder.cc +++ b/runtime/vm/compiler/frontend/bytecode_flow_graph_builder.cc @@ -879,6 +879,35 @@ void BytecodeFlowGraphBuilder::BuildUncheckedInterfaceCall() { BuildInterfaceCallCommon(/*is_unchecked_call=*/true); } +void BytecodeFlowGraphBuilder::BuildUncheckedClosureCall() { + if (is_generating_interpreter()) { + UNIMPLEMENTED(); // TODO(alexmarkov): interpreter + } + + const Array& arg_desc_array = + Array::Cast(ConstantAt(DecodeOperandD()).value()); + const ArgumentsDescriptor arg_desc(arg_desc_array); + + const intptr_t argc = DecodeOperandF().value(); + + LocalVariable* receiver_temp = B->MakeTemporary(); + code_ += B->CheckNull(position_, receiver_temp, Symbols::Call(), + /*clear_temp=*/false); + + code_ += B->LoadNativeField(Slot::Closure_function()); + Value* function = Pop(); + + const ArgumentArray arguments = GetArguments(argc); + + ClosureCallInstr* call = new (Z) ClosureCallInstr( + function, arguments, arg_desc.TypeArgsLen(), + Array::ZoneHandle(Z, arg_desc.GetArgumentNames()), position_, + B->GetNextDeoptId(), Code::EntryKind::kUnchecked); + + code_ <<= call; + B->Push(call); +} + void BytecodeFlowGraphBuilder::BuildDynamicCall() { if (is_generating_interpreter()) { UNIMPLEMENTED(); // TODO(alexmarkov): interpreter diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc index 55ee9eaac96..60e150d38f5 100644 --- a/runtime/vm/compiler/frontend/kernel_to_il.cc +++ b/runtime/vm/compiler/frontend/kernel_to_il.cc @@ -479,28 +479,6 @@ Fragment FlowGraphBuilder::Return(TokenPosition position, return instructions; } -Fragment FlowGraphBuilder::CheckNull(TokenPosition position, - LocalVariable* receiver, - const String& function_name, - bool clear_the_temp /* = true */) { - Fragment instructions = LoadLocal(receiver); - - CheckNullInstr* check_null = - new (Z) CheckNullInstr(Pop(), function_name, GetNextDeoptId(), position); - - instructions <<= check_null; - - if (clear_the_temp) { - // Null out receiver to make sure it is not saved into the frame before - // doing the call. - instructions += NullConstant(); - instructions += StoreLocal(TokenPosition::kNoSource, receiver); - instructions += Drop(); - } - - return instructions; -} - Fragment FlowGraphBuilder::StaticCall(TokenPosition position, const Function& target, intptr_t argument_count, diff --git a/runtime/vm/compiler/frontend/kernel_to_il.h b/runtime/vm/compiler/frontend/kernel_to_il.h index 991a45c017d..c2185bd70c3 100644 --- a/runtime/vm/compiler/frontend/kernel_to_il.h +++ b/runtime/vm/compiler/frontend/kernel_to_il.h @@ -165,10 +165,6 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder { Fragment InitStaticField(const Field& field); Fragment NativeCall(const String* name, const Function* function); Fragment Return(TokenPosition position, bool omit_result_type_check = false); - Fragment CheckNull(TokenPosition position, - LocalVariable* receiver, - const String& function_name, - bool clear_the_temp = true); void SetResultTypeForStaticCall(StaticCallInstr* call, const Function& target, intptr_t argument_count, diff --git a/runtime/vm/constants_kbc.h b/runtime/vm/constants_kbc.h index 5c8269bf6b0..16cfb6eda44 100644 --- a/runtime/vm/constants_kbc.h +++ b/runtime/vm/constants_kbc.h @@ -657,8 +657,8 @@ namespace dart { V(Unused24, 0, RESV, ___, ___, ___) \ V(Unused25, 0, RESV, ___, ___, ___) \ V(Unused26, 0, RESV, ___, ___, ___) \ - V(Unused27, 0, RESV, ___, ___, ___) \ - V(Unused28, 0, RESV, ___, ___, ___) \ + V(UncheckedClosureCall, D_F, ORDN, num, num, ___) \ + V(UncheckedClosureCall_Wide, D_F, WIDE, num, num, ___) \ V(UncheckedInterfaceCall, D_F, ORDN, num, num, ___) \ V(UncheckedInterfaceCall_Wide, D_F, WIDE, num, num, ___) \ V(DynamicCall, D_F, ORDN, num, num, ___) \ @@ -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 = 14; + static const intptr_t kMaxSupportedBytecodeFormatVersion = 15; enum Opcode { #define DECLARE_BYTECODE(name, encoding, kind, op1, op2, op3) k##name, @@ -973,6 +973,8 @@ class KernelBytecode { case KernelBytecode::kDirectCall_Wide: case KernelBytecode::kInterfaceCall: case KernelBytecode::kInterfaceCall_Wide: + case KernelBytecode::kUncheckedClosureCall: + case KernelBytecode::kUncheckedClosureCall_Wide: case KernelBytecode::kUncheckedInterfaceCall: case KernelBytecode::kUncheckedInterfaceCall_Wide: case KernelBytecode::kDynamicCall: diff --git a/runtime/vm/interpreter.cc b/runtime/vm/interpreter.cc index b437ea46326..977ab87c034 100644 --- a/runtime/vm/interpreter.cc +++ b/runtime/vm/interpreter.cc @@ -1996,6 +1996,33 @@ SwitchDispatch: DISPATCH(); } + { + BYTECODE(UncheckedClosureCall, D_F); + DEBUG_CHECK; + { + const uint32_t argc = rF; + const uint32_t kidx = rD; + + RawClosure* receiver = Closure::RawCast(*SP--); + RawObject** call_base = SP - argc + 1; + RawObject** call_top = SP + 1; + + InterpreterHelpers::IncrementUsageCounter(FrameFunction(FP)); + if (UNLIKELY(receiver == null_value)) { + SP[0] = Symbols::Call().raw(); + goto ThrowNullError; + } + argdesc_ = static_cast(LOAD_CONSTANT(kidx)); + call_top[0] = receiver->ptr()->function_; + + if (!Invoke(thread, call_base, call_top, &pc, &FP, &SP)) { + HANDLE_EXCEPTION; + } + } + + DISPATCH(); + } + { BYTECODE(UncheckedInterfaceCall, D_F); DEBUG_CHECK;