From e09282c8f2bfda1dfa5bef64e4c63efd71953764 Mon Sep 17 00:00:00 2001 From: Alexander Markov Date: Wed, 13 May 2026 05:58:22 -0700 Subject: [PATCH] [modular_aot] Closures, part 2 Issue: https://github.com/dart-lang/sdk/issues/61635 Change-Id: Id8029dddd75b5f771ddf8197190d18aea9efd192 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/503080 Commit-Queue: Alexander Markov Reviewed-by: Slava Egorov --- pkg/cfg/lib/front_end/ast_to_ir.dart | 138 +++++- pkg/cfg/lib/front_end/computed_scopes.dart | 8 +- pkg/cfg/lib/ir/field.dart | 62 +++ pkg/cfg/lib/ir/flow_graph_builder.dart | 5 +- pkg/cfg/lib/ir/flow_graph_checker.dart | 7 +- pkg/cfg/lib/ir/functions.dart | 13 + pkg/cfg/lib/ir/instructions.dart | 9 +- pkg/cfg/testcases/capturing.dart | 5 +- pkg/cfg/testcases/capturing.dart.expect | 432 ++++++++++-------- .../lib/back_end/arm64/code_generator.dart | 40 +- .../lib/back_end/arm64/constraints.dart | 17 +- .../lib/runtime/type_utils.dart | 32 -- .../register_allocator_test.dart.expect | 25 +- 13 files changed, 481 insertions(+), 312 deletions(-) diff --git a/pkg/cfg/lib/front_end/ast_to_ir.dart b/pkg/cfg/lib/front_end/ast_to_ir.dart index 249b2602ec3..42514101c31 100644 --- a/pkg/cfg/lib/front_end/ast_to_ir.dart +++ b/pkg/cfg/lib/front_end/ast_to_ir.dart @@ -56,6 +56,7 @@ class AstToIr extends ast.RecursiveVisitor { function.member, GlobalContext.instance.typeEnvironment, ); + late final ClosureLayout _currentClosureLayout; Map? labeledStatements; Map? switchCases; @@ -85,6 +86,10 @@ class AstToIr extends ast.RecursiveVisitor { scopes, function, ); + final f = function; + if (f is ClosureFunction) { + _currentClosureLayout = _computeClosureLayout(f); + } } /// Create [FlowGraph] for the body of the [function]. @@ -151,13 +156,34 @@ class AstToIr extends ast.RecursiveVisitor { if (function.hasClassTypeParameters) { if (function.hasReceiverParameter) { builder.addLoadLocal(localVarIndexer.receiver); - } else { - assert(_isCaptured(localVarIndexer.receiverDeclaration!)); - throw 'Unimplemented _buildPrologue for class type parameters with captured receiver'; + classTypeParameters = builder.addTypeParameters( + .classTypeParameters, + ); + } else if (_isCaptured(localVarIndexer.receiverDeclaration!)) { + // Read captured receiver. Load context from closure + // as contexts are not defined yet. + final variable = localVarIndexer.receiverDeclaration!; + builder.addLoadLocal(localVarIndexer.closure); + builder.addLoadInstanceField( + CField( + ClosureField( + _currentClosureLayout.firstContextIndex + + scopes + .getCapturedContexts( + function.functionNode!, + enableAsserts: enableAsserts, + ) + .indexOf(scopes.getVariableContext(variable)), + ), + ), + ); + builder.addLoadInstanceField( + localVarIndexer.contextField(variable), + ); + classTypeParameters = builder.addTypeParameters( + .classTypeParameters, + ); } - classTypeParameters = builder.addTypeParameters( - .classTypeParameters, - ); } } } @@ -819,16 +845,26 @@ class AstToIr extends ast.RecursiveVisitor { return; } if (node is ast.FunctionNode) { - var index = 0; - for (final context in scopes.getCapturedContexts( - node, - enableAsserts: enableAsserts, - )) { - assert(_isCapturedContext(context)); - builder.addLoadLocal(localVarIndexer.closure); - builder.addLoadInstanceField(CField(ClosureField(index))); - localVarIndexer.defineContext(context, builder.pop()); - ++index; + assert(function.functionNode == node); + if (function is ClosureFunction) { + var index = _currentClosureLayout.firstContextIndex; + for (final context in scopes.getCapturedContexts( + node, + enableAsserts: enableAsserts, + )) { + assert(_isCapturedContext(context)); + assert(index < _currentClosureLayout.length); + builder.addLoadLocal(localVarIndexer.closure); + builder.addLoadInstanceField(CField(ClosureField(index))); + localVarIndexer.defineContext(context, builder.pop()); + ++index; + } + } else { + assert( + scopes + .getCapturedContexts(node, enableAsserts: enableAsserts) + .isEmpty, + ); } } final scope = scopes.getScope(node); @@ -1763,18 +1799,82 @@ class AstToIr extends ast.RecursiveVisitor { as ClosureFunction; onLocalFunction(closureFunction); - // TODO: capture (parent) function type arguments and instantiator - // type arguments if needed. + final closureLayout = _computeClosureLayout(closureFunction); + final closure = builder.addAllocateClosure( + closureFunction, + closureLayout, + type, + ); + + if (closureLayout.hasClassTypeArgs) { + assert(typeParametersStyle == .separateFunctionAndClassTypeParameters); + builder.push(closure); + builder.push(classTypeParameters!); + builder.addStoreInstanceField( + CField(ClosureField(closureLayout.classTypeArgsIndex)), + ); + } + + if (closureLayout.hasFunctionTypeArgs) { + assert(typeParametersStyle == .separateFunctionAndClassTypeParameters); + builder.push(closure); + builder.push(functionTypeParameters!); + builder.addStoreInstanceField( + CField(ClosureField(closureLayout.functionTypeArgsIndex)), + ); + } final contexts = scopes.getCapturedContexts( node.function, enableAsserts: enableAsserts, ); + var index = closureLayout.firstContextIndex; for (final context in contexts) { assert(_isCapturedContext(context)); + assert(index < closureLayout.length); + builder.push(closure); builder.push(localVarIndexer.contextDef(context)); + // TODO: cache and reuse ClosureField objects. + builder.addStoreInstanceField(CField(ClosureField(index++))); } - builder.addAllocateClosure(closureFunction, type, contexts.length); + } + + ClosureLayout _computeClosureLayout(ClosureFunction closureFunction) { + var hasDelayedTypeArgs = false; + var hasClassTypeArgs = false; + var hasFunctionTypeArgs = false; + switch (typeParametersStyle) { + case .separateFunctionAndClassTypeParameters: + hasDelayedTypeArgs = closureFunction.hasFunctionTypeParameters; + + final visitor = _FindTypeParameters(); + closureFunction.functionNode! + .computeFunctionType(ast.Nullability.nonNullable) + .accept(visitor); + hasClassTypeArgs = visitor.containsClassTypeParams; + + hasFunctionTypeArgs = switch (closureFunction) { + LocalFunction() => closureFunction.hasGenericEnclosingFunction(), + TearOffFunction() => false, + }; + } + final numContexts = switch (closureFunction) { + LocalFunction() => + scopes + .getCapturedContexts( + closureFunction.functionNode!, + enableAsserts: enableAsserts, + ) + .length, + TearOffFunction() => + closureFunction.member.isInstanceMember ? /* receiver */ 1 : 0, + }; + return ClosureLayout( + numContexts, + hasDelayedTypeArgs: hasDelayedTypeArgs, + hasClassTypeArgs: hasClassTypeArgs, + hasFunctionTypeArgs: hasFunctionTypeArgs, + ); } @override diff --git a/pkg/cfg/lib/front_end/computed_scopes.dart b/pkg/cfg/lib/front_end/computed_scopes.dart index 691b4af2844..046c730ba69 100644 --- a/pkg/cfg/lib/front_end/computed_scopes.dart +++ b/pkg/cfg/lib/front_end/computed_scopes.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'package:cfg/front_end/scopes.dart'; +import 'package:cfg/ir/global_context.dart'; import 'package:kernel/ast.dart' as ast; /// Implementation of [Scopes] by computing scopes and contexts. @@ -16,8 +17,13 @@ final class ComputedScopes implements Scopes { ComputedScopes(this.member, {required this.enableAsserts}) { if (member.isInstanceMember || member is ast.Constructor) { + final type = member.enclosingClass!.getThisType( + GlobalContext.instance.coreTypes, + .nonNullable, + ); thisVariable = - member.function?.thisVariable ?? ast.VariableDeclaration('this'); + member.function?.thisVariable ?? + ast.VariableDeclaration('this', type: type); } final builder = _ScopeBuilder(this); member.accept(builder); diff --git a/pkg/cfg/lib/ir/field.dart b/pkg/cfg/lib/ir/field.dart index f7681a1688c..3e30f025bcc 100644 --- a/pkg/cfg/lib/ir/field.dart +++ b/pkg/cfg/lib/ir/field.dart @@ -56,3 +56,65 @@ final class ClosureField extends SyntheticField { isFinal: true, ); } + +/// Defines assignment of the closure elements. +class ClosureLayout { + static const int hasDelayedTypeArgsFlag = 1 << 0; + static const int hasClassTypeArgsFlag = 1 << 1; + static const int hasFunctionTypeArgsFlag = 1 << 2; + + final int _flags; + + /// Total number of closure elements. + final int length; + + ClosureLayout( + int numContexts, { + required bool hasDelayedTypeArgs, + required bool hasClassTypeArgs, + required bool hasFunctionTypeArgs, + }) : _flags = + (hasDelayedTypeArgs ? hasDelayedTypeArgsFlag : 0) | + (hasClassTypeArgs ? hasClassTypeArgsFlag : 0) | + (hasFunctionTypeArgs ? hasFunctionTypeArgsFlag : 0), + length = + (hasDelayedTypeArgs ? 1 : 0) + + (hasClassTypeArgs ? 1 : 0) + + (hasFunctionTypeArgs ? 1 : 0) + + numContexts { + assert(length == firstContextIndex + numContexts); + } + + /// Whether closure has an element for delayed type arguments. + bool get hasDelayedTypeArgs => (_flags & hasDelayedTypeArgsFlag) != 0; + + /// Whether closure has an element for enclosing class type arguments. + bool get hasClassTypeArgs => (_flags & hasClassTypeArgsFlag) != 0; + + /// Whether closure has an element for enclosing function type arguments. + bool get hasFunctionTypeArgs => (_flags & hasFunctionTypeArgsFlag) != 0; + + /// Index of the delayed type arguments element. + int get delayedTypeArgsIndex { + assert(hasDelayedTypeArgs); + return 0; + } + + /// Index of the enclosing class type arguments element. + int get classTypeArgsIndex { + assert(hasClassTypeArgs); + return hasDelayedTypeArgs ? 1 : 0; + } + + /// Index of the enclosing function type arguments element. + int get functionTypeArgsIndex { + assert(hasFunctionTypeArgs); + return (hasDelayedTypeArgs ? 1 : 0) + (hasClassTypeArgs ? 1 : 0); + } + + int get firstContextIndex { + return (hasDelayedTypeArgs ? 1 : 0) + + (hasClassTypeArgs ? 1 : 0) + + (hasFunctionTypeArgs ? 1 : 0); + } +} diff --git a/pkg/cfg/lib/ir/flow_graph_builder.dart b/pkg/cfg/lib/ir/flow_graph_builder.dart index 193d642dde8..c9b1d03dbd5 100644 --- a/pkg/cfg/lib/ir/flow_graph_builder.dart +++ b/pkg/cfg/lib/ir/flow_graph_builder.dart @@ -546,17 +546,16 @@ class FlowGraphBuilder { /// Append [AllocateClosure] to the graph. AllocateClosure addAllocateClosure( ClosureFunction function, + ClosureLayout closureLayout, CType type, - int inputCount, ) { final instr = AllocateClosure( graph, currentSourcePosition, function, + closureLayout, type, - inputCount: inputCount, ); - popInputs(instr, 0, inputCount); push(instr); appendInstruction(instr); return instr; diff --git a/pkg/cfg/lib/ir/flow_graph_checker.dart b/pkg/cfg/lib/ir/flow_graph_checker.dart index 790fde6db94..e549b97f736 100644 --- a/pkg/cfg/lib/ir/flow_graph_checker.dart +++ b/pkg/cfg/lib/ir/flow_graph_checker.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'package:cfg/ir/constant_value.dart'; +import 'package:cfg/ir/field.dart'; import 'package:cfg/ir/instructions.dart'; import 'package:cfg/ir/ir_to_text.dart'; import 'package:cfg/ir/types.dart'; @@ -352,11 +353,15 @@ final class FlowGraphChecker extends Pass implements InstructionVisitor { void visitTypeParameters(TypeParameters instr) { assert(instr.block is EntryBlock); // TypeParameters can only be used in TypeCast, TypeTest, - // TypeArguments and TypeLiteral. + // TypeArguments, TypeLiteral and StoreInstanceField for capturing. for (final use in instr.inputUses) { final user = use.getInstruction(graph); switch (user) { case TypeCast() || TypeTest() || TypeArguments() || TypeLiteral(): + case StoreInstanceField(:var field) + when field.isSynthetic && + (field.asSynthetic is ClosureField || + field.asSynthetic is ContextField): break; default: throw 'Unexpected user ${IrToText.instruction(user)} of TypeParameters'; diff --git a/pkg/cfg/lib/ir/functions.dart b/pkg/cfg/lib/ir/functions.dart index 3193bf4df4f..d12bc9c57be 100644 --- a/pkg/cfg/lib/ir/functions.dart +++ b/pkg/cfg/lib/ir/functions.dart @@ -232,6 +232,19 @@ final class LocalFunction extends ClosureFunction { @override SourcePosition get sourcePosition => SourcePosition(localFunction.fileOffset); + + bool hasGenericEnclosingFunction() { + ast.TreeNode node = localFunction; + for (;;) { + node = node.parent!; + if (node is ast.Member) { + return false; + } + if (node is ast.FunctionNode && node.typeParameters.isNotEmpty) { + return true; + } + } + } } /// Tear-off (result of function closurization). diff --git a/pkg/cfg/lib/ir/instructions.dart b/pkg/cfg/lib/ir/instructions.dart index e52c86816d5..189043a4594 100644 --- a/pkg/cfg/lib/ir/instructions.dart +++ b/pkg/cfg/lib/ir/instructions.dart @@ -1319,10 +1319,9 @@ final class AllocateObject extends Definition with CanThrow, Pure { } /// Allocate a closure instance. -/// -/// Takes captured values as inputs. final class AllocateClosure extends Definition with CanThrow, Pure { final ClosureFunction function; + final ClosureLayout closureLayout; @override final CType type; @@ -1331,9 +1330,9 @@ final class AllocateClosure extends Definition with CanThrow, Pure { super.graph, super.sourcePosition, this.function, - this.type, { - required super.inputCount, - }); + this.closureLayout, + this.type, + ) : super(inputCount: 0); @override R accept(InstructionVisitor v) => v.visitAllocateClosure(this); diff --git a/pkg/cfg/testcases/capturing.dart b/pkg/cfg/testcases/capturing.dart index 3314ab44895..85b10003c51 100644 --- a/pkg/cfg/testcases/capturing.dart +++ b/pkg/cfg/testcases/capturing.dart @@ -156,12 +156,11 @@ void foo46() { }); } -// TODO: support class type parameters via captured receiver -class A /**/ { +class A { List aField = [ for (int i = 0; i < 10; ++i) () { - // print(T); + print(T); print(i); i += 2; return i; diff --git a/pkg/cfg/testcases/capturing.dart.expect b/pkg/cfg/testcases/capturing.dart.expect index e1d1cea26d4..7c763c684b8 100644 --- a/pkg/cfg/testcases/capturing.dart.expect +++ b/pkg/cfg/testcases/capturing.dart.expect @@ -1,66 +1,83 @@ --- A.foo47 -B0 = EntryBlock() dominates:(B9) - v3 = Constant() - v4 = Constant(0) - v12 = Constant(10) - v22 = Constant(1) - v30 = Constant(null) +B0 = EntryBlock() dominates:(B14) + v8 = Constant() + v9 = Constant(0) + v17 = Constant(10) + v29 = Constant(1) + v37 = Constant(null) v1 = Parameter(this) - v5 = DirectCall _GrowableList.(v3, v4) - v7 = AllocateContext() - StoreInstanceField(#context-field:i, v7, v4) - Goto(B9) -B9 = JoinBlock(B0, B14) idom:B0 dominates:(B15, B14) loop-header (depth:1 body:(B9, B14) back-edges:(B14)) - v11 = LoadInstanceField(#context-field:i, v7) - v13 = Comparison int <(v11, v12) - Branch(v13, true: B14, false: B15) -B14 = TargetBlock() idom:B9 in-loop:B9 - v18 = AllocateClosure(v7) - v32 = DirectCall closure FunctionExpression(int () { + TypeParameters(v1) + v4 = AllocateContext() + StoreInstanceField(#context-field:this, v4, v1) + v10 = DirectCall _GrowableList.(v8, v9) + v12 = AllocateContext() + StoreInstanceField(#context-field:i, v12, v9) + Goto(B14) +B14 = JoinBlock(B0, B19) idom:B0 dominates:(B20, B19) loop-header (depth:1 body:(B14, B19) back-edges:(B19)) + v16 = LoadInstanceField(#context-field:i, v12) + v18 = Comparison int <(v16, v17) + Branch(v18, true: B19, false: B20) +B19 = TargetBlock() idom:B14 in-loop:B14 + v23 = AllocateClosure() + StoreInstanceField(#closure-field[0], v23, v4) + StoreInstanceField(#closure-field[1], v23, v12) + v39 = DirectCall closure FunctionExpression(int () { + print(A.T%); print(i); i = i.{num.+}(2); return i; -}) at A.foo47(v18) - InterfaceCall List.add(v5, v32) - v21 = LoadInstanceField(#context-field:i, v7) - v23 = BinaryIntOp +(v21, v22) - StoreInstanceField(#context-field:i, v7, v23) - Goto(B9) -B15 = TargetBlock() idom:B9 - StoreInstanceField(A.aField, v1, v5) - DirectCall Object.(v1) - Return(v30) +}) at A.foo47(v23) + InterfaceCall List.add(v10, v39) + v28 = LoadInstanceField(#context-field:i, v12) + v30 = BinaryIntOp +(v28, v29) + StoreInstanceField(#context-field:i, v12, v30) + Goto(B14) +B20 = TargetBlock() idom:B14 + StoreInstanceField(A.aField, v1, v10) + v35 = LoadInstanceField(#context-field:this, v4) + DirectCall Object.(v35) + Return(v37) --- closure FunctionExpression(int () { + print(A.T%); print(i); i = i.{num.+}(2); return i; }) at A.foo47 B0 = EntryBlock() - v7 = Constant(2) + v10 = Constant(null) + v16 = Constant(2) v1 = Parameter(#closure) v3 = LoadInstanceField(#closure-field[0], v1) - v4 = LoadInstanceField(#context-field:i, v3) - DirectCall print(v4) - v6 = LoadInstanceField(#context-field:i, v3) - v8 = BinaryIntOp +(v6, v7) - StoreInstanceField(#context-field:i, v3, v8) - v10 = LoadInstanceField(#context-field:i, v3) - Return(v10) + v4 = LoadInstanceField(#context-field:this, v3) + v5 = TypeParameters(v4) + LoadInstanceField(#closure-field[0], v1) + v9 = LoadInstanceField(#closure-field[1], v1) + v11 = TypeLiteral(A.T%, v5, v10) + DirectCall print(v11) + v13 = LoadInstanceField(#context-field:i, v9) + DirectCall print(v13) + v15 = LoadInstanceField(#context-field:i, v9) + v17 = BinaryIntOp +(v15, v16) + StoreInstanceField(#context-field:i, v9, v17) + v19 = LoadInstanceField(#context-field:i, v9) + Return(v19) --- getter A.aField B0 = EntryBlock() v1 = Parameter(this) - v3 = LoadInstanceField(A.aField, v1) - Return(v3) + TypeParameters(v1) + v5 = LoadInstanceField(A.aField, v1) + Return(v5) --- setter A.aField B0 = EntryBlock() - v6 = Constant(null) + v8 = Constant(null) v1 = Parameter(this) v2 = Parameter(#value) + TypeParameters(v1) StoreInstanceField(A.aField, v1, v2) - Return(v6) + Return(v8) --- opaqueVal B0 = EntryBlock() @@ -85,16 +102,16 @@ B8 = JoinBlock(B6, B5) idom:B0 Return(v13) --- foo42 -B0 = EntryBlock() dominates:(B14) +B0 = EntryBlock() dominates:(B16) Constant(true) - v21 = Constant(50) - v23 = Constant(60) - v25 = Constant(70) - v42 = Constant(1) - v54 = Constant(0) - v59 = Constant(10) - v77 = Constant(90) - v91 = Constant(null) + v23 = Constant(50) + v25 = Constant(60) + v27 = Constant(70) + v47 = Constant(1) + v59 = Constant(0) + v64 = Constant(10) + v83 = Constant(90) + v98 = Constant(null) v1 = AllocateContext() v2 = DirectCall opaqueVal() StoreInstanceField(#context-field:a, v1, v2) @@ -104,72 +121,79 @@ B0 = EntryBlock() dominates:(B14) StoreInstanceField(#context-field:c, v1, v6) v8 = DirectCall opaqueVal() StoreInstanceField(#context-field:d, v1, v8) - v10 = AllocateClosure(v1) + v10 = AllocateClosure() + StoreInstanceField(#closure-field[0], v10, v1) DirectCall callClosure(v10) - v12 = AllocateClosure(v1) - DirectCall callClosure(v12) - Goto(B14) -B14 = JoinBlock(B0, B45) idom:B0 dominates:(B45, B44) loop-header (depth:1 body:(B14, B45) back-edges:(B45)) - v20 = AllocateContext() - StoreInstanceField(#context-field:e, v20, v21) - StoreInstanceField(#context-field:f, v20, v23) - StoreInstanceField(#context-field:g, v20, v25) - v27 = AllocateClosure(v1) - DirectCall callClosure(v27) - v29 = AllocateClosure(v20) + v13 = AllocateClosure() + StoreInstanceField(#closure-field[0], v13, v1) + DirectCall callClosure(v13) + Goto(B16) +B16 = JoinBlock(B0, B50) idom:B0 dominates:(B50, B49) loop-header (depth:1 body:(B16, B50) back-edges:(B50)) + v22 = AllocateContext() + StoreInstanceField(#context-field:e, v22, v23) + StoreInstanceField(#context-field:f, v22, v25) + StoreInstanceField(#context-field:g, v22, v27) + v29 = AllocateClosure() + StoreInstanceField(#closure-field[0], v29, v1) DirectCall callClosure(v29) - v31 = AllocateClosure(v20) - DirectCall callClosure(v31) - v33 = LoadInstanceField(#context-field:d, v1) - DirectCall print(v33) - v35 = LoadInstanceField(#context-field:e, v20) - DirectCall print(v35) - v37 = LoadInstanceField(#context-field:f, v20) - DirectCall print(v37) - v39 = LoadInstanceField(#context-field:g, v20) - DirectCall print(v39) - v41 = DirectCall opaqueVal() - v43 = Comparison int ==(v41, v42) - Branch(v43, true: B44, false: B45) -B44 = TargetBlock() idom:B14 dominates:(B56) - v53 = AllocateContext() - StoreInstanceField(#context-field:i, v53, v54) - Goto(B56) -B56 = JoinBlock(B44, B75) idom:B44 dominates:(B62, B61) loop-header (depth:1 body:(B56, B61, B70, B75, B74) back-edges:(B75)) - v58 = LoadInstanceField(#context-field:i, v53) - v60 = Comparison int <(v58, v59) - Branch(v60, true: B61, false: B62) -B61 = TargetBlock() idom:B56 dominates:(B70) in-loop:B56 - v64 = DirectCall opaqueVal() - StoreInstanceField(#context-field:h, v53, v64) - v66 = AllocateClosure(v53) - DirectCall callClosure(v66) - v68 = AllocateContext() - StoreInstanceField(#context-field:j, v68, v54) - Goto(B70) -B70 = JoinBlock(B61, B74) idom:B61 dominates:(B75, B74) loop-header (depth:2 body:(B70, B74) back-edges:(B74)) - v72 = LoadInstanceField(#context-field:j, v68) - v73 = Comparison int <(v72, v59) - Branch(v73, true: B74, false: B75) -B74 = TargetBlock() idom:B70 in-loop:B70 - StoreInstanceField(#context-field:k, v68, v77) - v79 = AllocateClosure(v68) - DirectCall callClosure(v79) - v81 = LoadInstanceField(#context-field:k, v68) - DirectCall print(v81) - v83 = LoadInstanceField(#context-field:j, v68) - v84 = BinaryIntOp +(v83, v42) - StoreInstanceField(#context-field:j, v68, v84) - Goto(B70) -B75 = TargetBlock() idom:B70 in-loop:B56 - v87 = LoadInstanceField(#context-field:i, v53) - v88 = BinaryIntOp +(v87, v42) - StoreInstanceField(#context-field:i, v53, v88) - Goto(B56) -B62 = TargetBlock() idom:B56 - Return(v91) -B45 = TargetBlock() idom:B14 in-loop:B14 - Goto(B14) + v32 = AllocateClosure() + StoreInstanceField(#closure-field[0], v32, v22) + DirectCall callClosure(v32) + v35 = AllocateClosure() + StoreInstanceField(#closure-field[0], v35, v22) + DirectCall callClosure(v35) + v38 = LoadInstanceField(#context-field:d, v1) + DirectCall print(v38) + v40 = LoadInstanceField(#context-field:e, v22) + DirectCall print(v40) + v42 = LoadInstanceField(#context-field:f, v22) + DirectCall print(v42) + v44 = LoadInstanceField(#context-field:g, v22) + DirectCall print(v44) + v46 = DirectCall opaqueVal() + v48 = Comparison int ==(v46, v47) + Branch(v48, true: B49, false: B50) +B49 = TargetBlock() idom:B16 dominates:(B61) + v58 = AllocateContext() + StoreInstanceField(#context-field:i, v58, v59) + Goto(B61) +B61 = JoinBlock(B49, B81) idom:B49 dominates:(B67, B66) loop-header (depth:1 body:(B61, B66, B76, B81, B80) back-edges:(B81)) + v63 = LoadInstanceField(#context-field:i, v58) + v65 = Comparison int <(v63, v64) + Branch(v65, true: B66, false: B67) +B66 = TargetBlock() idom:B61 dominates:(B76) in-loop:B61 + v69 = DirectCall opaqueVal() + StoreInstanceField(#context-field:h, v58, v69) + v71 = AllocateClosure() + StoreInstanceField(#closure-field[0], v71, v58) + DirectCall callClosure(v71) + v74 = AllocateContext() + StoreInstanceField(#context-field:j, v74, v59) + Goto(B76) +B76 = JoinBlock(B66, B80) idom:B66 dominates:(B81, B80) loop-header (depth:2 body:(B76, B80) back-edges:(B80)) + v78 = LoadInstanceField(#context-field:j, v74) + v79 = Comparison int <(v78, v64) + Branch(v79, true: B80, false: B81) +B80 = TargetBlock() idom:B76 in-loop:B76 + StoreInstanceField(#context-field:k, v74, v83) + v85 = AllocateClosure() + StoreInstanceField(#closure-field[0], v85, v74) + DirectCall callClosure(v85) + v88 = LoadInstanceField(#context-field:k, v74) + DirectCall print(v88) + v90 = LoadInstanceField(#context-field:j, v74) + v91 = BinaryIntOp +(v90, v47) + StoreInstanceField(#context-field:j, v74, v91) + Goto(B76) +B81 = TargetBlock() idom:B76 in-loop:B61 + v94 = LoadInstanceField(#context-field:i, v58) + v95 = BinaryIntOp +(v94, v47) + StoreInstanceField(#context-field:i, v58, v95) + Goto(B61) +B67 = TargetBlock() idom:B61 + Return(v98) +B50 = TargetBlock() idom:B16 in-loop:B16 + Goto(B16) --- closure FunctionExpression(void () { print(a); @@ -289,21 +313,25 @@ B0 = EntryBlock() v4 = Constant(20) v6 = Constant(30) v8 = Constant(40) - v18 = Constant(null) + v22 = Constant(null) v1 = AllocateContext() StoreInstanceField(#context-field:a, v1, v2) StoreInstanceField(#context-field:b, v1, v4) StoreInstanceField(#context-field:c, v1, v6) StoreInstanceField(#context-field:d, v1, v8) - v10 = AllocateClosure(v1) + v10 = AllocateClosure() + StoreInstanceField(#closure-field[0], v10, v1) DirectCall callClosure(v10) - v12 = AllocateClosure(v1) - DirectCall callClosure(v12) - v14 = AllocateClosure(v1) - DirectCall callClosure(v14) - v16 = AllocateClosure(v1) + v13 = AllocateClosure() + StoreInstanceField(#closure-field[0], v13, v1) + DirectCall callClosure(v13) + v16 = AllocateClosure() + StoreInstanceField(#closure-field[0], v16, v1) DirectCall callClosure(v16) - Return(v18) + v19 = AllocateClosure() + StoreInstanceField(#closure-field[0], v19, v1) + DirectCall callClosure(v19) + Return(v22) --- closure FunctionExpression(void () { a = a.{num.+}(11); @@ -373,39 +401,42 @@ B0 = EntryBlock() Return(v8) --- foo45 -B0 = EntryBlock() dominates:(B10, B12, B9) +B0 = EntryBlock() dominates:(B11, B13, B10) v2 = Constant(10) - v7 = Constant(1) - v14 = Constant(20) - v26 = Constant(30) - v31 = Constant(null) + v8 = Constant(1) + v15 = Constant(20) + v28 = Constant(30) + v34 = Constant(null) v1 = AllocateContext() StoreInstanceField(#context-field:a, v1, v2) - v4 = AllocateClosure(v1) + v4 = AllocateClosure() + StoreInstanceField(#closure-field[0], v4, v1) DirectCall callClosure(v4) - v6 = DirectCall opaqueVal() - v8 = Comparison int ==(v6, v7) - Branch(v8, true: B9, false: B10) -B9 = TargetBlock() idom:B0 - StoreInstanceField(#context-field:b, v1, v14) - v16 = AllocateClosure(v1) - DirectCall callClosure(v16) - Goto(B12) + v7 = DirectCall opaqueVal() + v9 = Comparison int ==(v7, v8) + Branch(v9, true: B10, false: B11) B10 = TargetBlock() idom:B0 - Goto(B12) -B12 = JoinBlock(B10, B9) idom:B0 dominates:(B22, B24, B21) - v19 = DirectCall opaqueVal() - v20 = Comparison int ==(v19, v7) - Branch(v20, true: B21, false: B22) -B21 = TargetBlock() idom:B12 - StoreInstanceField(#context-field:c, v1, v26) - v28 = AllocateClosure(v1) - DirectCall callClosure(v28) - Goto(B24) -B22 = TargetBlock() idom:B12 - Goto(B24) -B24 = JoinBlock(B22, B21) idom:B12 - Return(v31) + StoreInstanceField(#context-field:b, v1, v15) + v17 = AllocateClosure() + StoreInstanceField(#closure-field[0], v17, v1) + DirectCall callClosure(v17) + Goto(B13) +B11 = TargetBlock() idom:B0 + Goto(B13) +B13 = JoinBlock(B11, B10) idom:B0 dominates:(B24, B26, B23) + v21 = DirectCall opaqueVal() + v22 = Comparison int ==(v21, v8) + Branch(v22, true: B23, false: B24) +B23 = TargetBlock() idom:B13 + StoreInstanceField(#context-field:c, v1, v28) + v30 = AllocateClosure() + StoreInstanceField(#closure-field[0], v30, v1) + DirectCall callClosure(v30) + Goto(B26) +B24 = TargetBlock() idom:B13 + Goto(B26) +B26 = JoinBlock(B24, B23) idom:B13 + Return(v34) --- closure FunctionExpression(void () { a = a.{num.+}(11); @@ -458,17 +489,19 @@ B0 = EntryBlock() --- foo46 B0 = EntryBlock() - v10 = Constant(null) + v12 = Constant(null) v1 = AllocateContext() v2 = DirectCall opaqueVal() StoreInstanceField(#context-field:a, v1, v2) v4 = DirectCall opaqueVal() StoreInstanceField(#context-field:b, v1, v4) - v6 = AllocateClosure(v1) + v6 = AllocateClosure() + StoreInstanceField(#closure-field[0], v6, v1) DirectCall callClosure(v6) - v8 = AllocateClosure(v1) - DirectCall callClosure(v8) - Return(v10) + v9 = AllocateClosure() + StoreInstanceField(#closure-field[0], v9, v1) + DirectCall callClosure(v9) + Return(v12) --- closure FunctionExpression(void () { callClosure(void () { @@ -476,12 +509,13 @@ B0 = EntryBlock() }); }) at foo46 B0 = EntryBlock() - v6 = Constant(null) + v7 = Constant(null) v1 = Parameter(#closure) v3 = LoadInstanceField(#closure-field[0], v1) - v4 = AllocateClosure(v3) + v4 = AllocateClosure() + StoreInstanceField(#closure-field[0], v4, v3) DirectCall callClosure(v4) - Return(v6) + Return(v7) --- closure FunctionExpression(void () { print(a); @@ -500,12 +534,13 @@ B0 = EntryBlock() }); }) at foo46 B0 = EntryBlock() - v6 = Constant(null) + v7 = Constant(null) v1 = Parameter(#closure) v3 = LoadInstanceField(#closure-field[0], v1) - v4 = AllocateClosure(v3) + v4 = AllocateClosure() + StoreInstanceField(#closure-field[0], v4, v3) DirectCall callClosure(v4) - Return(v6) + Return(v7) --- closure FunctionExpression(void () { b = b.{num.+}(11); @@ -524,33 +559,35 @@ B0 = EntryBlock() B0 = EntryBlock() dominates:(B5, B7, B4) v2 = Constant(1) v10 = Constant(10) - v23 = Constant(20) - v28 = Constant(null) + v24 = Constant(20) + v30 = Constant(null) v1 = DirectCall opaqueVal() v3 = Comparison int ==(v1, v2) Branch(v3, true: B4, false: B5) B4 = TargetBlock() idom:B0 v9 = AllocateContext() StoreInstanceField(#context-field:a, v9, v10) - v12 = AllocateClosure(v9) + v12 = AllocateClosure() + StoreInstanceField(#closure-field[0], v12, v9) DirectCall callClosure(v12) Goto(B7) B5 = TargetBlock() idom:B0 Goto(B7) -B7 = JoinBlock(B5, B4) idom:B0 dominates:(B18, B20, B17) - v15 = DirectCall opaqueVal() - v16 = Comparison int ==(v15, v2) - Branch(v16, true: B17, false: B18) -B17 = TargetBlock() idom:B7 - v22 = AllocateContext() - StoreInstanceField(#context-field:b, v22, v23) - v25 = AllocateClosure(v22) - DirectCall callClosure(v25) - Goto(B20) +B7 = JoinBlock(B5, B4) idom:B0 dominates:(B19, B21, B18) + v16 = DirectCall opaqueVal() + v17 = Comparison int ==(v16, v2) + Branch(v17, true: B18, false: B19) B18 = TargetBlock() idom:B7 - Goto(B20) -B20 = JoinBlock(B18, B17) idom:B7 - Return(v28) + v23 = AllocateContext() + StoreInstanceField(#context-field:b, v23, v24) + v26 = AllocateClosure() + StoreInstanceField(#closure-field[0], v26, v23) + DirectCall callClosure(v26) + Goto(B21) +B19 = TargetBlock() idom:B7 + Goto(B21) +B21 = JoinBlock(B19, B18) idom:B7 + Return(v30) --- closure FunctionExpression(void () { a = a.{num.+}(11); @@ -579,28 +616,30 @@ B0 = EntryBlock() Return(v8) --- foo49 -B0 = EntryBlock() dominates:(B8) - v6 = Constant(0) - v11 = Constant(4) - v19 = Constant(1) - v23 = Constant(null) +B0 = EntryBlock() dominates:(B9) + v7 = Constant(0) + v12 = Constant(4) + v21 = Constant(1) + v25 = Constant(null) v1 = AllocateContext() v2 = DirectCall opaqueVal() StoreInstanceField(#context-field:a, v1, v2) - v4 = AllocateClosure(v1) + v4 = AllocateClosure() + StoreInstanceField(#closure-field[0], v4, v1) DirectCall callClosure(v4) - Goto(B8) -B8 = JoinBlock(B0, B13) idom:B0 dominates:(B14, B13) loop-header (depth:1 body:(B8, B13) back-edges:(B13)) - v25 = Phi(v6, v20) - v12 = Comparison int <(v25, v11) - Branch(v12, true: B13, false: B14) -B13 = TargetBlock() idom:B8 in-loop:B8 - v16 = AllocateClosure(v1) - DirectCall callClosure(v16) - v20 = BinaryIntOp +(v25, v19) - Goto(B8) -B14 = TargetBlock() idom:B8 - Return(v23) + Goto(B9) +B9 = JoinBlock(B0, B14) idom:B0 dominates:(B15, B14) loop-header (depth:1 body:(B9, B14) back-edges:(B14)) + v27 = Phi(v7, v22) + v13 = Comparison int <(v27, v12) + Branch(v13, true: B14, false: B15) +B14 = TargetBlock() idom:B9 in-loop:B9 + v17 = AllocateClosure() + StoreInstanceField(#closure-field[0], v17, v1) + DirectCall callClosure(v17) + v22 = BinaryIntOp +(v27, v21) + Goto(B9) +B15 = TargetBlock() idom:B9 + Return(v25) --- closure FunctionExpression(void () { a = a.{num.+}(10); @@ -623,15 +662,16 @@ B0 = EntryBlock() }) at foo49 B0 = EntryBlock() v5 = Constant(20) - v10 = Constant(null) + v11 = Constant(null) v1 = Parameter(#closure) v3 = LoadInstanceField(#closure-field[0], v1) v4 = LoadInstanceField(#context-field:a, v3) v6 = BinaryIntOp +(v4, v5) StoreInstanceField(#context-field:a, v3, v6) - v8 = AllocateClosure(v3) + v8 = AllocateClosure() + StoreInstanceField(#closure-field[0], v8, v3) DirectCall callClosure(v8) - Return(v10) + Return(v11) --- closure FunctionExpression(void () { a = a.{num.+}(30); diff --git a/pkg/native_compiler/lib/back_end/arm64/code_generator.dart b/pkg/native_compiler/lib/back_end/arm64/code_generator.dart index c15e5a46f2c..44907d3b5b7 100644 --- a/pkg/native_compiler/lib/back_end/arm64/code_generator.dart +++ b/pkg/native_compiler/lib/back_end/arm64/code_generator.dart @@ -719,7 +719,9 @@ final class Arm64CodeGenerator extends CodeGenerator { bool _canBeSmi(Definition def) => switch (def) { Constant(:var value) => value.isInt && objectLayout.isSmi(value.intValue), - _ => def.type is IntType || const IntType().isSubtypeOf(def.type), + Definition(type: IntType()) => true, + Definition(type: ExtendedType()) => false, + Definition(:var type) => const IntType().isSubtypeOf(type), }; void _writeBarrier( @@ -1283,37 +1285,16 @@ final class Arm64CodeGenerator extends CodeGenerator { @override void visitAllocateClosure(AllocateClosure instr) { final function = instr.function; - final hasDelayedTypeArgs = function.hasFunctionTypeParameters; - final hasInstantiatorTypeArgs = switch (function) { - LocalFunction() => containsClassTypeParameters( - function.functionNode!.computeFunctionType(ast.Nullability.nonNullable), - ), - TearOffFunction() => - function.member.isInstanceMember && - containsClassTypeParameters( - function.member.function!.computeFunctionType( - ast.Nullability.nonNullable, - ), - ), - }; - final hasFunctionTypeArgs = switch (function) { - LocalFunction() => hasGenericEnclosingFunction(function.localFunction), - TearOffFunction() => false, - }; - final numElements = - (hasDelayedTypeArgs ? 1 : 0) + - (hasInstantiatorTypeArgs ? 1 : 0) + - (hasFunctionTypeArgs ? 1 : 0) + - 1 /* context */; + final closureLayout = instr.closureLayout; final lengthAndFlags = vmOffsets.encodeClosureLengthAndFlags( - numElements, - hasDelayedTypeArgs: hasDelayedTypeArgs, - hasInstantiatorTypeArgs: hasInstantiatorTypeArgs, - hasFunctionTypeArgs: hasFunctionTypeArgs, + closureLayout.length, + hasDelayedTypeArgs: closureLayout.hasDelayedTypeArgs, + hasInstantiatorTypeArgs: closureLayout.hasClassTypeArgs, + hasFunctionTypeArgs: closureLayout.hasFunctionTypeArgs, ); final instanceSize = roundUp( vmOffsets.Closure_elementsStartOffset + - numElements * objectLayout.compressedWordSize, + closureLayout.length * objectLayout.compressedWordSize, objectAlignment(wordSize), ); @@ -1357,8 +1338,7 @@ final class Arm64CodeGenerator extends CodeGenerator { _asm.fieldAddress(resultReg, vmOffsets.Closure_length_and_flags_offset), ); _asm.str(ZR, _asm.fieldAddress(resultReg, vmOffsets.Closure_hash_offset)); - // TODO: initialize the rest of the fields. - assert(instr.inputCount == 0); + // TODO: initialize delayed type arguments. _asm.bind(done); } diff --git a/pkg/native_compiler/lib/back_end/arm64/constraints.dart b/pkg/native_compiler/lib/back_end/arm64/constraints.dart index 707929869aa..f7aa4319c06 100644 --- a/pkg/native_compiler/lib/back_end/arm64/constraints.dart +++ b/pkg/native_compiler/lib/back_end/arm64/constraints.dart @@ -330,18 +330,11 @@ final class Arm64Constraints extends Constraints { @override InstructionConstraints? visitAllocateClosure(AllocateClosure instr) => - InstructionConstraints( - AllocationStub.resultReg, - List.generate( - instr.inputCount, - (int i) => anyLocationOrImmediate(instr.inputDefAt(i)), - ), - const [ - AllocationStub.tagsReg, - AllocationStub.scratch1Reg, - AllocationStub.scratch2Reg, - ], - ); + const InstructionConstraints(AllocationStub.resultReg, [], [ + AllocationStub.tagsReg, + AllocationStub.scratch1Reg, + AllocationStub.scratch2Reg, + ]); @override InstructionConstraints? visitAllocateContext(AllocateContext instr) => diff --git a/pkg/native_compiler/lib/runtime/type_utils.dart b/pkg/native_compiler/lib/runtime/type_utils.dart index c9003ac774c..70c90d77904 100644 --- a/pkg/native_compiler/lib/runtime/type_utils.dart +++ b/pkg/native_compiler/lib/runtime/type_utils.dart @@ -150,35 +150,3 @@ bool hasNonTrivialInitializer(ast.Field field) { }; } } - -/// Returns true if [type] references class type parameters. -bool containsClassTypeParameters(ast.DartType type) { - final visitor = _FindClassTypeParameters(); - type.accept(visitor); - return visitor.containsClassTypeParams; -} - -class _FindClassTypeParameters extends ast.RecursiveVisitor { - bool containsClassTypeParams = false; - - _FindClassTypeParameters(); - - @override - void visitTypeParameterType(ast.TypeParameterType node) { - if (node.parameter.declaration is ast.Class) { - containsClassTypeParams = true; - } - } -} - -bool hasGenericEnclosingFunction(ast.TreeNode node) { - for (;;) { - node = node.parent!; - if (node is ast.Member) { - return false; - } - if (node is ast.FunctionNode && node.typeParameters.isNotEmpty) { - return true; - } - } -} diff --git a/pkg/native_compiler/testcases/register_allocator_test.dart.expect b/pkg/native_compiler/testcases/register_allocator_test.dart.expect index d095de6fa23..981a59505ea 100644 --- a/pkg/native_compiler/testcases/register_allocator_test.dart.expect +++ b/pkg/native_compiler/testcases/register_allocator_test.dart.expect @@ -506,7 +506,7 @@ B0 = EntryBlock() dominates:(B11, B10) v3 = Parameter(compare) # RA: R2 <- () v4 = Parameter(isValidKey) # RA: R3 <- () ParallelMove output(R3 -> vloc:R10, R2 -> vloc:R9, R1 -> vloc:R6, R0 -> vloc:R5) - ParallelMove spill(R6 -> stack[0]) + ParallelMove spill(R5 -> stack[0], R6 -> stack[1]) ParallelMove input(vloc:R6 -> R0, NullConstant(null) -> R2, vloc:R5 -> R1) v9 = TypeTest(v2, v8, v1, Map) # RA: R7 <- (R0, R2, R1) temps: [R8, R3, R4] ParallelMove output(R7 -> vloc:R7) @@ -527,12 +527,14 @@ B11 = TargetBlock() idom:B0 ParallelMove input(vloc:R1 -> R1) v23 = AllocateObject SplayTreeMap(v22) # RA: R0 <- (R1) temps: [R2, R3, R4] ParallelMove output(R0 -> vloc:R0) - ParallelMove spill(R0 -> stack[1]) + ParallelMove spill(R0 -> stack[2]) DirectCall SplayTreeMap.(v23, v3, v4) # RA: R0 <- (R0, R9, R10) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30] v29 = AllocateClosure() # RA: R0 <- () temps: [R2, R3, R4] - ParallelMove output(R0 -> vloc:R0, stack[0] -> R1) - InterfaceCall Map.forEach(v2, v29) # RA: R0 <- (R1, R0) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30] + ParallelMove output(R0 -> vloc:R0, stack[0] -> R3) + StoreInstanceField(#closure-field[0], v29, v1) # RA: (R0, R3) temps: [R1, R2] ParallelMove output(stack[1] -> R1) + InterfaceCall Map.forEach(v2, v29) # RA: R0 <- (R1, R0) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30] + ParallelMove output(stack[2] -> R1) ParallelMove input(vloc:R1 -> R0) Return(v23) # RA: (R0) @@ -1784,7 +1786,7 @@ B0 = EntryBlock() --- closures B0 = EntryBlock() - v9 = Constant(null) + v11 = Constant(null) Parameter(a1) # RA: R0 <- () v2 = Parameter(a2) # RA: R1 <- () ParallelMove output(R1 -> vloc:R1) @@ -1792,13 +1794,16 @@ B0 = EntryBlock() ParallelMove output(R0 -> vloc:R0) StoreInstanceField(#context-field:a2, v3, v2) # RA: (R0, R1) temps: [R2, R3] ParallelMove output(R0 -> R1) - v6 = AllocateClosure(v3) # RA: R0 <- (R1) temps: [R2, R3, R4] - ParallelMove output(R0 -> vloc:R5) - v7 = AllocateClosure(v3) # RA: R0 <- (R1) temps: [R2, R3, R4] + v6 = AllocateClosure() # RA: R0 <- () temps: [R2, R3, R4] ParallelMove output(R0 -> vloc:R0) - DirectCall foo2(v6, v7) # RA: R0 <- (R5, R0) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30] + StoreInstanceField(#closure-field[0], v6, v3) # RA: (R0, R1) temps: [R2, R3] + ParallelMove output(R0 -> R5) + v8 = AllocateClosure() # RA: R0 <- () temps: [R2, R3, R4] + ParallelMove output(R0 -> vloc:R0) + StoreInstanceField(#closure-field[0], v8, v3) # RA: (R0, R1) temps: [R2, R3] + DirectCall foo2(v6, v8) # RA: R0 <- (R5, R0) temps: [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R19, R20, R23, R25, V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30] ParallelMove input(NullConstant(null) -> R0) - Return(v9) # RA: (R0) + Return(v11) # RA: (R0) --- closure FunctionExpression(Null (dynamic _#wc0#formal) { print(a2);