diff --git a/pkg/dart2wasm/lib/class_info.dart b/pkg/dart2wasm/lib/class_info.dart index 204ab35d3ea..f2f6f4b348b 100644 --- a/pkg/dart2wasm/lib/class_info.dart +++ b/pkg/dart2wasm/lib/class_info.dart @@ -24,7 +24,7 @@ class FieldIndex { static const hashBaseIndex = 2; static const hashBaseData = 4; static const closureContext = 2; - static const closureFunction = 3; + static const closureVtable = 3; static const typeIsNullable = 2; static const interfaceTypeTypeArguments = 4; static const functionTypeNamedParameters = 6; diff --git a/pkg/dart2wasm/lib/closures.dart b/pkg/dart2wasm/lib/closures.dart index c53019d20ff..8635a3e4c94 100644 --- a/pkg/dart2wasm/lib/closures.dart +++ b/pkg/dart2wasm/lib/closures.dart @@ -2,13 +2,372 @@ // 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/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. + final List functions; + + /// The constant global variable pointing to the vtable. + final w.Global vtable; + + ClosureImplementation(this.representation, this.functions, 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 vtableBaseIndex; + + /// 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? _indexOfCombination; + + ClosureRepresentation(this.vtableBaseIndex, this.vtableStruct, + this.closureStruct, this._indexOfCombination); + + /// 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 argNames) { + if (argNames.isEmpty) { + return vtableBaseIndex + posArgCount; + } else { + return vtableBaseIndex + + posArgCount + + _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 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 { + List 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 procedureAttributeMetadata; + + List> representations = []; + + Set visitedConstants = Set.identity(); + + // Base struct for vtables. + // TODO(joshualitt): Add function type metadata here. + late final w.StructType vtableBaseStruct = m.addStructType("#VtableBase"); + + // Base struct for closures. + late final w.StructType closureBaseStruct = _makeClosureStruct("#ClosureBase", + vtableBaseStruct, translator.classInfo[translator.functionClass]!.struct); + + w.StructType _makeClosureStruct( + String name, w.StructType vtableStruct, w.StructType superType) { + // A closure contains: + // - A class ID (always the `_Function` class ID) + // - An identity hash + // - A context reference (used for `this` in tear-offs) + // - A vtable reference + return m.addStructType("#ClosureBase", + fields: [ + w.FieldType(w.NumType.i32), + w.FieldType(w.NumType.i32), + w.FieldType(w.RefType.data(nullable: false)), + w.FieldType(w.RefType.def(vtableStruct, nullable: false), + mutable: false) + ], + superType: superType); + } + + w.Module get m => translator.m; + w.ValueType get topType => translator.topInfo.nullableType; + w.ValueType get typeType => + translator.classInfo[translator.typeClass]!.nonNullableType; + + 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]; + 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 names) { + final representations = + _representationsForCounts(typeCount, positionalCount); + if (representations.withoutNamed == null) { + ClosureRepresentation parent = positionalCount == 0 + ? ClosureRepresentation(vtableBaseStruct.fields.length, + vtableBaseStruct, closureBaseStruct, 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 names, + ClosureRepresentation parent, + Map? indexOfCombination, + Iterable paramCounts) { + List nameTags = ["$typeCount", "$positionalCount", ...names]; + String vtableName = ["#Vtable", ...nameTags].join("-"); + String closureName = ["#Closure", ...nameTags].join("-"); + w.StructType vtableStruct = m.addStructType(vtableName, + fields: parent.vtableStruct.fields, superType: parent.vtableStruct); + for (int paramCount in paramCounts) { + w.FunctionType entry = m.addFunctionType([ + w.RefType.data(nullable: false), + ...List.filled(typeCount, typeType), + ...List.filled(paramCount, topType) + ], [ + topType + ]); + vtableStruct.fields.add( + w.FieldType(w.RefType.def(entry, nullable: false), mutable: false)); + } + w.StructType closureStruct = + _makeClosureStruct(closureName, vtableStruct, parent.closureStruct); + return ClosureRepresentation(vtableBaseStruct.fields.length, vtableStruct, + closureStruct, indexOfCombination); + } + + ClosureRepresentationsForParameterCount _representationsForCounts( + int typeCount, int positionalCount) { + while (representations.length <= typeCount) { + representations.add([]); + } + List 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); + } + + void _visitFunctionInvocation(Arguments arguments) { + final representations = _representationsForCounts( + arguments.types.length, arguments.positional.length); + representations.registerCall(arguments); + } + + @override + void visitFunctionExpression(FunctionExpression node) { + _visitFunctionNode(node.function); + super.visitFunctionExpression(node); + } + + @override + void visitFunctionDeclaration(FunctionDeclaration node) { + _visitFunctionNode(node.function); + super.visitFunctionDeclaration(node); + } + + @override + void visitProcedure(Procedure node) { + if (node.isInstanceMember) { + ProcedureAttributesMetadata metadata = procedureAttributeMetadata[node]!; + if (metadata.hasTearOffUses) { + _visitFunctionNode(node.function); + } + } + super.visitProcedure(node); + } + + @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 callCombinations = SplayTreeSet(); + final Map nameIds = SplayTreeMap(); + final UnionFind nameUnions = UnionFind(); + final Map 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); + } + } + + ClosureRepresentationCluster? clusterForNames(List 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 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 names = []; + final Map indexOfCombination = SplayTreeMap(); + ClosureRepresentation? representation; +} + /// A local function or function expression. class Lambda { final FunctionNode functionNode; @@ -105,7 +464,7 @@ class Closures { w.Module get m => translator.m; late final w.ValueType typeType = - translator.classInfo[translator.typeClass]!.nullableType; + translator.classInfo[translator.typeClass]!.nonNullableType; void findCaptures(Member member) { var find = CaptureFinder(this, member); @@ -155,7 +514,7 @@ class Closures { } for (TypeParameter parameter in context.typeParameters) { int index = struct.fields.length; - struct.fields.add(w.FieldType(typeType)); + struct.fields.add(w.FieldType(typeType.withNullability(true))); captures[parameter]!.fieldIndex = index; } } @@ -260,18 +619,12 @@ class CaptureFinder extends RecursiveVisitor { } void _visitLambda(FunctionNode node) { - if (node.positionalParameters.length != node.requiredParameterCount || - node.namedParameters.isNotEmpty) { - throw "Not supported: Optional parameters for " - "function expression or local function at ${node.location}"; - } - if (node.typeParameters.isNotEmpty) { - throw "Not supported: Type parameters for " - "function expression or local function at ${node.location}"; - } List inputs = [ w.RefType.data(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 outputs = [ diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart index b8779ab0c9d..762bd8aa8cc 100644 --- a/pkg/dart2wasm/lib/code_generator.dart +++ b/pkg/dart2wasm/lib/code_generator.dart @@ -175,11 +175,8 @@ class CodeGenerator extends ExpressionVisitor1 } void generateTearOffGetter(Procedure procedure) { - w.DefinedFunction closureFunction = - translator.getTearOffFunction(procedure); - - int parameterCount = procedure.function.requiredParameterCount; - w.DefinedGlobal global = translator.makeFunctionRef(closureFunction); + ClosureImplementation closure = translator.getTearOffClosure(procedure); + w.StructType struct = closure.representation.closureStruct; ClassInfo info = translator.classInfo[translator.functionClass]!; translator.functions.allocateClass(info.classId); @@ -187,8 +184,8 @@ class CodeGenerator extends ExpressionVisitor1 b.i32_const(info.classId); b.i32_const(initialIdentityHash); b.local_get(paramLocals[0]); - b.global_get(global); - b.struct_new(translator.closureStructType(parameterCount)); + b.global_get(closure.vtable); + b.struct_new(struct); b.end(); } @@ -421,25 +418,30 @@ class CodeGenerator extends ExpressionVisitor1 /// Generate code for the body of a lambda. w.DefinedFunction generateLambda(Lambda lambda, Closures closures) { - if (lambda.functionNode.asyncMarker == AsyncMarker.Async && + FunctionNode functionNode = lambda.functionNode; + if (functionNode.asyncMarker == AsyncMarker.Async && lambda.function == function) { w.DefinedFunction inner = translator.functions.addAsyncInnerFunctionFor(function); - generateAsyncWrapper(lambda.functionNode, inner); + generateAsyncWrapper(functionNode, inner); return CodeGenerator(translator, inner, reference) .generateLambda(lambda, closures); } this.closures = closures; - final int implicitParams = 1; - List positional = - lambda.functionNode.positionalParameters; - for (int i = 0; i < positional.length; i++) { - locals[positional[i]] = paramLocals[implicitParams + i]; + int paramIndex = 1; + for (TypeParameter typeParam in functionNode.typeParameters) { + typeLocals[typeParam] = paramLocals[paramIndex++]; + } + for (VariableDeclaration param in functionNode.positionalParameters) { + locals[param] = paramLocals[paramIndex++]; + } + for (VariableDeclaration param in functionNode.namedParameters) { + locals[param] = paramLocals[paramIndex++]; } - Context? context = closures.contexts[lambda.functionNode]?.parent; + Context? context = closures.contexts[functionNode]?.parent; if (context != null) { b.local_get(paramLocals[0]); b.ref_cast(context.struct); @@ -468,10 +470,10 @@ class CodeGenerator extends ExpressionVisitor1 b.local_set(thisLocal!); } } - allocateContext(lambda.functionNode); + allocateContext(functionNode); captureParameters(); - visitStatement(lambda.functionNode.body!); + visitStatement(functionNode.body!); _implicitReturn(); b.end(); @@ -1374,8 +1376,7 @@ class CodeGenerator extends ExpressionVisitor1 DynamicInvocation node, w.ValueType expectedType) { // Handle dynamic 'call' seperately. if (node.name.text == "call") { - return _functionCall( - node.arguments.positional.length, node.receiver, node.arguments); + return _functionCall(node.receiver, node.arguments); } return translator.dynamics.emitDynamicCall(this, node); } @@ -1889,20 +1890,21 @@ class CodeGenerator extends ExpressionVisitor1 } w.StructType _instantiateClosure(FunctionNode functionNode) { - int parameterCount = functionNode.requiredParameterCount; Lambda lambda = closures.lambdas[functionNode]!; - w.DefinedFunction wrapper = translator.getClosureWrapper(functionNode, - lambda.function, "closure wrapper at ${functionNode.location}"); - w.DefinedGlobal global = translator.makeFunctionRef(wrapper); + ClosureImplementation closure = translator.getClosure( + functionNode, + lambda.function, + ParameterInfo.fromLocalFunction(functionNode), + "closure wrapper at ${functionNode.location}"); + w.StructType struct = closure.representation.closureStruct; ClassInfo info = translator.classInfo[translator.functionClass]!; translator.functions.allocateClass(info.classId); - w.StructType struct = translator.closureStructType(parameterCount); b.i32_const(info.classId); b.i32_const(initialIdentityHash); _pushContext(functionNode); - b.global_get(global); + b.global_get(closure.vtable); b.struct_new(struct); return struct; @@ -1925,23 +1927,57 @@ class CodeGenerator extends ExpressionVisitor1 intrinsifier.generateFunctionCallIntrinsic(node); if (intrinsicResult != null) return intrinsicResult; - int parameterCount = node.functionType?.requiredParameterCount ?? - node.arguments.positional.length; - return _functionCall(parameterCount, node.receiver, node.arguments); + return _functionCall(node.receiver, node.arguments); } - w.ValueType _functionCall( - int parameterCount, Expression receiver, Arguments arguments) { - w.StructType struct = translator.closureStructType(parameterCount); + w.ValueType _functionCall(Expression receiver, Arguments arguments) { + int typeCount = arguments.types.length; + int posArgCount = arguments.positional.length; + List argNames = arguments.named.map((a) => a.name).toList()..sort(); + ClosureRepresentation? representation = translator.closureLayouter + .getClosureRepresentation(typeCount, posArgCount, argNames); + if (representation == null) { + // This is a dynamic function call with a signature that matches no + // functions in the program. + b.unreachable(); + return translator.topInfo.nullableType; + } + + // Evaluate receiver + w.StructType struct = representation.closureStruct; w.Local temp = addLocal(w.RefType.def(struct, nullable: false)); wrap(receiver, temp.type); b.local_tee(temp); b.struct_get(struct, FieldIndex.closureContext); + + // Type arguments + for (DartType typeArg in arguments.types) { + types.makeType(this, typeArg); + } + + // Positional arguments for (Expression arg in arguments.positional) { wrap(arg, translator.topInfo.nullableType); } + + // Named arguments + final Map namedLocals = {}; + for (final namedArg in arguments.named) { + final w.Local namedLocal = addLocal(translator.topInfo.nullableType); + namedLocals[namedArg.name] = namedLocal; + wrap(namedArg.value, namedLocal.type); + b.local_set(namedLocal); + } + for (String name in argNames) { + b.local_get(namedLocals[name]!); + } + + // Call entry point in vtable + int vtableIndex = + representation.fieldIndexForSignature(posArgCount, argNames); b.local_get(temp); - b.struct_get(struct, FieldIndex.closureFunction); + b.struct_get(struct, FieldIndex.closureVtable); + b.struct_get(representation.vtableStruct, vtableIndex); b.call_ref(); return translator.topInfo.nullableType; } @@ -1951,11 +1987,11 @@ class CodeGenerator extends ExpressionVisitor1 LocalFunctionInvocation node, w.ValueType expectedType) { var decl = node.variable.parent as FunctionDeclaration; Lambda lambda = closures.lambdas[decl.function]!; - List inputs = lambda.function.type.inputs; _pushContext(decl.function); - for (int i = 0; i < node.arguments.positional.length; i++) { - wrap(node.arguments.positional[i], inputs[1 + i]); - } + Arguments arguments = node.arguments; + visitArgumentsLists(arguments.positional, lambda.function.type, + ParameterInfo.fromLocalFunction(decl.function), 1, + typeArguments: arguments.types, named: arguments.named); b.comment("Local call of ${decl.variable.name}"); b.call(lambda.function); return translator.outputOrVoid(lambda.function.type.outputs); diff --git a/pkg/dart2wasm/lib/constants.dart b/pkg/dart2wasm/lib/constants.dart index 360392e36fe..c80e87c3a88 100644 --- a/pkg/dart2wasm/lib/constants.dart +++ b/pkg/dart2wasm/lib/constants.dart @@ -6,6 +6,7 @@ import 'dart:math'; import 'dart:typed_data'; import 'package:dart2wasm/class_info.dart'; +import 'package:dart2wasm/closures.dart'; import 'package:dart2wasm/translator.dart'; import 'package:dart2wasm/types.dart'; @@ -682,10 +683,9 @@ class ConstantCreator extends ConstantVisitor { @override ConstantInfo? visitStaticTearOffConstant(StaticTearOffConstant constant) { - w.DefinedFunction closureFunction = - translator.getTearOffFunction(constant.targetReference.asProcedure); - int parameterCount = closureFunction.type.inputs.length - 1; - w.StructType struct = translator.closureStructType(parameterCount); + Procedure member = constant.targetReference.asProcedure; + ClosureImplementation closure = translator.getTearOffClosure(member); + w.StructType struct = closure.representation.closureStruct; w.RefType type = w.RefType.def(struct, nullable: false); return createConstant(constant, type, (function, b) { ClassInfo info = translator.classInfo[translator.functionClass]!; @@ -694,13 +694,8 @@ class ConstantCreator extends ConstantVisitor { b.i32_const(info.classId); b.i32_const(initialIdentityHash); b.global_get(translator.globals.dummyGlobal); // Dummy context - if (lazyConstants) { - w.DefinedGlobal global = translator.makeFunctionRef(closureFunction); - b.global_get(global); - } else { - b.ref_func(closureFunction); - } - b.struct_new(translator.closureStructType(parameterCount)); + b.global_get(closure.vtable); + b.struct_new(struct); }); } diff --git a/pkg/dart2wasm/lib/dispatch_table.dart b/pkg/dart2wasm/lib/dispatch_table.dart index 68bf8ad9c7c..c70cba3090e 100644 --- a/pkg/dart2wasm/lib/dispatch_table.dart +++ b/pkg/dart2wasm/lib/dispatch_table.dart @@ -38,7 +38,7 @@ class SelectorInfo { w.Module get m => translator.m; - String get name => paramInfo.member.name.text; + String get name => paramInfo.member!.name.text; bool get alive => callCount > 0 && targetCount > 1 || calledDynamically; diff --git a/pkg/dart2wasm/lib/globals.dart b/pkg/dart2wasm/lib/globals.dart index dd0bc369cb0..a2a1553ecf0 100644 --- a/pkg/dart2wasm/lib/globals.dart +++ b/pkg/dart2wasm/lib/globals.dart @@ -15,6 +15,7 @@ class Globals { final Map globals = {}; final Map globalInitializers = {}; final Map globalInitializedFlag = {}; + final Map dummyFunctions = {}; final Map dummyValues = {}; late final w.DefinedGlobal dummyGlobal; @@ -37,6 +38,18 @@ class Globals { dummyValues[w.HeapType.data] = dummyGlobal; } + /// Provide a dummy function with the given signature. Used for empty entries + /// in vtables and for dummy values of function reference type. + w.DefinedFunction getDummyFunction(w.FunctionType type) { + return dummyFunctions.putIfAbsent(type, () { + w.DefinedFunction function = m.addFunction(type, "#dummy function $type"); + w.Instructions b = function.body; + b.unreachable(); + b.end(); + return function; + }); + } + w.Global? prepareDummyValue(w.ValueType type) { if (type is w.RefType && !type.nullable) { w.HeapType heapType = type.heapType; @@ -60,14 +73,9 @@ class Globals { ib.array_new_fixed(heapType, 0); ib.end(); } else if (heapType is w.FunctionType) { - w.DefinedFunction function = - m.addFunction(heapType, "#dummy function $heapType"); - w.Instructions b = function.body; - b.unreachable(); - b.end(); global = m.addGlobal(w.GlobalType(type, mutable: false)); w.Instructions ib = global.initializer; - ib.ref_func(function); + ib.ref_func(getDummyFunction(heapType)); ib.end(); } dummyValues[heapType] = global!; @@ -78,8 +86,10 @@ class Globals { return null; } + /// Produce a dummy value of any Wasm type. For non-nullable reference types, + /// the value is constructed in a global initializer, and the instantiation + /// of the value merely reads the global. void instantiateDummyValue(w.Instructions b, w.ValueType type) { - w.Global? global = prepareDummyValue(type); switch (type) { case w.NumType.i32: b.i32_const(0); @@ -99,7 +109,7 @@ class Globals { if (type.nullable) { b.ref_null(heapType); } else { - b.global_get(global!); + b.global_get(prepareDummyValue(type)!); } } else { throw "Unsupported global type ${type} ($type)"; diff --git a/pkg/dart2wasm/lib/param_info.dart b/pkg/dart2wasm/lib/param_info.dart index 182af47906b..dcd18e52760 100644 --- a/pkg/dart2wasm/lib/param_info.dart +++ b/pkg/dart2wasm/lib/param_info.dart @@ -9,7 +9,7 @@ import 'package:kernel/ast.dart'; /// Information about optional parameters and their default values for a /// member or a set of members belonging to the same override group. class ParameterInfo { - final Member member; + final Member? member; int typeParamCount = 0; late final List positional; late final Map named; @@ -34,13 +34,13 @@ class ParameterInfo { } ParameterInfo.fromMember(Reference target) : member = target.asMember { - FunctionNode? function = member.function; + FunctionNode? function = member!.function; if (target.isTearOffReference) { positional = []; named = {}; } else if (function != null) { typeParamCount = (member is Constructor - ? member.enclosingClass!.typeParameters + ? member!.enclosingClass!.typeParameters : function.typeParameters) .length; positional = List.generate(function.positionalParameters.length, (i) { @@ -59,6 +59,19 @@ class ParameterInfo { } } + ParameterInfo.fromLocalFunction(FunctionNode function) : member = null { + typeParamCount = function.typeParameters.length; + positional = List.generate(function.positionalParameters.length, (i) { + // A required parameter has no default value. + if (i < function.requiredParameterCount) return null; + return defaultValue(function.positionalParameters[i]); + }); + named = { + for (VariableDeclaration param in function.namedParameters) + param.name!: defaultValue(param) + }; + } + void merge(ParameterInfo other) { assert(typeParamCount == other.typeParamCount); for (int i = 0; i < other.positional.length; i++) { diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart index 997d51ba244..27eaf1677df 100644 --- a/pkg/dart2wasm/lib/translator.dart +++ b/pkg/dart2wasm/lib/translator.dart @@ -138,6 +138,7 @@ class Translator { late final Map boxedClasses; // Other parts of the global compiler state. + late final ClosureLayouter closureLayouter; late final ClassInfoCollector classInfoCollector; late final DispatchTable dispatchTable; late final Globals globals; @@ -166,9 +167,8 @@ class Translator { // Caches for when identical source constructs need a common representation. final Map arrayTypeCache = {}; - final Map functionTypeCache = {}; final Map functionRefCache = {}; - final Map tearOffFunctionCache = {}; + final Map tearOffFunctionCache = {}; ClassInfo get topInfo => classes[0]; ClassInfo get objectInfo => classInfo[coreTypes.objectClass]!; @@ -179,6 +179,7 @@ class Translator { hierarchy = ClassHierarchy(component, coreTypes) as ClosedWorldClassHierarchy { subtypes = hierarchy.computeSubtypesInformation(); + closureLayouter = ClosureLayouter(this); classInfoCollector = ClassInfoCollector(this); dispatchTable = DispatchTable(this); functions = FunctionCollector(this); @@ -361,6 +362,7 @@ class Translator { voidMarker = w.RefType.def(w.StructType("void"), nullable: true); dynamics.collect(); + closureLayouter.collect(); classInfoCollector.collect(); functions.collectImportsAndExports(); @@ -630,11 +632,15 @@ class Translator { return topInfo.typeWithNullability(type.isPotentiallyNullable); } if (type is FunctionType) { - if (type.requiredParameterCount != type.positionalParameters.length || - type.namedParameters.isNotEmpty) { - throw "Function types with optional parameters not supported: $type"; - } - return w.RefType.def(closureStructType(type.requiredParameterCount), + ClosureRepresentation? representation = + closureLayouter.getClosureRepresentation( + type.typeParameters.length, + type.positionalParameters.length, + type.namedParameters.map((p) => p.name).toList()); + return w.RefType.def( + representation != null + ? representation.closureStruct + : classInfo[typeClass]!.struct, nullable: type.isPotentiallyNullable); } throw "Unsupported type ${type.runtimeType}"; @@ -653,28 +659,6 @@ class Translator { () => m.addArrayType("Array<$name>", elementType: w.FieldType(type))); } - w.StructType closureStructType(int parameterCount) { - return functionTypeCache.putIfAbsent(parameterCount, () { - ClassInfo info = classInfo[functionClass]!; - w.StructType struct = m.addStructType("Function$parameterCount", - fields: info.struct.fields, superType: info.struct); - assert(struct.fields.length == FieldIndex.closureFunction); - struct.fields.add(w.FieldType( - w.RefType.def(closureFunctionType(parameterCount), nullable: false), - mutable: false)); - return struct; - }); - } - - w.FunctionType closureFunctionType(int parameterCount) { - return m.addFunctionType([ - w.RefType.data(nullable: false), - ...List.filled(parameterCount, topInfo.nullableType) - ], [ - topInfo.nullableType - ]); - } - w.DefinedGlobal makeFunctionRef(w.BaseFunction f) { return functionRefCache.putIfAbsent(f, () { w.DefinedGlobal global = m.addGlobal( @@ -685,44 +669,149 @@ class Translator { }); } - w.DefinedFunction getTearOffFunction(Procedure member) { + ClosureImplementation getTearOffClosure(Procedure member) { return tearOffFunctionCache.putIfAbsent(member, () { assert(member.kind == ProcedureKind.Method); - FunctionNode functionNode = member.function; - if (functionNode.positionalParameters.length != - functionNode.requiredParameterCount || - functionNode.namedParameters.isNotEmpty) { - throw "Not supported: Tear-off with optional parameters" - " at ${member.location}"; - } - if (functionNode.typeParameters.isNotEmpty) { - throw "Not supported: Tear-off with type parameters" - " at ${member.location}"; - } w.BaseFunction target = functions.getFunction(member.reference); - return getClosureWrapper(functionNode, target, "$member tear-off"); + return getClosure(member.function, target, paramInfoFor(member.reference), + "$member tear-off"); }); } - w.DefinedFunction getClosureWrapper( - FunctionNode functionNode, w.BaseFunction target, String name) { - int parameterCount = functionNode.requiredParameterCount; - w.FunctionType targetSignature = target.type; - w.FunctionType closureSignature = closureFunctionType(parameterCount); - assert(closureSignature.inputs.length == 1 + parameterCount); - int signatureOffset = targetSignature.inputs.length - parameterCount; - w.DefinedFunction function = m.addFunction(closureSignature, name); - w.Instructions b = function.body; - for (int i = 0; i < targetSignature.inputs.length; i++) { - w.Local paramLocal = function.locals[(1 - signatureOffset) + i]; - b.local_get(paramLocal); - convertType(function, paramLocal.type, targetSignature.inputs[i]); + ClosureImplementation getClosure(FunctionNode functionNode, + w.BaseFunction target, ParameterInfo paramInfo, String name) { + // The target function takes an extra initial parameter if it's a function + // expression / local function (which takes a context) or a tear-off of an + // instance method (which takes a receiver). + bool takesContextOrReceiver = + paramInfo.member == null || paramInfo.member!.isInstanceMember; + + // Look up the closure representation for the signature. + int typeCount = functionNode.typeParameters.length; + int positionalCount = functionNode.positionalParameters.length; + List names = + functionNode.namedParameters.map((p) => p.name!).toList(); + assert(typeCount == paramInfo.typeParamCount); + assert(positionalCount <= paramInfo.positional.length); + assert(names.length <= paramInfo.named.length); + assert(target.type.inputs.length == + (takesContextOrReceiver ? 1 : 0) + + paramInfo.typeParamCount + + paramInfo.positional.length + + paramInfo.named.length); + ClosureRepresentation representation = closureLayouter + .getClosureRepresentation(typeCount, positionalCount, names)!; + assert(representation.vtableStruct.fields.length == + representation.vtableBaseIndex + + (1 + positionalCount) + + representation.nameCombinations.length); + + List functions = []; + + bool canBeCalledWith(int posArgCount, List argNames) { + if (posArgCount < functionNode.requiredParameterCount) { + return false; + } + int i = 0, j = 0; + while (i < argNames.length && j < functionNode.namedParameters.length) { + int comp = argNames[i].compareTo(functionNode.namedParameters[j].name!); + if (comp < 0) return false; + if (comp > 0) { + if (functionNode.namedParameters[j++].isRequired) return false; + continue; + } + i++; + j++; + } + if (i < argNames.length) return false; + while (j < functionNode.namedParameters.length) { + if (functionNode.namedParameters[j++].isRequired) return false; + } + return true; } - b.call(target); - convertType(function, outputOrVoid(target.type.outputs), - outputOrVoid(closureSignature.outputs)); - b.end(); - return function; + + w.DefinedFunction makeTrampoline( + w.FunctionType signature, int posArgCount, List argNames) { + w.DefinedFunction function = m.addFunction(signature, name); + w.Instructions b = function.body; + int targetIndex = 0; + if (takesContextOrReceiver) { + w.Local receiver = function.locals[0]; + b.local_get(receiver); + convertType(function, receiver.type, target.type.inputs[targetIndex++]); + } + int argIndex = 1; + for (int i = 0; i < typeCount; i++) { + b.local_get(function.locals[argIndex++]); + targetIndex++; + } + for (int i = 0; i < paramInfo.positional.length; i++) { + if (i < posArgCount) { + w.Local arg = function.locals[argIndex++]; + b.local_get(arg); + convertType(function, arg.type, target.type.inputs[targetIndex++]); + } else { + constants.instantiateConstant(function, b, paramInfo.positional[i]!, + target.type.inputs[targetIndex++]); + } + } + int argNameIndex = 0; + for (int i = 0; i < paramInfo.names.length; i++) { + String argName = paramInfo.names[i]; + if (argNameIndex < argNames.length && + argNames[argNameIndex] == argName) { + w.Local arg = function.locals[argIndex++]; + b.local_get(arg); + convertType(function, arg.type, target.type.inputs[targetIndex++]); + argNameIndex++; + } else { + constants.instantiateConstant(function, b, paramInfo.named[argName]!, + target.type.inputs[targetIndex++]); + } + } + assert(argIndex == signature.inputs.length); + assert(targetIndex == target.type.inputs.length); + assert(argNameIndex == argNames.length); + + b.call(target); + + convertType(function, outputOrVoid(target.type.outputs), + outputOrVoid(signature.outputs)); + b.end(); + + return function; + } + + void fillVtableEntry( + w.Instructions ib, int posArgCount, List argNames) { + int fieldIndex = representation.vtableBaseIndex + functions.length; + assert(fieldIndex == + representation.fieldIndexForSignature(posArgCount, argNames)); + w.FunctionType signature = + (representation.vtableStruct.fields[fieldIndex].type as w.RefType) + .heapType as w.FunctionType; + w.DefinedFunction function = canBeCalledWith(posArgCount, argNames) + ? makeTrampoline(signature, posArgCount, argNames) + : globals.getDummyFunction(signature); + functions.add(function); + ib.ref_func(function); + } + + w.DefinedGlobal vtable = m.addGlobal(w.GlobalType( + w.RefType.def(representation.vtableStruct, nullable: false), + mutable: false)); + w.Instructions ib = vtable.initializer; + // TODO(joshualitt): Generate function type metadata here. + for (int posArgCount = 0; posArgCount <= positionalCount; posArgCount++) { + fillVtableEntry(ib, posArgCount, const []); + } + for (NameCombination nameCombination in representation.nameCombinations) { + fillVtableEntry(ib, positionalCount, nameCombination.names); + } + ib.struct_new(representation.vtableStruct); + ib.end(); + + return ClosureImplementation(representation, functions, vtable); } w.ValueType outputOrVoid(List outputs) {