Reland "[dart2wasm] Replace struct.new_default with struct.new for object allocation."

This reverts commit 67f0d4daf0, and further optimises constructor contexts by preventing empty contexts.

Reason for revert: Includes fix for Flutter engine unit test failures.

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

Original change's description:
[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: I13499bdc412f474bc76473115b6e63d6954f4d23
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/326080
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Jess Lally <jessicalally@google.com>
Reviewed-by: Aske Simon Christensen <askesc@google.com>
This commit is contained in:
Jess Lally 2023-09-26 10:07:42 +00:00 committed by Commit Queue
parent 70ce3d9c32
commit 3c4d4ad450
11 changed files with 1928 additions and 115 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),
@ -945,8 +945,9 @@ class Lambda {
/// child context (and its descendants).
class Context {
/// The node containing the scope covered by the context. This is either a
/// [FunctionNode] (for members, local functions and function expressions),
/// a [ForStatement], a [DoStatement] or a [WhileStatement].
/// [FunctionNode] (for members, local functions, constructor bodies and
/// function expressions), a [Constructor], a [ForStatement], a [DoStatement]
/// or a [WhileStatement].
final TreeNode owner;
/// The parent of this context, corresponding to the lexically enclosing
@ -961,7 +962,7 @@ class Context {
final List<TypeParameter> typeParameters = [];
/// Whether this context contains a captured `this`. Only member contexts can.
bool containsThis = false;
final bool containsThis;
/// The Wasm struct representing this context at runtime.
late final w.StructType struct;
@ -980,10 +981,11 @@ class Context {
int get thisFieldIndex {
assert(containsThis);
return 0;
return parent != null ? 1 : 0;
}
Context(this.owner, this.parent);
Context(this.owner, this.parent, this.containsThis);
}
/// A captured variable.
@ -1012,6 +1014,9 @@ class Closures {
final Map<TreeNode, Capture> captures = {};
bool isThisCaptured = false;
final Map<FunctionNode, Lambda> lambdas = {};
// This [TreeNode] is the context owner, and can be a [FunctionNode],
// [Constructor], [ForStatement], [DoStatement] or a [WhileStatement].
final Map<TreeNode, Context> contexts = {};
final Set<FunctionDeclaration> closurizedFunctions = {};
@ -1046,7 +1051,17 @@ class Closures {
// Make struct definitions
for (Context context in contexts.values) {
if (!context.isEmpty) {
context.struct = m.types.defineStruct("<context>");
if (context.owner is Constructor) {
Constructor constructor = context.owner as Constructor;
context.struct =
m.types.defineStruct("<${constructor}-constructor-context>");
} else if (context.owner.parent is Constructor) {
Constructor constructor = context.owner.parent as Constructor;
context.struct =
m.types.defineStruct("<${constructor}-constructor-body-context>");
} else {
context.struct = m.types.defineStruct("<context>");
}
}
}
@ -1055,7 +1070,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)));
@ -1084,6 +1098,9 @@ class Closures {
class CaptureFinder extends RecursiveVisitor {
final Closures closures;
final Member member;
// Stores the depth of captured type parameters and variables. The [TreeNode]
// key must be either a [VariableDeclaration] or a [TypeParameter].
final Map<TreeNode, int> variableDepth = {};
final List<bool> functionIsSyncStarOrAsync = [false];
@ -1191,7 +1208,16 @@ class CaptureFinder extends RecursiveVisitor {
@override
void visitTypeParameterType(TypeParameterType node) {
if (node.parameter.declaration == member.enclosingClass) {
bool classTypeParameter =
node.parameter.declaration == member.enclosingClass;
if (classTypeParameter && member is Constructor) {
// Type parameters can be captured by lambdas inside the initializer
// list, which does not have access to `this` as the object has not been
// allocated yet. Therefore, these captured type parameters must be
// added to the context instead.
_visitVariableUse(node.parameter);
} else if (classTypeParameter) {
_visitThis();
} else if (node.parameter.declaration is GenericFunction) {
_visitVariableUse(node.parameter);
@ -1261,10 +1287,8 @@ class ContextCollector extends RecursiveVisitor {
while (parent != null && parent.isEmpty) {
parent = parent.parent;
}
currentContext = Context(node, parent);
if (closures.isThisCaptured && outerMost) {
currentContext!.containsThis = true;
}
bool containsThis = closures.isThisCaptured && outerMost;
currentContext = Context(node, parent, containsThis);
closures.contexts[node] = currentContext!;
node.visitChildren(this);
currentContext = oldContext;
@ -1272,9 +1296,83 @@ class ContextCollector extends RecursiveVisitor {
@override
void visitConstructor(Constructor node) {
node.function.accept(this);
currentContext = closures.contexts[node.function]!;
// Constructors should always be the outermost context.
assert(currentContext == null);
// Create constructor context.
final Context constructorAllocatorContext = Context(node, null, false);
currentContext = constructorAllocatorContext;
// Visit the class's type parameters so that captured type parameters can
// be added to the context. Initializer lists don't have access to `this`,
// which would contain the type parameters, so the type parameters must
// be captured from the constructor arguments instead.
visitList(node.enclosingClass.typeParameters, this);
// Visit the constructor function's parameters directly instead of calling
// node.visitChildren(), so that a new context is not allocated for the
// FunctionNode, and any captured parameters are added to the Constructor
// context.
visitList(node.function.typeParameters, this);
visitList(node.function.positionalParameters, this);
visitList(node.function.namedParameters, this);
// Visit the constructor's initializers to add captured arguments to the
// context.
visitList(node.initializers, this);
// If no type parameters, arguments, or `this` are captured by the
// constructor body, we do not need to allocate a context for the
// constructor or constructor body. If parameters are captured, we want
// the constructor context to contain these, so that they can be shared
// between the constructor initializer and body functions. If `this` is
// captured, we want the constructor body function context to contain it.
if (!constructorAllocatorContext.isEmpty) {
// Some type arguments or variables have been captured by the
// initializer list.
if (closures.isThisCaptured) {
// In this case, we need two contexts: a constructor context to store
// the captured arguments/type parameters (shared by the initializer
// and constructor body, and a separate context just for the
// constructor body to store the captured `this`, as initializer lists
// cannot have access to `this`.
assert(!constructorAllocatorContext.containsThis);
final constructorBodyContext =
Context(node.function, constructorAllocatorContext, true);
closures.contexts[node.function] = constructorBodyContext;
closures.contexts[node] = constructorAllocatorContext;
currentContext = constructorBodyContext;
} else {
// We only need the constructor context, so contexts in the constructor
// body can have this as parent.
closures.contexts[node] = constructorAllocatorContext;
}
node.function.body?.accept(this);
} else {
// We may only need a context for the constructor body function, as no
// parameters have been captured by the initializer list, and we only
// need the body context if the body captures parameters, or contains
// `this`. We must create a new context with the correct owner
// (node.function) for debugging purposes, and drop the
// constructor allocator context as it is not used.
final Context constructorBodyContext =
Context(node.function, null, closures.isThisCaptured);
currentContext = constructorBodyContext;
node.function.body?.accept(this);
if (!constructorBodyContext.isEmpty) {
// We only allocate the context if it is not empty.
closures.contexts[node.function] = constructorBodyContext;
}
}
currentContext = null;
}
@override

View file

@ -59,6 +59,10 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
w.Local? returnValueLocal;
final Map<TypeParameter, w.Local> typeLocals = {};
// Maps a classes' fields to corresponding locals so that we can update the
// local directly if a field has both a default value and a FieldInitializer.
final Map<Field, w.Local> fieldLocals = {};
/// Finalizers to run on `return`.
final List<TryBlockFinalizer> returnFinalizers = [];
@ -85,7 +89,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
/// return (instead of emitting a `return` instruction).
CodeGenerator(this.translator, this.function, this.reference,
{List<w.Local>? paramLocals, this.returnLabel}) {
this.paramLocals = paramLocals ?? function.locals;
this.paramLocals = paramLocals ?? function.locals.toList();
intrinsifier = Intrinsifier(this);
typeContext = StaticTypeContext(member, translator.typeEnvironment);
}
@ -158,11 +162,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);
}
@ -200,6 +209,16 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
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);
@ -217,7 +236,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;
@ -283,11 +302,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;
@ -342,6 +358,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);
@ -351,27 +381,106 @@ 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 hasConstructorContext = context != null;
if (hasConstructorContext) {
assert(!context.isEmpty);
_initializeContextLocals(member, 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) + (hasConstructorContext ? 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,
@ -420,24 +529,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]]!);
}
final List<TypeParameter> typeParameters = member is Constructor
? member.enclosingClass.typeParameters
: member.function!.typeParameters;
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) {
assert(member is! Constructor);
setupParametersAndContexts(member.reference);
final List<TypeParameter> 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;
if (hasContext) {
assert(!context.isEmpty);
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);
@ -477,8 +720,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);
@ -507,20 +752,50 @@ 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) {
Context? context = closures.contexts[functionNode]?.parent;
void _initializeContextLocals(TreeNode node, {int contextParamIndex = 0}) {
Context? context = null;
if (node is Constructor) {
// The context parameter is for the constructor context.
context = closures.contexts[node];
} else {
assert(node is FunctionNode);
// The context parameter is for the parent context.
context = closures.contexts[node]?.parent;
}
if (context != null) {
assert(!context.isEmpty);
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);
@ -528,15 +803,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!);
}
}
}
@ -689,40 +955,58 @@ 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!);
final Member targetMember = node.targetReference.asMember;
for (TypeParameter typeParam in cls.typeParameters) {
types.makeType(
this, TypeParameterType(typeParam, Nullability.nonNullable));
}
_visitArguments(
node.arguments, node.targetReference, 1 + cls.typeParameters.length);
call(node.targetReference);
_visitArguments(node.arguments, targetMember.initializerReference,
cls.typeParameters.length);
b.comment("Direct call of '${targetMember} Redirected Initializer'");
call(targetMember.initializerReference);
}
@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
@ -1460,20 +1744,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
@ -2306,6 +2580,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
void _pushContext(FunctionNode functionNode) {
Context? context = closures.contexts[functionNode]?.parent;
if (context != null) {
assert(!context.isEmpty);
b.local_get(context.currentLocal);
if (context.currentLocal.type.nullable) {
b.ref_as_non_null();
@ -2767,7 +3042,10 @@ 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.declaration is GenericFunction) {
// `this` will not be initialized yet for constructor initializer lists
if (parameter.declaration is GenericFunction ||
reference.isInitializerReference) {
// Type argument to function
w.Local? local = typeLocals[parameter];
if (local != null) {
@ -2841,6 +3119,145 @@ 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() {
// Skips the receiver param and the current constructor's context
// (if it exists)
Context? context = closures.contexts[member];
bool hasContext = context != null;
if (hasContext) {
assert(!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 ||
initializer is RedirectingInitializer) {
Constructor target = initializer is SuperInitializer
? initializer.target
: (initializer as RedirectingInitializer).target;
Supertype? supersupertype = target.enclosingClass.supertype;
if (supersupertype == null) {
break;
}
int startIndex =
getStartIndexForSuperOrRedirectedConstructorArguments();
List<w.Local> superOrRedirectedConstructorArgs =
paramLocals.sublist(startIndex);
w.Local object = thisLocal!;
b.local_get(object);
for (w.Local local in superOrRedirectedConstructorArgs) {
b.local_get(local);
}
call(target.constructorBodyReference);
break;
}
}
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();
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();
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) {
assert(!context.isEmpty);
b.local_get(context.currentLocal);
}
// push initialized fields
for (w.Local field in initializedFields) {
b.local_get(field);
}
b.end();
}
/// Generate type checker method for a setter.
///
/// This function will be called by a setter forwarder in a dynamic set to
@ -2849,7 +3266,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);
@ -2912,7 +3329,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';
@ -157,8 +159,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}");
}
@ -219,8 +228,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) {
// We need the contexts of the constructor before generating the
// initializer and constructor body functions, as these functions will
// return/take a context argument if context must be shared between them.
// Generate the contexts the first time we visit a 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) {
assert(!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) {
assert(!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 ||
initializer is RedirectingInitializer) {
Constructor target = initializer is SuperInitializer
? initializer.target
: (initializer as RedirectingInitializer).target;
Supertype? supersupertype = target.enclosingClass.supertype;
if (supersupertype != null) {
w.FunctionType superOrRedirectedConstructorBodyType = translator
.functions
.getFunctionType(target.constructorBodyReference);
// drop receiver param
inputs += superOrRedirectedConstructorBodyType.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

@ -208,7 +208,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,18 @@ 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)");
// The constructor allocator, initializer, and body functions all
// share the same Closures, which will contain all the lambdas in the
// constructor initializer and body. But, we only want to generate
// the lambda functions once, so we only generate lambdas when the
// constructor 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 +370,7 @@ class Translator with KernelNodes {
_pendingFunctions.clear();
}
constructorClosures.clear();
dispatchTable.output();
initFunction.body.end();
@ -906,6 +915,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")

File diff suppressed because it is too large Load diff