[dart2wasm] Type parameters and optional parameters for closures

Changes the representation of closures to include a vtable with entry
points for the various ways in which the function can be called.

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 combination of type parameter count and positional parameter
count gets a vtable layout for signatures without named parameters,
plus one for each cluster of parameter names belonging to the
combination.

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 from
its cluster that occurs in a function call in the program.

This layout scheme is consistent with function subtyping in the sense
that if signature B is a subtype of signature A, then the Wasm struct
for the vtable layout corresponding to B is a subtype of the Wasm struct for the vtable layout corresponding to A, i.e. the fields in the layout
for A is a prefix of the fields in the layout for B.

Change-Id: I36569be5251cc0cca4373b08c48d37c214478c3c
Cq-Include-Trybots: luci.dart.try:dart2wasm-linux-x64-d8-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/256822
Commit-Queue: Aske Simon Christensen <askesc@google.com>
Reviewed-by: Joshua Litt <joshualitt@google.com>
This commit is contained in:
Aske Simon Christensen
2022-09-12 10:04:46 +00:00
committed by Commit Bot
parent 0cb91b9824
commit 51c4e03928
8 changed files with 627 additions and 131 deletions
+1 -1
View File
@@ -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;
+364 -11
View File
@@ -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<w.DefinedFunction> 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<NameCombination, int>? _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<String> 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<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();
// 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<String> 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<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 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<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);
}
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<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);
}
}
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;
@@ -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<w.ValueType> 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<w.ValueType> outputs = [
+72 -36
View File
@@ -175,11 +175,8 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
}
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<w.ValueType, w.ValueType>
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<w.ValueType, w.ValueType>
/// 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<VariableDeclaration> 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<w.ValueType, w.ValueType>
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<w.ValueType, w.ValueType>
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.ValueType, w.ValueType>
}
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<w.ValueType, w.ValueType>
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<String> 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<String, w.Local> 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<w.ValueType, w.ValueType>
LocalFunctionInvocation node, w.ValueType expectedType) {
var decl = node.variable.parent as FunctionDeclaration;
Lambda lambda = closures.lambdas[decl.function]!;
List<w.ValueType> 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);
+6 -11
View File
@@ -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<ConstantInfo?> {
@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<ConstantInfo?> {
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);
});
}
+1 -1
View File
@@ -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;
+18 -8
View File
@@ -15,6 +15,7 @@ class Globals {
final Map<Field, w.Global> globals = {};
final Map<Field, w.BaseFunction> globalInitializers = {};
final Map<Field, w.Global> globalInitializedFlag = {};
final Map<w.FunctionType, w.DefinedFunction> dummyFunctions = {};
final Map<w.HeapType, w.DefinedGlobal> 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)";
+16 -3
View File
@@ -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<Constant?> positional;
late final Map<String, Constant?> 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++) {
+149 -60
View File
@@ -138,6 +138,7 @@ class Translator {
late final Map<w.ValueType, Class> 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<w.StorageType, w.ArrayType> arrayTypeCache = {};
final Map<int, w.StructType> functionTypeCache = {};
final Map<w.BaseFunction, w.DefinedGlobal> functionRefCache = {};
final Map<Procedure, w.DefinedFunction> tearOffFunctionCache = {};
final Map<Procedure, ClosureImplementation> 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<w.ValueType>.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<String> 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<w.DefinedFunction> functions = [];
bool canBeCalledWith(int posArgCount, List<String> 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<String> 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<String> 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<w.ValueType> outputs) {