[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:
committed by
dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent
8664b0f98b
commit
12174830ec
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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)) <...>)
|
||||
)
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user