diff --git a/pkg/dart2wasm/lib/closures.dart b/pkg/dart2wasm/lib/closures.dart index 278ebfa32652..9a4c4b4d6ffc 100644 --- a/pkg/dart2wasm/lib/closures.dart +++ b/pkg/dart2wasm/lib/closures.dart @@ -61,8 +61,6 @@ class ClosureRepresentation { /// The struct type for the context of an instantiated closure. final w.StructType? instantiationContextStruct; - final String exportSuffix; - /// Entry point functions for instantiations of this generic closure. final Map> _instantiationTrampolines = {}; @@ -121,8 +119,7 @@ class ClosureRepresentation { this.vtableStruct, this.closureStruct, this._indexOfCombination, - this.instantiationContextStruct, - this.exportSuffix); + this.instantiationContextStruct); bool get isGeneric => typeCount > 0; @@ -265,8 +262,8 @@ class ClosureLayouter extends RecursiveVisitor { late final w.StructType closureBaseStruct = _makeClosureStruct( "#ClosureBase", _vtableBaseStructBare, translator.closureInfo.struct); - late final w.RefType typeType = - translator.classInfo[translator.typeClass]!.nonNullableType; + w.RefType get typeType => translator.types.nonNullableTypeType; + late final w.RefType functionTypeType = translator.classInfo[translator.functionTypeClass]!.nonNullableType; @@ -469,8 +466,7 @@ class ClosureLayouter extends RecursiveVisitor { vtableStruct, closureStruct, indexOfCombination, - instantiationContextStruct, - nameTags.join('-')); + instantiationContextStruct); if (typeCount > 0) { // The instantiation trampolines and the instantiation function can't be @@ -1073,10 +1069,15 @@ class Context { Context(this.owner, this.parent, this.containsThis); } -/// A captured variable. +/// A captured variable or type parameter. class Capture { + /// The captured [VariableDeclaration] or [TypeParameter]. final TreeNode variable; + late final Context context; + + /// The index of the captured variable or type parameter in its context + /// struct. late final int fieldIndex; /// Whether the captured variable is updated after initialization. @@ -1086,39 +1087,76 @@ class Capture { /// context. bool written = false; - Capture(this.variable); + Capture(this.variable) { + assert(variable is VariableDeclaration || variable is TypeParameter); + } w.ValueType get type => context.struct.fields[fieldIndex].type.unpacked; } -/// Compiler passes to find all captured variables and construct the context -/// tree for a member. +/// Information about contexts and closures of a member. class Closures { final Translator translator; - final Class? enclosingClass; - final Map captures = {}; - bool isThisCaptured = false; + + /// Maps [FunctionDeclaration]s and [FunctionExpression]s in the member to + /// [Lambda]s. final Map lambdas = {}; - late final w.RefType? nullableThisType; - // This [TreeNode] is the context owner, and can be a [FunctionNode], - // [Constructor], [ForStatement], [DoStatement] or a [WhileStatement]. + /// Maps [VariableDeclaration]s and [TypeParameter]s in the member to + /// [Capture]s. + final Map captures = {}; + + /// Maps AST nodes with contexts to their contexts. + /// + /// AST nodes that can have a context are: + /// + /// - [FunctionNode] + /// - [Constructor] + /// - [ForStatement] + /// - [DoStatement] + /// - [WhileStatement] final Map contexts = {}; + + /// Set of function declarations in the member that need to be compiled as + /// closures. These functions are used as variables. Example: + /// ``` + /// void f() { + /// void g () {} + /// print(g); + /// } + /// ``` + /// In the `Closures` for `f`, `g` will be in this set. final Set closurizedFunctions = {}; - Closures(this.translator, Member member) - : enclosingClass = member.enclosingClass { - final hasThis = member is Constructor || member.isInstanceMember; - nullableThisType = hasThis - ? translator.preciseThisFor(member, nullable: true) as w.RefType - : null; + final Member _member; + + /// Whether the member captures `this`. Set by [_CaptureFinder]. + bool _isThisCaptured = false; + + /// When the member is a constructor or an instance member, nullable type of + /// `this`. + final w.RefType? _nullableThisType; + + /// When `findCaptures` is `false`, this does not analyze the member body and + /// does not populate [lambdas], [contexts], [captures], and + /// [closurizedFunctions]. This mode is useful in the code generators that + /// always have direct access to variables (instead of via a context). + Closures(this.translator, this._member, {bool findCaptures = true}) + : _nullableThisType = _member is Constructor || _member.isInstanceMember + ? translator.preciseThisFor(_member, nullable: true) as w.RefType + : null { + if (findCaptures) { + _findCaptures(); + _collectContexts(); + _buildContexts(); + } } - late final w.ValueType typeType = - translator.classInfo[translator.typeClass]!.nonNullableType; + w.RefType get typeType => translator.types.nonNullableTypeType; - void findCaptures(Member member) { - var find = CaptureFinder(this, member); + void _findCaptures() { + final member = _member; + final find = _CaptureFinder(this, member); if (member is Constructor) { Class cls = member.enclosingClass; for (Field field in cls.fields) { @@ -1130,62 +1168,63 @@ class Closures { member.accept(find); } - void collectContexts(TreeNode node) { - if (captures.isNotEmpty || isThisCaptured) { - node.accept(ContextCollector(this, translator.options.enableAsserts)); + void _collectContexts() { + if (captures.isNotEmpty || _isThisCaptured) { + _member.accept(_ContextCollector(this, translator.options.enableAsserts)); } } - void buildContexts() { + void _buildContexts() { // Make struct definitions for (Context context in contexts.values) { - if (!context.isEmpty) { - if (context.owner is Constructor) { - Constructor constructor = context.owner as Constructor; - context.struct = translator.typesBuilder - .defineStruct("<$constructor-constructor-context>"); - } else if (context.owner.parent is Constructor) { - Constructor constructor = context.owner.parent as Constructor; - context.struct = translator.typesBuilder - .defineStruct("<$constructor-constructor-body-context>"); - } else { - context.struct = translator.typesBuilder - .defineStruct(""); - } + if (context.isEmpty) continue; + + final owner = context.owner; + if (owner is Constructor) { + context.struct = translator.typesBuilder + .defineStruct("<$owner-constructor-context>"); + } else if (owner.parent is Constructor) { + Constructor constructor = owner.parent as Constructor; + context.struct = translator.typesBuilder + .defineStruct("<$constructor-constructor-body-context>"); + } else { + context.struct = + translator.typesBuilder.defineStruct(""); } } // Build object layouts for (Context context in contexts.values) { - if (!context.isEmpty) { - w.StructType struct = context.struct; - if (context.parent != null) { - assert(!context.parent!.isEmpty); - struct.fields.add(w.FieldType( - w.RefType.def(context.parent!.struct, nullable: true))); - } - if (context.containsThis) { - assert(enclosingClass != null); - struct.fields.add(w.FieldType(nullableThisType!)); - } - for (VariableDeclaration variable in context.variables) { - int index = struct.fields.length; - struct.fields.add(w.FieldType(translator - .translateTypeOfLocalVariable(variable) - .withNullability(true))); - captures[variable]!.fieldIndex = index; - } - for (TypeParameter parameter in context.typeParameters) { - int index = struct.fields.length; - struct.fields.add(w.FieldType(typeType.withNullability(true))); - captures[parameter]!.fieldIndex = index; - } + if (context.isEmpty) continue; + + w.StructType struct = context.struct; + final parent = context.parent; + if (parent != null) { + assert(!parent.isEmpty); + struct.fields + .add(w.FieldType(w.RefType.def(parent.struct, nullable: true))); + } + if (context.containsThis) { + assert(_member.enclosingClass != null); + struct.fields.add(w.FieldType(_nullableThisType!)); + } + for (VariableDeclaration variable in context.variables) { + int index = struct.fields.length; + struct.fields.add(w.FieldType(translator + .translateTypeOfLocalVariable(variable) + .withNullability(true))); + captures[variable]!.fieldIndex = index; + } + for (TypeParameter parameter in context.typeParameters) { + int index = struct.fields.length; + struct.fields.add(w.FieldType(typeType.withNullability(true))); + captures[parameter]!.fieldIndex = index; } } } } -class CaptureFinder extends RecursiveVisitor { +class _CaptureFinder extends RecursiveVisitor { final Closures closures; final Member member; @@ -1196,7 +1235,7 @@ class CaptureFinder extends RecursiveVisitor { int get depth => functionIsSyncStarOrAsync.length - 1; - CaptureFinder(this.closures, this.member) + _CaptureFinder(this.closures, this.member) : _currentSource = member.enclosingComponent!.uriToSource[member.fileUri]!; @@ -1259,6 +1298,8 @@ class CaptureFinder extends RecursiveVisitor { if (functionIsSyncStarOrAsync[declDepth]) capture.written = true; } else if (variable is VariableDeclaration && variable.parent is FunctionDeclaration) { + // Variable is for a function declaration, the function needs to be + // compiled as a closure. closures.closurizedFunctions.add(variable.parent as FunctionDeclaration); } } @@ -1277,7 +1318,7 @@ class CaptureFinder extends RecursiveVisitor { void _visitThis() { if (depth > 0 || functionIsSyncStarOrAsync[0]) { - closures.isThisCaptured = true; + closures._isThisCaptured = true; } } @@ -1365,12 +1406,12 @@ class CaptureFinder extends RecursiveVisitor { } } -class ContextCollector extends RecursiveVisitor { +class _ContextCollector extends RecursiveVisitor { final Closures closures; Context? currentContext; final bool enableAsserts; - ContextCollector(this.closures, this.enableAsserts); + _ContextCollector(this.closures, this.enableAsserts); @override void visitAssertStatement(AssertStatement node) { @@ -1393,7 +1434,7 @@ class ContextCollector extends RecursiveVisitor { while (parent != null && parent.isEmpty) { parent = parent.parent; } - bool containsThis = closures.isThisCaptured && outerMost; + bool containsThis = closures._isThisCaptured && outerMost; currentContext = Context(node, parent, containsThis); closures.contexts[node] = currentContext!; node.visitChildren(this); @@ -1438,7 +1479,7 @@ class ContextCollector extends RecursiveVisitor { // Some type arguments or variables have been captured by the // initializer list. - if (closures.isThisCaptured) { + if (closures._isThisCaptured) { // In this case, we need two contexts: a constructor context to store // the captured arguments/type parameters (shared by the initializer // and constructor body, and a separate context just for the @@ -1467,7 +1508,7 @@ class ContextCollector extends RecursiveVisitor { // (node.function) for debugging purposes, and drop the // constructor allocator context as it is not used. final Context constructorBodyContext = - Context(node.function, null, closures.isThisCaptured); + Context(node.function, null, closures._isThisCaptured); currentContext = constructorBodyContext; node.function.body?.accept(this); diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart index aed139ae5fc6..52f65ff754bd 100644 --- a/pkg/dart2wasm/lib/code_generator.dart +++ b/pkg/dart2wasm/lib/code_generator.dart @@ -420,10 +420,6 @@ abstract class AstCodeGenerator setupParameters(member.reference, canSafelyOmitImplicitChecks: canSafelyOmitImplicitChecks); - closures.findCaptures(member); - closures.collectContexts(member); - closures.buildContexts(); - allocateContext(member.function!); captureParameters(); } @@ -601,23 +597,23 @@ abstract class AstCodeGenerator void allocateContext(TreeNode node) { Context? context = closures.contexts[node]; - if (context != null && !context.isEmpty) { - w.Local contextLocal = - addLocal(w.RefType.def(context.struct, nullable: true)); - context.currentLocal = contextLocal; - b.struct_new_default(context.struct); - b.local_set(contextLocal); - if (context.containsThis) { - b.local_get(contextLocal); - b.local_get(preciseThisLocal!); - b.struct_set(context.struct, context.thisFieldIndex); - } - if (context.parent != null) { - w.Local parentLocal = context.parent!.currentLocal; - b.local_get(contextLocal); - b.local_get(parentLocal); - b.struct_set(context.struct, context.parentFieldIndex); - } + if (context == null || context.isEmpty) return; + + w.Local contextLocal = + addLocal(w.RefType.def(context.struct, nullable: true)); + context.currentLocal = contextLocal; + b.struct_new_default(context.struct); + b.local_set(contextLocal); + if (context.containsThis) { + b.local_get(contextLocal); + b.local_get(preciseThisLocal!); + b.struct_set(context.struct, context.thisFieldIndex); + } + if (context.parent != null) { + w.Local parentLocal = context.parent!.currentLocal; + b.local_get(contextLocal); + b.local_get(parentLocal); + b.struct_set(context.struct, context.parentFieldIndex); } } @@ -2409,9 +2405,9 @@ abstract class AstCodeGenerator // Evaluate receiver w.StructType struct = representation.closureStruct; - w.Local temp = addLocal(w.RefType.def(struct, nullable: false)); - translateExpression(receiver, temp.type); - b.local_tee(temp); + w.Local closureLocal = addLocal(w.RefType.def(struct, nullable: false)); + translateExpression(receiver, closureLocal.type); + b.local_tee(closureLocal); b.struct_get(struct, FieldIndex.closureContext); // Type arguments @@ -2441,7 +2437,7 @@ abstract class AstCodeGenerator representation.fieldIndexForSignature(posArgCount, argNames); w.FunctionType functionType = representation.getVtableFieldType(vtableFieldIndex); - b.local_get(temp); + b.local_get(closureLocal); b.struct_get(struct, FieldIndex.closureVtable); b.struct_get(representation.vtableStruct, vtableFieldIndex); b.call_ref(functionType); @@ -3210,12 +3206,14 @@ class TearOffCodeGenerator extends AstCodeGenerator { @override void generateInternal() { - closures = Closures(translator, member); - generateTearOffGetter(member as Procedure); - } + // Initialize [Closures] without [Closures.captures]: [Closures.captures] is + // used by `makeType` below, when generating runtime types of type + // parameters of the function type, but the type parameters are not + // captured, always loaded from the `this` struct. + closures = Closures(translator, member, findCaptures: false); - void generateTearOffGetter(Procedure procedure) { _initializeThis(member.reference); + Procedure procedure = member as Procedure; DartType functionType = translator.getTearOffType(procedure); ClosureImplementation closure = translator.getTearOffClosure(procedure); w.StructType struct = closure.representation.closureStruct; @@ -3242,7 +3240,10 @@ class TypeCheckerCodeGenerator extends AstCodeGenerator { @override void generateInternal() { - closures = Closures(translator, member); + // Initialize [Closures] without [Closures.captures]: Similar to + // [TearOffCodeGenerator], type parameters will be loaded from the `this` + // struct. + closures = Closures(translator, member, findCaptures: false); if (member is Field || (member is Procedure && (member as Procedure).isSetter)) { _generateFieldSetterTypeCheckerMethod(); @@ -3849,9 +3850,6 @@ class StaticFieldInitializerCodeGenerator extends AstCodeGenerator { // Static field initializer function closures = Closures(translator, field); - closures.findCaptures(field); - closures.collectContexts(field); - closures.buildContexts(); w.Global global = translator.globals.getGlobalForStaticField(field); w.Global? flag = translator.globals.getGlobalInitializedFlag(field); @@ -3947,7 +3945,7 @@ class ImplicitFieldAccessorCodeGenerator extends AstCodeGenerator { // that instantiates types uses closure information to see whether a type // parameter was captured (and loads it from context chain) or not (and // loads it directly from `this`). - closures = Closures(translator, field); + closures = Closures(translator, field, findCaptures: false); final source = field.enclosingComponent!.uriToSource[field.fileUri]!; setSourceMapSourceAndFileOffset(source, field.fileOffset); diff --git a/pkg/dart2wasm/lib/functions.dart b/pkg/dart2wasm/lib/functions.dart index 21d9be3005d5..c48e1663e149 100644 --- a/pkg/dart2wasm/lib/functions.dart +++ b/pkg/dart2wasm/lib/functions.dart @@ -268,19 +268,12 @@ class _FunctionTypeGenerator extends MemberVisitor1 { List arguments = _getInputTypes( translator, target, null, false, translator.translateType); - if (translator.constructorClosures[node.reference] == null) { - // We need the contexts of the constructor before generating the - // initializer and constructor body functions, as these functions will - // return/take a context argument if context must be shared between them. - // Generate the contexts the first time we visit a constructor. - Closures closures = Closures(translator, node); - - closures.findCaptures(node); - closures.collectContexts(node); - closures.buildContexts(); - - translator.constructorClosures[node.reference] = closures; - } + // We need the contexts of the constructor before generating the initializer + // and constructor body functions, as these functions will return/take a + // context argument if context must be shared between them. Generate the + // contexts the first time we visit a constructor. + translator.constructorClosures[node.reference] ??= + Closures(translator, node); if (target.isInitializerReference) { return _getInitializerType(node, target, arguments); diff --git a/pkg/dart2wasm/lib/state_machine.dart b/pkg/dart2wasm/lib/state_machine.dart index e870cde07b5e..3ac5bb1a3b28 100644 --- a/pkg/dart2wasm/lib/state_machine.dart +++ b/pkg/dart2wasm/lib/state_machine.dart @@ -592,10 +592,11 @@ abstract class ProcedureStateMachineEntryCodeGenerator @override void generateInternal() { final source = member.enclosingComponent!.uriToSource[member.fileUri]!; - closures = Closures(translator, member); setSourceMapSource(source); setSourceMapFileOffset(member.fileOffset); + closures = Closures(translator, member); + // We don't support inlining state machine functions atm. Only when we // inline and have call-site guarantees we would use the unchecked entry. setupParametersAndContexts(member, useUncheckedEntry: false); diff --git a/pkg/dart2wasm/lib/types.dart b/pkg/dart2wasm/lib/types.dart index 7a1b4d759307..6ab597c28556 100644 --- a/pkg/dart2wasm/lib/types.dart +++ b/pkg/dart2wasm/lib/types.dart @@ -95,8 +95,8 @@ class Types { w.ValueType classAndFieldToType(Class cls, int fieldIndex) => translator.classInfo[cls]!.struct.fields[fieldIndex].type.unpacked; - /// Wasm value type for non-nullable `_Type` values - w.ValueType get nonNullableTypeType => typeClassInfo.nonNullableType; + /// Wasm value type for non-nullable `_Type` values. + w.RefType get nonNullableTypeType => typeClassInfo.nonNullableType; InterfaceType get namedParameterType => InterfaceType(translator.namedParameterClass, Nullability.nonNullable);