[dart2wasm] Optimize the way we implement constructors

This reduces essentials main module around -0.4% and possibly
opens up for changes in the inlining (specifically to possibly
not force-inline all initializers anymore)

This shrinks the amount of information
* initializer result values
* the body parameters
* the allocator needs to forward less from initializer to body

We do that by analyzing constructor parameters to see
which parameters are needed for the constructor

Change-Id: I967fa4102ea6e9d498ff07aedabc368b038e1085
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/496341
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Martin Kustermann
2026-04-21 00:13:42 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent 8664b0f98b
commit 12174830ec
8 changed files with 1816 additions and 472 deletions
+53 -73
View File
@@ -1416,6 +1416,10 @@ class Capture {
/// The captured [VariableDeclaration] or [TypeParameter].
final TreeNode variable;
/// Whether the variable was captured in the initializer (if constructor
/// parameter).
final bool isInInitializer;
late final Context context;
/// The index of the captured variable or type parameter in its context
@@ -1429,7 +1433,7 @@ class Capture {
/// context.
bool written = false;
Capture(this.variable) {
Capture(this.variable, this.isInInitializer) {
assert(variable is VariableDeclaration || variable is TypeParameter);
}
@@ -1503,12 +1507,20 @@ class Closures {
final member = _member;
final find = _CaptureFinder(this, member);
if (member is Constructor) {
find.isInInitializer = true;
Class cls = member.enclosingClass;
for (Field field in cls.fields) {
if (field.isInstanceMember && field.initializer != null) {
field.initializer!.accept(find);
}
}
for (final init in member.initializers) {
init.accept(find);
}
find.isInInitializer = false;
member.function.body?.accept(find);
return;
}
member.accept(find);
}
@@ -1581,6 +1593,8 @@ class _CaptureFinder extends RecursiveVisitor {
final Closures closures;
final Member member;
bool isInInitializer = false;
// 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 = {};
@@ -1604,6 +1618,7 @@ class _CaptureFinder extends RecursiveVisitor {
@override
void visitFunctionNode(FunctionNode node) {
assert(depth == 0); // Nested function nodes are skipped by [_visitLambda].
assert(member.function == node);
functionIsSyncStarOrAsync[0] =
node.asyncMarker == AsyncMarker.SyncStar ||
node.asyncMarker == AsyncMarker.Async;
@@ -1647,7 +1662,10 @@ class _CaptureFinder extends RecursiveVisitor {
int declDepth = variableDepth[variable] ?? 0;
assert(declDepth <= depth);
if (declDepth < depth || functionIsSyncStarOrAsync[declDepth]) {
final capture = closures.captures[variable] ??= Capture(variable);
final capture = closures.captures[variable] ??= Capture(
variable,
isInInitializer,
);
if (functionIsSyncStarOrAsync[declDepth]) capture.written = true;
} else if (variable is VariableDeclaration &&
variable.parent is FunctionDeclaration) {
@@ -1703,15 +1721,18 @@ class _CaptureFinder extends RecursiveVisitor {
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) {
if (classTypeParameter) {
if (member is Constructor && isInInitializer) {
// 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 {
_visitThis();
}
} else {
assert(node.parameter.declaration is GenericFunction);
_visitVariableUse(node.parameter);
}
super.visitTypeParameterType(node);
@@ -1774,6 +1795,7 @@ class _ContextCollector extends RecursiveVisitor {
final Closures closures;
Context? currentContext;
final bool enableAsserts;
bool isInInitializer = false;
_ContextCollector(this.closures, this.enableAsserts);
@@ -1792,7 +1814,9 @@ class _ContextCollector extends RecursiveVisitor {
}
void _newContext(TreeNode node) {
bool outerMost = currentContext == null;
bool outerMost =
currentContext == null ||
node.parent is Constructor && !isInInitializer;
Context? oldContext = currentContext;
Context? parent = currentContext;
while (parent != null && parent.isEmpty) {
@@ -1810,9 +1834,11 @@ class _ContextCollector extends RecursiveVisitor {
// Constructors should always be the outermost context.
assert(currentContext == null);
isInInitializer = true;
// Create constructor context.
final Context constructorAllocatorContext = Context(node, null, false);
currentContext = constructorAllocatorContext;
final Context constructorContext = Context(node, null, false);
currentContext = constructorContext;
// 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`,
@@ -1832,63 +1858,13 @@ class _ContextCollector extends RecursiveVisitor {
// 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.
isInInitializer = false;
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;
}
if (!constructorContext.isEmpty) {
closures.contexts[node] = constructorContext;
currentContext = constructorContext;
}
_newContext(node.function);
currentContext = null;
}
@@ -1916,8 +1892,10 @@ class _ContextCollector extends RecursiveVisitor {
void visitVariableDeclaration(VariableDeclaration node) {
Capture? capture = closures.captures[node];
if (capture != null) {
currentContext!.variables.add(node);
capture.context = currentContext!;
if (isInInitializer == capture.isInInitializer) {
currentContext!.variables.add(node);
capture.context = currentContext!;
}
}
super.visitVariableDeclaration(node);
}
@@ -1926,8 +1904,10 @@ class _ContextCollector extends RecursiveVisitor {
void visitTypeParameter(TypeParameter node) {
Capture? capture = closures.captures[node];
if (capture != null) {
currentContext!.typeParameters.add(node);
capture.context = currentContext!;
if (isInInitializer == capture.isInInitializer) {
currentContext!.typeParameters.add(node);
capture.context = currentContext!;
}
}
super.visitTypeParameter(node);
}
+396 -291
View File
@@ -468,7 +468,7 @@ abstract class AstCodeGenerator
void setupContexts(Member member) {
allocateContext(member.function!);
captureParameters();
captureParameters(member.function!);
}
void setupLambdaParametersAndContexts(Lambda lambda) {
@@ -487,7 +487,7 @@ abstract class AstCodeGenerator
}
allocateContext(functionNode);
captureParameters();
captureParameters(functionNode);
}
/// Initialize locals containing `this` in constructors and instance members.
@@ -610,10 +610,13 @@ abstract class AstCodeGenerator
}
}
void captureParameters() {
void captureParameters(TreeNode node) {
Context? context = closures.contexts[node];
if (context == null || context.isEmpty) return;
locals.forEach((variable, local) {
Capture? capture = closures.captures[variable];
if (capture != null) {
if (capture != null && capture.context == context) {
b.local_get(capture.context.currentLocal);
b.local_get(local);
translator.convertType(b, local.type, capture.type);
@@ -622,7 +625,7 @@ abstract class AstCodeGenerator
});
typeLocals.forEach((parameter, local) {
Capture? capture = closures.captures[parameter];
if (capture != null) {
if (capture != null && capture.context == context) {
b.local_get(capture.context.currentLocal);
b.local_get(local);
translator.convertType(b, local.type, capture.type);
@@ -3499,9 +3502,13 @@ CodeGenerator? getInlinableMemberCodeGenerator(
if (member is Constructor) {
if (reference.isConstructorBodyReference) {
return ConstructorCodeGenerator(translator, functionType, member);
return ConstructorBodyCodeGenerator(translator, functionType, member);
} else if (reference.isInitializerReference) {
return InitializerListCodeGenerator(translator, functionType, member);
return ConstructorInitializerCodeGenerator(
translator,
functionType,
member,
);
} else {
return ConstructorAllocatorCodeGenerator(
translator,
@@ -4141,62 +4148,63 @@ void createInvocationObject(
}
abstract class ConstructorCodeGeneratorBase extends AstCodeGenerator {
ConstructorCodeGeneratorBase(
super.translator,
super.functionType,
super.member,
);
List<w.Local> _getConstructorArgumentLocals(
Reference target, [
reverse = false,
]) {
Constructor member = target.asConstructor;
List<w.Local> constructorArgs = [];
final Constructor member;
List<TypeParameter> typeParameters = member.enclosingClass.typeParameters;
ConstructorCodeGeneratorBase(
Translator translator,
w.FunctionType functionType,
this.member,
) : super(translator, functionType, member);
List<w.Local> _getConstructorArgumentLocals(
List<TypeParameter> typeParameters,
List<VariableDeclaration> parameters,
) {
List<w.Local> constructorArgs = [];
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.paramInfoForDirectCall(target);
for (String name in paramInfo.names) {
w.Local namedLocal = namedArgs[name]!;
constructorArgs.add(namedLocal);
}
if (reverse) {
return constructorArgs.reversed.toList();
for (VariableDeclaration param in parameters) {
constructorArgs.add(locals[param]!);
}
return constructorArgs;
}
int _setupConstructorParameters(
List<TypeParameter> typeParameters,
List<VariableDeclaration> parameters,
int parameterOffset,
) {
for (int i = 0; i < typeParameters.length; i++) {
typeLocals[typeParameters[i]] = paramLocals[parameterOffset++];
}
for (int i = 0; i < parameters.length; i++) {
final variable = parameters[i];
final local = paramLocals[parameterOffset++];
final variableName = variable.name;
if (variableName != null && variableName.isNotEmpty) {
b.localNames[local.index] = variableName;
}
locals[variable] = local;
}
return parameterOffset;
}
}
class InitializerListCodeGenerator extends ConstructorCodeGeneratorBase {
final Constructor member;
class ConstructorInitializerCodeGenerator extends ConstructorCodeGeneratorBase {
// 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 = {};
InitializerListCodeGenerator(
Translator translator,
w.FunctionType functionType,
this.member,
) : super(translator, functionType, member);
ConstructorInitializerCodeGenerator(
super.translator,
super.functionType,
super.member,
);
@override
void generateInternal() {
@@ -4209,41 +4217,76 @@ class InitializerListCodeGenerator extends ConstructorCodeGeneratorBase {
if (member.isExternal) {
emitUnimplementedExternalError(member);
} else {
generateInitializerList();
final lastInit = member.initializers.lastOrNull;
if (lastInit is RedirectingInitializer) {
generateRedirectingInitializerList();
} else {
generateInitializerList();
}
}
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 generateRedirectingInitializerList() {
_setupInitializerListParametersAndContexts();
for (final init in member.initializers) {
assert(init is! SuperInitializer);
init.accept(this);
}
// Redirecting generative constructors don't have a body.
assert(member.function.body is EmptyStatement);
assert(translator.getConstructorInfo(member).bodyParameters.isEmpty);
// The last was a call to the redirectee which means we have
// [redirectee-body-args, fields] already on the stack.
assert(member.initializers.last is RedirectingInitializer);
}
/// Code for the initializer function.
///
/// For a class hierarchy like
///
/// class Object { class-id, identity-hashcode }
/// class A<...> extends Object { ...A-fields }
/// class B<...> extends A<...> { ...B-fields }
/// class C<...> extends C<...> { ...C-fields }
///
/// the resulting stack looks like:
///
/// [
/// C-initializer-context?
/// ...C-body-args
/// B-initializer-context?
/// ...B-body-args
/// A-initializer-context?
/// ...A-body-args
/// // NOTE: No Object-fields (class-id, hashcode) here.
/// ...A-type-fields
/// ...A-fields
/// ...B-type-fields
/// ...B-fields
/// ...C-type-fields
/// ...C-fields
/// ]
///
void generateInitializerList() {
_setupInitializerListParametersAndContexts();
Class cls = member.enclosingClass;
ClassInfo info = translator.classInfo[cls]!;
final context = closures.contexts[member];
final constructorInfo = translator.getConstructorInfo(member);
final lastInit = member.initializers.lastOrNull;
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;
}
_setupDefaultFieldValues(info);
for (final initializer in member.initializers) {
initializer.accept(this);
}
if (cls.superclass != null && !containsRedirectingInitializer) {
if (cls.superclass != null) {
// checks if a SuperInitializer was dropped because the constructor body
// throws an error
if (!containsSuperInitializer) {
if (lastInit is! SuperInitializer) {
b.unreachable();
return;
}
@@ -4258,82 +4301,18 @@ class InitializerListCodeGenerator extends ConstructorCodeGeneratorBase {
}
}
// push constructor arguments
List<w.Local> constructorArgs = _getConstructorArgumentLocals(
member.reference,
true,
);
final superInit = lastInit as SuperInitializer?;
final superInfo = info.superInfo!;
final superClassFields = superInfo.getClassFieldTypes();
for (w.Local arg in constructorArgs) {
b.local_get(arg);
}
final superInitOutputs = superInit == null
? <w.ValueType>[]
: translator
.signatureForDirectCall(superInit.target.initializerReference)
.outputs;
// 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);
}
}
void _setupInitializerListParametersAndContexts() {
setupParameters(member.initializerReference, isForwarder: true);
allocateContext(member);
captureParameters();
}
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) {
initializer.accept(this);
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(
final extraTypeFields = getTypeFields(cls, info);
final extraFields = Map.fromEntries(
fieldLocals.entries.toList()..sort(
(x, y) => translator.fieldIndex[x.key]!.compareTo(
translator.fieldIndex[y.key]!,
@@ -4341,7 +4320,92 @@ class InitializerListCodeGenerator extends ConstructorCodeGeneratorBase {
),
).values.toList();
return superclassFields.reversed.toList() + typeFields + orderedFieldLocals;
// The last evaluation was a super initializer call that pushed
// [...superBodyArgs, ...superFields].
//
// If we don't have anything to pass to our body constructor, then we can
// simply add a few more fields of our class and return.
if (context == null && constructorInfo.bodyParameters.isEmpty) {
for (final local in [...extraTypeFields, ...extraFields]) {
b.local_get(local);
}
// Now we have
// [...superBodyArgs, ...superFields, ...extraTypeFields, ...extraFields].
// i.e.
// [...superBodyArgs, ...thisFields].
} else {
// We have to inject extra args for our body, so pop, inject, push
// Pop
final superclassFieldsReversed = <w.Local>[];
final superBodyArgsReversed = <w.Local>[];
if (superInitOutputs.isNotEmpty) {
// Pop super fields.
for (final type in superClassFields.reversed) {
final local = addLocal(type);
b.local_set(local);
superclassFieldsReversed.add(local);
}
// Pop args to super bodies.
final superBodyArgs = superInitOutputs.sublist(
0,
superInitOutputs.length - superClassFields.length,
);
for (final type in superBodyArgs.reversed) {
w.Local local = addLocal(type);
b.local_set(local);
superBodyArgsReversed.add(local);
}
}
// Push things for our constructor body
if (context != null) {
assert(!context.isEmpty);
b.local_get(context.currentLocal);
}
for (final param in constructorInfo.bodyParameters) {
b.local_get(locals[param]!);
}
// Push things for super constructor bodies.
for (final local in superBodyArgsReversed.reversed) {
b.local_get(local);
}
// push super fields
for (final local in superclassFieldsReversed.reversed) {
b.local_get(local);
}
// push our fields
for (final local in [...extraTypeFields, ...extraFields]) {
b.local_get(local);
}
}
}
void _setupInitializerListParametersAndContexts() {
int parameterOffset = 0;
final info = translator.getConstructorInfo(member);
_setupConstructorParameters(
info.initializerTypeParameters,
info.initializerParameters,
parameterOffset,
);
allocateContext(member);
captureParameters(member);
}
/// The locals for type parameter values which end up in fields (i.e. type
/// parameters that aren't shared with super class).
List<w.Local> getTypeFields(Class cls, ClassInfo info) {
final typeFields = <w.Local>[];
for (final typeParam in cls.typeParameters) {
final match = info.typeParameterMatch[typeParam];
if (match == null) {
// Type is not contained in super class' fields
typeFields.add(typeLocals[typeParam]!);
}
}
return typeFields;
}
void _setupDefaultFieldValues(ClassInfo info) {
@@ -4448,16 +4512,64 @@ class InitializerListCodeGenerator extends ConstructorCodeGeneratorBase {
call(target);
}
}
@override
w.ValueType visitVariableGet(VariableGet node, w.ValueType expectedType) {
final capture = closures.captures[node.variable];
if (capture == null) {
return super.visitVariableGet(node, expectedType);
}
// If the parameter was captured in the initializer we can load it from
// initializer context. But if it will be captured in body, then we use
// normal parameter here.
if (capture.isInInitializer) {
assert(capture.isInInitializer);
return super.visitVariableGet(node, expectedType);
}
// Even though the parameter is captured, it's only captured in the body and
// the body will put it in a newly allocated context. The initializer uses
// the normal variable.
final local = locals[node.variable]!;
b.local_get(local);
return local.type;
}
@override
w.ValueType visitVariableSet(VariableSet node, w.ValueType expectedType) {
final capture = closures.captures[node.variable];
if (capture == null) {
return super.visitVariableSet(node, expectedType);
}
// If the parameter was captured in the initializer we can load it from
// initializer context. But if it will be captured in body, then we use
// normal parameter here.
if (capture.isInInitializer) {
return super.visitVariableSet(node, expectedType);
}
// Even though the parameter is captured, it's only captured in the body and
// the body will put it in a newly allocated context. The initializer uses
// the normal variable.
final local = locals[node.variable]!;
translateExpression(node.value, local.type);
if (expectedType == voidMarker) {
b.local_set(local);
return voidMarker;
}
b.local_tee(local);
return local.type;
}
}
class ConstructorAllocatorCodeGenerator extends ConstructorCodeGeneratorBase {
final Constructor member;
ConstructorAllocatorCodeGenerator(
Translator translator,
w.FunctionType functionType,
this.member,
) : super(translator, functionType, member);
super.translator,
super.functionType,
super.member,
);
@override
void generateInternal() {
@@ -4474,113 +4586,101 @@ class ConstructorAllocatorCodeGenerator extends ConstructorCodeGeneratorBase {
// initializer list and constructor body methods, and allocates a struct for
// the object.
void generateConstructorAllocator() {
setupParameters(member.reference, isForwarder: true);
int parameterOffset = 0;
final constructorInfo = translator.getConstructorInfo(member);
_setupConstructorParameters(
member.enclosingClass.typeParameters,
constructorInfo.allParameters,
parameterOffset,
);
w.FunctionType initializerMethodType = translator.signatureForDirectCall(
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
final info = translator.classInfo[member.enclosingClass]!;
final 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;
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(
final bodyCallArgTypes = initializerMethodType.outputs.sublist(
0,
numConstructorBodyArgs,
initializerMethodType.outputs.length - fieldTypes.length,
);
List<w.Local> constructorArguments = [];
final initializerArgs = _getConstructorArgumentLocals(
constructorInfo.initializerTypeParameters,
constructorInfo.initializerParameters,
);
for (w.ValueType argType in constructorArgTypes.reversed) {
w.Local local = addLocal(argType);
b.local_set(local);
constructorArguments.add(local);
final bodyCallArgsReversed = <w.Local>[];
if (bodyCallArgTypes.isEmpty) {
// Common situation: Initializers don't capture constructor parameters,
// initializers don't modify constructor parameters, constructor
// parameters end up in fields
// => No arguments to the body function.
b.comment('Allocate wasm struct optimized');
b.pushObjectHeaderFields(translator, info);
b.comment('Calling $member initializer function');
for (final local in initializerArgs) {
b.local_get(local);
}
call(member.initializerReference);
b.struct_new(info.struct);
} else {
b.comment('Calling $member initializer function');
for (final local in initializerArgs) {
b.local_get(local);
}
call(member.initializerReference);
b.comment('Pop all field values to locals');
final fieldValuesReversed = <w.Local>[];
for (final field in fieldTypes.reversed) {
final local = addLocal(field.type.unpacked);
fieldValuesReversed.add(local);
b.local_set(local);
}
b.comment('Pop body and super/redirect body args to locals');
for (final argType in bodyCallArgTypes.reversed) {
final local = addLocal(argType);
b.local_set(local);
bodyCallArgsReversed.add(local);
}
b.comment('Allocate wasm struct');
b.pushObjectHeaderFields(translator, info);
for (final local in fieldValuesReversed.reversed) {
b.local_get(local);
}
b.struct_new(info.struct);
}
// Set field values
b.pushObjectHeaderFields(translator, info);
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);
// Mark the class as allocated now, which enqueues those methods of the
// class that could be targeted by already emitted instance calls.
translator.functions.recordClassAllocation(info.classId);
// 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('Push receiver');
final receiverVar = addLocal(info.nonNullableType);
b.local_tee(receiverVar);
b.comment('Push constructor body and super arguments');
for (final arg in bodyCallArgsReversed.reversed) {
b.local_get(arg);
}
b.comment("Direct call of $member Constructor Body");
call(member.constructorBodyReference);
b.local_get(temp);
b.local_get(receiverVar);
b.end();
}
}
class ConstructorCodeGenerator extends ConstructorCodeGeneratorBase {
final Constructor member;
ConstructorCodeGenerator(
Translator translator,
w.FunctionType functionType,
this.member,
) : super(translator, functionType, member);
class ConstructorBodyCodeGenerator extends ConstructorCodeGeneratorBase {
ConstructorBodyCodeGenerator(
super.translator,
super.functionType,
super.member,
);
@override
void generateInternal() {
@@ -4590,69 +4690,43 @@ class ConstructorCodeGenerator extends ConstructorCodeGeneratorBase {
final source = member.enclosingComponent!.uriToSource[member.fileUri]!;
setSourceMapSourceAndFileOffset(source, member.fileOffset);
if (member.isExternal) {
// Currently all external constructors are throwing NSM in the initializer
// function (see [ConstructorInitializerCodeGenerator.generateInternal]).
// So the body function should be unreachable.
b.unreachable();
b.end();
return;
}
generateConstructorBody();
}
// Generates a function for a constructor's body, where the allocated struct
// object is passed to this function.
void generateConstructorBody() {
_setupConstructorBodyParametersAndContexts();
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(
member.constructorBodyReference,
).length;
return numSkippedParams + numConstructorArgs;
}
final parameterOffset = _setupConstructorBodyParametersAndContexts();
// Call super class' constructor body, or redirected constructor
for (Initializer initializer in member.initializers) {
for (final initializer in member.initializers) {
if (initializer is SuperInitializer ||
initializer is RedirectingInitializer) {
Constructor target = initializer is SuperInitializer
final 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,
);
if (target.enclosingClass.supertype == null) break;
w.Local object = thisLocal!;
b.local_get(object);
for (w.Local local in superOrRedirectedConstructorArgs) {
for (w.Local local in paramLocals.sublist(parameterOffset)) {
b.local_get(local);
}
call(target.constructorBodyReference);
break;
}
}
Statement? body = member.function.body;
if (body != null) {
translateStatement(body);
}
@@ -4660,31 +4734,62 @@ class ConstructorCodeGenerator extends ConstructorCodeGeneratorBase {
b.end();
}
void _setupConstructorBodyParametersAndContexts() {
ParameterInfo paramInfo = translator.paramInfoForDirectCall(
member.constructorBodyReference,
);
int _setupConstructorBodyParametersAndContexts() {
// Setup [thisLocal] / [preciseThisLocal].
int parameterOffset = _initializeThis(member.constructorBodyReference);
assert(parameterOffset == 1);
// 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);
// Setup context variables
// Redirecting constructors don't have a real body and don't need the
// context in the body.
final isRedirectInitializer =
member.initializers.lastOrNull is RedirectingInitializer;
if (!isRedirectInitializer) {
if (closures.contexts[member] case var context?) {
assert(!context.isEmpty);
_initializeContextLocals(member, contextParamIndex: parameterOffset);
parameterOffset++;
}
}
// Skips the receiver param (_initializeThis will return 1), and the
// context param if this exists.
int parameterOffset =
_initializeThis(member.constructorBodyReference) +
(hasConstructorContext ? 1 : 0);
int implicitParams = parameterOffset + paramInfo.typeParamCount;
final info = translator.getConstructorInfo(member);
parameterOffset = _setupConstructorParameters(
const [],
info.bodyParameters,
parameterOffset,
);
// Populate type parameters and normal parameters from fields if we emitted
// them from the body function signature.
final classInfo = translator.classInfo[member.enclosingClass]!;
for (final typeParam in member.enclosingClass.typeParameters) {
if (!typeLocals.containsKey(typeParam)) {
final fieldIndex = translator.typeParameterIndex[typeParam]!;
final typeType =
translator.classInfo[translator.typeClass]!.nonNullableType;
final local = addLocal(typeType);
b.local_get(preciseThisLocal!);
b.struct_get(classInfo.struct, fieldIndex);
b.local_set(local);
typeLocals[typeParam] = local;
}
}
info.parameterToField.forEach((variable, field) {
if (!locals.containsKey(variable)) {
final fieldIndex = translator.fieldIndex[field]!;
final wasmType = translator.translateTypeOfField(field);
final local = addLocal(wasmType);
b.local_get(preciseThisLocal!);
b.struct_get(classInfo.struct, fieldIndex);
b.local_set(local);
locals[variable] = local;
}
});
_setupLocalParameters(member, paramInfo, parameterOffset, implicitParams);
allocateContext(member.function);
captureParameters(member.function);
return parameterOffset;
}
}
+205
View File
@@ -0,0 +1,205 @@
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:kernel/ast.dart';
import 'closures.dart';
import 'translator.dart';
/// Information about a constructor's parameter usages, used to optimize
/// the signatures of the 3 (allocator, initializer,
/// body) constructor functions.
class ConstructorInfo {
final Constructor constructor;
final Translator translator;
/// All parameters of the constructor in canonical order (positional first,
/// then named sorted by name).
final List<VariableDeclaration> allParameters;
/// Parameters that must be passed to the initializer function.
late final List<TypeParameter> initializerTypeParameters;
late final List<VariableDeclaration> initializerParameters;
/// Parameters that must be passed to the body function from the allocator.
///
/// NOTE: Type parameters of a constructor always end up in fields and can be
/// loaded from there. The constructor body therefore never needs to get them
/// passed explicitly.
late final List<VariableDeclaration> bodyParameters;
/// Parameters that constructor bodies can load via `this`.
final Map<VariableDeclaration, Field> parameterToField = {};
ConstructorInfo(this.constructor, this.translator)
: allParameters = [
...constructor.function.positionalParameters,
...(constructor.function.namedParameters.toList()
..sort((a, b) => a.name!.compareTo(b.name!))),
] {
// The initializer gets all arguments and produces
// - arguments to be passed to the body function
// - field values.
initializerTypeParameters = constructor.enclosingClass.typeParameters;
initializerParameters = allParameters;
if (constructor.isExternal) {
// Currently the backend generates NSM in the initializer function
// and a trap in body function and doesn't need the parameters.
bodyParameters = [];
return;
}
// Analyze parameter usages in initializers & body.
final closures = translator.getClosures(constructor);
final initializerCollector = _UsageCollector(translator, closures)
..collectInitializerUsages(constructor);
final bodyCollector = _UsageCollector(translator, closures)
..collectBodyUsages(constructor);
// Identify parameters that end up in fields and are not modified, so the
// body function can load them from `this` instead of getting them as
// parameters.
for (final p in allParameters) {
if (initializerCollector.variablesCaptured.contains(p)) {
// The parameter will reside in the initializer context and doesn't
// have to be passed to the body explicitly.
continue;
}
if (initializerCollector.variablesWritten.contains(p)) {
// If the parameter is modified by initializers the value that ends up
// in a field may not be the same as the value of the (modified)
// parameter. So the body function may need to get the modified value
// as the value from the field may not reflect the modification.
continue;
}
if (initializerCollector.variablesStoredInFields[p] case final field?) {
// The parameter is
// * not captured by initializers
// * never written to by initializers
// * is available via `this` to the body
parameterToField[p] = field;
}
}
bodyParameters = [];
for (final param in allParameters) {
if (!bodyCollector.variablesRead.contains(param) &&
!bodyCollector.variablesWritten.contains(param)) {
// The body doesn't use the parameter.
continue;
}
if (initializerCollector.variablesCaptured.contains(param)) {
// The body should use the parameter from the context.
continue;
}
if (parameterToField.containsKey(param)) {
// The body can load the parameter via `this`.
continue;
}
bodyParameters.add(param);
}
}
}
/// Collects used variables and type parameters from an AST subtree.
class _UsageCollector extends RecursiveVisitor {
final Translator translator;
final Closures closures;
final variablesRead = <VariableDeclaration>{};
final variablesWritten = <VariableDeclaration>{};
final variablesCaptured = <VariableDeclaration>{};
final usedTypeParameters = <TypeParameter>{};
final variablesStoredInFields = <VariableDeclaration, Field>{};
_UsageCollector(this.translator, this.closures);
void collectInitializerUsages(Constructor constructor) {
final rootContext = closures.contexts[constructor];
if (rootContext != null) {
assert(rootContext.parent == null);
if (closures.contexts[constructor] case final context?) {
variablesCaptured.addAll(context.variables);
}
}
for (final init in constructor.initializers) {
init.accept(this);
}
}
void collectBodyUsages(Constructor constructor) {
constructor.function.body?.accept(this);
}
@override
void visitFieldInitializer(FieldInitializer node) {
super.visitFieldInitializer(node);
final value = node.value;
if (value is VariableGet) {
variablesStoredInFields[value.variable] = node.field;
}
}
@override
void visitSuperInitializer(SuperInitializer node) {
super.visitSuperInitializer(node);
_findVariablesStoredInFields(node.target, node.arguments);
}
@override
void visitRedirectingInitializer(RedirectingInitializer node) {
super.visitRedirectingInitializer(node);
_findVariablesStoredInFields(node.target, node.arguments);
}
void _findVariablesStoredInFields(Constructor target, Arguments arguments) {
final targetInfo = translator.getConstructorInfo(target);
for (int i = 0; i < arguments.positional.length; ++i) {
final arg = arguments.positional[i];
if (arg is VariableGet) {
final targetParam = target.function.positionalParameters[i];
final field = targetInfo.parameterToField[targetParam];
if (field != null) {
variablesStoredInFields[arg.variable] = field;
}
}
}
for (final namedArg in arguments.named) {
final arg = namedArg.value;
if (arg is VariableGet) {
for (final targetNamedParameter in target.function.namedParameters) {
if (targetNamedParameter.name == namedArg.name) {
final field = targetInfo.parameterToField[targetNamedParameter];
if (field != null) {
variablesStoredInFields[arg.variable] = field;
}
}
}
}
}
}
@override
void visitVariableGet(VariableGet node) {
variablesRead.add(node.variable);
}
@override
void visitVariableSet(VariableSet node) {
node.value.accept(this);
variablesWritten.add(node.variable);
}
@override
void visitTypeParameterType(TypeParameterType node) {
usedTypeParameters.add(node.parameter);
}
@override
void visitClassTypeParameterType(ClassTypeParameterType node) {
usedTypeParameters.add(node.parameter);
}
}
+122 -108
View File
@@ -519,15 +519,6 @@ class _FunctionTypeGenerator extends MemberVisitor1<w.FunctionType, Reference> {
@override
w.FunctionType visitConstructor(Constructor node, Reference target) {
// Get this constructor's argument types
List<w.ValueType> arguments = _getInputTypes(
translator,
target,
null,
false,
translator.translateType,
);
// 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
@@ -537,135 +528,133 @@ class _FunctionTypeGenerator extends MemberVisitor1<w.FunctionType, Reference> {
);
if (target.isInitializerReference) {
return _getInitializerType(node, target, arguments);
return _getInitializerType(node, target);
}
if (target.isConstructorBodyReference) {
return _getConstructorBodyType(node, arguments);
return _getConstructorBodyType(node);
}
return _getConstructorAllocatorType(node, arguments);
return _getConstructorAllocatorType(node);
}
w.FunctionType _getConstructorAllocatorType(
Constructor node,
List<w.ValueType> arguments,
) {
return translator.typesBuilder.defineFunction(arguments, [
w.FunctionType _getConstructorAllocatorType(Constructor node) {
final constructorInfo = translator.getConstructorInfo(node);
List<w.ValueType> inputs = _getConstructorInputTypes(
translator,
node,
node.enclosingClass.typeParameters,
constructorInfo.allParameters,
translator.translateType,
);
return translator.typesBuilder.defineFunction(inputs, [
translator.classInfo[node.enclosingClass]!.nonNullableType.unpacked,
]);
}
w.FunctionType _getInitializerType(
Constructor node,
Reference target,
List<w.ValueType> arguments,
) {
final ClassInfo info = translator.classInfo[node.enclosingClass]!;
w.FunctionType _getInitializerType(Constructor node, Reference target) {
final info = translator.classInfo[node.enclosingClass]!;
assert(translator.constructorClosures.containsKey(node.reference));
Closures closures = translator.constructorClosures[node.reference]!;
List<w.ValueType> superOrRedirectedInitializerArgs = [];
final constructorInfo = translator.getConstructorInfo(node);
final inputs = _getConstructorInputTypes(
translator,
node,
constructorInfo.initializerTypeParameters,
constructorInfo.initializerParameters,
translator.translateType,
);
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.signatureForDirectCall(
initializer.target.initializerReference,
final outputs = <w.ValueType>[];
final closures = translator.constructorClosures[node.reference]!;
// Redirecting constructors don't have a real body and don't need the
// context in the body.
final isRedirectInitializer =
node.initializers.lastOrNull is RedirectingInitializer;
if (!isRedirectInitializer) {
if (closures.contexts[node] case var context?) {
assert(!context.isEmpty);
outputs.add(const w.RefType.struct(nullable: true));
}
}
outputs.addAll(
_getConstructorInputTypes(
translator,
node,
const [],
constructorInfo.bodyParameters,
translator.translateType,
),
);
for (final initializer in node.initializers) {
if (initializer is SuperInitializer ||
initializer is RedirectingInitializer) {
final target = initializer is SuperInitializer
? initializer.target
: (initializer as RedirectingInitializer).target;
if (target.enclosingClass.supertype != null) {
final targetInfo = translator.classInfo[target.enclosingClass]!;
final targetOutputs = translator
.signatureForDirectCall(target.initializerReference)
.outputs;
outputs.addAll(
targetOutputs.sublist(
0,
targetOutputs.length - targetInfo.getClassFieldTypes().length,
),
);
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
.signatureForDirectCall(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);
break;
}
}
}
// Get this classes's field types
final List<w.ValueType> fieldTypes = info.getClassFieldTypes();
outputs.addAll(info.getClassFieldTypes());
// Add nullable context reference for when the constructor has a non-empty
// context
Context? context = closures.contexts[node];
w.ValueType? contextRef;
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.typesBuilder.defineFunction(arguments, outputs);
return translator.typesBuilder.defineFunction(inputs, outputs);
}
w.FunctionType _getConstructorBodyType(
Constructor node,
List<w.ValueType> arguments,
) {
w.FunctionType _getConstructorBodyType(Constructor node) {
assert(translator.constructorClosures.containsKey(node.reference));
Closures closures = translator.constructorClosures[node.reference]!;
Context? context = closures.contexts[node];
List<w.ValueType> inputs = [
final inputs = <w.ValueType>[
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);
final closures = translator.constructorClosures[node.reference]!;
// Redirecting constructors don't have a real body and don't need the
// context in the body.
final isRedirectInitializer =
node.initializers.lastOrNull is RedirectingInitializer;
if (!isRedirectInitializer) {
if (closures.contexts[node] case var context?) {
assert(!context.isEmpty);
inputs.add(w.RefType.struct(nullable: true));
}
}
inputs += arguments;
final constructorInfo = translator.getConstructorInfo(node);
inputs.addAll(
_getConstructorInputTypes(
translator,
node,
const [],
constructorInfo.bodyParameters,
translator.translateType,
),
);
for (Initializer initializer in node.initializers) {
for (final initializer in node.initializers) {
if (initializer is SuperInitializer ||
initializer is RedirectingInitializer) {
Constructor target = initializer is SuperInitializer
final target = initializer is SuperInitializer
? initializer.target
: (initializer as RedirectingInitializer).target;
Supertype? supersupertype = target.enclosingClass.supertype;
if (supersupertype != null) {
w.FunctionType superOrRedirectedConstructorBodyType = translator
.signatureForDirectCall(target.constructorBodyReference);
if (target.enclosingClass.supertype != null) {
final targetBodyType = translator.signatureForDirectCall(
target.constructorBodyReference,
);
// drop receiver param
inputs += superOrRedirectedConstructorBodyType.inputs.sublist(1);
inputs.addAll(targetBodyType.inputs.sublist(1));
}
}
}
@@ -674,6 +663,34 @@ class _FunctionTypeGenerator extends MemberVisitor1<w.FunctionType, Reference> {
}
}
List<w.ValueType> _getConstructorInputTypes(
Translator translator,
Constructor member,
List<TypeParameter> typeParameters,
List<VariableDeclaration> parameters,
w.ValueType Function(DartType) translateType,
) {
final List<w.ValueType> inputs = [];
final List<w.ValueType> wasmTypeParameters = List.filled(
typeParameters.length,
translateType(InterfaceType(translator.typeClass, Nullability.nonNullable)),
);
inputs.addAll(wasmTypeParameters);
final List<DartType> params = parameters.map((p) {
final function = p.parent as FunctionNode;
final positionalIndex = function.positionalParameters.indexOf(p);
final isRequired = positionalIndex != -1
? positionalIndex < function.requiredParameterCount
: p.isRequired;
return translator.typeOfParameterVariable(p, isRequired);
}).toList();
inputs.addAll(params.map(translateType));
return inputs;
}
List<w.ValueType> _getInputTypes(
Translator translator,
Reference target,
@@ -687,12 +704,9 @@ List<w.ValueType> _getInputTypes(
if (member is Field) {
params = [if (target.isImplicitSetter) member.setterType];
} else {
assert(member is Procedure);
FunctionNode function = member.function!;
typeParamCount =
(member is Constructor
? member.enclosingClass.typeParameters
: function.typeParameters)
.length;
typeParamCount = function.typeParameters.length;
List<String> names = [for (var p in function.namedParameters) p.name!]
..sort();
final typeForParam = translator.typeOfParameterVariable;
+5
View File
@@ -20,6 +20,7 @@ import 'class_info.dart';
import 'closures.dart';
import 'code_generator.dart';
import 'constants.dart';
import 'constructor_info.dart';
import 'dispatch_table.dart';
import 'dynamic_dispatch_table.dart';
import 'dynamic_dispatchers.dart';
@@ -255,6 +256,7 @@ class Translator with KernelNodes {
final Set<Member> membersContainingInnerFunctions = {};
final Set<Member> membersBeingGenerated = {};
final Map<Reference, Closures> constructorClosures = {};
final Map<Reference, ConstructorInfo> constructorInfo = {};
late final w.ValueType voidMarker = w.RefType.def(
w.StructType("void"),
nullable: true,
@@ -510,6 +512,9 @@ class Translator with KernelNodes {
)
: Closures(this, member, findCaptures: false);
ConstructorInfo getConstructorInfo(Constructor node) =>
constructorInfo[node.reference] ??= ConstructorInfo(node, this);
Translator(
this.component,
this.coreTypes,
@@ -0,0 +1,102 @@
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// functionFilter=SubPos1|SubPos2|SubNamed|SubOptionalPos|SubOptionalNamed|SubMixin
// typeFilter=NoMatch
// globalFilter=NoMatch
// compilerOption=-O0
void main() {
final bs = <Base<Object>>[
SubPos1<int>(1, 1, 1, 1),
SubPos1<int>(2, 2, 2, 2),
SubPos2<int>(3, 3),
SubPos2<int>(4, 4),
SubNamed<int>(onlyUsedInSubField: 5, onlyUsedInSuper: 5),
SubNamed<int>(onlyUsedInSubField: 6, onlyUsedInSuper: 6),
SubOptionalPos<int>(),
SubOptionalPos<int>(7, 7, 7, 7),
SubOptionalNamed<int>(),
SubOptionalNamed<int>(onlyUsedInSubField: 8, onlyUsedInSuper: 8),
];
for (final b in bs) {
print(b.onlyUsedInBaseField);
print(b.onlyUsedInSubField);
print(b.baseInitializerField);
print(b.subInitializerField);
}
}
mixin SubMixin<T> {}
abstract class Base<T> {
final baseInitializerField = [];
final onlyUsedInBaseField;
Base.sub1(this.onlyUsedInBaseField, int onlyUsedInBaseBody) {
print('Base<$T>: $baseInitializerField $onlyUsedInBaseBody');
}
Base.sub2(this.onlyUsedInBaseField) {
print('Base<$T>: $baseInitializerField');
}
Base.named({required this.onlyUsedInBaseField}) {
print('Base<$T>.named: $baseInitializerField');
}
int get onlyUsedInSubField;
dynamic get subInitializerField;
}
class SubPos1<T> extends Base<Iterable<T>> with SubMixin<T> {
final dynamic subInitializerField = [];
final int onlyUsedInSubField;
SubPos1(
this.onlyUsedInSubField,
int onlyUsedInSubBody,
int onlyUsedInSuper1,
int onlyUsedInSuper2,
) : super.sub1(onlyUsedInSuper1, onlyUsedInSuper2) {
print('SubPos1<$T>: $subInitializerField, $onlyUsedInSubBody');
}
}
class SubPos2<T> extends Base<T> with SubMixin<T> {
final dynamic subInitializerField = [];
final int onlyUsedInSubField;
SubPos2(this.onlyUsedInSubField, int onlyUsedInSuper1)
: super.sub2(onlyUsedInSuper1) {
print('SubPos2<$T>: $subInitializerField');
}
}
class SubOptionalPos<T> extends Base<List<T>> with SubMixin<T> {
final dynamic subInitializerField = [];
final int onlyUsedInSubField;
SubOptionalPos([
this.onlyUsedInSubField = 10,
int onlyUsedInSubBody = 11,
int onlyUsedInSuper1 = 12,
int onlyUsedInSuper2 = 13,
]) : super.sub1(onlyUsedInSuper1, onlyUsedInSuper2) {
print('SubOptionalPos<$T>: $subInitializerField, $onlyUsedInSubBody');
}
}
class SubNamed<T> extends Base<T> with SubMixin<T> {
final dynamic subInitializerField = [];
final int onlyUsedInSubField;
SubNamed({required this.onlyUsedInSubField, required int onlyUsedInSuper})
: super.named(onlyUsedInBaseField: onlyUsedInSuper) {
print('SubNamed<$T>: $subInitializerField');
}
}
class SubOptionalNamed<T> extends Base<Comparable<T>> with SubMixin<T> {
final dynamic subInitializerField = [];
final int onlyUsedInSubField;
SubOptionalNamed({this.onlyUsedInSubField = 20, int onlyUsedInSuper = 21})
: super.named(onlyUsedInBaseField: onlyUsedInSuper) {
print('SubOptionalNamed<$T>: $subInitializerField');
}
}
+620
View File
@@ -0,0 +1,620 @@
(module $module0
(type $#Top <...>)
(type $Array<Object?> <...>)
(type $Array<_Type> <...>)
(type $Base <...>)
(type $BoxedInt <...>)
(type $JSExternWrapper <...>)
(type $SubNamed <...>)
(type $SubOptionalNamed <...>)
(type $SubOptionalPos <...>)
(type $SubPos1 <...>)
(type $SubPos2 <...>)
(type $WasmListBase <...>)
(type $_InterfaceType <...>)
(type $_MixinApplication0&Base&SubMixin <...>)
(type $_MixinApplication2&Base&SubMixin <...>)
(type $_MixinApplication3&Base&SubMixin <...>)
(type $_Type <...>)
(global $"\", \"" (ref $JSExternWrapper) <...>)
(global $"\">: \"" (ref $JSExternWrapper) <...>)
(global $"\"SubNamed<\"" (ref $JSExternWrapper) <...>)
(global $"\"SubOptionalNamed<\"" (ref $JSExternWrapper) <...>)
(global $"\"SubOptionalPos<\"" (ref $JSExternWrapper) <...>)
(global $"\"SubPos1<\"" (ref $JSExternWrapper) <...>)
(global $"\"SubPos2<\"" (ref $JSExternWrapper) <...>)
(func $"SubNamed.onlyUsedInSubField implicit getter" (param $var0 (ref $Base)) (result i64)
local.get $var0
ref.cast $SubNamed
struct.get $SubNamed $onlyUsedInSubField
)
(func $"SubNamed.subInitializerField implicit getter" (param $var0 (ref $Base)) (result (ref null $#Top))
local.get $var0
ref.cast $SubNamed
struct.get $SubNamed $subInitializerField
)
(func $"SubOptionalNamed.onlyUsedInSubField implicit getter" (param $var0 (ref $Base)) (result i64)
local.get $var0
ref.cast $SubOptionalNamed
struct.get $SubOptionalNamed $onlyUsedInSubField
)
(func $"SubOptionalNamed.subInitializerField implicit getter" (param $var0 (ref $Base)) (result (ref null $#Top))
local.get $var0
ref.cast $SubOptionalNamed
struct.get $SubOptionalNamed $subInitializerField
)
(func $"SubOptionalPos.onlyUsedInSubField implicit getter" (param $var0 (ref $Base)) (result i64)
local.get $var0
ref.cast $SubOptionalPos
struct.get $SubOptionalPos $onlyUsedInSubField
)
(func $"SubOptionalPos.subInitializerField implicit getter" (param $var0 (ref $Base)) (result (ref null $#Top))
local.get $var0
ref.cast $SubOptionalPos
struct.get $SubOptionalPos $subInitializerField
)
(func $"SubPos1.onlyUsedInSubField implicit getter" (param $var0 (ref $Base)) (result i64)
local.get $var0
ref.cast $SubPos1
struct.get $SubPos1 $onlyUsedInSubField
)
(func $"SubPos1.subInitializerField implicit getter" (param $var0 (ref $Base)) (result (ref null $#Top))
local.get $var0
ref.cast $SubPos1
struct.get $SubPos1 $subInitializerField
)
(func $"SubPos2.onlyUsedInSubField implicit getter" (param $var0 (ref $Base)) (result i64)
local.get $var0
ref.cast $SubPos2
struct.get $SubPos2 $onlyUsedInSubField
)
(func $"SubPos2.subInitializerField implicit getter" (param $var0 (ref $Base)) (result (ref null $#Top))
local.get $var0
ref.cast $SubPos2
struct.get $SubPos2 $subInitializerField
)
(func $createEmptyList<DynamicType(dynamic)> (result (ref $WasmListBase)) <...>)
(func $new Base.named (constructor body) (param $this (ref $Base)) <...>)
(func $new Base.named (initializer) (param $var0 (ref $_Type)) (param $onlyUsedInBaseField (ref null $#Top)) (result (ref $_Type)) (result (ref $WasmListBase)) (result (ref null $#Top)) <...>)
(func $new Base.sub1 (constructor body) (param $this (ref $Base)) (param $onlyUsedInBaseBody i64) <...>)
(func $new Base.sub1 (initializer) (param $var0 (ref $_Type)) (param $onlyUsedInBaseField (ref null $#Top)) (param $onlyUsedInBaseBody i64) (result i64) (result (ref $_Type)) (result (ref $WasmListBase)) (result (ref null $#Top)) <...>)
(func $new Base.sub2 (constructor body) (param $this (ref $Base)) <...>)
(func $new Base.sub2 (initializer) (param $var0 (ref $_Type)) (param $onlyUsedInBaseField (ref null $#Top)) (result (ref $_Type)) (result (ref $WasmListBase)) (result (ref null $#Top)) <...>)
(func $"new SubNamed (constructor body)" (param $this (ref $SubNamed))
(local $var0 (ref $_Type))
(local $var1 i64)
(local $var2 (ref null $#Top))
local.get $this
struct.get $SubNamed $field2
local.set $var0
local.get $this
struct.get $SubNamed $onlyUsedInSubField
local.set $var1
local.get $this
struct.get $SubNamed $onlyUsedInBaseField
local.set $var2
local.get $this
call $"new _MixinApplication1&Base&SubMixin.named (constructor body)"
global.get $"\"SubNamed<\""
local.get $var0
global.get $"\">: \""
local.get $this
struct.get $SubNamed $subInitializerField
call $JSStringImpl._interpolate4
call $print
drop
)
(func $"new SubNamed (initializer)" (param $var0 (ref $_Type)) (param $onlyUsedInSubField i64) (param $onlyUsedInSuper i64) (result (ref $_Type)) (result (ref $WasmListBase)) (result (ref null $#Top)) (result (ref null $#Top)) (result i64)
(local $var1 (ref null $#Top))
(local $var2 i64)
(local $var3 i64)
call $"createEmptyList<DynamicType(dynamic)>"
local.set $var1
local.get $onlyUsedInSubField
local.set $var2
local.get $var0
local.get $onlyUsedInSuper
local.set $var3
i32.const 65
local.get $var3
struct.new $BoxedInt
call $"new _MixinApplication1&Base&SubMixin.named (initializer)"
local.get $var1
local.get $var2
)
(func $"new SubOptionalNamed (constructor body)" (param $this (ref $SubOptionalNamed))
(local $var0 (ref $_Type))
(local $var1 i64)
(local $var2 (ref null $#Top))
local.get $this
struct.get $SubOptionalNamed $field5
local.set $var0
local.get $this
struct.get $SubOptionalNamed $onlyUsedInSubField
local.set $var1
local.get $this
struct.get $SubOptionalNamed $onlyUsedInBaseField
local.set $var2
local.get $this
call $"new _MixinApplication3&Base&SubMixin.named (constructor body)"
global.get $"\"SubOptionalNamed<\""
local.get $var0
global.get $"\">: \""
local.get $this
struct.get $SubOptionalNamed $subInitializerField
call $JSStringImpl._interpolate4
call $print
drop
)
(func $"new SubOptionalNamed (initializer)" (param $var0 (ref $_Type)) (param $onlyUsedInSubField (ref null $BoxedInt)) (param $onlyUsedInSuper (ref null $BoxedInt)) (result (ref $_Type)) (result (ref $WasmListBase)) (result (ref null $#Top)) (result (ref $_Type)) (result (ref null $#Top)) (result i64)
(local $var1 (ref null $#Top))
(local $var2 i64)
call $"createEmptyList<DynamicType(dynamic)>"
local.set $var1
local.get $onlyUsedInSubField
struct.get $BoxedInt $value
local.set $var2
local.get $var0
local.get $onlyUsedInSuper
call $"new _MixinApplication3&Base&SubMixin.named (initializer)"
local.get $var1
local.get $var2
)
(func $"new SubOptionalPos (constructor body)" (param $this (ref $SubOptionalPos)) (param $onlyUsedInSubBody (ref null $BoxedInt)) (param $var0 i64)
(local $var1 (ref $_Type))
(local $var2 i64)
(local $var3 (ref null $#Top))
local.get $this
struct.get $SubOptionalPos $field5
local.set $var1
local.get $this
struct.get $SubOptionalPos $onlyUsedInSubField
local.set $var2
local.get $this
struct.get $SubOptionalPos $onlyUsedInBaseField
local.set $var3
local.get $this
local.get $var0
call $"new _MixinApplication2&Base&SubMixin.sub1 (constructor body)"
global.get $"\"SubOptionalPos<\""
local.get $var1
global.get $"\">: \""
local.get $this
struct.get $SubOptionalPos $subInitializerField
global.get $"\", \""
local.get $onlyUsedInSubBody
array.new_fixed $Array<Object?> 6
call $JSStringImpl._interpolate
call $print
drop
)
(func $"new SubOptionalPos (initializer)" (param $var0 (ref $_Type)) (param $onlyUsedInSubField (ref null $BoxedInt)) (param $onlyUsedInSubBody (ref null $BoxedInt)) (param $onlyUsedInSuper1 (ref null $BoxedInt)) (param $onlyUsedInSuper2 (ref null $BoxedInt)) (result (ref null $BoxedInt)) (result i64) (result (ref $_Type)) (result (ref $WasmListBase)) (result (ref null $#Top)) (result (ref $_Type)) (result (ref null $#Top)) (result i64)
(local $var1 (ref null $#Top))
(local $var2 i64)
(local $var3 (ref $_Type))
(local $var4 (ref null $#Top))
(local $var5 (ref $WasmListBase))
(local $var6 (ref $_Type))
(local $var7 i64)
call $"createEmptyList<DynamicType(dynamic)>"
local.set $var1
local.get $onlyUsedInSubField
struct.get $BoxedInt $value
local.set $var2
local.get $var0
local.get $onlyUsedInSuper1
local.get $onlyUsedInSuper2
struct.get $BoxedInt $value
call $"new _MixinApplication2&Base&SubMixin.sub1 (initializer)"
local.set $var3
local.set $var4
local.set $var5
local.set $var6
local.set $var7
local.get $onlyUsedInSubBody
local.get $var7
local.get $var6
local.get $var5
local.get $var4
local.get $var3
local.get $var1
local.get $var2
)
(func $"new SubPos1 (constructor body)" (param $this (ref $SubPos1)) (param $onlyUsedInSubBody i64) (param $var0 i64)
(local $var1 (ref $_Type))
(local $var2 i64)
(local $var3 (ref null $#Top))
(local $var4 i64)
local.get $this
struct.get $SubPos1 $field5
local.set $var1
local.get $this
struct.get $SubPos1 $onlyUsedInSubField
local.set $var2
local.get $this
struct.get $SubPos1 $onlyUsedInBaseField
local.set $var3
local.get $this
local.get $var0
call $"new _MixinApplication0&Base&SubMixin.sub1 (constructor body)"
global.get $"\"SubPos1<\""
local.get $var1
global.get $"\">: \""
local.get $this
struct.get $SubPos1 $subInitializerField
global.get $"\", \""
local.get $onlyUsedInSubBody
local.set $var4
i32.const 65
local.get $var4
struct.new $BoxedInt
array.new_fixed $Array<Object?> 6
call $JSStringImpl._interpolate
call $print
drop
)
(func $"new SubPos1 (initializer)" (param $var0 (ref $_Type)) (param $onlyUsedInSubField i64) (param $onlyUsedInSubBody i64) (param $onlyUsedInSuper1 i64) (param $onlyUsedInSuper2 i64) (result i64) (result i64) (result (ref $_Type)) (result (ref $WasmListBase)) (result (ref null $#Top)) (result (ref $_Type)) (result (ref null $#Top)) (result i64)
(local $var1 (ref null $#Top))
(local $var2 i64)
(local $var3 i64)
(local $var4 (ref $_Type))
(local $var5 (ref null $#Top))
(local $var6 (ref $WasmListBase))
(local $var7 (ref $_Type))
(local $var8 i64)
call $"createEmptyList<DynamicType(dynamic)>"
local.set $var1
local.get $onlyUsedInSubField
local.set $var2
local.get $var0
local.get $onlyUsedInSuper1
local.set $var3
i32.const 65
local.get $var3
struct.new $BoxedInt
local.get $onlyUsedInSuper2
call $"new _MixinApplication0&Base&SubMixin.sub1 (initializer)"
local.set $var4
local.set $var5
local.set $var6
local.set $var7
local.set $var8
local.get $onlyUsedInSubBody
local.get $var8
local.get $var7
local.get $var6
local.get $var5
local.get $var4
local.get $var1
local.get $var2
)
(func $"new SubPos2 (constructor body)" (param $this (ref $SubPos2))
(local $var0 (ref $_Type))
(local $var1 i64)
(local $var2 (ref null $#Top))
local.get $this
struct.get $SubPos2 $field2
local.set $var0
local.get $this
struct.get $SubPos2 $onlyUsedInSubField
local.set $var1
local.get $this
struct.get $SubPos2 $onlyUsedInBaseField
local.set $var2
local.get $this
call $"new _MixinApplication1&Base&SubMixin.sub2 (constructor body)"
global.get $"\"SubPos2<\""
local.get $var0
global.get $"\">: \""
local.get $this
struct.get $SubPos2 $subInitializerField
call $JSStringImpl._interpolate4
call $print
drop
)
(func $"new SubPos2 (initializer)" (param $var0 (ref $_Type)) (param $onlyUsedInSubField i64) (param $onlyUsedInSuper1 i64) (result (ref $_Type)) (result (ref $WasmListBase)) (result (ref null $#Top)) (result (ref null $#Top)) (result i64)
(local $var1 (ref null $#Top))
(local $var2 i64)
(local $var3 i64)
call $"createEmptyList<DynamicType(dynamic)>"
local.set $var1
local.get $onlyUsedInSubField
local.set $var2
local.get $var0
local.get $onlyUsedInSuper1
local.set $var3
i32.const 65
local.get $var3
struct.new $BoxedInt
call $"new _MixinApplication1&Base&SubMixin.sub2 (initializer)"
local.get $var1
local.get $var2
)
(func $"new _MixinApplication0&Base&SubMixin.sub1 (constructor body)" (param $this (ref $_MixinApplication0&Base&SubMixin)) (param $var0 i64)
(local $preciseThis (ref $SubPos1))
(local $var1 (ref $_Type))
(local $var2 (ref null $#Top))
local.get $this
ref.cast $SubPos1
local.set $preciseThis
local.get $preciseThis
struct.get $_MixinApplication0&Base&SubMixin $field5
local.set $var1
local.get $preciseThis
struct.get $_MixinApplication0&Base&SubMixin $onlyUsedInBaseField
local.set $var2
local.get $this
local.get $var0
call $"new Base.sub1 (constructor body)"
)
(func $"new _MixinApplication0&Base&SubMixin.sub1 (initializer)" (param $var0 (ref $_Type)) (param $onlyUsedInBaseField (ref null $#Top)) (param $onlyUsedInBaseBody i64) (result i64) (result (ref $_Type)) (result (ref $WasmListBase)) (result (ref null $#Top)) (result (ref $_Type))
i32.const 9
i32.const 0
i32.const 0
i32.const 173
local.get $var0
array.new_fixed $Array<_Type> 1
struct.new $_InterfaceType
local.get $onlyUsedInBaseField
local.get $onlyUsedInBaseBody
call $"new Base.sub1 (initializer)"
local.get $var0
)
(func $"new _MixinApplication1&Base&SubMixin.named (constructor body)" (param $this (ref $Base))
(local $var0 (ref $_Type))
(local $var1 (ref null $#Top))
local.get $this
struct.get $Base $field2
local.set $var0
local.get $this
struct.get $Base $onlyUsedInBaseField
local.set $var1
local.get $this
call $"new Base.named (constructor body)"
)
(func $"new _MixinApplication1&Base&SubMixin.named (initializer)" (param $var0 (ref $_Type)) (param $onlyUsedInBaseField (ref null $#Top)) (result (ref $_Type)) (result (ref $WasmListBase)) (result (ref null $#Top))
local.get $var0
local.get $onlyUsedInBaseField
call $"new Base.named (initializer)"
)
(func $"new _MixinApplication1&Base&SubMixin.sub2 (constructor body)" (param $this (ref $Base))
(local $var0 (ref $_Type))
(local $var1 (ref null $#Top))
local.get $this
struct.get $Base $field2
local.set $var0
local.get $this
struct.get $Base $onlyUsedInBaseField
local.set $var1
local.get $this
call $"new Base.sub2 (constructor body)"
)
(func $"new _MixinApplication1&Base&SubMixin.sub2 (initializer)" (param $var0 (ref $_Type)) (param $onlyUsedInBaseField (ref null $#Top)) (result (ref $_Type)) (result (ref $WasmListBase)) (result (ref null $#Top))
local.get $var0
local.get $onlyUsedInBaseField
call $"new Base.sub2 (initializer)"
)
(func $"new _MixinApplication2&Base&SubMixin.sub1 (constructor body)" (param $this (ref $_MixinApplication2&Base&SubMixin)) (param $var0 i64)
(local $preciseThis (ref $SubOptionalPos))
(local $var1 (ref $_Type))
(local $var2 (ref null $#Top))
local.get $this
ref.cast $SubOptionalPos
local.set $preciseThis
local.get $preciseThis
struct.get $_MixinApplication2&Base&SubMixin $field5
local.set $var1
local.get $preciseThis
struct.get $_MixinApplication2&Base&SubMixin $onlyUsedInBaseField
local.set $var2
local.get $this
local.get $var0
call $"new Base.sub1 (constructor body)"
)
(func $"new _MixinApplication2&Base&SubMixin.sub1 (initializer)" (param $var0 (ref $_Type)) (param $onlyUsedInBaseField (ref null $#Top)) (param $onlyUsedInBaseBody i64) (result i64) (result (ref $_Type)) (result (ref $WasmListBase)) (result (ref null $#Top)) (result (ref $_Type))
i32.const 9
i32.const 0
i32.const 0
i32.const 177
local.get $var0
array.new_fixed $Array<_Type> 1
struct.new $_InterfaceType
local.get $onlyUsedInBaseField
local.get $onlyUsedInBaseBody
call $"new Base.sub1 (initializer)"
local.get $var0
)
(func $"new _MixinApplication3&Base&SubMixin.named (constructor body)" (param $this (ref $_MixinApplication3&Base&SubMixin))
(local $preciseThis (ref $SubOptionalNamed))
(local $var0 (ref $_Type))
(local $var1 (ref null $#Top))
local.get $this
ref.cast $SubOptionalNamed
local.set $preciseThis
local.get $preciseThis
struct.get $_MixinApplication3&Base&SubMixin $field5
local.set $var0
local.get $preciseThis
struct.get $_MixinApplication3&Base&SubMixin $onlyUsedInBaseField
local.set $var1
local.get $this
call $"new Base.named (constructor body)"
)
(func $"new _MixinApplication3&Base&SubMixin.named (initializer)" (param $var0 (ref $_Type)) (param $onlyUsedInBaseField (ref null $#Top)) (result (ref $_Type)) (result (ref $WasmListBase)) (result (ref null $#Top)) (result (ref $_Type))
i32.const 9
i32.const 0
i32.const 0
i32.const 166
local.get $var0
array.new_fixed $Array<_Type> 1
struct.new $_InterfaceType
local.get $onlyUsedInBaseField
call $"new Base.named (initializer)"
local.get $var0
)
(func $JSStringImpl._interpolate (param $values (ref $Array<Object?>)) (result (ref $JSExternWrapper)) <...>)
(func $JSStringImpl._interpolate4 (param $value1 (ref null $#Top)) (param $value2 (ref null $#Top)) (param $value3 (ref null $#Top)) (param $value4 (ref null $#Top)) (result (ref $JSExternWrapper)) <...>)
(func $SubNamed (param $var0 (ref $_Type)) (param $onlyUsedInSubField i64) (param $onlyUsedInSuper i64) (result (ref $SubNamed))
(local $var1 (ref $SubNamed))
i32.const 110
i32.const 0
local.get $var0
local.get $onlyUsedInSubField
local.get $onlyUsedInSuper
call $"new SubNamed (initializer)"
struct.new $SubNamed
local.tee $var1
call $"new SubNamed (constructor body)"
local.get $var1
)
(func $SubNamed._typeArguments (param $var0 (ref $#Top)) (result (ref $Array<_Type>))
(local $this (ref $SubNamed))
local.get $var0
ref.cast $SubNamed
local.set $this
local.get $this
struct.get $SubNamed $field2
array.new_fixed $Array<_Type> 1
)
(func $SubOptionalNamed (param $var0 (ref $_Type)) (param $onlyUsedInSubField (ref null $BoxedInt)) (param $onlyUsedInSuper (ref null $BoxedInt)) (result (ref $SubOptionalNamed))
(local $var1 (ref $SubOptionalNamed))
i32.const 112
i32.const 0
local.get $var0
local.get $onlyUsedInSubField
local.get $onlyUsedInSuper
call $"new SubOptionalNamed (initializer)"
struct.new $SubOptionalNamed
local.tee $var1
call $"new SubOptionalNamed (constructor body)"
local.get $var1
)
(func $SubOptionalNamed._typeArguments (param $var0 (ref $#Top)) (result (ref $Array<_Type>))
(local $this (ref $SubOptionalNamed))
local.get $var0
ref.cast $SubOptionalNamed
local.set $this
local.get $this
struct.get $SubOptionalNamed $field5
array.new_fixed $Array<_Type> 1
)
(func $SubOptionalPos (param $var0 (ref $_Type)) (param $onlyUsedInSubField (ref null $BoxedInt)) (param $onlyUsedInSubBody (ref null $BoxedInt)) (param $onlyUsedInSuper1 (ref null $BoxedInt)) (param $onlyUsedInSuper2 (ref null $BoxedInt)) (result (ref $SubOptionalPos))
(local $var1 i64)
(local $var2 (ref null $#Top))
(local $var3 (ref $_Type))
(local $var4 (ref null $#Top))
(local $var5 (ref $WasmListBase))
(local $var6 (ref $_Type))
(local $var7 i64)
(local $var8 (ref null $BoxedInt))
(local $var9 (ref $SubOptionalPos))
local.get $var0
local.get $onlyUsedInSubField
local.get $onlyUsedInSubBody
local.get $onlyUsedInSuper1
local.get $onlyUsedInSuper2
call $"new SubOptionalPos (initializer)"
local.set $var1
local.set $var2
local.set $var3
local.set $var4
local.set $var5
local.set $var6
local.set $var7
local.set $var8
i32.const 111
i32.const 0
local.get $var6
local.get $var5
local.get $var4
local.get $var3
local.get $var2
local.get $var1
struct.new $SubOptionalPos
local.tee $var9
local.get $var8
local.get $var7
call $"new SubOptionalPos (constructor body)"
local.get $var9
)
(func $SubOptionalPos._typeArguments (param $var0 (ref $#Top)) (result (ref $Array<_Type>))
(local $this (ref $SubOptionalPos))
local.get $var0
ref.cast $SubOptionalPos
local.set $this
local.get $this
struct.get $SubOptionalPos $field5
array.new_fixed $Array<_Type> 1
)
(func $SubPos1 (param $var0 (ref $_Type)) (param $onlyUsedInSubField i64) (param $onlyUsedInSubBody i64) (param $onlyUsedInSuper1 i64) (param $onlyUsedInSuper2 i64) (result (ref $SubPos1))
(local $var1 i64)
(local $var2 (ref null $#Top))
(local $var3 (ref $_Type))
(local $var4 (ref null $#Top))
(local $var5 (ref $WasmListBase))
(local $var6 (ref $_Type))
(local $var7 i64)
(local $var8 i64)
(local $var9 (ref $SubPos1))
local.get $var0
local.get $onlyUsedInSubField
local.get $onlyUsedInSubBody
local.get $onlyUsedInSuper1
local.get $onlyUsedInSuper2
call $"new SubPos1 (initializer)"
local.set $var1
local.set $var2
local.set $var3
local.set $var4
local.set $var5
local.set $var6
local.set $var7
local.set $var8
i32.const 108
i32.const 0
local.get $var6
local.get $var5
local.get $var4
local.get $var3
local.get $var2
local.get $var1
struct.new $SubPos1
local.tee $var9
local.get $var8
local.get $var7
call $"new SubPos1 (constructor body)"
local.get $var9
)
(func $SubPos1._typeArguments (param $var0 (ref $#Top)) (result (ref $Array<_Type>))
(local $this (ref $SubPos1))
local.get $var0
ref.cast $SubPos1
local.set $this
local.get $this
struct.get $SubPos1 $field5
array.new_fixed $Array<_Type> 1
)
(func $SubPos2 (param $var0 (ref $_Type)) (param $onlyUsedInSubField i64) (param $onlyUsedInSuper1 i64) (result (ref $SubPos2))
(local $var1 (ref $SubPos2))
i32.const 109
i32.const 0
local.get $var0
local.get $onlyUsedInSubField
local.get $onlyUsedInSuper1
call $"new SubPos2 (initializer)"
struct.new $SubPos2
local.tee $var1
call $"new SubPos2 (constructor body)"
local.get $var1
)
(func $SubPos2._typeArguments (param $var0 (ref $#Top)) (result (ref $Array<_Type>))
(local $this (ref $SubPos2))
local.get $var0
ref.cast $SubPos2
local.set $this
local.get $this
struct.get $SubPos2 $field2
array.new_fixed $Array<_Type> 1
)
(func $print (param $object (ref null $#Top)) (result (ref null $#Top)) <...>)
)
+313
View File
@@ -0,0 +1,313 @@
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:expect/expect.dart';
final int opaqueOne = int.parse('1');
void main() {
final d = Derived<String>("hello", 42 * opaqueOne);
Expect.equals("hello", d.t);
Expect.equals(42, d.u);
Expect.equals("hello", d.v);
Expect.isTrue(d.t is String);
Expect.isTrue(d.u is int);
{
final c = InitCapture(10 * opaqueOne);
Expect.equals(10, c.getP());
}
{
final c = BodyCapture(20 * opaqueOne);
Expect.equals(20, c.getP());
}
{
final c = BodyCaptureType<num>(opaqueOne);
Expect.equals(1, c.p);
Expect.equals(num, c.getT());
}
{
final c = BodyCaptureParamField(opaqueOne);
Expect.equals(1, c.p);
Expect.equals(1, c.getP());
}
{
final c = InitCaptureModify(30 * opaqueOne);
Expect.equals(32, c.pAfter);
Expect.equals(33, c.getP());
Expect.equals(34, c.getP());
}
{
final c = BodyCaptureModify(40 * opaqueOne);
Expect.equals(50, c.pInBody);
Expect.equals(51, c.getP());
Expect.equals(52, c.getP());
}
{
final c = InitStoreModify(60 * opaqueOne);
Expect.equals(60, c.field1);
Expect.equals(61, c.field2);
}
{
final c = BodyModify(70 * opaqueOne);
Expect.equals(70, c.field);
Expect.equals(170, c.finalP);
}
{
final c = SuperDerived(90 * opaqueOne);
Expect.equals(90, c.y);
Expect.equals(91, c.x);
}
{
final c = ModifyInInitAndUseInBody(100 * opaqueOne);
Expect.equals(101, c.inInit);
Expect.equals(101, c.inBody);
}
{
final c = CaptureInInitModifyInBody(200 * opaqueOne);
Expect.equals(210, c.inBody);
Expect.equals(210, c.closure());
}
{
final c = DoubleCapture(300 * opaqueOne);
Expect.equals(310, c.initClosure());
Expect.equals(310, c.bodyClosure());
}
{
final c1 = MixedParams(opaqueOne);
Expect.equals(1, c1.a);
Expect.equals(2, c1.b);
Expect.equals(3, c1.c);
final c2 = MixedParams(opaqueOne, 10 * opaqueOne);
Expect.equals(1, c2.a);
Expect.equals(10, c2.b);
Expect.equals(3, c2.c);
}
{
final c1 = NamedParams(a: 5 * opaqueOne);
Expect.equals(5, c1.a);
Expect.equals(20, c1.b);
final c2 = NamedParams(b: 30 * opaqueOne, a: 7 * opaqueOne);
Expect.equals(7, c2.a);
Expect.equals(30, c2.b);
}
{
final c = RedirectDerived(10 * opaqueOne, 20 * opaqueOne);
Expect.equals(30, c.c);
Expect.equals(10, c.a);
Expect.equals(11, c.b);
}
{
final c = FactoryClass(50 * opaqueOne);
Expect.equals(150, c.x);
}
{
final c = MultiUse(400 * opaqueOne);
Expect.equals(400, c.a);
Expect.equals(401, c.b);
Expect.equals(401, c.c);
}
{
final c = CaptureAndModifyInInit(500 * opaqueOne);
Expect.equals(505, c.field);
Expect.equals(505, c.getP());
}
}
// Generic class inheritance with partial type arguments
class Base<T, U> {
T t;
U u;
Base(this.t, this.u);
}
class Derived<V> extends Base<V, int> {
V v;
Derived(V v_in, int i) : v = v_in, super(v_in, i);
}
// Initializer captures parameter (via closure)
class InitCapture {
final int Function() getP;
InitCapture(int p) : getP = (() => p);
}
// Body captures parameter (via closure)
class BodyCapture {
late final int Function() getP;
BodyCapture(int p) {
getP = () => p;
}
}
class BodyCaptureParamField {
final int p;
late int Function() getP;
BodyCaptureParamField(this.p) {
getP = (() => p);
}
}
class BodyCaptureType<T> {
final int p;
late Type Function() getT;
BodyCaptureType(this.p) {
getT = (() => T);
}
}
// Initializer captures and modifies parameter
class InitCaptureModify {
final int Function() getP;
final int pAfter;
InitCaptureModify(int p)
: getP = (() {
p = p + 1;
return p;
}),
pAfter = (p = p + 2);
}
// Body captures and modifies parameter
class BodyCaptureModify {
late final int Function() getP;
int pInBody = 0;
BodyCaptureModify(int p) {
p = p + 10;
pInBody = p;
getP = () {
p = p + 1;
return p;
};
}
}
// Initializer stores parameter in field but also modifies it
class InitStoreModify {
int field1;
int field2;
InitStoreModify(int p) : field1 = p, field2 = (p = p + 1);
}
// Constructor body that modifies parameter
class BodyModify {
int field;
int finalP = 0;
BodyModify(int p) : field = p {
p = p + 100;
finalP = p;
}
}
// Super call with modified parameters
class SuperBase {
int x;
SuperBase(this.x);
}
class SuperDerived extends SuperBase {
int y;
SuperDerived(int p) : y = p, super(p = p + 1);
}
// Parameter modified in initializer and then used in body
class ModifyInInitAndUseInBody {
int inInit;
int inBody = 0;
ModifyInInitAndUseInBody(int p) : inInit = (p = p + 1) {
inBody = p;
}
}
// Closure created in initializer captures p, and p is modified in body
class CaptureInInitModifyInBody {
int Function() closure;
int inBody = 0;
CaptureInInitModifyInBody(int p) : closure = (() => p) {
p = p + 10;
inBody = p;
}
}
// Double capture (both in initializer and body)
class DoubleCapture {
int Function() initClosure;
late final int Function() bodyClosure;
DoubleCapture(int p) : initClosure = (() => p) {
p = p + 5;
bodyClosure = () => p;
p = p + 5;
}
}
// Mixed parameters (named, optional)
class MixedParams {
int a;
int b;
int c;
MixedParams(this.a, [this.b = 2, this.c = 3]);
}
class NamedParams {
int a;
int b;
NamedParams({required this.a, this.b = 20});
}
// Redirecting constructors
class RedirectBase {
int a;
int b;
RedirectBase(this.a, this.b);
RedirectBase.named(int x) : this(x, x + 1);
}
class RedirectDerived extends RedirectBase {
int c;
RedirectDerived(int x, int y) : c = x + y, super.named(x);
}
// Factory constructors
class FactoryClass {
int x;
FactoryClass._(this.x);
factory FactoryClass(int x) => FactoryClass._(x + 100);
}
// Parameter used in multiple initializers and body
class MultiUse {
int a;
int b;
int c = 0;
MultiUse(int p) : a = p, b = (p = p + 1) {
c = p;
}
}
// Parameter captured in initializer and modified in initializer
class CaptureAndModifyInInit {
int Function() getP;
int field;
CaptureAndModifyInInit(int p) : getP = (() => p), field = (p = p + 5);
}