blob: 13648fc61bfc82d2b7e09008bd0491390c64f85f [file] [log] [blame]
// 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:dart2wasm/code_generator.dart';
import 'package:dart2wasm/class_info.dart';
import 'package:dart2wasm/translator.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;
/// 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.DefinedFunction> functions;
/// The vtable entry used for dynamic calls.
final w.DefinedFunction dynamicCallEntry;
/// The constant global variable pointing to the vtable.
final w.Global vtable;
ClosureImplementation(
this.representation, this.functions, this.dynamicCallEntry, this.vtable);
}
/// 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.
late final List<w.DefinedFunction> instantiationTrampolines =
_instantiationTrampolinesThunk!();
List<w.DefinedFunction> Function()? _instantiationTrampolinesThunk;
/// The function that instantiates this generic closure.
late final w.DefinedFunction instantiationFunction =
_instantiationFunctionThunk!();
w.DefinedFunction Function()? _instantiationFunctionThunk;
/// 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(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.
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> {
List<String> names;
NameCombination(this.names);
@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;
static const int vtableBaseIndexNonGeneric = 1;
static const int vtableBaseIndexGeneric = 2;
// 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 =
m.addStructType("#VtableBase");
// Base struct for vtables.
late final w.StructType vtableBaseStruct = _vtableBaseStructBare
..fields.add(w.FieldType(
w.RefType.def(translator.dynamicCallVtableEntryFunctionType,
nullable: false),
mutable: false));
// Base struct for closures.
late final w.StructType closureBaseStruct = _makeClosureStruct(
"#ClosureBase", _vtableBaseStructBare, translator.closureInfo.struct);
late final w.RefType typeType =
translator.classInfo[translator.typeClass]!.nonNullableType;
late final w.RefType functionTypeType =
translator.classInfo[translator.functionTypeClass]!.nonNullableType;
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 m.addStructType(name,
fields: [
w.FieldType(w.NumType.i32),
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.Module get m => translator.m;
w.ValueType get topType => translator.topInfo.nullableType;
ClosureLayouter(this.translator)
: procedureAttributeMetadata =
(translator.component.metadata["vm.procedure-attributes.metadata"]
as ProcedureAttributesMetadataRepository)
.mapping;
void collect(List<FunctionNode> extraClosurizedFunctions) {
translator.component.accept(this);
for (FunctionNode function in extraClosurizedFunctions) {
_visitFunctionNode(function);
}
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.
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 ?? vtableBaseStruct;
w.StructType vtableStruct = m.addStructType(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 = m.addFunctionType(
[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 = m.addStructType(instantiationContextName,
fields: [
w.FieldType(w.RefType.def(closureStruct, nullable: false),
mutable: false),
...List.filled(typeCount, w.FieldType(typeType, mutable: false))
],
superType: parent?.instantiationContextStruct);
}
// Add vtable fields for additional entry points relative to the parent.
for (int paramCount in paramCounts) {
w.FunctionType entry = m.addFunctionType([
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(
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._instantiationTrampolinesThunk = () {
List<w.DefinedFunction> instantiationTrampolines = [
...?parent?.instantiationTrampolines
];
if (names.isEmpty) {
// Add trampoline to the corresponding entry in the generic closure.
w.DefinedFunction trampoline = _createInstantiationTrampoline(
typeCount,
closureStruct,
instantiationContextStruct!,
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.DefinedFunction trampoline = genericIndex != null
? _createInstantiationTrampoline(
typeCount,
closureStruct,
instantiationContextStruct!,
instantiatedRepresentation.vtableStruct,
vtableBaseIndexNonGeneric + instantiationTrampolines.length,
vtableStruct,
vtableBaseIndexGeneric +
(positionalCount + 1) +
genericIndex)
: translator.globals.getDummyFunction(
(instantiatedRepresentation
.vtableStruct
.fields[vtableBaseIndexNonGeneric +
instantiationTrampolines.length]
.type as w.RefType)
.heapType as w.FunctionType);
instantiationTrampolines.add(trampoline);
}
}
return instantiationTrampolines;
};
representation._instantiationFunctionThunk = () {
String instantiationFunctionName =
["#Instantiation", ...nameTags].join("-");
return _createInstantiationFunction(
typeCount,
instantiatedRepresentation!,
representation.instantiationTrampolines,
representation.instantiationFunctionType,
instantiationContextStruct!,
closureStruct,
instantiationFunctionName);
};
}
return representation;
}
w.DefinedFunction _createInstantiationTrampoline(
int typeCount,
w.StructType genericClosureStruct,
w.StructType contextStruct,
w.StructType instantiatedVtableStruct,
int instantiatedVtableFieldIndex,
w.StructType genericVtableStruct,
int genericVtableFieldIndex) {
assert(contextStruct.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);
w.DefinedFunction trampoline = m.addFunction(instantiatedFunctionType);
w.Instructions b = trampoline.body;
// Cast context reference to actual context type.
w.RefType contextType = w.RefType.def(contextStruct, nullable: false);
w.Local contextLocal = trampoline.addLocal(contextType);
b.local_get(trampoline.locals[0]);
b.ref_cast(contextType);
b.local_tee(contextLocal);
// Push inner context
b.struct_get(contextStruct, FieldIndex.instantiationContextInner);
b.struct_get(genericClosureStruct, FieldIndex.closureContext);
// Push type arguments
for (int t = 0; t < typeCount; t++) {
b.local_get(contextLocal);
b.struct_get(
contextStruct, 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(contextStruct, FieldIndex.instantiationContextInner);
b.struct_get(genericClosureStruct, FieldIndex.closureVtable);
b.struct_get(genericVtableStruct, genericVtableFieldIndex);
b.call_ref(genericFunctionType);
b.end();
return trampoline;
}
w.DefinedFunction _createInstantiationDynamicCallEntry(
int typeCount, w.StructType instantiationContextStruct) {
w.DefinedFunction function = m.addFunction(
translator.dynamicCallVtableEntryFunctionType,
"instantiation dynamic call entry");
w.Instructions 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 =
function.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, as list
translator.makeList(
function,
(b) {
translator.constants.instantiateConstant(
function,
b,
TypeLiteralConstant(
InterfaceType(translator.typeClass, Nullability.nonNullable)),
translator.types.nonNullableTypeType);
},
typeCount,
(elementType, elementIdx) {
b.local_get(instantiationContextLocal);
b.struct_get(instantiationContextStruct,
FieldIndex.instantiationContextTypeArgumentsBase + elementIdx);
},
isGrowable: true);
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.DefinedFunction _createInstantiationFunction(
int typeCount,
ClosureRepresentation instantiatedRepresentation,
List<w.DefinedFunction> 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.
w.DefinedGlobal vtable = m.addGlobal(w.GlobalType(
w.RefType.def(instantiatedRepresentation.vtableStruct, nullable: false),
mutable: false));
w.Instructions ib = vtable.initializer;
ib.ref_func(_createInstantiationDynamicCallEntry(typeCount, contextStruct));
for (w.DefinedFunction trampoline in instantiationTrampolines) {
ib.ref_func(trampoline);
}
ib.struct_new(instantiatedRepresentation.vtableStruct);
ib.end();
w.DefinedFunction instantiationFunction = m.addFunction(functionType, name);
w.Local preciseClosure = instantiationFunction.addLocal(genericClosureType);
w.Instructions b = instantiationFunction.body;
// 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.i32_const(translator.closureInfo.classId);
b.i32_const(initialIdentityHash);
// 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);
b.global_get(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 `List<_Type>`.
ClassInfo listInfo = translator.classInfo[translator.fixedLengthListClass]!;
translator.functions.allocateClass(listInfo.classId);
Constant typeConstant = TypeLiteralConstant(
InterfaceType(translator.typeClass, Nullability.nonNullable));
b.i32_const(listInfo.classId);
b.i32_const(initialIdentityHash);
translator.constants
.instantiateConstant(instantiationFunction, b, typeConstant, typeType);
b.i64_const(typeCount);
for (int i = 0; i < typeCount; i++) {
b.local_get(typeParam(i));
}
b.array_new_fixed(translator.listArrayType, typeCount);
b.struct_new(listInfo.struct);
// Call [_TypeUniverse.substituteFunctionTypeArgument].
b.call(translator.functions
.getFunction(translator.substituteFunctionTypeArgument.reference));
// Finally, allocate closure struct.
b.struct_new(instantiatedRepresentation.closureStruct);
b.end();
return instantiationFunction;
}
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) {
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 = null;
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;
final w.DefinedFunction function;
Lambda(this.functionNode, this.function);
}
/// 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 and function expressions),
/// 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.
bool containsThis = false;
/// 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 0;
}
Context(this.owner, this.parent);
}
/// A captured variable.
class Capture {
final TreeNode variable;
late final Context context;
late final int fieldIndex;
bool written = false;
Capture(this.variable);
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.
class Closures {
final CodeGenerator codeGen;
final Map<TreeNode, Capture> captures = {};
bool isThisCaptured = false;
final Map<FunctionNode, Lambda> lambdas = {};
final Map<TreeNode, Context> contexts = {};
final Set<FunctionDeclaration> closurizedFunctions = {};
Closures(this.codeGen);
Translator get translator => codeGen.translator;
w.Module get m => translator.m;
late final w.ValueType typeType =
translator.classInfo[translator.typeClass]!.nonNullableType;
void findCaptures(Member member) {
var 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(TreeNode node, {TreeNode? container}) {
if (captures.isNotEmpty || isThisCaptured) {
node.accept(
ContextCollector(this, container, translator.options.enableAsserts));
}
}
void buildContexts() {
// Make struct definitions
for (Context context in contexts.values) {
if (!context.isEmpty) {
context.struct = m.addStructType("<context>");
}
}
// Build object layouts
for (Context context in contexts.values) {
if (!context.isEmpty) {
w.StructType struct = context.struct;
if (context.parent != null) {
assert(!context.containsThis);
struct.fields.add(w.FieldType(
w.RefType.def(context.parent!.struct, nullable: true)));
}
if (context.containsThis) {
struct.fields.add(w.FieldType(
codeGen.preciseThisLocal!.type.withNullability(true)));
}
for (VariableDeclaration variable in context.variables) {
int index = struct.fields.length;
struct.fields.add(w.FieldType(
translator.translateType(variable.type).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;
final Map<TreeNode, int> variableDepth = {};
int depth = 0;
CaptureFinder(this.closures, this.member);
Translator get translator => closures.translator;
w.Module get m => translator.m;
@override
void visitAssertStatement(AssertStatement node) {
if (translator.options.enableAsserts) {
node.visitChildren(this);
}
}
@override
void visitVariableDeclaration(VariableDeclaration node) {
if (depth > 0) {
variableDepth[node] = depth;
}
super.visitVariableDeclaration(node);
}
@override
void visitTypeParameter(TypeParameter node) {
if (node.parent is FunctionNode) {
if (depth > 0) {
variableDepth[node] = depth;
}
}
super.visitTypeParameter(node);
}
void _visitVariableUse(TreeNode variable) {
int declDepth = variableDepth[variable] ?? 0;
assert(declDepth <= depth);
if (declDepth < depth) {
closures.captures[variable] = Capture(variable);
} else if (variable is VariableDeclaration &&
variable.parent is FunctionDeclaration) {
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) {
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) {
if (node.parameter.parent != null &&
node.parameter.parent == member.enclosingClass) {
_visitThis();
} else if (node.parameter.parent is FunctionNode) {
_visitVariableUse(node.parameter);
}
super.visitTypeParameterType(node);
}
void _visitLambda(FunctionNode node) {
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 = m.addFunctionType(inputs, outputs);
w.DefinedFunction function =
m.addFunction(type, "$member closure at ${node.location}");
closures.lambdas[node] = Lambda(node, function);
depth++;
node.visitChildren(this);
depth--;
}
@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);
}
}
class ContextCollector extends RecursiveVisitor {
final Closures closures;
Context? currentContext;
final bool enableAsserts;
ContextCollector(this.closures, TreeNode? container, this.enableAsserts) {
if (container != null) {
currentContext = closures.contexts[container]!;
}
}
@override
void visitAssertStatement(AssertStatement node) {
if (enableAsserts) {
node.visitChildren(this);
}
}
void _newContext(TreeNode node) {
bool outerMost = currentContext == null;
Context? oldContext = currentContext;
Context? parent = currentContext;
while (parent != null && parent.isEmpty) {
parent = parent.parent;
}
currentContext = Context(node, parent);
if (closures.isThisCaptured && outerMost) {
currentContext!.containsThis = true;
}
closures.contexts[node] = currentContext!;
node.visitChildren(this);
currentContext = oldContext;
}
@override
void visitConstructor(Constructor node) {
node.function.accept(this);
currentContext = closures.contexts[node.function]!;
visitList(node.initializers, this);
}
@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);
}
}