[dart2wasm] Replace struct.new_default with struct.new for object

allocation.

When using the `struct.new_default` instruction for object allocation,
fields are always nullable and mutable. By using the `struct.new`
instruction instead, class fields can now have the same mutability and
nullability in Wasm as declared in Dart. In addition, the class ID and
type parameters (which are also stored in an object's struct), can now
be immutable and nonnullable as well.

To do this, object construction is now split into three functions:
(1) Initializer: evaluates initializers for instance fields and
constructor initializers (this constructor before super constructor).
(2) Constructor body: executes the constructor body (super constructor
before this constructor), with `this` pointed to the constructed object.
(3) Constructor allocator: which calls (1), allocates the object using
`struct.new`, then calls (2).

Because fields now have the correct mutability and nullability in Wasm,
this removes unnecessary null checks for nonnullable fields, and may
allow for better optimisations by Binaryen.

Fixes https://github.com/dart-lang/sdk/issues/51492

Change-Id: Ib26046686f772a70509a870301217e9b1c91b77e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/315820
Commit-Queue: Jess Lally <jessicalally@google.com>
Reviewed-by: Aske Simon Christensen <askesc@google.com>
This commit is contained in:
Jess Lally 2023-09-07 09:52:57 +00:00 committed by Commit Queue
parent 2bbf99351e
commit 5a4b252252
10 changed files with 770 additions and 112 deletions

View file

@ -536,7 +536,7 @@ class AsyncCodeGenerator extends CodeGenerator {
@override
void generate() {
closures = Closures(translator, member);
setupParametersAndContexts(member);
setupParametersAndContexts(member.reference);
generateTypeChecks(member.function!.typeParameters, member.function!,
translator.paramInfoFor(reference));
_generateBodies(member.function!);

View file

@ -29,6 +29,7 @@ class FieldIndex {
static const classId = 0;
static const boxValue = 1;
static const identityHash = 1;
static const objectFieldBase = 2;
static const stringArray = 2;
static const listLength = 3;
static const listArray = 4;
@ -174,6 +175,13 @@ class ClassInfo {
assert(expectedIndex == null || expectedIndex == struct.fields.length);
struct.fields.add(fieldType);
}
// This returns the types of all the class's fields (including
// superclass fields), except for the class id and the identity hash
List<w.ValueType> getClassFieldTypes() => [
for (var fieldType in struct.fields.skip(FieldIndex.objectFieldBase))
fieldType.type.unpacked
];
}
ClassInfo upperBound(Set<ClassInfo> classes) {
@ -291,8 +299,9 @@ class ClassInfoCollector {
/// This field is initialized when a class with a type parameter is first
/// encountered. Initialization depends on [Translator] visiting the [_Type]
/// class first and creating a [ClassInfo] for it.
late final w.FieldType typeType =
w.FieldType(translator.classInfo[translator.typeClass]!.nullableType);
late final w.FieldType typeType = w.FieldType(
translator.classInfo[translator.typeClass]!.nonNullableType,
mutable: false);
ClassInfoCollector(this.translator);
@ -436,7 +445,8 @@ class ClassInfoCollector {
ClassInfo? superInfo = info.superInfo;
if (superInfo == null) {
// Top - add class id field
info._addField(w.FieldType(w.NumType.i32), FieldIndex.classId);
info._addField(
w.FieldType(w.NumType.i32, mutable: false), FieldIndex.classId);
} else if (info.struct != superInfo.struct) {
// Copy fields from superclass
for (w.FieldType fieldType in superInfo.struct.fields) {
@ -462,12 +472,8 @@ class ClassInfoCollector {
for (Field field in info.cls!.fields) {
if (field.isInstanceMember) {
w.ValueType wasmType = translator.translateType(field.type);
// TODO(askesc): Generalize this check for finer nullability control
if (wasmType != w.RefType.struct(nullable: false)) {
wasmType = wasmType.withNullability(true);
}
translator.fieldIndex[field] = info.struct.fields.length;
info._addField(w.FieldType(wasmType));
info._addField(w.FieldType(wasmType, mutable: !field.isFinal));
}
}
} else {

View file

@ -255,7 +255,7 @@ class ClosureLayouter extends RecursiveVisitor {
// - A `_FunctionType`
return m.types.defineStruct(name,
fields: [
w.FieldType(w.NumType.i32),
w.FieldType(w.NumType.i32, mutable: false),
w.FieldType(w.NumType.i32),
w.FieldType(w.RefType.struct(nullable: false)),
w.FieldType(w.RefType.def(vtableStruct, nullable: false),
@ -980,9 +980,13 @@ class Context {
int get thisFieldIndex {
assert(containsThis);
return 0;
return parent != null ? 1 : 0;
}
// Set if the context is within a constructor (either in an initializer list,
// or within a constructor body function)
bool inConstructor = false;
Context(this.owner, this.parent);
}
@ -1055,7 +1059,6 @@ class Closures {
if (!context.isEmpty) {
w.StructType struct = context.struct;
if (context.parent != null) {
assert(!context.containsThis);
assert(!context.parent!.isEmpty);
struct.fields.add(w.FieldType(
w.RefType.def(context.parent!.struct, nullable: true)));
@ -1191,7 +1194,9 @@ class CaptureFinder extends RecursiveVisitor {
@override
void visitTypeParameterType(TypeParameterType node) {
if (node.parameter.parent != null &&
if (member is Constructor) {
_visitVariableUse(node.parameter);
} else if (node.parameter.parent != null &&
node.parameter.parent == member.enclosingClass) {
_visitThis();
} else if (node.parameter.parent is FunctionNode) {
@ -1255,27 +1260,64 @@ class ContextCollector extends RecursiveVisitor {
}
}
void _newContext(TreeNode node) {
void _newContext(TreeNode node, {bool constructorBody = false}) {
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) {
if (parent != null) {
currentContext!.inConstructor = constructorBody || parent.inConstructor;
}
if (closures.isThisCaptured && (outerMost || constructorBody)) {
currentContext!.containsThis = true;
}
closures.contexts[node] = currentContext!;
node.visitChildren(this);
currentContext = oldContext;
if (!constructorBody) {
node.visitChildren(this);
currentContext = oldContext;
}
}
@override
void visitConstructor(Constructor node) {
node.function.accept(this);
currentContext = closures.contexts[node.function]!;
assert(currentContext == null);
currentContext = Context(node, null);
currentContext!.inConstructor = true;
closures.contexts[node] = currentContext!;
visitList(node.enclosingClass.typeParameters, this);
// visit the constructor arguments now so that captures are added to the
// constructor context not the constructor body context.
FunctionNode functionNode = node.function;
visitList(functionNode.typeParameters, this);
visitList(functionNode.positionalParameters, this);
visitList(functionNode.namedParameters, this);
visitList(node.initializers, this);
_newContext(functionNode, constructorBody: true);
functionNode.returnType.accept(this);
functionNode.futureValueType?.accept(this);
functionNode.redirectingFactoryTarget?.target?.acceptReference(this);
if (functionNode.redirectingFactoryTarget?.typeArguments != null) {
visitList(functionNode.redirectingFactoryTarget!.typeArguments!, this);
}
functionNode.body?.accept(this);
currentContext = null;
}
@override

View file

@ -44,6 +44,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
final Reference reference;
late final List<w.Local> paramLocals;
final w.Label? returnLabel;
final bool isInlined;
late final Intrinsifier intrinsifier;
late final StaticTypeContext typeContext;
@ -57,6 +58,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
w.Local? preciseThisLocal;
w.Local? returnValueLocal;
final Map<TypeParameter, w.Local> typeLocals = {};
final Map<Field, w.Local> fieldLocals = {};
/// Finalizers to run on `return`.
final List<TryBlockFinalizer> returnFinalizers = [];
@ -83,7 +85,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
/// parameters (instead of the function inputs) and the label to jump to on
/// return (instead of emitting a `return` instruction).
CodeGenerator(this.translator, this.function, this.reference,
{List<w.Local>? paramLocals, this.returnLabel}) {
{List<w.Local>? paramLocals, this.returnLabel, this.isInlined = false}) {
this.paramLocals = paramLocals ?? function.locals;
intrinsifier = Intrinsifier(this);
typeContext = StaticTypeContext(member, translator.typeEnvironment);
@ -167,11 +169,16 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
/// Generate code for the member.
void generate() {
// Build closure information.
closures = Closures(this.translator, this.member);
Member member = this.member;
if (member is Constructor) {
// Closures are built when constructor functions are added to worklist.
closures = translator.constructorClosures[member.reference]!;
} else {
// Build closure information.
closures = Closures(this.translator, this.member);
}
if (reference.isTearOffReference) {
return generateTearOffGetter(member as Procedure);
}
@ -205,10 +212,26 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
call(translator
.noSuchMethodErrorThrowUnimplementedExternalMemberError.reference);
b.unreachable();
b.end();
// Initializer methods are often inlined, and may throw an unimplemented
// external member error.
if (!isInlined) {
b.end();
}
return;
}
if (member is Constructor) {
if (reference.isConstructorBodyReference) {
return generateConstructorBody(reference);
} else if (reference.isInitializerReference) {
return generateInitializerList(reference);
}
return generateConstructorAllocator(member);
}
if (member is Field) {
if (member.isStatic) {
return generateStaticFieldInitializer(member);
@ -226,7 +249,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
}
void generateTearOffGetter(Procedure procedure) {
_initializeThis(member);
_initializeThis(member.reference);
DartType functionType = translator.getTearOffType(procedure);
ClosureImplementation closure = translator.getTearOffClosure(procedure);
w.StructType struct = closure.representation.closureStruct;
@ -292,11 +315,8 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
b.end();
}
void setupParametersAndContexts(Member member) {
ParameterInfo paramInfo = translator.paramInfoFor(reference);
int parameterOffset = _initializeThis(member);
int implicitParams = parameterOffset + paramInfo.typeParamCount;
void _setupLocalParameters(Member member, ParameterInfo paramInfo,
int parameterOffset, int implicitParams) {
List<TypeParameter> typeParameters = member is Constructor
? member.enclosingClass.typeParameters
: member.function!.typeParameters;
@ -351,6 +371,20 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
locals[parameter] = newLocal;
}
});
}
void setupParameters(Reference reference) {
Member member = reference.asMember;
ParameterInfo paramInfo = translator.paramInfoFor(reference);
int parameterOffset = _initializeThis(reference);
int implicitParams = parameterOffset + paramInfo.typeParamCount;
_setupLocalParameters(member, paramInfo, parameterOffset, implicitParams);
}
void setupParametersAndContexts(Reference reference) {
setupParameters(reference);
closures.findCaptures(member);
closures.collectContexts(member);
@ -360,27 +394,104 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
captureParameters();
}
void generateInitializerList(Constructor member) {
Class cls = member.enclosingClass;
ClassInfo info = translator.classInfo[cls]!;
for (TypeParameter typeParam in cls.typeParameters) {
b.local_get(thisLocal!);
b.local_get(typeLocals[typeParam]!);
b.struct_set(info.struct, translator.typeParameterIndex[typeParam]!);
void setupInitializerListParametersAndContexts(Reference reference) {
setupParameters(reference);
allocateContext(member);
captureParameters();
}
void setupConstructorBodyParametersAndContexts(Reference reference) {
Constructor member = reference.asConstructor;
ParameterInfo paramInfo = translator.paramInfoFor(reference);
// For constructor body functions, the first parameter is always the
// receiver parameter, and the second parameter is a reference to the
// current context (if it exists)
Context? context = closures.contexts[member];
bool hasContext = context != null && !context.isEmpty;
if (hasContext) {
_initializeContextLocals(member.function, contextParamIndex: 1);
}
for (Field field in cls.fields) {
if (field.isInstanceMember &&
field.initializer != null &&
field.type is! VoidType) {
// Skips the receiver param (_initializeThis will return 1), and the
// context param if this exists
int parameterOffset = _initializeThis(reference) + (hasContext ? 1 : 0);
int implicitParams = parameterOffset + paramInfo.typeParamCount;
_setupLocalParameters(member, paramInfo, parameterOffset, implicitParams);
allocateContext(member.function);
}
void _setupDefaultFieldValues(ClassInfo info) {
fieldLocals.clear();
for (Field field in info.cls!.fields) {
if (field.isInstanceMember && field.initializer != null) {
int fieldIndex = translator.fieldIndex[field]!;
b.local_get(thisLocal!);
w.Local local = addLocal(info.struct.fields[fieldIndex].type.unpacked);
wrap(field.initializer!, info.struct.fields[fieldIndex].type.unpacked);
b.struct_set(info.struct, fieldIndex);
b.local_set(local);
fieldLocals[field] = local;
}
}
}
List<w.Local> _generateInitializers(Constructor member) {
Class cls = member.enclosingClass;
ClassInfo info = translator.classInfo[cls]!;
List<w.Local> superclassFields = [];
_setupDefaultFieldValues(info);
// Generate initializer list
for (Initializer initializer in member.initializers) {
visitInitializer(initializer);
if (initializer is SuperInitializer) {
// Save super classes' fields to locals
ClassInfo superInfo = info.superInfo!;
for (w.ValueType outputType
in superInfo.getClassFieldTypes().reversed) {
w.Local local = addLocal(outputType);
b.local_set(local);
superclassFields.add(local);
}
} else if (initializer is RedirectingInitializer) {
// Save redirected classes' fields to locals
List<w.Local> redirectedFields = [];
for (w.ValueType outputType in info.getClassFieldTypes().reversed) {
w.Local local = addLocal(outputType);
b.local_set(local);
redirectedFields.add(local);
}
return redirectedFields.reversed.toList();
}
}
List<w.Local> typeFields = [];
for (TypeParameter typeParam in cls.typeParameters) {
TypeParameter? match = info.typeParameterMatch[typeParam];
if (match == null) {
// Type is not contained in super class' fields
typeFields.add(typeLocals[typeParam]!);
}
}
List<w.Local> orderedFieldLocals = Map.fromEntries(
fieldLocals.entries.toList()
..sort((x, y) => translator.fieldIndex[x.key]!
.compareTo(translator.fieldIndex[y.key]!)))
.values
.toList();
return superclassFields.reversed.toList() + typeFields + orderedFieldLocals;
}
void generateTypeChecks(List<TypeParameter> typeParameters,
@ -429,24 +540,158 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
}
}
void generateBody(Member member) {
setupParametersAndContexts(member);
if (member is Constructor) {
generateInitializerList(member);
List<w.Local> _getConstructorArgumentLocals(Reference target,
[reverse = false]) {
Constructor member = target.asConstructor;
List<w.Local> constructorArgs = [];
List<TypeParameter> typeParameters = member.enclosingClass.typeParameters;
for (int i = 0; i < typeParameters.length; i++) {
constructorArgs.add(typeLocals[typeParameters[i]]!);
}
List<VariableDeclaration> positional = member.function.positionalParameters;
for (VariableDeclaration pos in positional) {
constructorArgs.add(locals[pos]!);
}
Map<String, w.Local> namedArgs = {};
List<VariableDeclaration> named = member.function.namedParameters;
for (VariableDeclaration param in named) {
namedArgs[param.name!] = locals[param]!;
}
final ParameterInfo paramInfo = translator.paramInfoFor(target);
for (String name in paramInfo.names) {
w.Local namedLocal = namedArgs[name]!;
constructorArgs.add(namedLocal);
}
if (reverse) {
return constructorArgs.reversed.toList();
}
return constructorArgs;
}
void generateBody(Member member) {
setupParametersAndContexts(member.reference);
final List<TypeParameter> typeParameters = member is Constructor
? member.enclosingClass.typeParameters
: member.function!.typeParameters;
generateTypeChecks(
typeParameters, member.function!, translator.paramInfoFor(reference));
Statement? body = member.function!.body;
if (body != null) {
visitStatement(body);
}
_implicitReturn();
b.end();
}
// Generates a function for allocating an object. This calls the separate
// initializer list and constructor body methods, and allocates a struct for
// the object.
void generateConstructorAllocator(Constructor member) {
setupParameters(member.reference);
final List<TypeParameter> typeParameters =
member.enclosingClass.typeParameters;
generateTypeChecks(
typeParameters, member.function, translator.paramInfoFor(reference));
w.FunctionType initializerMethodType =
translator.functions.getFunctionType(member.initializerReference);
List<w.Local> constructorArgs =
_getConstructorArgumentLocals(member.reference);
for (w.Local local in constructorArgs) {
b.local_get(local);
}
b.comment("Direct call of '${member} Initializer'");
call(member.initializerReference);
ClassInfo info = translator.classInfo[member.enclosingClass]!;
// Add evaluated fields to locals
List<w.Local> orderedFieldLocals = [];
List<w.FieldType> fieldTypes = info.struct.fields
.sublist(FieldIndex.objectFieldBase)
.reversed
.toList();
for (w.FieldType field in fieldTypes) {
w.Local local = addLocal(field.type.unpacked);
orderedFieldLocals.add(local);
b.local_set(local);
}
Context? context = closures.contexts[member];
w.Local? contextLocal = null;
bool hasContext = context != null && !context.isEmpty;
if (hasContext) {
w.ValueType contextRef = w.RefType.struct(nullable: true);
contextLocal = addLocal(contextRef);
b.local_set(contextLocal);
}
List<w.ValueType> initializerOutputTypes = initializerMethodType.outputs;
int numConstructorBodyArgs = initializerOutputTypes.length -
fieldTypes.length -
(hasContext ? 1 : 0);
// Pop all arguments to constructor body
List<w.ValueType> constructorArgTypes =
initializerOutputTypes.sublist(0, numConstructorBodyArgs);
List<w.Local> constructorArguments = [];
for (w.ValueType argType in constructorArgTypes.reversed) {
w.Local local = addLocal(argType);
b.local_set(local);
constructorArguments.add(local);
}
// Set field values
b.i32_const(info.classId);
b.i32_const(initialIdentityHash);
for (w.Local local in orderedFieldLocals.reversed) {
b.local_get(local);
}
// create new struct with these fields and set to local
w.Local temp = addLocal(info.nonNullableType);
b.struct_new(info.struct);
b.local_tee(temp);
// Push context local if it is present
if (contextLocal != null) {
b.local_get(contextLocal);
}
// Push all constructor arguments
for (w.Local constructorArg in constructorArguments) {
b.local_get(constructorArg);
}
b.comment("Direct call of ${member} Constructor Body");
call(member.constructorBodyReference);
b.local_get(temp);
b.end();
}
void setupLambdaParametersAndContexts(Lambda lambda) {
FunctionNode functionNode = lambda.functionNode;
_initializeContextLocals(functionNode);
@ -486,8 +731,10 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
/// Returns the number of parameter locals taken up by the receiver parameter,
/// i.e. the parameter offset for the first type parameter (or the first
/// parameter if there are no type parameters).
int _initializeThis(Member member) {
bool hasThis = member.isInstanceMember || member is Constructor;
int _initializeThis(Reference reference) {
Member member = reference.asMember;
bool hasThis =
member.isInstanceMember || reference.isConstructorBodyReference;
if (hasThis) {
thisLocal = paramLocals[0];
assert(!thisLocal!.type.nullable);
@ -516,20 +763,41 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
/// Initialize locals pointing to every context in the context chain of a
/// closure, plus the locals containing `this` if `this` is captured by the
/// closure.
void _initializeContextLocals(FunctionNode functionNode) {
void _initializeContextLocals(FunctionNode functionNode,
{int contextParamIndex = 0}) {
Context? context = closures.contexts[functionNode]?.parent;
if (context != null) {
w.RefType contextType = w.RefType.def(context.struct, nullable: false);
b.local_get(paramLocals[0]);
b.local_get(paramLocals[contextParamIndex]);
b.ref_cast(contextType);
while (true) {
w.Local contextLocal = addLocal(contextType);
context!.currentLocal = contextLocal;
if (context.parent != null || context.containsThis) {
b.local_tee(contextLocal);
} else {
b.local_set(contextLocal);
}
if (context.containsThis) {
thisLocal = addLocal(context
.struct.fields[context.thisFieldIndex].type.unpacked
.withNullability(false));
preciseThisLocal = thisLocal;
b.struct_get(context.struct, context.thisFieldIndex);
b.ref_as_non_null();
b.local_set(thisLocal!);
if (context.parent != null) {
b.local_get(contextLocal);
}
}
if (context.parent == null) break;
b.struct_get(context.struct, context.parentFieldIndex);
@ -537,15 +805,6 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
context = context.parent!;
contextType = w.RefType.def(context.struct, nullable: false);
}
if (context.containsThis) {
thisLocal = addLocal(context
.struct.fields[context.thisFieldIndex].type.unpacked
.withNullability(false));
preciseThisLocal = thisLocal;
b.struct_get(context.struct, context.thisFieldIndex);
b.ref_as_non_null();
b.local_set(thisLocal!);
}
}
}
@ -598,6 +857,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
b.struct_set(capture.context.struct, capture.fieldIndex);
}
});
typeLocals.forEach((parameter, local) {
Capture? capture = closures.captures[parameter];
if (capture != null) {
@ -665,10 +925,14 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
for (w.Local local in inlinedLocals.reversed) {
b.local_set(local);
}
w.Label block = b.block(const [], targetFunctionType.outputs);
// Because we do not recurse on initializer methods, we do not need to
// allocate a block.
w.Label? block = target.isInitializerReference
? null
: b.block(const [], targetFunctionType.outputs);
b.comment("Inlined ${target.asMember}");
CodeGenerator(translator, function, target,
paramLocals: inlinedLocals, returnLabel: block)
paramLocals: inlinedLocals, returnLabel: block, isInlined: true)
.generate();
return targetFunctionType.outputs;
} else {
@ -698,40 +962,62 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
void visitFieldInitializer(FieldInitializer node) {
Class cls = (node.parent as Constructor).enclosingClass;
w.StructType struct = translator.classInfo[cls]!.struct;
int fieldIndex = translator.fieldIndex[node.field]!;
Field field = node.field;
int fieldIndex = translator.fieldIndex[field]!;
w.Local? local = fieldLocals[field];
if (local == null) {
local = addLocal(struct.fields[fieldIndex].type.unpacked);
}
b.local_get(thisLocal!);
wrap(node.value, struct.fields[fieldIndex].type.unpacked);
b.struct_set(struct, fieldIndex);
b.local_set(local);
fieldLocals[field] = local;
}
@override
void visitRedirectingInitializer(RedirectingInitializer node) {
Class cls = (node.parent as Constructor).enclosingClass;
b.local_get(thisLocal!);
for (TypeParameter typeParam in cls.typeParameters) {
types.makeType(
this, TypeParameterType(typeParam, Nullability.nonNullable));
Supertype? supersupertype = node.target.enclosingClass.supertype;
// Skip calls to the constructor for Object, as this is empty
if (supersupertype != null) {
final Member targetMember = node.targetReference.asMember;
for (TypeParameter typeParam in cls.typeParameters) {
types.makeType(
this, TypeParameterType(typeParam, Nullability.nonNullable));
}
_visitArguments(node.arguments, targetMember.initializerReference,
cls.typeParameters.length);
b.comment("Direct call of '${targetMember} Redirected Initializer'");
call(targetMember.initializerReference);
}
_visitArguments(
node.arguments, node.targetReference, 1 + cls.typeParameters.length);
call(node.targetReference);
}
@override
void visitSuperInitializer(SuperInitializer node) {
Supertype? supertype =
(node.parent as Constructor).enclosingClass.supertype;
if (supertype?.classNode.superclass == null) {
return;
Supertype? supersupertype = node.target.enclosingClass.supertype;
// Skip calls to the constructor for Object, as this is empty
if (supersupertype != null) {
final Member targetMember = node.targetReference.asMember;
for (DartType typeArg in supertype!.typeArguments) {
types.makeType(this, typeArg);
}
_visitArguments(node.arguments, targetMember.initializerReference,
supertype.typeArguments.length);
b.comment("Direct call of '${targetMember} Initializer'");
call(targetMember.initializerReference);
}
b.local_get(thisLocal!);
for (DartType typeArg in supertype!.typeArguments) {
types.makeType(this, typeArg);
}
_visitArguments(node.arguments, node.targetReference,
1 + supertype.typeArguments.length);
call(node.targetReference);
}
@override
@ -1467,20 +1753,10 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
ClassInfo info = translator.classInfo[node.target.enclosingClass]!;
translator.functions.allocateClass(info.classId);
w.Local temp = addLocal(info.nonNullableType);
b.struct_new_default(info.struct);
b.local_tee(temp);
b.local_get(temp);
b.i32_const(info.classId);
b.struct_set(info.struct, FieldIndex.classId);
_visitArguments(node.arguments, node.targetReference, 1);
call(node.targetReference);
if (expectedType != voidMarker) {
b.local_get(temp);
return temp.type;
} else {
return voidMarker;
}
_visitArguments(node.arguments, node.targetReference, 0);
return call(node.targetReference).single;
}
@override
@ -2779,7 +3055,9 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
/// stack and returns the value type of the object.
w.ValueType instantiateTypeParameter(TypeParameter parameter) {
w.ValueType resultType;
if (parameter.parent is FunctionNode) {
// `this` will not be initialized yet for constructor initializer lists
if (parameter.parent is FunctionNode || reference.isInitializerReference) {
// Type argument to function
w.Local? local = typeLocals[parameter];
if (local != null) {
@ -2853,6 +3131,170 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
return translator.topInfo.nullableType;
}
// Generates a function for a constructor's body, where the allocated struct
// object is passed to this function.
void generateConstructorBody(Reference target) {
assert(target.isConstructorBodyReference);
Constructor member = target.asConstructor;
setupConstructorBodyParametersAndContexts(target);
int getStartIndexForSuperOrRedirectedConstructorArguments(
Member currentMember) {
// Skips the receiver param and the current constructor's context
// (if it exists)
Context? context = closures.contexts[currentMember];
bool hasContext = context != null && !context.isEmpty;
int numSkippedParams = hasContext ? 2 : 1;
// Skips the current constructor's arguments
int numConstructorArgs = _getConstructorArgumentLocals(target).length;
return numSkippedParams + numConstructorArgs;
}
// Call super class' constructor body, or redirected constructor
for (Initializer initializer in member.initializers) {
if (initializer is SuperInitializer) {
Supertype? supersupertype = initializer.target.enclosingClass.supertype;
if (supersupertype == null) {
break;
}
w.FunctionType superConstructorBodyType = translator.functions
.getFunctionType(initializer.target.constructorBodyReference);
// The receiver object will be the first argument to the super
// constructor
int numSuperConstructorArgs =
superConstructorBodyType.inputs.length - 1;
int superConstructorArgsStartIndex =
getStartIndexForSuperOrRedirectedConstructorArguments(member);
List<w.Local> superConstructorArgs = paramLocals.sublist(
superConstructorArgsStartIndex,
superConstructorArgsStartIndex + numSuperConstructorArgs);
w.Local object = thisLocal!;
b.local_get(object);
for (w.Local local in superConstructorArgs) {
b.local_get(local);
}
call(initializer.target.constructorBodyReference);
break;
} else if (initializer is RedirectingInitializer) {
Supertype? supersupertype = initializer.target.enclosingClass.supertype;
if (supersupertype == null) {
break;
}
int redirectedConstructorArgsStartIndex =
getStartIndexForSuperOrRedirectedConstructorArguments(member);
List<w.Local> redirectedConstructorArgs =
paramLocals.sublist(redirectedConstructorArgsStartIndex);
w.Local object = thisLocal!;
b.local_get(object);
for (w.Local local in redirectedConstructorArgs) {
b.local_get(local);
}
call(initializer.target.constructorBodyReference);
}
}
Statement? body = member.function.body;
if (body != null) {
visitStatement(body);
}
b.end();
}
// Generates a constructor's initializer list method, and returns:
// 1. Arguments and contexts returned from a super or redirecting initializer
// method (in reverse order).
// 2. Arguments for this constructor (in reverse order).
// 3. A reference to the context for this constructor (or null if there is no
// context).
// 4. Class fields (including superclass fields, excluding class id and
// identity hash).
void generateInitializerList(Reference target) {
assert(target.isInitializerReference);
Constructor member = target.asConstructor;
setupInitializerListParametersAndContexts(target);
Class cls = member.enclosingClass;
ClassInfo info = translator.classInfo[cls]!;
List<w.Local> initializedFields = _generateInitializers(member);
bool containsSuperInitializer = false;
bool containsRedirectingInitializer = false;
for (Initializer initializer in member.initializers) {
if (initializer is SuperInitializer) {
containsSuperInitializer = true;
} else if (initializer is RedirectingInitializer) {
containsRedirectingInitializer = true;
}
}
if (cls.superclass != null && !containsRedirectingInitializer) {
// checks if a SuperInitializer was dropped because the constructor body
// throws an error
if (!containsSuperInitializer) {
b.unreachable();
if (!isInlined) {
b.end();
}
return;
}
// checks if a FieldInitializer was dropped because the constructor body
// throws an error
for (Field field in info.cls!.fields) {
if (field.isInstanceMember && !fieldLocals.containsKey(field)) {
b.unreachable();
if (!isInlined) {
b.end();
}
return;
}
}
}
// push constructor arguments
List<w.Local> constructorArgs =
_getConstructorArgumentLocals(member.reference, true);
for (w.Local arg in constructorArgs) {
b.local_get(arg);
}
// push reference to context
Context? context = closures.contexts[member];
if (context != null && !context.isEmpty) {
b.local_get(context.currentLocal);
}
// push initialized fields
for (w.Local field in initializedFields) {
b.local_get(field);
}
if (!isInlined) {
b.end();
}
}
/// Generate type checker method for a setter.
///
/// This function will be called by a setter forwarder in a dynamic set to
@ -2861,7 +3303,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
final receiverLocal = function.locals[0];
final positionalArgLocal = function.locals[1];
_initializeThis(member);
_initializeThis(member.reference);
// Local for the argument.
final argLocal = addLocal(translator.topInfo.nullableType);
@ -2924,7 +3366,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
final positionalArgsLocal = function.locals[2];
final namedArgsLocal = function.locals[3];
_initializeThis(member);
_initializeThis(member.reference);
final typeType =
translator.classInfo[translator.typeClass]!.nonNullableType;

View file

@ -2,6 +2,8 @@
// 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 'package:dart2wasm/class_info.dart';
import 'package:dart2wasm/closures.dart';
import 'package:dart2wasm/dispatch_table.dart';
import 'package:dart2wasm/reference_extensions.dart';
import 'package:dart2wasm/translator.dart';
@ -130,6 +132,7 @@ class FunctionCollector {
w.BaseFunction getFunction(Reference target) {
return _functions.putIfAbsent(target, () {
_worklist.add(target);
return _getFunctionTypeAndName(target, m.functions.define);
});
}
@ -157,8 +160,15 @@ class FunctionCollector {
"${target.asMember} tear-off");
}
final ftype =
target.asMember.accept1(_FunctionTypeGenerator(translator), target);
Member member = target.asMember;
final ftype = member.accept1(_FunctionTypeGenerator(translator), target);
if (target.isInitializerReference) {
return action(ftype, '${member} initializer');
} else if (target.isConstructorBodyReference) {
return action(ftype, '${member} constructor body');
}
return action(ftype, "${target.asMember}");
}
@ -224,8 +234,148 @@ class _FunctionTypeGenerator extends MemberVisitor1<w.FunctionType, Reference> {
@override
w.FunctionType visitConstructor(Constructor node, Reference target) {
return _makeFunctionType(translator, target, [],
translator.classInfo[node.enclosingClass]!.nonNullableType);
// Get this constructor's argument types
List<w.ValueType> arguments = _getInputTypes(
translator, target, null, false, translator.translateType);
if (translator.constructorClosures[node.reference] == null) {
// If this is the first of the constructor's methods to be generated,
// generate the closures for the constructor
Closures closures = Closures(translator, node);
closures.findCaptures(node);
closures.collectContexts(node);
closures.buildContexts();
translator.constructorClosures[node.reference] = closures;
}
if (target.isInitializerReference) {
return _getInitializerType(node, target, arguments);
}
if (target.isConstructorBodyReference) {
return _getConstructorBodyType(node, arguments);
}
return _getConstructorAllocatorType(node, arguments);
}
w.FunctionType _getConstructorAllocatorType(
Constructor node, List<w.ValueType> arguments) {
return translator.m.types.defineFunction(arguments,
[translator.classInfo[node.enclosingClass]!.nonNullableType.unpacked]);
}
w.FunctionType _getInitializerType(
Constructor node, Reference target, List<w.ValueType> arguments) {
final ClassInfo info = translator.classInfo[node.enclosingClass]!;
assert(translator.constructorClosures.containsKey(node.reference));
Closures closures = translator.constructorClosures[node.reference]!;
List<w.ValueType> superOrRedirectedInitializerArgs = [];
for (Initializer initializer in node.initializers) {
if (initializer is SuperInitializer) {
Supertype? supersupertype = initializer.target.enclosingClass.supertype;
if (supersupertype != null) {
ClassInfo superInfo = info.superInfo!;
w.FunctionType superInitializer = translator.functions
.getFunctionType(initializer.target.initializerReference);
final int numSuperclassFields = superInfo.getClassFieldTypes().length;
final int numSuperContextAndConstructorArgs =
superInitializer.outputs.length - numSuperclassFields;
// get types of super initializer outputs, ignoring the superclass
// fields
superOrRedirectedInitializerArgs = superInitializer.outputs
.sublist(0, numSuperContextAndConstructorArgs);
}
} else if (initializer is RedirectingInitializer) {
Supertype? supersupertype = initializer.target.enclosingClass.supertype;
if (supersupertype != null) {
w.FunctionType redirectedInitializer = translator.functions
.getFunctionType(initializer.target.initializerReference);
final int numClassFields = info.getClassFieldTypes().length;
final int numRedirectedContextAndConstructorArgs =
redirectedInitializer.outputs.length - numClassFields;
// get types of redirecting initializer outputs, ignoring the class
// fields
superOrRedirectedInitializerArgs = redirectedInitializer.outputs
.sublist(0, numRedirectedContextAndConstructorArgs);
}
}
}
// Get this classes's field types
final List<w.ValueType> fieldTypes = info.getClassFieldTypes();
// Add nullable context reference for when the constructor has a non-empty
// context
Context? context = closures.contexts[node];
w.ValueType? contextRef = null;
if (context != null && !context.isEmpty) {
contextRef = w.RefType.struct(nullable: true);
}
final List<w.ValueType> outputs = superOrRedirectedInitializerArgs +
arguments.reversed.toList() +
(contextRef != null ? [contextRef] : []) +
fieldTypes;
return translator.m.types.defineFunction(arguments, outputs);
}
w.FunctionType _getConstructorBodyType(
Constructor node, List<w.ValueType> arguments) {
assert(translator.constructorClosures.containsKey(node.reference));
Closures closures = translator.constructorClosures[node.reference]!;
Context? context = closures.contexts[node];
List<w.ValueType> inputs = [
translator.classInfo[node.enclosingClass]!.nonNullableType.unpacked
];
if (context != null && !context.isEmpty) {
// Nullable context reference for when the constructor has a non-empty
// context
w.ValueType contextRef = w.RefType.struct(nullable: true);
inputs.add(contextRef);
}
inputs += arguments;
for (Initializer initializer in node.initializers) {
if (initializer is SuperInitializer) {
Supertype? supersupertype = initializer.target.enclosingClass.supertype;
if (supersupertype != null) {
w.FunctionType superConstructorBodyType = translator.functions
.getFunctionType(initializer.target.constructorBodyReference);
// drop receiver param
inputs += superConstructorBodyType.inputs.sublist(1);
}
} else if (initializer is RedirectingInitializer) {
Supertype? supersupertype = initializer.target.enclosingClass.supertype;
if (supersupertype != null) {
w.FunctionType redirectedConstructorBodyType = translator.functions
.getFunctionType(initializer.target.constructorBodyReference);
// drop receiver param
inputs += redirectedConstructorBodyType.inputs.sublist(1);
}
}
}
return translator.m.types.defineFunction(inputs, []);
}
}

View file

@ -1364,9 +1364,6 @@ class Intrinsifier {
int typeParameterIndex = translator.typeParameterIndex[typeParameter]!;
b.local_get(preciseObject);
b.struct_get(classInfo.struct, typeParameterIndex);
// TODO(jessicalally): Remove this null check when type argument fields
// are made non-nullable.
b.ref_as_non_null();
}
b.array_new_fixed(arrayType, cls.typeParameters.length);
return true;

View file

@ -36,6 +36,8 @@ extension GetterSetterReference on Reference {
// Use Expandos to avoid keeping the procedure alive.
final Expando<Reference> _tearOffReference = Expando();
final Expando<Reference> _typeCheckerReference = Expando();
final Expando<Reference> _initializerReference = Expando();
final Expando<Reference> _constructorBodyReference = Expando();
extension CustomReference on Member {
Reference get tearOffReference =>
@ -43,12 +45,23 @@ extension CustomReference on Member {
Reference get typeCheckerReference =>
_typeCheckerReference[this] ??= Reference()..node = this;
Reference get initializerReference =>
_initializerReference[this] ??= Reference()..node = this;
Reference get constructorBodyReference =>
_constructorBodyReference[this] ??= Reference()..node = this;
}
extension IsCustomReference on Reference {
bool get isTearOffReference => _tearOffReference[asMember] == this;
bool get isTypeCheckerReference => _typeCheckerReference[asMember] == this;
bool get isInitializerReference => _initializerReference[asMember] == this;
bool get isConstructorBodyReference =>
_constructorBodyReference[asMember] == this;
}
extension ReferenceAs on Member {

View file

@ -207,7 +207,7 @@ class SyncStarCodeGenerator extends CodeGenerator {
@override
void generate() {
closures = Closures(translator, member);
setupParametersAndContexts(member);
setupParametersAndContexts(member.reference);
generateTypeChecks(member.function!.typeParameters, member.function!,
translator.paramInfoFor(reference));
generateBodies(member.function!);

View file

@ -91,6 +91,7 @@ class Translator with KernelNodes {
final Map<Field, w.Table> declaredTables = {};
final Set<Member> membersContainingInnerFunctions = {};
final Set<Member> membersBeingGenerated = {};
final Map<Reference, Closures> constructorClosures = {};
final List<_FunctionGenerator> _pendingFunctions = [];
late final Procedure mainFunction;
late final w.ModuleBuilder m;
@ -347,11 +348,16 @@ class Translator with KernelNodes {
print(codeGen.function.body.trace);
}
for (Lambda lambda in codeGen.closures.lambdas.values) {
w.BaseFunction lambdaFunction = CodeGenerator.forFunction(
this, lambda.functionNode, lambda.function, reference)
.generateLambda(lambda, codeGen.closures);
_printFunction(lambdaFunction, "$canonicalName (closure)");
// All the constructor methods have access to the constructor's lambdas,
// but we only want to generate the lambda functions once, so only
// generate lambdas after the initializer methods are generated.
if (member is! Constructor || reference.isInitializerReference) {
for (Lambda lambda in codeGen.closures.lambdas.values) {
w.BaseFunction lambdaFunction = CodeGenerator.forFunction(
this, lambda.functionNode, lambda.function, reference)
.generateLambda(lambda, codeGen.closures);
_printFunction(lambdaFunction, "$canonicalName (closure)");
}
}
// Use an indexed loop to handle pending closure trampolines, since new
@ -362,6 +368,7 @@ class Translator with KernelNodes {
_pendingFunctions.clear();
}
constructorClosures.clear();
dispatchTable.output();
initFunction.body.end();
@ -906,6 +913,7 @@ class Translator with KernelNodes {
Member member = target.asMember;
if (membersContainingInnerFunctions.contains(member)) return false;
if (membersBeingGenerated.contains(member)) return false;
if (target.isInitializerReference) return true;
if (member is Field) return true;
if (member.function!.asyncMarker != AsyncMarker.Sync) return false;
if (getPragma<Constant>(member, "wasm:prefer-inline") != null) return true;

View file

@ -27,7 +27,7 @@ class _AsyncSuspendState {
// Context containing the local variables of the function.
@pragma("wasm:entry-point")
final WasmStructRef? _context;
WasmStructRef? _context;
// CFG target index for the next resumption.
@pragma("wasm:entry-point")