| // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:collection'; |
| import 'dart:math' show min; |
| |
| import 'package:collection/collection.dart'; |
| import 'package:kernel/ast.dart'; |
| import 'package:vm/metadata/procedure_attributes.dart'; |
| import 'package:vm/transformations/type_flow/utils.dart' show UnionFind; |
| import 'package:wasm_builder/wasm_builder.dart' as w; |
| |
| import 'class_info.dart'; |
| import 'code_generator.dart'; |
| import 'param_info.dart'; |
| import 'translator.dart'; |
| |
| /// Describes the implementation of a concrete closure, including its vtable |
| /// contents. |
| class ClosureImplementation { |
| /// The representation of the closure. |
| final ClosureRepresentation representation; |
| |
| /// The functions pointed to by the function entries in the vtable. |
| /// |
| /// This list does not include the dynamic call entry and the instantiation |
| /// function. |
| final List<w.BaseFunction> functions; |
| |
| /// The vtable entry used for dynamic calls. |
| final w.BaseFunction dynamicCallEntry; |
| |
| /// The constant global variable pointing to the vtable. |
| final w.Global vtable; |
| |
| /// The module this closure is implemented in. |
| final w.ModuleBuilder module; |
| |
| /// [ParameterInfo] to be used when directly calling the closure. |
| final ParameterInfo directCallParamInfo; |
| |
| ClosureImplementation( |
| this.representation, |
| this.functions, |
| this.dynamicCallEntry, |
| this.vtable, |
| this.module, |
| this.directCallParamInfo); |
| } |
| |
| /// Describes the representation of closures for a particular function |
| /// signature, including the layout of their vtable. |
| /// |
| /// Each vtable layout will have an entry for each number of positional |
| /// arguments from 0 up to the maximum number for the signature, followed by |
| /// an entry for each (non-empty) combination of argument names that closures |
| /// with this layout can be called with. |
| class ClosureRepresentation { |
| /// The struct field index in the vtable struct at which the function |
| /// entries start. |
| final int typeCount; |
| |
| /// The Wasm struct type for the vtable. |
| final w.StructType vtableStruct; |
| |
| /// The Wasm struct type for the closure object. |
| final w.StructType closureStruct; |
| |
| final Map<NameCombination, int>? _indexOfCombination; |
| |
| /// The struct type for the context of an instantiated closure. |
| final w.StructType? instantiationContextStruct; |
| |
| /// Entry point functions for instantiations of this generic closure. |
| final Map<w.ModuleBuilder, List<w.BaseFunction>> _instantiationTrampolines = |
| {}; |
| late List<w.BaseFunction> Function(w.ModuleBuilder module) |
| _instantiationTrampolinesGenerator; |
| List<w.BaseFunction> _instantiationTrampolinesForModule( |
| w.ModuleBuilder module) => |
| _instantiationTrampolines.putIfAbsent( |
| module, () => _instantiationTrampolinesGenerator(module)); |
| |
| /// The function that instantiates this generic closure. |
| final Map<w.ModuleBuilder, w.BaseFunction> _instantiationFunctions = {}; |
| late w.BaseFunction Function(w.ModuleBuilder module) |
| _instantiationFunctionGenerator; |
| w.BaseFunction instantiationFunctionForModule(w.ModuleBuilder module) { |
| return _instantiationFunctions.putIfAbsent( |
| module, () => _instantiationFunctionGenerator(module)); |
| } |
| |
| /// The function that takes instantiation context of this generic closure and |
| /// another instantiation context (both as `ref |
| /// #InstantiationClosureContextBase`) and compares types in the contexts. |
| /// This function is used to implement function equality of instantiations. |
| final Map<w.ModuleBuilder, w.BaseFunction> |
| _instantiationTypeComparisonFunctions = {}; |
| late w.BaseFunction Function(w.ModuleBuilder module) |
| _instantiationTypeComparisonFunctionGenerator; |
| w.BaseFunction instantiationTypeComparisonFunctionForModule( |
| w.ModuleBuilder module) { |
| return _instantiationTypeComparisonFunctions.putIfAbsent( |
| module, () => _instantiationTypeComparisonFunctionGenerator(module)); |
| } |
| |
| final Map<w.ModuleBuilder, w.BaseFunction> _instantiationTypeHashFunction = |
| {}; |
| late w.BaseFunction Function(w.ModuleBuilder module) |
| _instantiationTypeHashFunctionGenerator; |
| w.BaseFunction instantiationTypeHashFunctionForModule( |
| w.ModuleBuilder module) => |
| _instantiationTypeHashFunction.putIfAbsent( |
| module, () => _instantiationTypeHashFunctionGenerator(module)); |
| |
| /// The signature of the function that instantiates this generic closure. |
| w.FunctionType get instantiationFunctionType { |
| assert(isGeneric); |
| return getVtableFieldType(FieldIndex.vtableInstantiationFunction); |
| } |
| |
| /// The type of the vtable function at given index. |
| w.FunctionType getVtableFieldType(int index) => |
| (vtableStruct.fields[index].type as w.RefType).heapType as w.FunctionType; |
| |
| ClosureRepresentation( |
| Translator translator, |
| this.typeCount, |
| this.vtableStruct, |
| this.closureStruct, |
| this._indexOfCombination, |
| this.instantiationContextStruct); |
| |
| bool get isGeneric => typeCount > 0; |
| |
| /// Where the vtable entries for function calls start in the vtable struct. |
| int get vtableBaseIndex => isGeneric |
| ? ClosureLayouter.vtableBaseIndexGeneric |
| : ClosureLayouter.vtableBaseIndexNonGeneric; |
| |
| /// The field index in the vtable struct for the function entry to use when |
| /// calling the closure with the given number of positional arguments and the |
| /// given set of named arguments. |
| /// |
| /// `argNames` should be sorted. |
| int fieldIndexForSignature(int posArgCount, List<String> argNames) { |
| if (argNames.isEmpty) { |
| return vtableBaseIndex + posArgCount; |
| } else { |
| return vtableBaseIndex + |
| (posArgCount + 1) + |
| _indexOfCombination![NameCombination(argNames)]!; |
| } |
| } |
| |
| /// The combinations of parameter names for which there are entries in the |
| /// vtable of this closure, not including the empty combination, if |
| /// applicable. |
| Iterable<NameCombination> get nameCombinations => |
| _indexOfCombination?.keys ?? const []; |
| } |
| |
| /// A combination of argument names for a call of a closure. The names within a |
| /// name combination are sorted alphabetically. Name combinations can be sorted |
| /// lexicographically according to their lists of names, corresponding to the |
| /// order in which entry points taking named arguments will appear in vtables. |
| class NameCombination implements Comparable<NameCombination> { |
| final List<String> names; |
| |
| NameCombination(this.names) { |
| assert(names.isSorted(Comparable.compare)); |
| } |
| |
| @override |
| int compareTo(NameCombination other) { |
| int common = min(names.length, other.names.length); |
| for (int i = 0; i < common; i++) { |
| int comp = names[i].compareTo(other.names[i]); |
| if (comp != 0) return comp; |
| } |
| return names.length - other.names.length; |
| } |
| |
| @override |
| String toString() => names.toString(); |
| } |
| |
| /// Visitor to collect all closures and closure calls in the program to |
| /// compute the vtable layouts necessary to cover all signatures that occur. |
| /// |
| /// For each combination of type parameter count and positional parameter count, |
| /// the names of named parameters occurring together with that combination are |
| /// partitioned into clusters such that any combination of names that occurs |
| /// together is contained within a single cluster. |
| /// |
| /// Each cluster gets a corresponding vtable layout with en extry point for each |
| /// combination of names from the cluster that occurs in a call in the program. |
| class ClosureLayouter extends RecursiveVisitor { |
| final Translator translator; |
| final Map<TreeNode, ProcedureAttributesMetadata> procedureAttributeMetadata; |
| |
| List<List<ClosureRepresentationsForParameterCount>> representations = []; |
| |
| Set<Constant> visitedConstants = Set.identity(); |
| |
| // The member currently being visited while collecting function signatures. |
| Member? currentMember; |
| |
| // For non-generic closures. The entries are: |
| // 0: Dynamic call entry |
| // 1-...: Entries for calling the closure |
| static const int vtableBaseIndexNonGeneric = 1; |
| |
| // For generic closures. The entries are: |
| // 0: Dynamic call entry |
| // 1: Instantiation type comparison function |
| // 2: Instantiation type hash function |
| // 3: Instantiation function |
| // 4-...: Entries for calling the closure |
| static const int vtableBaseIndexGeneric = 4; |
| |
| // Base struct for vtables without the dynamic call entry added. Referenced |
| // by [closureBaseStruct] instead of the fully initialized version |
| // ([vtableBaseStruct]) to break the type cycle. |
| late final w.StructType _vtableBaseStructBare = |
| translator.typesBuilder.defineStruct("#VtableBase"); |
| |
| /// Base struct for instantiation closure contexts. Type tests against this |
| /// type is used in `_Closure._equals` to check if a closure is an |
| /// instantiation. |
| late final w.StructType instantiationContextBaseStruct = translator |
| .typesBuilder |
| .defineStruct("#InstantiationClosureContextBase", fields: [ |
| w.FieldType(w.RefType.def(closureBaseStruct, nullable: false), |
| mutable: false), |
| ]); |
| |
| /// Base struct for non-generic closure vtables. |
| late final w.StructType vtableBaseStruct = _vtableBaseStructBare |
| ..fields.add(w.FieldType( |
| w.RefType.def(translator.dynamicCallVtableEntryFunctionType, |
| nullable: false), |
| mutable: false)); |
| |
| /// Base struct for generic closure vtables. |
| late final w.StructType genericVtableBaseStruct = translator.typesBuilder |
| .defineStruct("#GenericVtableBase", |
| fields: vtableBaseStruct.fields.toList() |
| ..add(w.FieldType( |
| w.RefType.def(instantiationClosureTypeComparisonFunctionType, |
| nullable: false), |
| mutable: false)) |
| ..add(w.FieldType( |
| w.RefType.def(instantiationClosureTypeHashFunctionType, |
| nullable: false), |
| mutable: false)), |
| superType: vtableBaseStruct); |
| |
| /// Type of [ClosureRepresentation._instantiationTypeComparisonFunction]. |
| late final w.FunctionType instantiationClosureTypeComparisonFunctionType = |
| translator.typesBuilder.defineFunction( |
| [ |
| w.RefType.def(instantiationContextBaseStruct, nullable: false), |
| w.RefType.def(instantiationContextBaseStruct, nullable: false) |
| ], |
| [w.NumType.i32], // bool |
| ); |
| |
| late final w.FunctionType instantiationClosureTypeHashFunctionType = |
| translator.typesBuilder.defineFunction( |
| [w.RefType.def(instantiationContextBaseStruct, nullable: false)], |
| [w.NumType.i64], // hash |
| ); |
| |
| // Base struct for closures. |
| late final w.StructType closureBaseStruct = _makeClosureStruct( |
| "#ClosureBase", _vtableBaseStructBare, translator.closureInfo.struct); |
| |
| w.RefType get typeType => translator.types.nonNullableTypeType; |
| |
| late final w.RefType functionTypeType = |
| translator.classInfo[translator.functionTypeClass]!.nonNullableType; |
| |
| final Map<int, w.StructType> _instantiationContextBaseStructs = {}; |
| |
| w.StructType _getInstantiationContextBaseStruct(int numTypes) => |
| _instantiationContextBaseStructs.putIfAbsent( |
| numTypes, |
| () => translator.typesBuilder.defineStruct( |
| "#InstantiationClosureContextBase-$numTypes", |
| fields: [ |
| w.FieldType(w.RefType.def(closureBaseStruct, nullable: false), |
| mutable: false), |
| ...List.filled(numTypes, w.FieldType(typeType, mutable: false)) |
| ], |
| superType: instantiationContextBaseStruct)); |
| |
| final Map<int, Map<w.ModuleBuilder, w.BaseFunction>> |
| _instantiationTypeComparisonFunctions = {}; |
| |
| w.BaseFunction _getInstantiationTypeComparisonFunction( |
| w.ModuleBuilder module, int numTypes) => |
| _instantiationTypeComparisonFunctions |
| .putIfAbsent(numTypes, () => {}) |
| .putIfAbsent( |
| module, |
| () => |
| _createInstantiationTypeComparisonFunction(module, numTypes)); |
| |
| final Map<int, Map<w.ModuleBuilder, w.BaseFunction>> |
| _instantiationTypeHashFunctions = {}; |
| |
| w.BaseFunction _getInstantiationTypeHashFunction( |
| w.ModuleBuilder module, int numTypes) => |
| _instantiationTypeHashFunctions |
| .putIfAbsent(numTypes, () => {}) |
| .putIfAbsent(module, |
| () => _createInstantiationTypeHashFunction(module, numTypes)); |
| |
| w.StructType _makeClosureStruct( |
| String name, w.StructType vtableStruct, w.StructType superType) { |
| // A closure contains: |
| // - A class ID (always the `_Closure` class ID) |
| // - An identity hash |
| // - A context reference (used for `this` in tear-offs) |
| // - A vtable reference |
| // - A `_FunctionType` |
| return translator.typesBuilder.defineStruct(name, |
| fields: [ |
| w.FieldType(w.NumType.i32, mutable: false), |
| w.FieldType(w.NumType.i32), |
| w.FieldType(w.RefType.struct(nullable: false)), |
| w.FieldType(w.RefType.def(vtableStruct, nullable: false), |
| mutable: false), |
| w.FieldType(functionTypeType, mutable: false) |
| ], |
| superType: superType); |
| } |
| |
| w.ValueType get topType => translator.topInfo.nullableType; |
| |
| ClosureLayouter(this.translator) |
| : procedureAttributeMetadata = |
| (translator.component.metadata["vm.procedure-attributes.metadata"] |
| as ProcedureAttributesMetadataRepository) |
| .mapping; |
| |
| void collect() { |
| translator.component.accept(this); |
| computeClusters(); |
| } |
| |
| void computeClusters() { |
| for (int typeCount = 0; typeCount < representations.length; typeCount++) { |
| final representationsForTypeCount = representations[typeCount]; |
| for (int positionalCount = 0; |
| positionalCount < representationsForTypeCount.length; |
| positionalCount++) { |
| final representationsForCounts = |
| representationsForTypeCount[positionalCount]; |
| if (typeCount > 0) { |
| // Due to generic function instantiations, any name combination that |
| // occurs in a call of a non-generic function also counts as occurring |
| // in a call of all corresponding generic functions. |
| // Thus, the generic closure inherits the combinations for the |
| // corresponding closure with zero type parameters. |
| final instantiatedRepresentations = |
| representations[0][positionalCount]; |
| representationsForCounts |
| .inheritCombinationsFrom(instantiatedRepresentations); |
| } |
| representationsForCounts.computeClusters(); |
| } |
| } |
| } |
| |
| /// Get the representation for closures with a specific signature, described |
| /// by the number of type parameters, the maximum number of positional |
| /// parameters and the names of named parameters. |
| /// |
| /// `names` should be sorted. |
| ClosureRepresentation? getClosureRepresentation( |
| int typeCount, int positionalCount, List<String> names) { |
| final representations = |
| _representationsForCounts(typeCount, positionalCount); |
| if (representations.withoutNamed == null) { |
| ClosureRepresentation? parent = positionalCount == 0 |
| ? null |
| : getClosureRepresentation(typeCount, positionalCount - 1, const [])!; |
| representations.withoutNamed = _createRepresentation(typeCount, |
| positionalCount, const [], parent, null, [positionalCount]); |
| } |
| |
| if (names.isEmpty) return representations.withoutNamed!; |
| |
| ClosureRepresentationCluster? cluster = |
| representations.clusterForNames(names); |
| if (cluster == null) return null; |
| return cluster.representation ??= _createRepresentation( |
| typeCount, |
| positionalCount, |
| names, |
| representations.withoutNamed!, |
| cluster.indexOfCombination, |
| cluster.indexOfCombination.keys |
| .map((c) => positionalCount + c.names.length)); |
| } |
| |
| ClosureRepresentation _createRepresentation( |
| int typeCount, |
| int positionalCount, |
| List<String> names, |
| ClosureRepresentation? parent, |
| Map<NameCombination, int>? indexOfCombination, |
| Iterable<int> paramCounts) { |
| List<String> nameTags = ["$typeCount", "$positionalCount", ...names]; |
| String vtableName = ["#Vtable", ...nameTags].join("-"); |
| String closureName = ["#Closure", ...nameTags].join("-"); |
| w.StructType parentVtableStruct = parent?.vtableStruct ?? |
| (typeCount == 0 ? vtableBaseStruct : genericVtableBaseStruct); |
| w.StructType vtableStruct = translator.typesBuilder.defineStruct(vtableName, |
| fields: parentVtableStruct.fields, superType: parentVtableStruct); |
| w.StructType closureStruct = _makeClosureStruct( |
| closureName, vtableStruct, parent?.closureStruct ?? closureBaseStruct); |
| |
| ClosureRepresentation? instantiatedRepresentation; |
| w.StructType? instantiationContextStruct; |
| if (typeCount > 0) { |
| // Add or set vtable field for the instantiation function. |
| instantiatedRepresentation = |
| getClosureRepresentation(0, positionalCount, names)!; |
| w.RefType inputType = w.RefType.def(closureBaseStruct, nullable: false); |
| w.RefType outputType = w.RefType.def( |
| instantiatedRepresentation.closureStruct, |
| nullable: false); |
| w.FunctionType instantiationFunctionType = translator.typesBuilder |
| .defineFunction( |
| [inputType, ...List.filled(typeCount, typeType)], [outputType], |
| superType: parent?.instantiationFunctionType); |
| w.FieldType functionFieldType = w.FieldType( |
| w.RefType.def(instantiationFunctionType, nullable: false), |
| mutable: false); |
| if (parent == null) { |
| assert(vtableStruct.fields.length == |
| FieldIndex.vtableInstantiationFunction); |
| vtableStruct.fields.add(functionFieldType); |
| } else { |
| vtableStruct.fields[FieldIndex.vtableInstantiationFunction] = |
| functionFieldType; |
| } |
| |
| // Build layout for the context of instantiated closures, containing the |
| // original closure plus the type arguments. |
| String instantiationContextName = |
| ["#InstantiationContext", ...nameTags].join("-"); |
| instantiationContextStruct = |
| translator.typesBuilder.defineStruct(instantiationContextName, |
| fields: [ |
| w.FieldType(w.RefType.def(closureStruct, nullable: false), |
| mutable: false), |
| ...List.filled(typeCount, w.FieldType(typeType, mutable: false)) |
| ], |
| superType: _getInstantiationContextBaseStruct(typeCount)); |
| } |
| |
| // Add vtable fields for additional entry points relative to the parent. |
| for (int paramCount in paramCounts) { |
| w.FunctionType entry = translator.typesBuilder.defineFunction([ |
| w.RefType.struct(nullable: false), |
| ...List.filled(typeCount, typeType), |
| ...List.filled(paramCount, topType) |
| ], [ |
| topType |
| ]); |
| vtableStruct.fields.add( |
| w.FieldType(w.RefType.def(entry, nullable: false), mutable: false)); |
| } |
| |
| ClosureRepresentation representation = ClosureRepresentation( |
| translator, |
| typeCount, |
| vtableStruct, |
| closureStruct, |
| indexOfCombination, |
| instantiationContextStruct); |
| |
| if (typeCount > 0) { |
| // The instantiation trampolines and the instantiation function can't be |
| // produced now, since we might not have added the module imports yet, and |
| // we can't define any functions before we have added the imports. |
| // Therefore, we set thunks in the representation which will be called |
| // when the instantiation function is needed, which will be during code |
| // generation, after the imports have been added. |
| |
| representation._instantiationTrampolinesGenerator = (module) { |
| List<w.BaseFunction> instantiationTrampolines = [ |
| ...?parent?._instantiationTrampolinesForModule(module) |
| ]; |
| String instantiationTrampolineFunctionName = |
| "${["#Instantiation", ...nameTags].join("-")} trampoline"; |
| if (names.isEmpty) { |
| // Add trampoline to the corresponding entry in the generic closure. |
| w.BaseFunction trampoline = _createInstantiationTrampoline( |
| module, |
| instantiationTrampolineFunctionName, |
| typeCount, |
| closureStruct, |
| _getInstantiationContextBaseStruct(typeCount), |
| instantiatedRepresentation!.vtableStruct, |
| vtableBaseIndexNonGeneric + instantiationTrampolines.length, |
| vtableStruct, |
| vtableBaseIndexGeneric + instantiationTrampolines.length); |
| instantiationTrampolines.add(trampoline); |
| } else { |
| // For each name combination in the instantiated closure, add a |
| // trampoline to the entry for the same name combination in the |
| // generic closure, or a dummy entry if the generic closure does not |
| // have that name combination. |
| for (NameCombination combination |
| in instantiatedRepresentation!._indexOfCombination!.keys) { |
| int? genericIndex = indexOfCombination![combination]; |
| w.BaseFunction trampoline = genericIndex != null |
| ? _createInstantiationTrampoline( |
| module, |
| instantiationTrampolineFunctionName, |
| typeCount, |
| closureStruct, |
| _getInstantiationContextBaseStruct(typeCount), |
| instantiatedRepresentation.vtableStruct, |
| vtableBaseIndexNonGeneric + instantiationTrampolines.length, |
| vtableStruct, |
| vtableBaseIndexGeneric + |
| (positionalCount + 1) + |
| genericIndex) |
| : translator |
| .getDummyValuesCollectorForModule(module) |
| .getDummyFunction((instantiatedRepresentation |
| .vtableStruct |
| .fields[vtableBaseIndexNonGeneric + |
| instantiationTrampolines.length] |
| .type as w.RefType) |
| .heapType as w.FunctionType); |
| instantiationTrampolines.add(trampoline); |
| } |
| } |
| return instantiationTrampolines; |
| }; |
| |
| representation._instantiationFunctionGenerator = (module) { |
| String instantiationFunctionName = |
| ["#Instantiation", ...nameTags].join("-"); |
| return _createInstantiationFunction( |
| module, |
| typeCount, |
| instantiatedRepresentation!, |
| representation._instantiationTrampolinesForModule(module), |
| representation.instantiationFunctionType, |
| instantiationContextStruct!, |
| closureStruct, |
| instantiationFunctionName); |
| }; |
| |
| representation._instantiationTypeComparisonFunctionGenerator = (module) => |
| _getInstantiationTypeComparisonFunction(module, typeCount); |
| |
| representation._instantiationTypeHashFunctionGenerator = |
| (module) => _getInstantiationTypeHashFunction(module, typeCount); |
| } |
| |
| return representation; |
| } |
| |
| w.BaseFunction _createInstantiationTrampoline( |
| w.ModuleBuilder module, |
| String name, |
| int typeCount, |
| w.StructType genericClosureStruct, |
| w.StructType instantiationContextBaseStruct, |
| w.StructType instantiatedVtableStruct, |
| int instantiatedVtableFieldIndex, |
| w.StructType genericVtableStruct, |
| int genericVtableFieldIndex) { |
| assert(instantiationContextBaseStruct.fields.length == 1 + typeCount); |
| w.FunctionType instantiatedFunctionType = (instantiatedVtableStruct |
| .fields[instantiatedVtableFieldIndex].type as w.RefType) |
| .heapType as w.FunctionType; |
| w.FunctionType genericFunctionType = |
| (genericVtableStruct.fields[genericVtableFieldIndex].type as w.RefType) |
| .heapType as w.FunctionType; |
| assert(genericFunctionType.inputs.length == |
| instantiatedFunctionType.inputs.length + typeCount); |
| |
| final trampoline = module.functions.define(instantiatedFunctionType, name); |
| final b = trampoline.body; |
| |
| // Cast context reference to actual context type. |
| w.RefType contextType = |
| w.RefType.def(instantiationContextBaseStruct, nullable: false); |
| w.Local contextLocal = b.addLocal(contextType); |
| b.local_get(trampoline.locals[0]); |
| b.ref_cast(contextType); |
| b.local_tee(contextLocal); |
| |
| // Push inner context |
| b.struct_get( |
| instantiationContextBaseStruct, FieldIndex.instantiationContextInner); |
| b.struct_get(closureBaseStruct, FieldIndex.closureContext); |
| |
| // Push type arguments |
| for (int t = 0; t < typeCount; t++) { |
| b.local_get(contextLocal); |
| b.struct_get(instantiationContextBaseStruct, |
| FieldIndex.instantiationContextTypeArgumentsBase + t); |
| } |
| |
| // Push arguments |
| for (int p = 1; p < instantiatedFunctionType.inputs.length; p++) { |
| b.local_get(trampoline.locals[p]); |
| } |
| |
| // Call inner |
| b.local_get(contextLocal); |
| b.struct_get( |
| instantiationContextBaseStruct, FieldIndex.instantiationContextInner); |
| // #ClosureBase to closure struct with the right arguments |
| b.ref_cast(w.RefType(genericClosureStruct, nullable: false)); |
| b.struct_get(genericClosureStruct, FieldIndex.closureVtable); |
| b.struct_get(genericVtableStruct, genericVtableFieldIndex); |
| b.call_ref(genericFunctionType); |
| b.end(); |
| |
| return trampoline; |
| } |
| |
| w.BaseFunction _createInstantiationDynamicCallEntry(w.ModuleBuilder module, |
| int typeCount, w.StructType instantiationContextStruct) { |
| final function = module.functions.define( |
| translator.dynamicCallVtableEntryFunctionType, |
| "instantiation dynamic call entry"); |
| final b = function.body; |
| |
| final instantiatedClosureLocal = function.locals[0]; |
| // First argument is the type list, which will always be empty. We'll pass |
| // the instantiation types to the original vtable entry. |
| final posArgsListLocal = function.locals[2]; |
| final namedArgsListLocal = function.locals[3]; |
| |
| // Get instantiation context, which has the original closure and type |
| // arguments |
| final w.RefType instantiationContextType = |
| w.RefType.def(instantiationContextStruct, nullable: false); |
| final w.Local instantiationContextLocal = |
| b.addLocal(instantiationContextType); |
| b.local_get(instantiatedClosureLocal); |
| b.struct_get(closureBaseStruct, FieldIndex.closureContext); |
| b.ref_cast(instantiationContextType); |
| b.local_tee(instantiationContextLocal); |
| |
| // Push original closure |
| b.struct_get( |
| instantiationContextStruct, FieldIndex.instantiationContextInner); |
| |
| // Push types |
| translator.makeArray(b, translator.typeArrayType, typeCount, |
| (elementType, elementIdx) { |
| b.local_get(instantiationContextLocal); |
| b.struct_get(instantiationContextStruct, |
| FieldIndex.instantiationContextTypeArgumentsBase + elementIdx); |
| }); |
| |
| b.local_get(posArgsListLocal); |
| b.local_get(namedArgsListLocal); |
| |
| // Call inner |
| b.local_get(instantiationContextLocal); |
| b.struct_get( |
| instantiationContextStruct, FieldIndex.instantiationContextInner); |
| b.struct_get(closureBaseStruct, FieldIndex.closureVtable); |
| b.struct_get(vtableBaseStruct, FieldIndex.vtableDynamicCallEntry); |
| b.call_ref(translator.dynamicCallVtableEntryFunctionType); |
| b.end(); |
| |
| return function; |
| } |
| |
| w.BaseFunction _createInstantiationFunction( |
| w.ModuleBuilder module, |
| int typeCount, |
| ClosureRepresentation instantiatedRepresentation, |
| List<w.BaseFunction> instantiationTrampolines, |
| w.FunctionType functionType, |
| w.StructType contextStruct, |
| w.StructType genericClosureStruct, |
| String name) { |
| assert(typeCount > 0); |
| w.RefType genericClosureType = |
| w.RefType.def(genericClosureStruct, nullable: false); |
| w.RefType instantiatedClosureType = w.RefType.def( |
| instantiatedRepresentation.closureStruct, |
| nullable: false); |
| assert(functionType.outputs.single == instantiatedClosureType); |
| |
| // Create vtable for the instantiated closure, containing the trampolines. |
| final vtable = module.globals.define(w.GlobalType( |
| w.RefType.def(instantiatedRepresentation.vtableStruct, nullable: false), |
| mutable: false)); |
| final ib = vtable.initializer; |
| ib.ref_func( |
| _createInstantiationDynamicCallEntry(module, typeCount, contextStruct)); |
| for (w.BaseFunction trampoline in instantiationTrampolines) { |
| ib.ref_func(trampoline); |
| } |
| ib.struct_new(instantiatedRepresentation.vtableStruct); |
| ib.end(); |
| |
| final instantiationFunction = module.functions.define(functionType, name); |
| final b = instantiationFunction.body; |
| w.Local preciseClosure = b.addLocal(genericClosureType); |
| |
| // Parameters to the instantiation function |
| final w.Local closureParam = instantiationFunction.locals[0]; |
| w.Local typeParam(int i) => instantiationFunction.locals[1 + i]; |
| |
| // Header for the closure struct |
| b.pushObjectHeaderFields(translator.closureInfo); |
| |
| // Context for the instantiated closure, containing the original closure and |
| // the type arguments |
| b.local_get(closureParam); |
| b.ref_cast(genericClosureType); |
| b.local_tee(preciseClosure); |
| for (int i = 0; i < typeCount; i++) { |
| b.local_get(typeParam(i)); |
| } |
| b.struct_new(contextStruct); |
| |
| translator.globals.readGlobal(b, vtable); |
| |
| // Construct the type of the instantiated closure, which is the type of the |
| // original closure with the type arguments of the instantiation substituted |
| // for its type parameters. |
| |
| // Type of the original closure |
| b.local_get(preciseClosure); |
| b.struct_get(genericClosureStruct, FieldIndex.closureRuntimeType); |
| |
| // Put type arguments into a `WasmArray<_Type>`. |
| for (int i = 0; i < typeCount; i++) { |
| b.local_get(typeParam(i)); |
| } |
| b.array_new_fixed(translator.typeArrayType, typeCount); |
| |
| // Call [_TypeUniverse.substituteFunctionTypeArgument]. |
| translator.callReference( |
| translator.substituteFunctionTypeArgument.reference, b); |
| |
| // Finally, allocate closure struct. |
| b.struct_new(instantiatedRepresentation.closureStruct); |
| |
| b.end(); |
| |
| return instantiationFunction; |
| } |
| |
| w.BaseFunction _createInstantiationTypeComparisonFunction( |
| w.ModuleBuilder module, int numTypes) { |
| final function = module.functions.define( |
| instantiationClosureTypeComparisonFunctionType, |
| "#InstantiationTypeComparison-$numTypes"); |
| |
| final b = function.body; |
| |
| final contextStructType = _getInstantiationContextBaseStruct(numTypes); |
| final contextRefType = w.RefType.def(contextStructType, nullable: false); |
| |
| final thisContext = function.locals[0]; |
| final otherContext = function.locals[1]; |
| |
| final thisContextLocal = b.addLocal(contextRefType); |
| final otherContextLocal = b.addLocal(contextRefType); |
| |
| // Call site (`_Closure._equals`) checks that closures are instantiations |
| // of the same function, so we can assume they have the right instantiation |
| // context types. |
| b.local_get(otherContext); |
| b.ref_cast(contextRefType); |
| b.local_set(otherContextLocal); |
| |
| b.local_get(thisContext); |
| b.ref_cast(contextRefType); |
| b.local_set(thisContextLocal); |
| |
| for (int i = 0; i < numTypes; i += 1) { |
| final typeFieldIdx = FieldIndex.instantiationContextTypeArgumentsBase + i; |
| b.local_get(thisContextLocal); |
| b.struct_get(contextStructType, typeFieldIdx); |
| b.local_get(otherContextLocal); |
| b.struct_get(contextStructType, typeFieldIdx); |
| translator.callReference(translator.runtimeTypeEquals.reference, b); |
| b.if_(); |
| } |
| |
| b.i32_const(1); // true |
| b.return_(); |
| |
| for (int i = 0; i < numTypes; i += 1) { |
| b.end(); |
| } |
| |
| b.i32_const(0); // false |
| b.end(); // end of function |
| return function; |
| } |
| |
| w.BaseFunction _createInstantiationTypeHashFunction( |
| w.ModuleBuilder module, int numTypes) { |
| final function = module.functions.define( |
| instantiationClosureTypeHashFunctionType, |
| "#InstantiationTypeHash-$numTypes"); |
| |
| final b = function.body; |
| |
| final contextStructType = _getInstantiationContextBaseStruct(numTypes); |
| final contextRefType = w.RefType.def(contextStructType, nullable: false); |
| |
| final thisContext = function.locals[0]; |
| final thisContextLocal = b.addLocal(contextRefType); |
| |
| b.local_get(thisContext); |
| b.ref_cast(contextRefType); |
| b.local_set(thisContextLocal); |
| |
| // Same as `SystemHash.hashN` functions: combine first hash with |
| // `_hashSeed`. |
| translator.callReference(translator.hashSeed.getterReference, b); |
| |
| // Field 0 is the instantiated closure. Types start at 1. |
| for (int typeFieldIdx = 1; typeFieldIdx <= numTypes; typeFieldIdx += 1) { |
| b.local_get(thisContextLocal); |
| b.struct_get(contextStructType, typeFieldIdx); |
| translator.callReference(translator.runtimeTypeHashCode.reference, b); |
| translator.callReference(translator.systemHashCombine.reference, b); |
| } |
| |
| b.end(); |
| |
| return function; |
| } |
| |
| ClosureRepresentationsForParameterCount _representationsForCounts( |
| int typeCount, int positionalCount) { |
| while (representations.length <= typeCount) { |
| representations.add([]); |
| } |
| List<ClosureRepresentationsForParameterCount> positionals = |
| representations[typeCount]; |
| while (positionals.length <= positionalCount) { |
| positionals.add(ClosureRepresentationsForParameterCount()); |
| } |
| return positionals[positionalCount]; |
| } |
| |
| void _visitFunctionNode(FunctionNode functionNode) { |
| final representations = _representationsForCounts( |
| functionNode.typeParameters.length, |
| functionNode.positionalParameters.length); |
| representations.registerFunction(functionNode); |
| if (functionNode.typeParameters.isNotEmpty) { |
| // Due to generic function instantiations, any generic function present |
| // in the program also counts as a presence of the corresponding |
| // non-generic function. |
| final instantiatedRepresentations = _representationsForCounts( |
| 0, functionNode.positionalParameters.length); |
| instantiatedRepresentations.registerFunction(functionNode); |
| } |
| } |
| |
| void _visitFunctionInvocation(Arguments arguments) { |
| final representations = _representationsForCounts( |
| arguments.types.length, arguments.positional.length); |
| representations.registerCall(arguments); |
| } |
| |
| @override |
| void visitFunctionExpression(FunctionExpression node) { |
| _visitFunctionNode(node.function); |
| if (currentMember != null) { |
| translator.membersContainingInnerFunctions.add(currentMember!); |
| } |
| super.visitFunctionExpression(node); |
| } |
| |
| @override |
| void visitFunctionDeclaration(FunctionDeclaration node) { |
| _visitFunctionNode(node.function); |
| if (currentMember != null) { |
| translator.membersContainingInnerFunctions.add(currentMember!); |
| } |
| super.visitFunctionDeclaration(node); |
| } |
| |
| @override |
| void visitProcedure(Procedure node) { |
| if (node.isInstanceMember && |
| node.stubKind != ProcedureStubKind.RepresentationField) { |
| ProcedureAttributesMetadata metadata = procedureAttributeMetadata[node]!; |
| if (metadata.hasTearOffUses) { |
| _visitFunctionNode(node.function); |
| } |
| } |
| currentMember = node; |
| super.visitProcedure(node); |
| currentMember = null; |
| } |
| |
| @override |
| void visitConstructor(Constructor node) { |
| currentMember = node; |
| super.visitConstructor(node); |
| currentMember = null; |
| } |
| |
| @override |
| void visitStaticTearOffConstantReference(StaticTearOffConstant constant) { |
| _visitFunctionNode(constant.function); |
| } |
| |
| @override |
| void defaultConstantReference(Constant constant) { |
| if (visitedConstants.add(constant)) { |
| constant.visitChildren(this); |
| } |
| } |
| |
| @override |
| void visitFunctionInvocation(FunctionInvocation node) { |
| _visitFunctionInvocation(node.arguments); |
| super.visitFunctionInvocation(node); |
| } |
| |
| @override |
| void visitDynamicInvocation(DynamicInvocation node) { |
| if (node.name.text == "call") { |
| _visitFunctionInvocation(node.arguments); |
| } |
| super.visitDynamicInvocation(node); |
| } |
| } |
| |
| class ClosureRepresentationsForParameterCount { |
| ClosureRepresentation? withoutNamed; |
| final Set<NameCombination> callCombinations = SplayTreeSet(); |
| final Map<String, int> nameIds = SplayTreeMap(); |
| final UnionFind nameUnions = UnionFind(); |
| final Map<String, ClosureRepresentationCluster> clusterForName = {}; |
| |
| void registerFunction(FunctionNode functionNode) { |
| int? prevIndex; |
| for (VariableDeclaration named in functionNode.namedParameters) { |
| String name = named.name!; |
| int nameIndex = nameIds.putIfAbsent(name, () => nameUnions.add()); |
| if (prevIndex != null) { |
| nameUnions.union(prevIndex, nameIndex); |
| } |
| prevIndex = nameIndex; |
| } |
| } |
| |
| void registerCall(Arguments arguments) { |
| if (arguments.named.isNotEmpty) { |
| NameCombination combination = |
| NameCombination(arguments.named.map((a) => a.name).toList()..sort()); |
| callCombinations.add(combination); |
| } |
| } |
| |
| void inheritCombinationsFrom(ClosureRepresentationsForParameterCount other) { |
| callCombinations.addAll(other.callCombinations); |
| } |
| |
| ClosureRepresentationCluster? clusterForNames(List<String> names) { |
| final cluster = clusterForName[names[0]]; |
| for (int i = 1; i < names.length; i++) { |
| if (clusterForName[names[i]] != cluster) { |
| return null; |
| } |
| } |
| return cluster; |
| } |
| |
| void computeClusters() { |
| Map<int, ClosureRepresentationCluster> clusterForId = {}; |
| nameIds.forEach((name, id) { |
| int canonicalId = nameUnions.find(id); |
| final cluster = clusterForId.putIfAbsent(canonicalId, () { |
| return ClosureRepresentationCluster(); |
| }); |
| cluster.names.add(name); |
| clusterForName[name] = cluster; |
| }); |
| for (NameCombination combination in callCombinations) { |
| final cluster = clusterForNames(combination.names); |
| if (cluster != null) { |
| cluster.indexOfCombination[combination] = |
| cluster.indexOfCombination.length; |
| } |
| } |
| } |
| } |
| |
| class ClosureRepresentationCluster { |
| final List<String> names = []; |
| final Map<NameCombination, int> indexOfCombination = SplayTreeMap(); |
| ClosureRepresentation? representation; |
| } |
| |
| /// A local function or function expression. |
| class Lambda { |
| final FunctionNode functionNode; |
| |
| // Note: creating a `Lambda` does not add this function to the compilation |
| // queue. Make sure to get it with `Functions.getLambdaFunction` to add it |
| // to the compilation queue. |
| final w.FunctionBuilder function; |
| |
| final Source functionNodeSource; |
| |
| /// Index of the function within the enclosing member, based on pre-order |
| /// traversal of the member body. |
| final int index; |
| |
| Lambda._( |
| this.functionNode, this.function, this.functionNodeSource, this.index); |
| } |
| |
| /// The context for one or more closures, containing their captured variables. |
| /// |
| /// Contexts can be nested, corresponding to the scopes covered by the contexts. |
| /// Each local function, function expression or loop (`while`, `do`/`while` or |
| /// `for`) gives rise to its own context nested inside the context of its |
| /// surrounding scope. At runtime, each context has a reference to its parent |
| /// context. |
| /// |
| /// Closures corresponding to local functions or function expressions in the |
| /// same scope share the same context. Thus, a closure can potentially keep more |
| /// values alive than the ones captured by the closure itself. |
| /// |
| /// A context may be empty (containing no captured variables), in which case it |
| /// is skipped in the context parent chain and never allocated. A context can |
| /// also be skipped if it only contains variables that are not in scope for the |
| /// child context (and its descendants). |
| class Context { |
| /// The node containing the scope covered by the context. This is either a |
| /// [FunctionNode] (for members, local functions, constructor bodies and |
| /// function expressions), a [Constructor], a [ForStatement], a [DoStatement] |
| /// or a [WhileStatement]. |
| final TreeNode owner; |
| |
| /// The parent of this context, corresponding to the lexically enclosing |
| /// owner. This is null if the context is a member context, or if all contexts |
| /// in the parent chain are skipped. |
| final Context? parent; |
| |
| /// The variables captured by this context. |
| final List<VariableDeclaration> variables = []; |
| |
| /// The type parameters captured by this context. |
| final List<TypeParameter> typeParameters = []; |
| |
| /// Whether this context contains a captured `this`. Only member contexts can. |
| final bool containsThis; |
| |
| /// The Wasm struct representing this context at runtime. |
| late final w.StructType struct; |
| |
| /// The local variable currently pointing to this context. Used during code |
| /// generation. |
| late w.Local currentLocal; |
| |
| bool get isEmpty => |
| variables.isEmpty && typeParameters.isEmpty && !containsThis; |
| |
| int get parentFieldIndex { |
| assert(parent != null); |
| return 0; |
| } |
| |
| int get thisFieldIndex { |
| assert(containsThis); |
| |
| return parent != null ? 1 : 0; |
| } |
| |
| Context(this.owner, this.parent, this.containsThis); |
| } |
| |
| /// 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. |
| /// |
| /// If the variable is not updated, we can create a local for the variable |
| /// and use it for reads. If it's updated we need to read it from the |
| /// context. |
| bool written = false; |
| |
| Capture(this.variable) { |
| assert(variable is VariableDeclaration || variable is TypeParameter); |
| } |
| |
| w.ValueType get type => context.struct.fields[fieldIndex].type.unpacked; |
| } |
| |
| /// Information about contexts and closures of a member. |
| class Closures { |
| final Translator translator; |
| |
| /// Maps [FunctionDeclaration]s and [FunctionExpression]s in the member to |
| /// [Lambda]s. |
| final Map<FunctionNode, Lambda> lambdas = {}; |
| |
| /// Maps [VariableDeclaration]s and [TypeParameter]s in the member to |
| /// [Capture]s. |
| final Map<TreeNode, Capture> captures = {}; |
| |
| /// Maps AST nodes with contexts to their contexts. |
| /// |
| /// AST nodes that can have a context are: |
| /// |
| /// - [FunctionNode] |
| /// - [Constructor] |
| /// - [ForStatement] |
| /// - [DoStatement] |
| /// - [WhileStatement] |
| final Map<TreeNode, Context> 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<FunctionDeclaration> closurizedFunctions = {}; |
| |
| 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). |
| /// |
| /// When `findCaptures` is `true`, the created [Lambda]s are also added to the |
| /// compilation queue. |
| Closures(this.translator, this._member, {required bool findCaptures}) |
| : _nullableThisType = _member is Constructor || _member.isInstanceMember |
| ? translator.preciseThisFor(_member, nullable: true) as w.RefType |
| : null { |
| if (findCaptures) { |
| _findCaptures(); |
| _collectContexts(); |
| _buildContexts(); |
| } |
| } |
| |
| w.RefType get typeType => translator.types.nonNullableTypeType; |
| |
| void _findCaptures() { |
| final member = _member; |
| final find = _CaptureFinder(this, member); |
| if (member is Constructor) { |
| Class cls = member.enclosingClass; |
| for (Field field in cls.fields) { |
| if (field.isInstanceMember && field.initializer != null) { |
| field.initializer!.accept(find); |
| } |
| } |
| } |
| member.accept(find); |
| } |
| |
| void _collectContexts() { |
| if (captures.isNotEmpty || _isThisCaptured) { |
| _member.accept(_ContextCollector(this, translator.options.enableAsserts)); |
| } |
| } |
| |
| void _buildContexts() { |
| // Make struct definitions |
| for (Context context in contexts.values) { |
| 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("<context ${owner.location}>"); |
| } |
| } |
| |
| // Build object layouts |
| for (Context context in contexts.values) { |
| 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 { |
| final Closures closures; |
| final Member member; |
| |
| // Stores the depth of captured type parameters and variables. The [TreeNode] |
| // key must be either a [VariableDeclaration] or a [TypeParameter]. |
| final Map<TreeNode, int> variableDepth = {}; |
| final List<bool> functionIsSyncStarOrAsync = [false]; |
| |
| int get depth => functionIsSyncStarOrAsync.length - 1; |
| |
| _CaptureFinder(this.closures, this.member) |
| : _currentSource = |
| member.enclosingComponent!.uriToSource[member.fileUri]!; |
| |
| Translator get translator => closures.translator; |
| |
| Source _currentSource; |
| |
| @override |
| void visitFileUriExpression(FileUriExpression node) { |
| _currentSource = node.enclosingComponent!.uriToSource[node.fileUri]!; |
| super.visitFileUriExpression(node); |
| } |
| |
| @override |
| void visitFunctionNode(FunctionNode node) { |
| assert(depth == 0); // Nested function nodes are skipped by [_visitLambda]. |
| functionIsSyncStarOrAsync[0] = node.asyncMarker == AsyncMarker.SyncStar || |
| node.asyncMarker == AsyncMarker.Async; |
| node.visitChildren(this); |
| functionIsSyncStarOrAsync[0] = false; |
| } |
| |
| @override |
| void visitAssertStatement(AssertStatement node) { |
| if (translator.options.enableAsserts) { |
| super.visitAssertStatement(node); |
| } |
| } |
| |
| @override |
| void visitAssertBlock(AssertBlock node) { |
| if (translator.options.enableAsserts) { |
| super.visitAssertBlock(node); |
| } |
| } |
| |
| @override |
| void visitVariableDeclaration(VariableDeclaration node) { |
| if (depth > 0) { |
| variableDepth[node] = depth; |
| } |
| super.visitVariableDeclaration(node); |
| } |
| |
| @override |
| void visitTypeParameter(TypeParameter node) { |
| if (node.declaration is GenericFunction) { |
| if (depth > 0) { |
| variableDepth[node] = depth; |
| } |
| } |
| super.visitTypeParameter(node); |
| } |
| |
| void _visitVariableUse(TreeNode variable) { |
| int declDepth = variableDepth[variable] ?? 0; |
| assert(declDepth <= depth); |
| if (declDepth < depth || functionIsSyncStarOrAsync[declDepth]) { |
| final capture = closures.captures[variable] ??= Capture(variable); |
| 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); |
| } |
| } |
| |
| @override |
| void visitVariableGet(VariableGet node) { |
| _visitVariableUse(node.variable); |
| super.visitVariableGet(node); |
| } |
| |
| @override |
| void visitVariableSet(VariableSet node) { |
| _visitVariableUse(node.variable); |
| super.visitVariableSet(node); |
| } |
| |
| void _visitThis() { |
| if (depth > 0 || functionIsSyncStarOrAsync[0]) { |
| closures._isThisCaptured = true; |
| } |
| } |
| |
| @override |
| void visitThisExpression(ThisExpression node) { |
| _visitThis(); |
| } |
| |
| @override |
| void visitSuperMethodInvocation(SuperMethodInvocation node) { |
| _visitThis(); |
| super.visitSuperMethodInvocation(node); |
| } |
| |
| @override |
| void visitSuperPropertyGet(SuperPropertyGet node) { |
| _visitThis(); |
| super.visitSuperPropertyGet(node); |
| } |
| |
| @override |
| void visitSuperPropertySet(SuperPropertySet node) { |
| _visitThis(); |
| super.visitSuperPropertySet(node); |
| } |
| |
| @override |
| void visitTypeParameterType(TypeParameterType node) { |
| bool classTypeParameter = |
| node.parameter.declaration == member.enclosingClass; |
| |
| if (classTypeParameter && member is Constructor) { |
| // Type parameters can be captured by lambdas inside the initializer |
| // list, which does not have access to `this` as the object has not been |
| // allocated yet. Therefore, these captured type parameters must be |
| // added to the context instead. |
| _visitVariableUse(node.parameter); |
| } else if (classTypeParameter) { |
| _visitThis(); |
| } else if (node.parameter.declaration is GenericFunction) { |
| _visitVariableUse(node.parameter); |
| } |
| super.visitTypeParameterType(node); |
| } |
| |
| void _visitLambda(FunctionNode node, [VariableDeclaration? variable]) { |
| final module = translator.moduleForReference(member.reference); |
| List<w.ValueType> inputs = [ |
| w.RefType.struct(nullable: false), |
| ...List.filled(node.typeParameters.length, closures.typeType), |
| for (VariableDeclaration param in node.positionalParameters) |
| translator.translateType(param.type), |
| for (VariableDeclaration param in node.namedParameters) |
| translator.translateType(param.type) |
| ]; |
| List<w.ValueType> outputs = [translator.translateType(node.returnType)]; |
| w.FunctionType type = |
| translator.typesBuilder.defineFunction(inputs, outputs); |
| final String? functionNodeName = variable?.name; |
| final String functionName; |
| if (functionNodeName == null) { |
| functionName = "$member closure at ${node.location}"; |
| } else { |
| functionName = "$member closure $functionNodeName at ${node.location}"; |
| } |
| final function = module.functions.define(type, functionName); |
| final lambda = |
| Lambda._(node, function, _currentSource, closures.lambdas.length); |
| closures.lambdas[node] = lambda; |
| translator.functions.getLambdaFunction(lambda, member, closures); |
| |
| functionIsSyncStarOrAsync.add(node.asyncMarker == AsyncMarker.SyncStar || |
| node.asyncMarker == AsyncMarker.Async); |
| node.visitChildren(this); |
| functionIsSyncStarOrAsync.removeLast(); |
| } |
| |
| @override |
| void visitFunctionExpression(FunctionExpression node) { |
| _visitLambda(node.function); |
| } |
| |
| @override |
| void visitFunctionDeclaration(FunctionDeclaration node) { |
| // Variable is in outer scope |
| node.variable.accept(this); |
| _visitLambda(node.function, node.variable); |
| } |
| } |
| |
| class _ContextCollector extends RecursiveVisitor { |
| final Closures closures; |
| Context? currentContext; |
| final bool enableAsserts; |
| |
| _ContextCollector(this.closures, this.enableAsserts); |
| |
| @override |
| void visitAssertStatement(AssertStatement node) { |
| if (enableAsserts) { |
| super.visitAssertStatement(node); |
| } |
| } |
| |
| @override |
| void visitAssertBlock(AssertBlock node) { |
| if (enableAsserts) { |
| super.visitAssertBlock(node); |
| } |
| } |
| |
| void _newContext(TreeNode node) { |
| bool outerMost = currentContext == null; |
| Context? oldContext = currentContext; |
| Context? parent = currentContext; |
| while (parent != null && parent.isEmpty) { |
| parent = parent.parent; |
| } |
| bool containsThis = closures._isThisCaptured && outerMost; |
| currentContext = Context(node, parent, containsThis); |
| closures.contexts[node] = currentContext!; |
| node.visitChildren(this); |
| currentContext = oldContext; |
| } |
| |
| @override |
| void visitConstructor(Constructor node) { |
| // Constructors should always be the outermost context. |
| assert(currentContext == null); |
| |
| // Create constructor context. |
| final Context constructorAllocatorContext = Context(node, null, false); |
| currentContext = constructorAllocatorContext; |
| |
| // Visit the class's type parameters so that captured type parameters can |
| // be added to the context. Initializer lists don't have access to `this`, |
| // which would contain the type parameters, so the type parameters must |
| // be captured from the constructor arguments instead. |
| visitList(node.enclosingClass.typeParameters, this); |
| |
| // Visit the constructor function's parameters directly instead of calling |
| // node.visitChildren(), so that a new context is not allocated for the |
| // FunctionNode, and any captured parameters are added to the Constructor |
| // context. |
| visitList(node.function.typeParameters, this); |
| visitList(node.function.positionalParameters, this); |
| visitList(node.function.namedParameters, this); |
| |
| // Visit the constructor's initializers to add captured arguments to the |
| // context. |
| visitList(node.initializers, this); |
| |
| // If no type parameters, arguments, or `this` are captured by the |
| // constructor body, we do not need to allocate a context for the |
| // constructor or constructor body. If parameters are captured, we want |
| // the constructor context to contain these, so that they can be shared |
| // between the constructor initializer and body functions. If `this` is |
| // captured, we want the constructor body function context to contain it. |
| |
| if (!constructorAllocatorContext.isEmpty) { |
| // Some type arguments or variables have been captured by the |
| // initializer list. |
| |
| 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 |
| // constructor body to store the captured `this`, as initializer lists |
| // cannot have access to `this`. |
| assert(!constructorAllocatorContext.containsThis); |
| final constructorBodyContext = |
| Context(node.function, constructorAllocatorContext, true); |
| |
| closures.contexts[node.function] = constructorBodyContext; |
| closures.contexts[node] = constructorAllocatorContext; |
| |
| currentContext = constructorBodyContext; |
| } else { |
| // We only need the constructor context, so contexts in the constructor |
| // body can have this as parent. |
| closures.contexts[node] = constructorAllocatorContext; |
| } |
| |
| node.function.body?.accept(this); |
| } else { |
| // We may only need a context for the constructor body function, as no |
| // parameters have been captured by the initializer list, and we only |
| // need the body context if the body captures parameters, or contains |
| // `this`. We must create a new context with the correct owner |
| // (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); |
| currentContext = constructorBodyContext; |
| |
| node.function.body?.accept(this); |
| |
| if (!constructorBodyContext.isEmpty) { |
| // We only allocate the context if it is not empty. |
| closures.contexts[node.function] = constructorBodyContext; |
| } |
| } |
| |
| currentContext = null; |
| } |
| |
| @override |
| void visitFunctionNode(FunctionNode node) { |
| _newContext(node); |
| } |
| |
| @override |
| void visitWhileStatement(WhileStatement node) { |
| _newContext(node); |
| } |
| |
| @override |
| void visitDoStatement(DoStatement node) { |
| _newContext(node); |
| } |
| |
| @override |
| void visitForStatement(ForStatement node) { |
| _newContext(node); |
| } |
| |
| @override |
| void visitVariableDeclaration(VariableDeclaration node) { |
| Capture? capture = closures.captures[node]; |
| if (capture != null) { |
| currentContext!.variables.add(node); |
| capture.context = currentContext!; |
| } |
| super.visitVariableDeclaration(node); |
| } |
| |
| @override |
| void visitTypeParameter(TypeParameter node) { |
| Capture? capture = closures.captures[node]; |
| if (capture != null) { |
| currentContext!.typeParameters.add(node); |
| capture.context = currentContext!; |
| } |
| super.visitTypeParameter(node); |
| } |
| |
| @override |
| void visitVariableSet(VariableSet node) { |
| closures.captures[node.variable]?.written = true; |
| super.visitVariableSet(node); |
| } |
| } |