Files
sdk/pkg/dart2wasm/lib/code_generator.dart
T
Martin Kustermann 407afe84ca [dart2wasm] Remove dynamic closure call vtable entry in most cases
This reduces acx main: module -0.35%,  global section: -2.3%

Previous change in [0] made dynamic call sites have per-caller-shape
dynamic forwarders.

In this change we utilize this: Since we know the dynamic caller shape
we can make the dynamic forwarder call via a closure representation that
matches this caller shape (modulo the type arguments - as a dynamic call
without type arguments may get default type arguments of unknown count).

=> This means we no longer have to pass types, positionals and named
   arguments as wasm arrays to dynamic call forwarder. Instead we can
   pass the arguments unpacked to the vtable.

Additionally we change `Function.apply`: If we know there's no
`Function.apply` usage with named arguments, we can enumerate all
possible closure representations and call the corresponding vtable
entry. We use 2 `br_table` instructions for this:

  * branch on number of type parameters
  * branch on number of positional parameters

Together these two changes means that neither dynamic forwarders nor
`Function.apply` will use the dynamic call entry.

=> We remove the dynamic call entry from the vtable construction

The only exception to the above are:

* If dynamic submodules is enabled we don't compute closed world
  information about closures and always call via dynamic call entry

* If `Function.apply` is used with named arguments then call via dynamic
  call entry (and make vtables have it).

[0] https://dart-review.googlesource.com/c/sdk/+/461720


Issue https://github.com/dart-lang/sdk/issues/60458

Change-Id: I26e6a2a48ee37616061d701dc4d4327c2e109e82
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/462060
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-11-26 01:02:31 -08:00

5216 lines
174 KiB
Dart

// Copyright (c) 2022, 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 'dart:collection' show LinkedHashMap;
import 'package:kernel/ast.dart';
import 'package:kernel/type_environment.dart';
import 'package:wasm_builder/wasm_builder.dart' as w;
import 'async.dart';
import 'class_info.dart';
import 'closures.dart';
import 'dispatch_table.dart';
import 'dynamic_forwarders.dart';
import 'intrinsics.dart';
import 'param_info.dart';
import 'records.dart';
import 'reference_extensions.dart';
import 'sync_star.dart';
import 'translator.dart';
import 'types.dart';
abstract class CodeGenerator {
// The two parameters here are used for inlining:
//
// If the user
//
// * inlines the code, it will provide locals and a return label
//
// * doesn't inline (i.e. makes new function with this code) it will provide
// the parameters of the function and no return label.
//
void generate(
w.InstructionsBuilder b, List<w.Local> paramLocals, w.Label? returnLabel);
}
/// Main code generator for member bodies.
///
/// The [generate] method first collects all local functions and function
/// expressions in the body and then generates code for the body. Code for the
/// local functions and function expressions must be generated separately by
/// calling the [generateLambda] method on all lambdas in [closures].
///
/// A new [CodeGenerator] object must be created for each new member or lambda.
///
/// Every visitor method for an expression takes in the Wasm type that it is
/// expected to leave on the stack (or the special [voidMarker] to indicate that
/// it should leave nothing). It returns what it actually left on the stack. The
/// code generation for every expression or subexpression is done via the
/// [translateExpression] method, which emits appropriate conversion code if the
/// produced type is not a subtype of the expected type.
abstract class AstCodeGenerator
extends ExpressionVisitor1<w.ValueType, w.ValueType>
with
ExpressionVisitor1DefaultMixin<w.ValueType, w.ValueType>,
ExpressionVisitor1ExperimentExclusionMixin<w.ValueType, w.ValueType>,
StatementVisitorExperimentExclusionMixin<void>
implements InitializerVisitor<void>, StatementVisitor<void>, CodeGenerator {
final Translator translator;
final w.FunctionType functionType;
final Member enclosingMember;
// To be initialized in `generate()`
late w.InstructionsBuilder b;
late final List<w.Local> paramLocals;
late final w.Label? returnLabel;
late final Intrinsifier intrinsifier = Intrinsifier(this);
late final StaticTypeContext typeContext =
StaticTypeContext(enclosingMember, translator.typeEnvironment);
late final Closures closures;
bool exceptionLocationPrinted = false;
final Map<VariableDeclaration, w.Local> locals = {};
w.Local? thisLocal;
w.Local? preciseThisLocal;
w.Local? returnValueLocal;
final Map<TypeParameter, w.Local> typeLocals = {};
// Maps a classes' fields to corresponding locals so that we can update the
// local directly if a field has both a default value and a FieldInitializer.
final Map<Field, w.Local> fieldLocals = {};
/// Finalizers to run on `return`.
final List<TryBlockFinalizer> returnFinalizers = [];
/// Finalizers to run on a `break`. `breakFinalizers[L].last` (which should
/// always be present) is the `br` target for the label `L` that will run the
/// finalizers, or break out of the loop.
final LinkedHashMap<LabeledStatement, List<w.Label>> breakFinalizers =
LinkedHashMap();
final List<({w.Local exceptionLocal, w.Local stackTraceLocal})>
tryBlockLocals = [];
final Map<SwitchCase, w.Label> switchLabels = {};
/// Maps a switch statement to the information used when doing a backward
/// jump to one of the cases in the switch statement
final Map<SwitchStatement, SwitchBackwardJumpInfo> switchBackwardJumpInfos =
{};
/// Create a code generator for a member or one of its lambdas.
AstCodeGenerator(this.translator, this.functionType, this.enclosingMember);
List<w.ValueType> get outputs => functionType.outputs;
w.ValueType get returnType => translator.outputOrVoid(outputs);
TranslatorOptions get options => translator.options;
w.ValueType get voidMarker => translator.voidMarker;
Types get types => translator.types;
w.ValueType translateType(DartType type) => translator.translateType(type);
w.Local addLocal(w.ValueType type, {String? name}) =>
b.addLocal(type, name: name);
DartType dartTypeOf(Expression exp) {
if (exp is ConstantExpression) {
// For constant expressions `getStaticType` returns often `DynamicType`
// instead of a more precise type. See http://dartbug.com/60368
return exp.constant.getType(typeContext);
}
return exp.getStaticType(typeContext);
}
void unimplemented(
TreeNode node, Object message, List<w.ValueType> expectedTypes) {
final text = "Not implemented: $message at ${node.location}";
print(text);
b.comment(text);
b.block(const [], expectedTypes);
b.unreachable();
b.end();
}
@override
w.ValueType defaultExpression(Expression node, w.ValueType expectedType) {
unimplemented(
node, node.runtimeType, [if (expectedType != voidMarker) expectedType]);
return expectedType;
}
Source? _sourceMapSource;
int _sourceMapFileOffset = TreeNode.noOffset;
/// Update the [Source] for the AST nodes being compiled.
///
/// The [Source] is used to resolve [TreeNode.fileOffset]s to file URI, line,
/// and column numbers, to be able to generate source mappings, in
/// [setSourceMapFileOffset].
///
/// Setting this `null` disables source mapping for the instructions being
/// generated.
///
/// This should be called before [setSourceMapFileOffset] as the file offset
/// passed to that function is resolved using the [Source].
///
/// Returns the old [Source], which can be used to restore the source mapping
/// after visiting a sub-tree.
Source? setSourceMapSource(Source? source) {
final old = _sourceMapSource;
_sourceMapSource = source;
return old;
}
/// Update the source location of the AST nodes being compiled in the source
/// map.
///
/// When the offset is [TreeNode.noOffset], this disables mapping the
/// generated instructions.
///
/// Returns the old file offset, which can be used to restore the source
/// mapping after vising a sub-tree.
int setSourceMapFileOffset(int fileOffset) {
if (!b.recordSourceMaps) {
final old = _sourceMapFileOffset;
_sourceMapFileOffset = fileOffset;
return old;
}
if (fileOffset == TreeNode.noOffset) {
b.stopSourceMapping();
final old = _sourceMapFileOffset;
_sourceMapFileOffset = fileOffset;
return old;
}
final source = _sourceMapSource!;
final fileUri = source.fileUri!;
final location = source.getLocation(fileUri, fileOffset);
final old = _sourceMapFileOffset;
_sourceMapFileOffset = fileOffset;
b.startSourceMapping(fileUri, location.line - 1, location.column - 1,
enclosingMember.name.text);
return old;
}
/// Calls [setSourceMapSource] and [setSourceMapFileOffset].
(Source?, int) setSourceMapSourceAndFileOffset(
Source? source, int fileOffset) {
final oldSource = setSourceMapSource(source);
final oldFileOffset = setSourceMapFileOffset(fileOffset);
return (oldSource, oldFileOffset);
}
/// Generate code while preventing recursive inlining.
@override
void generate(w.InstructionsBuilder b, List<w.Local> paramLocals,
w.Label? returnLabel) {
this.b = b;
this.paramLocals = paramLocals;
this.returnLabel = returnLabel;
translator.membersBeingGenerated.add(enclosingMember);
generateInternal();
translator.membersBeingGenerated.remove(enclosingMember);
}
// Generate the body.
void generateInternal();
void _setupLocalParameters(Member member, ParameterInfo paramInfo,
int parameterOffset, int implicitParams,
{bool isForwarder = false, bool canSafelyOmitImplicitChecks = false}) {
final memberFunction = member.function!;
final (
:typeParameters,
:typeParametersToTypeCheck,
:positional,
:positionalToTypeCheck,
:named,
:namedToTypeCheck
) = translator.getParametersToCheck(member);
for (int i = 0; i < typeParameters.length; i++) {
final typeParameter = typeParameters[i];
typeLocals[typeParameter] = paramLocals[parameterOffset + i];
}
final mayNeedToCheckTypes = translator.needToCheckTypesFor(member);
if (mayNeedToCheckTypes) {
for (int i = 0; i < typeParametersToTypeCheck.length; i++) {
final typeParameter = typeParametersToTypeCheck[i];
if (translator.needToCheckTypeParameter(typeParameter)) {
_generateTypeArgumentBoundCheck(typeParameter.name!,
typeLocals[typeParameter]!, typeParameter.bound);
}
}
}
void setupParamLocal(
DartType variableTypeToCheck,
VariableDeclaration variable,
int index,
Constant? defaultValue,
bool isRequired) {
final localIndex = implicitParams + index;
w.Local local = paramLocals[localIndex];
final variableName = variable.name;
if (variableName != null && variableName.isNotEmpty) {
b.localNames[local.index] = variableName;
}
if (defaultValue == ParameterInfo.defaultValueSentinel) {
// The default value for this parameter differs between implementations
// within the same selector. This means that callers will pass the
// default value sentinel to indicate that the parameter is not given.
// The callee must check for the sentinel value and substitute the
// actual default value.
//
// NOTE: The default sentinel is a dummy instance of the wasm type of
// the parameter in the function signature. This type may be a super
// type of the kind of arguments we actually see in practice.
// (e.g. we may know that only nullable one byte strings can flow into
// the argument, but the wasm type may be of object type). So we first
// have to handle sentinel before we can downcast the value.
b.local_get(local);
translator.constants.instantiateConstant(
b, ParameterInfo.defaultValueSentinel, local.type);
b.ref_eq();
b.if_();
translateExpression(variable.initializer!, local.type);
b.local_set(local);
b.end();
}
if (!isForwarder) {
// TFA may have inferred a very precise type for the incoming arguments,
// but the wasm function parameter type may not reflect this (e.g. due
// to upper-bounding in dispatch table row building)
// => This means, we may need to do a downcast here.
final incomingArgumentType =
translator.translateTypeOfParameter(variable, isRequired);
if (!local.type.isSubtypeOf(incomingArgumentType)) {
final newLocal = addLocal(incomingArgumentType);
b.local_get(local);
translator.convertType(b, local.type, newLocal.type);
b.local_set(newLocal);
local = newLocal;
}
}
if (mayNeedToCheckTypes) {
if (translator.needToCheckParameter(variable,
uncheckedEntry: canSafelyOmitImplicitChecks)) {
final boxedType = variable.type.isPotentiallyNullable
? translator.topType
: translator.topTypeNonNullable;
w.Local operand = local;
if (!operand.type.isSubtypeOf(boxedType)) {
final boxedOperand = addLocal(boxedType);
b.local_get(operand);
translator.convertType(b, operand.type, boxedOperand.type);
b.local_set(boxedOperand);
operand = boxedOperand;
}
b.local_get(operand);
_generateArgumentTypeCheck(
variable.name!,
operand.type as w.RefType,
variableTypeToCheck,
);
}
}
if (!isForwarder && !variable.isFinal) {
// We now have a precise local that can contain the values passed by
// callers, but the body may assign less precise types to this variable,
// so we may introduce another local variable that is less precise.
// => Binaryen will simplify the above downcast and this upcast.
final variableType = translator.translateTypeOfLocalVariable(variable);
if (!variableType.isSubtypeOf(local.type)) {
w.Local newLocal = addLocal(variableType);
b.local_get(local);
translator.convertType(b, local.type, newLocal.type);
b.local_set(newLocal);
local = newLocal;
}
}
locals[variable] = local;
}
for (int i = 0; i < positional.length; i++) {
final bool isRequired = i < memberFunction.requiredParameterCount;
final typeToCheck = positionalToTypeCheck[i].type;
setupParamLocal(
typeToCheck, positional[i], i, paramInfo.positional[i], isRequired);
}
for (var param in named) {
final typeToCheck = identical(named, namedToTypeCheck)
? param.type
: namedToTypeCheck.singleWhere((n) => n.name == param.name).type;
setupParamLocal(typeToCheck, param, paramInfo.nameIndex[param.name]!,
paramInfo.named[param.name], param.isRequired);
}
// For all parameters whose Wasm type has been forced to `externref` due to
// this function being an export, internalize and cast the parameter to the
// canonical representation type for its Dart type.
locals.forEach((parameter, local) {
DartType parameterType = parameter.type;
if (local.type == w.RefType.extern(nullable: true) &&
!(parameterType is InterfaceType &&
parameterType.classNode == translator.wasmExternRefClass)) {
w.Local newLocal =
addLocal(translateType(parameterType), name: parameter.name);
b.local_get(local);
translator.convertType(b, local.type, newLocal.type);
b.local_set(newLocal);
locals[parameter] = newLocal;
}
});
}
void setupParameters(Reference reference,
{bool isForwarder = false, bool canSafelyOmitImplicitChecks = false}) {
Member member = reference.asMember;
ParameterInfo paramInfo = translator.paramInfoForDirectCall(reference);
int parameterOffset = _initializeThis(reference);
int implicitParams = parameterOffset + paramInfo.typeParamCount;
_setupLocalParameters(member, paramInfo, parameterOffset, implicitParams,
isForwarder: isForwarder,
canSafelyOmitImplicitChecks: canSafelyOmitImplicitChecks);
}
void setupParametersForNormalEntry(Member member) {
setupParameters(member.reference,
canSafelyOmitImplicitChecks: !translator.needToCheckTypesFor(member));
}
void setupParametersForCheckedEntry(Member member) {
assert(member.isInstanceMember);
assert(translator.needToCheckTypesFor(member));
setupParameters(member.checkedEntryReference,
canSafelyOmitImplicitChecks: false);
}
void setupParametersForUncheckedEntry(Member member) {
assert(member.isInstanceMember);
assert(translator.needToCheckTypesFor(member));
setupParameters(member.uncheckedEntryReference,
canSafelyOmitImplicitChecks: true);
}
void setupContexts(Member member) {
allocateContext(member.function!);
captureParameters();
}
void _setupDefaultFieldValues(ClassInfo info) {
fieldLocals.clear();
for (Field field in info.cls!.fields) {
if (field.isInstanceMember && field.initializer != null) {
final source = field.enclosingComponent!.uriToSource[field.fileUri]!;
final (oldSource, oldFileOffset) =
setSourceMapSourceAndFileOffset(source, field.fileOffset);
int fieldIndex = translator.fieldIndex[field]!;
w.Local local = addLocal(info.struct.fields[fieldIndex].type.unpacked);
translateExpression(
field.initializer!, info.struct.fields[fieldIndex].type.unpacked);
b.local_set(local);
fieldLocals[field] = local;
setSourceMapSourceAndFileOffset(oldSource, oldFileOffset);
}
}
}
List<w.Local> _getConstructorArgumentLocals(Reference target,
[reverse = false]) {
Constructor member = target.asConstructor;
List<w.Local> constructorArgs = [];
List<TypeParameter> typeParameters = member.enclosingClass.typeParameters;
for (int i = 0; i < typeParameters.length; i++) {
constructorArgs.add(typeLocals[typeParameters[i]]!);
}
List<VariableDeclaration> positional = member.function.positionalParameters;
for (VariableDeclaration pos in positional) {
constructorArgs.add(locals[pos]!);
}
Map<String, w.Local> namedArgs = {};
List<VariableDeclaration> named = member.function.namedParameters;
for (VariableDeclaration param in named) {
namedArgs[param.name!] = locals[param]!;
}
final ParameterInfo paramInfo = translator.paramInfoForDirectCall(target);
for (String name in paramInfo.names) {
w.Local namedLocal = namedArgs[name]!;
constructorArgs.add(namedLocal);
}
if (reverse) {
return constructorArgs.reversed.toList();
}
return constructorArgs;
}
void setupLambdaParametersAndContexts(Lambda lambda) {
FunctionNode functionNode = lambda.functionNode;
_initializeContextLocals(functionNode);
int paramIndex = 1;
for (TypeParameter typeParam in functionNode.typeParameters) {
typeLocals[typeParam] = paramLocals[paramIndex++];
}
for (VariableDeclaration param in functionNode.positionalParameters) {
locals[param] = paramLocals[paramIndex++];
}
for (VariableDeclaration param in functionNode.namedParameters) {
locals[param] = paramLocals[paramIndex++];
}
allocateContext(functionNode);
captureParameters();
}
/// Initialize locals containing `this` in constructors and instance members.
/// Returns the number of parameter locals taken up by the receiver parameter,
/// i.e. the parameter offset for the first type parameter (or the first
/// parameter if there are no type parameters).
int _initializeThis(Reference reference) {
Member member = reference.asMember;
final hasThis =
member.isInstanceMember || reference.isConstructorBodyReference;
if (hasThis) {
thisLocal = paramLocals[0];
b.localNames[thisLocal!.index] = "this";
final preciseThisType = translator.preciseThisFor(member);
if (translator.needsConversion(thisLocal!.type, preciseThisType)) {
preciseThisLocal = addLocal(preciseThisType, name: "preciseThis");
b.local_get(thisLocal!);
translator.convertType(b, thisLocal!.type, preciseThisType);
b.local_set(preciseThisLocal!);
} else {
preciseThisLocal = thisLocal!;
}
return 1;
}
return 0;
}
/// Initialize locals pointing to every context in the context chain of a
/// closure, plus the locals containing `this` if `this` is captured by the
/// closure.
void _initializeContextLocals(TreeNode node, {int contextParamIndex = 0}) {
Context? context;
if (node is Constructor) {
// The context parameter is for the constructor context.
context = closures.contexts[node];
} else {
assert(node is FunctionNode);
// The context parameter is for the parent context.
context = closures.contexts[node]?.parent;
}
if (context != null) {
assert(!context.isEmpty);
w.RefType contextType = w.RefType.def(context.struct, nullable: false);
b.local_get(paramLocals[contextParamIndex]);
b.ref_cast(contextType);
while (true) {
w.Local contextLocal = addLocal(contextType);
context!.currentLocal = contextLocal;
if (context.parent != null || context.containsThis) {
b.local_tee(contextLocal);
} else {
b.local_set(contextLocal);
}
if (context.containsThis) {
thisLocal = addLocal(
context.struct.fields[context.thisFieldIndex].type.unpacked
.withNullability(false),
name: "this");
preciseThisLocal = thisLocal;
b.struct_get(context.struct, context.thisFieldIndex);
b.ref_as_non_null();
b.local_set(thisLocal!);
if (context.parent != null) {
b.local_get(contextLocal);
}
}
if (context.parent == null) break;
b.struct_get(context.struct, context.parentFieldIndex);
b.ref_as_non_null();
context = context.parent!;
contextType = w.RefType.def(context.struct, nullable: false);
}
}
}
void _implicitReturn() {
if (outputs.isNotEmpty) {
w.ValueType returnType = outputs.single;
if (returnType is w.RefType && returnType.nullable) {
// Dart body may have an implicit return null.
b.ref_null(returnType.heapType.bottomType);
} else {
b.comment("Unreachable implicit return");
b.unreachable();
}
}
}
void allocateContext(TreeNode node) {
Context? context = closures.contexts[node];
if (context == null || context.isEmpty) return;
w.Local contextLocal =
addLocal(w.RefType.def(context.struct, nullable: true));
context.currentLocal = contextLocal;
b.struct_new_default(context.struct);
b.local_set(contextLocal);
if (context.containsThis) {
b.local_get(contextLocal);
b.local_get(preciseThisLocal!);
b.struct_set(context.struct, context.thisFieldIndex);
}
if (context.parent != null) {
w.Local parentLocal = context.parent!.currentLocal;
b.local_get(contextLocal);
b.local_get(parentLocal);
b.struct_set(context.struct, context.parentFieldIndex);
}
}
void captureParameters() {
locals.forEach((variable, local) {
Capture? capture = closures.captures[variable];
if (capture != null) {
b.local_get(capture.context.currentLocal);
b.local_get(local);
translator.convertType(b, local.type, capture.type);
b.struct_set(capture.context.struct, capture.fieldIndex);
}
});
typeLocals.forEach((parameter, local) {
Capture? capture = closures.captures[parameter];
if (capture != null) {
b.local_get(capture.context.currentLocal);
b.local_get(local);
translator.convertType(b, local.type, capture.type);
b.struct_set(capture.context.struct, capture.fieldIndex);
}
});
}
/// Helper function to throw a Wasm ref downcast error.
void throwWasmRefError(String expected) {
_emitString(expected);
call(translator.stackTraceCurrent.reference);
call(translator.throwWasmRefError.reference);
b.unreachable();
}
/// Generates code for an expression plus conversion code to convert the
/// result to the expected type if needed. All expression code generation goes
/// through this method.
w.ValueType translateExpression(Expression node, w.ValueType expectedType) {
var sourceUpdated = false;
Source? oldSource;
if (node is FileUriNode) {
final source =
node.enclosingComponent!.uriToSource[(node as FileUriNode).fileUri]!;
oldSource = setSourceMapSource(source);
sourceUpdated = true;
}
final oldFileOffset = setSourceMapFileOffset(node.fileOffset);
try {
w.ValueType resultType = node.accept1(this, expectedType);
translator.convertType(b, resultType, expectedType);
return expectedType;
} catch (_) {
_printLocation(node);
rethrow;
} finally {
if (sourceUpdated) {
setSourceMapSource(oldSource);
}
setSourceMapFileOffset(oldFileOffset);
}
}
void translateStatement(Statement node) {
final oldFileOffset = setSourceMapFileOffset(node.fileOffset);
try {
node.accept(this);
} catch (_) {
_printLocation(node);
rethrow;
} finally {
setSourceMapFileOffset(oldFileOffset);
}
}
void visitInitializer(Initializer node) {
try {
node.accept(this);
} catch (_) {
_printLocation(node);
rethrow;
}
}
void _printLocation(TreeNode node) {
if (!exceptionLocationPrinted) {
print("Exception in ${node.runtimeType} at ${node.location}");
exceptionLocationPrinted = true;
}
}
List<w.ValueType> call(Reference target) {
return translator.callReference(target, b);
}
@override
void visitInvalidInitializer(InvalidInitializer node) {}
@override
void visitAssertInitializer(AssertInitializer node) {
translateStatement(node.statement);
}
@override
void visitLocalInitializer(LocalInitializer node) {
translateStatement(node.variable);
}
@override
void visitFieldInitializer(FieldInitializer node) {
Class cls = (node.parent as Constructor).enclosingClass;
w.StructType struct = translator.classInfo[cls]!.struct;
Field field = node.field;
int fieldIndex = translator.fieldIndex[field]!;
w.Local? local = fieldLocals[field];
local ??= addLocal(struct.fields[fieldIndex].type.unpacked);
translateExpression(node.value, struct.fields[fieldIndex].type.unpacked);
b.local_set(local);
fieldLocals[field] = local;
}
@override
void visitRedirectingInitializer(RedirectingInitializer node) {
Class cls = (node.parent as Constructor).enclosingClass;
for (TypeParameter typeParam in cls.typeParameters) {
types.makeType(
this, TypeParameterType(typeParam, Nullability.nonNullable));
}
final targetMember = node.targetReference.asMember;
final target = targetMember.initializerReference;
_visitArguments(node.arguments, translator.signatureForDirectCall(target),
translator.paramInfoForDirectCall(target), cls.typeParameters.length);
b.comment("Direct call of '$targetMember Redirected Initializer'");
call(target);
}
@override
void visitSuperInitializer(SuperInitializer node) {
Supertype? supertype =
(node.parent as Constructor).enclosingClass.supertype;
Supertype? supersupertype = node.target.enclosingClass.supertype;
// Skip calls to the constructor for Object, as this is empty
if (supersupertype != null) {
for (DartType typeArg in supertype!.typeArguments) {
types.makeType(this, typeArg);
}
final targetMember = node.targetReference.asMember;
final target = targetMember.initializerReference;
_visitArguments(
node.arguments,
translator.signatureForDirectCall(target),
translator.paramInfoForDirectCall(target),
supertype.typeArguments.length);
b.comment("Direct call of '$targetMember Initializer'");
call(target);
}
}
@override
void visitBlock(Block node) {
for (Statement statement in node.statements) {
translateStatement(statement);
}
}
@override
void visitLabeledStatement(LabeledStatement node) {
w.Label label = b.block();
breakFinalizers[node] = <w.Label>[label];
translateStatement(node.body);
breakFinalizers.remove(node);
b.end();
}
@override
void visitBreakStatement(BreakStatement node) {
b.br(breakFinalizers[node.target]!.last);
}
@override
void visitVariableDeclaration(VariableDeclaration node) {
final w.ValueType type = translator.translateTypeOfLocalVariable(node);
w.Local? local;
Capture? capture = closures.captures[node];
if (capture == null || !capture.written) {
local = addLocal(type, name: node.name);
locals[node] = local;
}
// Handle variable initialization. Nullable variables have an implicit
// initializer.
if (node.initializer != null ||
node.type.nullability == Nullability.nullable) {
Expression initializer =
node.initializer ?? ConstantExpression(NullConstant());
if (capture != null) {
w.ValueType expectedType = capture.written ? capture.type : local!.type;
b.local_get(capture.context.currentLocal);
translateExpression(initializer, expectedType);
if (!capture.written) {
b.local_tee(local!);
}
b.struct_set(capture.context.struct, capture.fieldIndex);
} else {
translateExpression(initializer, local!.type);
b.local_set(local);
}
} else if (local != null && !local.type.defaultable) {
// Uninitialized variable
translator
.getDummyValuesCollectorForModule(b.moduleBuilder)
.instantiateDummyValue(b, local.type);
b.local_set(local);
}
}
/// Initialize a variable [node] to an initial value which must be left on
/// the stack by [pushInitialValue].
///
/// This is similar to [visitVariableDeclaration] but it gives more control
/// over how the variable is initialized.
void initializeVariable(
VariableDeclaration node, void Function() pushInitialValue) {
final w.ValueType type = translator.translateTypeOfLocalVariable(node);
w.Local? local;
final Capture? capture = closures.captures[node];
if (capture == null || !capture.written) {
local = addLocal(type, name: node.name);
locals[node] = local;
}
if (capture != null) {
b.local_get(capture.context.currentLocal);
pushInitialValue();
if (!capture.written) {
b.local_tee(local!);
}
b.struct_set(capture.context.struct, capture.fieldIndex);
} else {
pushInitialValue();
b.local_set(local!);
}
}
@override
void visitEmptyStatement(EmptyStatement node) {}
@override
void visitAssertStatement(AssertStatement node) {
if (options.enableAsserts) {
w.Label assertBlock = b.block();
translateExpression(node.condition, w.NumType.i32);
b.br_if(assertBlock);
Expression? message = node.message;
if (message != null) {
translateExpression(message, translator.topType);
} else {
b.ref_null(w.HeapType.none);
}
final Location? location = node.location;
final w.RefType stringRefType = translator.stringTypeNullable;
if (location != null) {
instantiateConstant(
StringConstant(location.file.toString()),
stringRefType,
);
b.i64_const(location.line);
b.i64_const(location.column);
final String sourceString =
node.enclosingComponent!.uriToSource[location.file]!.text;
final String conditionString = sourceString.substring(
node.conditionStartOffset, node.conditionEndOffset);
instantiateConstant(
StringConstant(conditionString),
stringRefType,
);
} else {
b.ref_null(stringRefType.heapType);
b.i64_const(0);
b.i64_const(0);
b.ref_null(stringRefType.heapType);
}
call(translator.throwAssertionError.reference);
b.unreachable();
b.end();
}
}
@override
void visitAssertBlock(AssertBlock node) {
if (!options.enableAsserts) return;
for (Statement statement in node.statements) {
translateStatement(statement);
}
}
@override
void visitTryCatch(TryCatch node) {
// It is not valid Dart to have a try without a catch.
assert(node.catches.isNotEmpty);
final w.RefType exceptionType = translator.topTypeNonNullable;
final w.RefType stackTraceType = translator.stackTraceType;
final w.Label wrapperBlock = b.block();
// Create a block target for each Dart `catch` block, to be able to share
// code when generating a `catch` and `catch_all` for the same Dart `catch`
// block, when the block can catch both Dart and JS exceptions.
// The `end` for the Wasm `try` block works as the first exception handler
// target.
List<w.Label> catchBlockLabels = List.generate(node.catches.length - 1,
(i) => b.block([], [exceptionType, stackTraceType]),
growable: true);
w.Label try_ = b.try_([], [exceptionType, stackTraceType]);
catchBlockLabels.add(try_);
catchBlockLabels = catchBlockLabels.reversed.toList();
translateStatement(node.body);
b.br(wrapperBlock);
// Stash the original exception in a local so we can push it back onto the
// stack after each type test. Also, store the stack trace in a local.
w.Local thrownException = addLocal(exceptionType);
w.Local thrownStackTrace = addLocal(stackTraceType);
tryBlockLocals.add(
(exceptionLocal: thrownException, stackTraceLocal: thrownStackTrace));
void emitCatchBlock(
w.Label catchBlockTarget, Catch catch_, bool emitGuard) {
// For each catch node:
// 1) Create a block for the catch.
// 2) Push the caught exception onto the stack.
// 3) Add a type test based on the guard of the catch.
// 4) If the test fails, we jump to the next catch. Otherwise, we
// jump to the block for the body of the catch.
w.Label catchBlock = b.block();
DartType guard = catch_.guard;
// Only emit the type test if the guard is not [Object].
if (emitGuard) {
b.local_get(thrownException);
types.emitIsTest(this, guard,
translator.coreTypes.objectNonNullableRawType, catch_.location);
b.i32_eqz();
b.br_if(catchBlock);
}
b.local_get(thrownException);
b.local_get(thrownStackTrace);
b.br(catchBlockTarget);
b.end(); // end catchBlock.
}
// Insert a catch instruction which will catch any thrown Dart
// exceptions.
b.catch_legacy(translator.getExceptionTag(b.moduleBuilder));
b.local_set(thrownStackTrace);
b.local_set(thrownException);
for (int catchBlockIndex = 0;
catchBlockIndex < node.catches.length;
catchBlockIndex += 1) {
final catch_ = node.catches[catchBlockIndex];
// Only insert type checks if the guard is not `Object`
final bool shouldEmitGuard =
catch_.guard != translator.coreTypes.objectNonNullableRawType;
emitCatchBlock(
catchBlockLabels[catchBlockIndex], catch_, shouldEmitGuard);
if (!shouldEmitGuard) {
// If we didn't emit a guard, we won't ever fall through to the
// following catch blocks.
break;
}
}
// Rethrow if all the catch blocks fall through
b.rethrow_(try_);
// If we have a catches that are generic enough to catch a JavaScript
// error, we need to put that into a catch_all block.
if (node.catches
.any((c) => guardCanMatchJSException(translator, c.guard))) {
// This catches any objects that aren't dart exceptions, such as
// JavaScript exceptions or objects.
b.catch_all_legacy();
// We can't inspect the thrown object in a catch_all and get a stack
// trace, so we just attach the current stack trace.
call(translator.stackTraceCurrent.reference);
b.local_set(thrownStackTrace);
// We create a generic JavaScript error in this case.
call(translator.javaScriptErrorFactory.reference);
b.local_set(thrownException);
for (int catchBlockIndex = 0;
catchBlockIndex < node.catches.length;
catchBlockIndex += 1) {
final catch_ = node.catches[catchBlockIndex];
if (!guardCanMatchJSException(translator, catch_.guard)) {
continue;
}
// Type guards based on a type parameter are special, in that we cannot
// statically determine whether a JavaScript error will always satisfy
// the guard, so we should emit the type checking code for it. All
// other guards will always match a JavaScript error, however, so no
// need to emit type checks for those.
final bool shouldEmitGuard = catch_.guard is TypeParameterType;
emitCatchBlock(
catchBlockLabels[catchBlockIndex], catch_, shouldEmitGuard);
if (!shouldEmitGuard) {
// If we didn't emit a guard, we won't ever fall through to the
// following catch blocks.
break;
}
}
// Rethrow if the catch block falls through
b.rethrow_(try_);
}
for (Catch catch_ in node.catches) {
b.end();
b.local_set(thrownStackTrace);
b.local_set(thrownException);
final VariableDeclaration? exceptionDeclaration = catch_.exception;
if (exceptionDeclaration != null) {
initializeVariable(exceptionDeclaration, () {
b.local_get(thrownException);
// Type test passed, downcast the exception to the expected type.
translator.convertType(
b,
thrownException.type,
translator.translateType(exceptionDeclaration.type),
);
});
}
final VariableDeclaration? stackTraceDeclaration = catch_.stackTrace;
if (stackTraceDeclaration != null) {
initializeVariable(
stackTraceDeclaration, () => b.local_get(thrownStackTrace));
}
translateStatement(catch_.body);
b.br(wrapperBlock);
}
tryBlockLocals.removeLast();
b.end(); // end tryWrapper
}
@override
void visitTryFinally(TryFinally node) {
// We lower a [TryFinally] to a number of nested blocks, depending on how
// many different code paths we have that run the finally block.
//
// We emit the finalizer once in a catch, to handle the case where the try
// throws. Once outside of the catch, to handle the case where the try does
// not throw. If there is a return within the try block, then we emit the
// finalizer one more time along with logic to continue walking up the
// stack.
//
// A `break L` can run more than one finalizer, and each of those
// finalizers will need to be run in a different `try` block. So for each
// wrapping label we generate a block to run the finalizer on `break` and
// then branch to the right Wasm block to either run the next finalizer or
// break.
// The block for the try-finally statement. Used as `br` target in normal
// execution after the finalizer (no throws, returns, or breaks).
w.Label tryFinallyBlock = b.block();
// Create one block for each wrapping label.
for (final labelBlocks in breakFinalizers.values.toList().reversed) {
labelBlocks.add(b.block());
}
// Continuation of this block runs the finalizer and returns (or jumps to
// the next finalizer block). Used as `br` target on `return`.
w.Label returnFinalizerBlock = b.block();
returnFinalizers.add(TryBlockFinalizer(returnFinalizerBlock));
w.Label tryBlock = b.try_();
translateStatement(node.body);
final bool mustHandleReturn =
returnFinalizers.removeLast().mustHandleReturn;
// `break` statements in the current finalizer and the rest will not run
// the current finalizer, update the `break` targets.
final removedBreakTargets = <LabeledStatement, w.Label>{};
for (final breakFinalizerEntry in breakFinalizers.entries) {
removedBreakTargets[breakFinalizerEntry.key] =
breakFinalizerEntry.value.removeLast();
}
// Handle Dart exceptions.
b.catch_legacy(translator.getExceptionTag(b.moduleBuilder));
translateStatement(node.finalizer);
b.rethrow_(tryBlock);
// Handle JS exceptions.
b.catch_all_legacy();
translateStatement(node.finalizer);
b.rethrow_(tryBlock);
b.end(); // tryBlock
// Run finalizer on normal execution (no breaks, throws, or returns).
translateStatement(node.finalizer);
b.br(tryFinallyBlock);
b.end(); // returnFinalizerBlock
// Run the finalizer on `return`.
if (mustHandleReturn) {
translateStatement(node.finalizer);
if (returnFinalizers.isNotEmpty) {
b.br(returnFinalizers.last.label);
} else {
if (returnValueLocal != null) {
b.local_get(returnValueLocal!);
translator.convertType(b, returnValueLocal!.type, returnType);
}
_returnFromFunction();
}
}
// Generate finalizers for `break`s in the `try` block.
for (final removedBreakTargetEntry in removedBreakTargets.entries) {
b.end();
translateStatement(node.finalizer);
b.br(breakFinalizers[removedBreakTargetEntry.key]!.last);
}
b.end(); // tryFinallyBlock
}
@override
void visitExpressionStatement(ExpressionStatement node) {
translateExpression(node.expression, voidMarker);
}
bool _hasLogicalOperator(Expression condition) {
while (condition is Not) {
condition = condition.operand;
}
return condition is LogicalExpression;
}
void branchIf(Expression? condition, w.Label target,
{required bool negated}) {
if (condition == null) {
if (!negated) b.br(target);
return;
}
while (condition is Not) {
negated = !negated;
condition = condition.operand;
}
if (condition is LogicalExpression) {
bool isConjunctive =
(condition.operatorEnum == LogicalExpressionOperator.AND) ^ negated;
if (isConjunctive) {
w.Label conditionBlock = b.block();
branchIf(condition.left, conditionBlock, negated: !negated);
branchIf(condition.right, target, negated: negated);
b.end();
} else {
branchIf(condition.left, target, negated: negated);
branchIf(condition.right, target, negated: negated);
}
} else {
translateExpression(condition!, w.NumType.i32);
if (negated) {
b.i32_eqz();
}
b.br_if(target);
}
}
void _conditional(Expression condition, void Function() then,
void Function()? otherwise, List<w.ValueType> result) {
if (!_hasLogicalOperator(condition)) {
// Simple condition
translateExpression(condition, w.NumType.i32);
b.if_(const [], result);
then();
if (otherwise != null) {
b.else_();
otherwise();
}
b.end();
} else {
// Complex condition
w.Label ifBlock = b.block(const [], result);
if (otherwise != null) {
w.Label elseBlock = b.block();
branchIf(condition, elseBlock, negated: true);
then();
b.br(ifBlock);
b.end();
otherwise();
} else {
branchIf(condition, ifBlock, negated: true);
then();
}
b.end();
}
}
@override
void visitIfStatement(IfStatement node) {
_conditional(
node.condition,
() => translateStatement(node.then),
node.otherwise != null
? () => translateStatement(node.otherwise!)
: null,
const []);
}
@override
void visitDoStatement(DoStatement node) {
w.Label loop = b.loop();
allocateContext(node);
translateStatement(node.body);
branchIf(node.condition, loop, negated: false);
b.end();
}
@override
void visitWhileStatement(WhileStatement node) {
w.Label block = b.block();
w.Label loop = b.loop();
allocateContext(node);
branchIf(node.condition, block, negated: true);
translateStatement(node.body);
b.br(loop);
b.end();
b.end();
}
@override
void visitForStatement(ForStatement node) {
allocateContext(node);
for (VariableDeclaration variable in node.variables) {
translateStatement(variable);
}
w.Label block = b.block();
w.Label loop = b.loop();
branchIf(node.condition, block, negated: true);
translateStatement(node.body);
emitForStatementUpdate(node);
b.br(loop);
b.end();
b.end();
}
void emitForStatementUpdate(ForStatement node) {
Context? context = closures.contexts[node];
if (context != null && !context.isEmpty) {
// Create a new context for each iteration of the loop.
w.Local oldContext = context.currentLocal;
allocateContext(node);
w.Local newContext = context.currentLocal;
// Copy the values of captured loop variables to the new context.
for (VariableDeclaration variable in node.variables) {
Capture? capture = closures.captures[variable];
if (capture != null) {
assert(capture.context == context);
b.local_get(newContext);
b.local_get(oldContext);
b.struct_get(context.struct, capture.fieldIndex);
b.struct_set(context.struct, capture.fieldIndex);
}
}
// Update the context local to point to the new context.
b.local_get(newContext);
b.local_set(oldContext);
}
for (Expression update in node.updates) {
translateExpression(update, voidMarker);
}
}
@override
void visitForInStatement(ForInStatement node) {
throw "ForInStatement should have been desugared: $node";
}
/// Handle the return from this function, either by jumping to [returnLabel]
/// in the case this function was inlined or just inserting a return
/// instruction.
void _returnFromFunction() {
if (returnLabel != null) {
b.br(returnLabel!);
} else {
b.return_();
}
}
@override
void visitReturnStatement(ReturnStatement node) {
Expression? expression = node.expression;
if (expression != null) {
translateExpression(expression, returnType);
} else {
translator.convertType(b, voidMarker, returnType);
}
// If we are wrapped in a [TryFinally] node then we have to run finalizers
// as the stack unwinds. When we get to the top of the finalizer stack, we
// will handle the return using [returnValueLocal] if this function returns
// a value.
if (returnFinalizers.isNotEmpty) {
for (TryBlockFinalizer finalizer in returnFinalizers) {
finalizer.mustHandleReturn = true;
}
if (returnType != voidMarker) {
// Since the flow of the return value through the returnValueLocal
// crosses control-flow constructs, the local needs to always have a
// defaultable type in order for the Wasm code to validate.
returnValueLocal ??=
addLocal(returnType.withNullability(true), name: "returnValue");
b.local_set(returnValueLocal!);
}
b.br(returnFinalizers.last.label);
} else {
_returnFromFunction();
}
}
@override
void visitSwitchStatement(SwitchStatement node) {
// If we have an empty switch, just evaluate the expression for any
// potential side effects. In this case, the return type does not matter.
if (node.cases.isEmpty) {
translateExpression(node.expression, voidMarker);
return;
}
final switchInfo = SwitchInfo(this, node);
bool isNullable = dartTypeOf(node.expression).isPotentiallyNullable;
// When the type is nullable we use two variables: one for the nullable
// value, one after the null check, with non-nullable type.
w.Local switchValueNonNullableLocal = addLocal(switchInfo.nonNullableType);
w.Local? switchValueNullableLocal =
isNullable ? addLocal(switchInfo.nullableType) : null;
// Initialize switch value local
translateExpression(node.expression,
isNullable ? switchInfo.nullableType : switchInfo.nonNullableType);
b.local_set(
isNullable ? switchValueNullableLocal! : switchValueNonNullableLocal);
// Special cases
SwitchCase? defaultCase = switchInfo.defaultCase;
SwitchCase? nullCase = switchInfo.nullCase;
// Create `loop` for backward jumps
w.Label loopLabel = b.loop();
// Set `switchValueLocal` for backward jumps
w.Local switchValueLocal =
isNullable ? switchValueNullableLocal! : switchValueNonNullableLocal;
// Add backward jump info
switchBackwardJumpInfos[node] =
SwitchBackwardJumpInfo(switchValueLocal, loopLabel);
// Set up blocks, in reverse order of cases so they end in forward order
w.Label doneLabel = b.block();
for (SwitchCase c in node.cases.reversed) {
switchLabels[c] = b.block();
}
// Compute value and handle null
if (isNullable) {
w.Label nullLabel = nullCase != null
? switchLabels[nullCase]!
: defaultCase != null
? switchLabels[defaultCase]!
: doneLabel;
b.local_get(switchValueNullableLocal!);
b.br_on_null(nullLabel);
translator.convertType(b, switchInfo.nullableType.withNullability(false),
switchInfo.nonNullableType);
b.local_set(switchValueNonNullableLocal);
}
final dynamicTypeGuard = switchInfo.dynamicTypeGuard;
if (dynamicTypeGuard != null) {
final success = b.block(const [], [translator.topTypeNonNullable]);
dynamicTypeGuard(switchValueNonNullableLocal, success);
b.br(switchLabels[defaultCase] ?? doneLabel);
b.end();
}
final brTable = switchInfo.brTable;
if (brTable != null) {
// Map each entry in the range to the appropriate jump table entry.
final indexBlocks = <w.Label>[];
final defaultLabel =
defaultCase != null ? switchLabels[defaultCase]! : doneLabel;
for (int i = brTable.minValue; i <= brTable.maxValue; ++i) {
final c = brTable.caseMap[i];
indexBlocks.add(c == null ? defaultLabel : switchLabels[c]!);
}
brTable.emitBrTableExpr(b, switchValueNonNullableLocal);
b.br_table(indexBlocks, defaultLabel);
} else {
// Compare against all case values
for (SwitchCase c in node.cases) {
for (Expression exp in c.expressions) {
if (exp is NullLiteral ||
exp is ConstantExpression && exp.constant is NullConstant) {
// Null already checked, skip
} else {
switchInfo.compare(
switchValueNonNullableLocal,
() => translateExpression(exp, switchInfo.nonNullableType),
);
b.br_if(switchLabels[c]!);
}
}
}
// No explicit cases matched
if (node.isExplicitlyExhaustive) {
b.unreachable();
} else {
w.Label defaultLabel =
defaultCase != null ? switchLabels[defaultCase]! : doneLabel;
b.br(defaultLabel);
}
}
// Emit case bodies
for (SwitchCase c in node.cases) {
b.end();
// Remove backward jump target from forward jump labels
switchLabels.remove(c);
// Create a `loop` in default case to allow backward jumps to it
if (c.isDefault) {
switchBackwardJumpInfos[node]!.defaultLoopLabel = b.loop();
}
translateStatement(c.body);
if (c.isDefault) {
b.end(); // defaultLoopLabel
}
b.br(doneLabel);
}
b.end(); // doneLabel
b.end(); // loopLabel
// Remove backward jump info
final removed = switchBackwardJumpInfos.remove(node);
assert(removed != null);
}
@override
void visitContinueSwitchStatement(ContinueSwitchStatement node) {
w.Label? label = switchLabels[node.target];
if (label != null) {
b.br(label);
} else {
// Backward jump. Find the case literal in jump target, set the switched
// values to the jump target's value, and loop.
final SwitchCase targetSwitchCase = node.target;
final SwitchStatement targetSwitch =
targetSwitchCase.parent! as SwitchStatement;
final SwitchBackwardJumpInfo targetInfo =
switchBackwardJumpInfos[targetSwitch]!;
if (targetSwitchCase.expressions.isEmpty) {
// Default case
assert(targetSwitchCase.isDefault);
b.br(targetInfo.defaultLoopLabel!);
return;
}
final Expression targetValue =
targetSwitchCase.expressions[0]; // pick any of the values
translateExpression(targetValue, targetInfo.switchValueLocal.type);
b.local_set(targetInfo.switchValueLocal);
b.br(targetInfo.loopLabel);
}
}
@override
void visitYieldStatement(YieldStatement node) {
unimplemented(node, node.runtimeType, const []);
}
@override
w.ValueType visitAwaitExpression(
AwaitExpression node, w.ValueType expectedType) {
throw 'Await expression in code generator: $node (${node.location})';
}
@override
w.ValueType visitBlockExpression(
BlockExpression node, w.ValueType expectedType) {
translateStatement(node.body);
return translateExpression(node.value, expectedType);
}
w.ModuleBuilder? _activeDeferredLoadingGuard;
@override
w.ValueType visitLet(Let node, w.ValueType expectedType) {
translateStatement(node.variable);
final oldGuard = _activeDeferredLoadingGuard;
final newGuard = _recognizeDeferredModuleGuard(node);
if (newGuard != null) {
_activeDeferredLoadingGuard = newGuard;
}
final result = translateExpression(node.body, expectedType);
_activeDeferredLoadingGuard = oldGuard;
return result;
}
@override
w.ValueType visitThisExpression(
ThisExpression node, w.ValueType expectedType) {
return visitThis(expectedType);
}
w.ValueType visitThis(w.ValueType expectedType) {
w.ValueType thisType = thisLocal!.type;
w.ValueType preciseThisType = preciseThisLocal!.type;
assert(!thisType.nullable);
assert(!preciseThisType.nullable);
if (thisType.isSubtypeOf(expectedType)) {
b.local_get(thisLocal!);
return thisType;
}
if (preciseThisType.isSubtypeOf(expectedType)) {
b.local_get(preciseThisLocal!);
return preciseThisType;
}
// A user of `this` may have more precise type information, in which case
// we downcast it here.
b.local_get(thisLocal!);
translator.convertType(b, thisType, expectedType);
return expectedType;
}
@override
w.ValueType visitConstructorInvocation(
ConstructorInvocation node, w.ValueType expectedType) {
w.ValueType? intrinsicResult =
intrinsifier.generateConstructorIntrinsic(node);
if (intrinsicResult != null) return intrinsicResult;
ClassInfo info = translator.classInfo[node.target.enclosingClass]!;
translator.functions.recordClassAllocation(info.classId);
final target = node.targetReference;
_visitArguments(node.arguments, translator.signatureForDirectCall(target),
translator.paramInfoForDirectCall(target), 0);
return call(target).single;
}
@override
w.ValueType visitStaticInvocation(
StaticInvocation node, w.ValueType expectedType) {
w.ValueType? intrinsicResult = intrinsifier.generateStaticIntrinsic(node);
if (intrinsicResult != null) return intrinsicResult;
final target = node.targetReference;
_visitArguments(node.arguments, translator.signatureForDirectCall(target),
translator.paramInfoForDirectCall(target), 0);
return translator.outputOrVoid(call(target));
}
Member _lookupSuperTarget(Member interfaceTarget, {required bool setter}) {
final staticTarget = translator.hierarchy.getDispatchTarget(
enclosingMember.enclosingClass!.superclass!, interfaceTarget.name,
setter: setter);
if (staticTarget != null) return staticTarget;
// During dynamic module compilation a mixin might include a super call to
// an abstract class with no implementations yet.
assert(translator.dynamicModuleSupportEnabled);
return interfaceTarget;
}
@override
w.ValueType visitSuperMethodInvocation(
SuperMethodInvocation node, w.ValueType expectedType) {
Reference target = translator.getFunctionEntry(
_lookupSuperTarget(node.interfaceTarget, setter: false).reference,
uncheckedEntry: true);
w.FunctionType targetFunctionType =
translator.signatureForDirectCall(target);
final w.ValueType receiverType = translator.preciseThisFor(target.asMember);
// When calling `==` and the argument is potentially nullable, check if the
// argument is `null`.
if (node.name.text == '==') {
assert(node.arguments.positional.length == 1);
assert(node.arguments.named.isEmpty);
final argument = node.arguments.positional[0];
if (dartTypeOf(argument).isPotentiallyNullable) {
w.Label resultBlock = b.block(const [], const [w.NumType.i32]);
w.ValueType argumentType = targetFunctionType.inputs[1];
// `==` arguments are non-nullable.
assert(argumentType.nullable == false);
final argumentNullBlock = b.block(const [], const []);
visitThis(receiverType);
translateExpression(argument, argumentType.withNullability(true));
b.br_on_null(argumentNullBlock);
final resultType = translator.outputOrVoid(call(target));
// `super ==` should return bool.
assert(resultType == w.NumType.i32);
b.br(resultBlock);
b.end(); // argumentNullBlock
b.i32_const(0); // false
b.br(resultBlock);
b.end(); // resultBlock
return w.NumType.i32;
}
}
visitThis(receiverType);
_visitArguments(node.arguments, translator.signatureForDirectCall(target),
translator.paramInfoForDirectCall(target), 1);
return translator.outputOrVoid(call(target));
}
@override
w.ValueType visitInstanceInvocation(
InstanceInvocation node, w.ValueType expectedType) {
w.ValueType? intrinsicResult = intrinsifier.generateInstanceIntrinsic(node);
if (intrinsicResult != null) return intrinsicResult;
final useUncheckedEntry =
translator.canUseUncheckedEntry(node.receiver, node);
w.ValueType callWithNullCheck(
Procedure target, void Function(w.ValueType) onNull) {
late w.Label done;
final w.ValueType resultType =
_virtualCall(node, target, _VirtualCallKind.Call, (signature) {
done = b.block(const [], signature.outputs);
final w.Label nullReceiver = b.block();
translateExpression(node.receiver, translator.topType);
b.br_on_null(nullReceiver);
}, (w.FunctionType signature, ParameterInfo paramInfo) {
_visitArguments(node.arguments, signature, paramInfo, 1);
}, useUncheckedEntry: useUncheckedEntry);
b.br(done);
b.end(); // end nullReceiver
onNull(resultType);
b.end();
return resultType;
}
final Procedure target = node.interfaceTarget;
if (node.kind == InstanceAccessKind.Object) {
switch (target.name.text) {
case "toString":
return callWithNullCheck(
target,
(resultType) =>
translateExpression(StringLiteral("null"), resultType));
case "noSuchMethod":
return callWithNullCheck(target, (resultType) {
final target = node.interfaceTargetReference;
final signature = translator.signatureForDirectCall(target);
final paramInfo = translator.paramInfoForDirectCall(target);
// Object? receiver
b.ref_null(translator.topType.heapType);
// Invocation invocation
_visitArguments(node.arguments, signature, paramInfo, 1);
call(translator.noSuchMethodErrorThrowWithInvocation.reference);
});
default:
unimplemented(node, "Nullable invocation of ${target.name.text}",
[if (expectedType != voidMarker) expectedType]);
return expectedType;
}
}
Member? singleTarget = translator.singleTarget(node);
// Custom devirtualization because TFA doesn't correctly devirtualize index
// accesses on constant lists (see https://dartbug.com/60313)
if (singleTarget == null &&
target.kind == ProcedureKind.Operator &&
target.name.text == '[]') {
final receiver = node.receiver;
if (receiver is ConstantExpression && receiver.constant is ListConstant) {
singleTarget = translator.listBaseIndexOperator;
}
}
if (singleTarget != null) {
final target = translator.getFunctionEntry(singleTarget.reference,
uncheckedEntry: useUncheckedEntry);
final signature = translator.signatureForDirectCall(target);
final paramInfo = translator.paramInfoForDirectCall(target);
translateExpression(node.receiver, signature.inputs.first);
_visitArguments(node.arguments, signature, paramInfo, 1);
return translator.outputOrVoid(call(target));
}
return _virtualCall(
node,
target,
_VirtualCallKind.Call,
(signature) =>
translateExpression(node.receiver, signature.inputs.first),
(w.FunctionType signature, ParameterInfo paramInfo) {
_visitArguments(node.arguments, signature, paramInfo, 1);
}, useUncheckedEntry: useUncheckedEntry);
}
@override
w.ValueType visitDynamicInvocation(
DynamicInvocation node, w.ValueType expectedType) {
// Call dynamic invocation forwarder
final receiver = node.receiver;
final typeArguments = node.arguments.types;
final positionalArguments = node.arguments.positional;
final namedArguments = node.arguments.named;
final memberName = node.name;
final callShape = CallShape(
memberName,
typeArguments.length,
positionalArguments.length,
namedArguments.map((n) => n.name).toList()..sort());
final forwarder = translator
.getDynamicForwardersForModule(b.moduleBuilder)
.getDynamicInvocationForwarder(callShape);
// Evaluate receiver
translateExpression(receiver, translator.topType);
// Evaluate type arguments.
for (final typeArgument in typeArguments) {
translator.types.makeType(this, typeArgument);
}
// Evaluate positional arguments
for (final argument in positionalArguments) {
translateExpression(argument, translator.topType);
}
// Evaluate named arguments. The arguments need to be evaluated in the
// order they appear in the AST, but need to be sorted based on names in
// the argument list passed to the dynamic forwarder. Create a local for
// each argument to allow adding values to the list in expected order.
final namedArgumentLocals = <String, w.Local>{};
for (final namedArgument in namedArguments) {
translateExpression(namedArgument.value, translator.topType);
final argumentLocal = addLocal(translator.topType);
b.local_set(argumentLocal);
namedArgumentLocals[namedArgument.name] = argumentLocal;
}
// Load named arguments in sorted order.
for (final name in callShape.named) {
b.local_get(namedArgumentLocals[name]!);
}
translator.callFunction(forwarder.function, b);
return translator.topType;
}
@override
w.ValueType visitEqualsCall(EqualsCall node, w.ValueType expectedType) {
w.ValueType? intrinsicResult = intrinsifier.generateEqualsIntrinsic(node);
if (intrinsicResult != null) return intrinsicResult;
final leftType = translator.translateType(dartTypeOf(node.left));
Member? singleTarget = translator.singleTarget(node);
if (singleTarget == translator.coreTypes.objectEquals ||
// If leftType is not a Dart type (or builtin value type) then use
// reference equality (e.g. the vtable type is not a subtype of
// topType).
(leftType is w.RefType && !leftType.isSubtypeOf(translator.topType))) {
// Plain reference comparison
translateExpression(node.left, w.RefType.eq(nullable: true));
translateExpression(node.right, w.RefType.eq(nullable: true));
b.ref_eq();
} else {
// Check operands for null, then call implementation
bool leftNullable = dartTypeOf(node.left).isPotentiallyNullable;
bool rightNullable = dartTypeOf(node.right).isPotentiallyNullable;
w.RefType leftType = translator.topType.withNullability(leftNullable);
w.RefType rightType = translator.topType.withNullability(rightNullable);
w.Local leftLocal = addLocal(leftType);
w.Local rightLocal = addLocal(rightType);
w.Label? operandNull;
w.Label? done;
if (leftNullable || rightNullable) {
done = b.block(const [], const [w.NumType.i32]);
operandNull = b.block();
}
translateExpression(node.left, leftLocal.type);
b.local_set(leftLocal);
translateExpression(node.right, rightLocal.type);
if (rightNullable) {
b.local_tee(rightLocal);
b.br_on_null(operandNull!);
b.drop();
} else {
b.local_set(rightLocal);
}
void left([_]) {
b.local_get(leftLocal);
if (leftNullable) {
b.br_on_null(operandNull!);
}
}
void right([_, __]) {
b.local_get(rightLocal);
if (rightNullable) {
b.ref_as_non_null();
}
}
final useUncheckedEntry =
translator.canUseUncheckedEntry(node.left, node);
if (singleTarget != null) {
left();
right();
call(translator.getFunctionEntry(singleTarget.reference,
uncheckedEntry: useUncheckedEntry));
} else {
_virtualCall(
node,
node.interfaceTarget,
_VirtualCallKind.Call,
left,
right,
useUncheckedEntry: useUncheckedEntry,
);
}
if (leftNullable || rightNullable) {
b.br(done!);
b.end(); // operandNull
if (leftNullable && rightNullable) {
// Both sides nullable - compare references
b.local_get(leftLocal);
b.local_get(rightLocal);
b.ref_eq();
} else {
// Only one side nullable - not equal if one is null
b.i32_const(0);
}
b.end(); // done
}
}
return w.NumType.i32;
}
@override
w.ValueType visitEqualsNull(EqualsNull node, w.ValueType expectedType) {
translateExpression(node.expression, const w.RefType.any(nullable: true));
b.ref_is_null();
return w.NumType.i32;
}
w.ValueType _virtualCall(
TreeNode node,
Member interfaceTarget,
_VirtualCallKind kind,
void Function(w.FunctionType signature) pushReceiver,
void Function(w.FunctionType signature, ParameterInfo) pushArguments,
{required bool useUncheckedEntry}) {
assert(kind != _VirtualCallKind.Get || !useUncheckedEntry);
final reference = interfaceTarget.referenceAs(
getter: kind.isGetter, setter: kind.isSetter);
final dispatchTable = translator.dispatchTableForTarget(reference);
SelectorInfo selector = dispatchTable.selectorForTarget(reference);
final signature = selector.signature;
final name = selector.entryPointName(useUncheckedEntry);
assert(selector.name == interfaceTarget.name.text);
pushReceiver(signature);
final targets = selector.targets(unchecked: useUncheckedEntry);
List<({Range range, Reference target})> targetRanges = targets.targetRanges;
List<({Range range, Reference target})> staticDispatchRanges =
targets.staticDispatchRanges;
// NOTE: Keep this in sync with
// `dynamic_forwarders.dart:generateNoSuchMethodCall`.
final bool noTarget =
targetRanges.isEmpty && !selector.isDynamicSubmoduleOverridable;
final bool directCall =
targetRanges.length == 1 && staticDispatchRanges.length == 1;
final callPolymorphicDispatcher =
!directCall && staticDispatchRanges.isNotEmpty;
if (noTarget) {
// Unreachable call
b.comment("Virtual call of $name with no targets"
" at ${node.location}");
pushArguments(signature, selector.paramInfo);
for (int i = 0; i < signature.inputs.length; ++i) {
b.drop();
}
b.block(const [], signature.outputs);
b.unreachable();
b.end();
return translator.outputOrVoid(signature.outputs);
}
if (directCall) {
final target = translator.getFunctionEntry(targetRanges[0].target,
uncheckedEntry: useUncheckedEntry);
final directCallSignature = translator.signatureForDirectCall(target);
final paramInfo = translator.paramInfoForDirectCall(target);
pushArguments(directCallSignature, paramInfo);
return translator.outputOrVoid(call(target));
}
// Receiver is already on stack.
w.Local receiverVar = addLocal(signature.inputs.first);
assert(!receiverVar.type.nullable);
b.local_tee(receiverVar);
if (callPolymorphicDispatcher) {
b.loadClassId(translator, receiverVar.type);
b.local_get(receiverVar);
}
pushArguments(signature, selector.paramInfo);
if (callPolymorphicDispatcher) {
b.invoke(translator
.getPolymorphicDispatchersForModule(b.moduleBuilder)
.getPolymorphicDispatcher(selector,
useUncheckedEntry: useUncheckedEntry));
} else {
b.comment("Instance $kind of '$name'");
b.local_get(receiverVar);
translator.callDispatchTable(b, selector,
interfaceTarget: reference,
useUncheckedEntry: useUncheckedEntry,
table: dispatchTable);
}
return translator.outputOrVoid(signature.outputs);
}
@override
w.ValueType visitVariableGet(VariableGet node, w.ValueType expectedType) {
w.Local? local = locals[node.variable];
Capture? capture = closures.captures[node.variable];
if (capture != null) {
if (!capture.written && local != null) {
b.local_get(local);
return local.type;
} else {
b.local_get(capture.context.currentLocal);
b.struct_get(capture.context.struct, capture.fieldIndex);
return capture.type;
}
} else {
if (local == null) {
throw "Read of undefined variable ${node.variable}";
}
b.local_get(local);
return local.type;
}
}
@override
w.ValueType visitVariableSet(VariableSet node, w.ValueType expectedType) {
w.Local? local = locals[node.variable];
Capture? capture = closures.captures[node.variable];
bool preserved = expectedType != voidMarker;
if (capture != null) {
assert(capture.written);
b.local_get(capture.context.currentLocal);
translateExpression(node.value, capture.type);
if (preserved) {
w.Local temp = addLocal(capture.type);
b.local_tee(temp);
b.struct_set(capture.context.struct, capture.fieldIndex);
b.local_get(temp);
return temp.type;
} else {
b.struct_set(capture.context.struct, capture.fieldIndex);
return voidMarker;
}
} else {
if (local == null) {
throw "Write of undefined variable ${node.variable}";
}
translateExpression(node.value, local.type);
if (preserved) {
b.local_tee(local);
return local.type;
} else {
b.local_set(local);
return voidMarker;
}
}
}
@override
w.ValueType visitStaticGet(StaticGet node, w.ValueType expectedType) {
w.ValueType? intrinsicResult =
intrinsifier.generateStaticGetterIntrinsic(node);
if (intrinsicResult != null) return intrinsicResult;
return translator.outputOrVoid(call(node.targetReference));
}
@override
w.ValueType visitStaticTearOff(StaticTearOff node, w.ValueType expectedType) {
instantiateConstant(StaticTearOffConstant(node.target), expectedType);
return expectedType;
}
@override
w.ValueType visitStaticSet(StaticSet node, w.ValueType expectedType) {
bool preserved = expectedType != voidMarker;
Member target = node.target;
final reference =
target is Field ? target.setterReference! : target.reference;
w.ValueType paramType =
translator.signatureForDirectCall(reference).inputs.single;
translateExpression(node.value, paramType);
if (!preserved) {
call(node.targetReference);
return voidMarker;
}
w.Local temp = addLocal(paramType);
b.local_tee(temp);
call(reference);
b.local_get(temp);
return temp.type;
}
@override
w.ValueType visitSuperPropertyGet(
SuperPropertyGet node, w.ValueType expectedType) {
Member target = _lookupSuperTarget(node.interfaceTarget, setter: false);
if (target is Procedure && !target.isGetter) {
// Super tear-off
w.StructType closureStruct = _pushClosure(
translator.getTearOffClosure(target, b.moduleBuilder),
translator.getTearOffType(target),
() => visitThis(w.RefType.struct(nullable: false)));
return w.RefType.def(closureStruct, nullable: false);
}
return _directGet(target, ThisExpression());
}
@override
w.ValueType visitSuperPropertySet(
SuperPropertySet node, w.ValueType expectedType) {
Member target = _lookupSuperTarget(node.interfaceTarget, setter: true);
return _directSet(target, ThisExpression(), node.value,
preserved: expectedType != voidMarker, useUncheckedEntry: true);
}
@override
w.ValueType visitInstanceGet(InstanceGet node, w.ValueType expectedType) {
Member target = node.interfaceTarget;
if (node.kind == InstanceAccessKind.Object) {
late w.Label doneLabel;
w.ValueType resultType =
_virtualCall(node, target, _VirtualCallKind.Get, (signature) {
doneLabel = b.block(const [], signature.outputs);
w.Label nullLabel = b.block();
translateExpression(node.receiver, translator.topType);
b.br_on_null(nullLabel);
}, (_, __) {}, useUncheckedEntry: false);
b.br(doneLabel);
b.end(); // nullLabel
switch (target.name.text) {
case "hashCode":
b.i64_const(2011);
break;
case "runtimeType":
translateExpression(
ConstantExpression(TypeLiteralConstant(NullType())), resultType);
break;
default:
unimplemented(
node, "Nullable get of ${target.name.text}", [resultType]);
break;
}
b.end(); // doneLabel
return resultType;
}
Member? singleTarget = translator.singleTarget(node);
if (singleTarget != null) {
final intrinsic = intrinsifier.generateInstanceGetterIntrinsic(node);
if (intrinsic != null) return intrinsic;
return _directGet(singleTarget, node.receiver);
} else {
return _virtualCall(
node,
target,
_VirtualCallKind.Get,
(signature) =>
translateExpression(node.receiver, signature.inputs.first),
(_, __) {},
useUncheckedEntry: false);
}
}
@override
w.ValueType visitDynamicGet(DynamicGet node, w.ValueType expectedType) {
final receiver = node.receiver;
final memberName = node.name;
final forwarder = translator
.getDynamicForwardersForModule(b.moduleBuilder)
.getDynamicGetForwarder(memberName);
// Evaluate receiver
translateExpression(receiver, translator.topType);
// Call get forwarder
translator.callFunction(forwarder.function, b);
return translator.topType;
}
@override
w.ValueType visitDynamicSet(DynamicSet node, w.ValueType expectedType) {
final receiver = node.receiver;
final value = node.value;
final memberName = node.name;
final forwarder = translator
.getDynamicForwardersForModule(b.moduleBuilder)
.getDynamicSetForwarder(memberName);
translateExpression(receiver, translator.topType);
translateExpression(value, translator.topType);
translator.callFunction(forwarder.function, b);
return translator.topType;
}
w.ValueType _directGet(Member target, Expression receiver) {
if (target is Field) {
ClassInfo info = translator.classInfo[target.enclosingClass]!;
int fieldIndex = translator.fieldIndex[target]!;
w.ValueType receiverType = info.nonNullableType;
w.ValueType fieldType = info.struct.fields[fieldIndex].type.unpacked;
translateExpression(receiver, receiverType);
b.struct_get(info.struct, fieldIndex);
return fieldType;
} else {
// Instance call of getter
assert(target is Procedure && target.isGetter);
w.FunctionType targetFunctionType =
translator.signatureForDirectCall(target.reference);
translateExpression(receiver, targetFunctionType.inputs.single);
return translator.outputOrVoid(call(target.reference));
}
}
@override
w.ValueType visitInstanceTearOff(
InstanceTearOff node, w.ValueType expectedType) {
Member target = node.interfaceTarget;
if (node.kind == InstanceAccessKind.Object) {
late w.Label doneLabel;
w.ValueType resultType =
_virtualCall(node, target, _VirtualCallKind.Get, (signature) {
doneLabel = b.block(const [], signature.outputs);
w.Label nullLabel = b.block();
translateExpression(node.receiver, translator.topType);
b.br_on_null(nullLabel);
translator.convertType(b, translator.topType, signature.inputs[0]);
}, (_, __) {}, useUncheckedEntry: false);
b.br(doneLabel);
b.end(); // nullLabel
switch (target.name.text) {
case "toString":
translateExpression(
ConstantExpression(
StaticTearOffConstant(translator.nullToString)),
resultType);
break;
case "noSuchMethod":
translateExpression(
ConstantExpression(
StaticTearOffConstant(translator.nullNoSuchMethod)),
resultType);
break;
default:
unimplemented(
node, "Nullable tear-off of ${target.name.text}", [resultType]);
break;
}
b.end(); // doneLabel
return resultType;
}
return _virtualCall(
node,
target,
_VirtualCallKind.Get,
(signature) =>
translateExpression(node.receiver, signature.inputs.first),
(_, __) {},
useUncheckedEntry: false);
}
@override
w.ValueType visitInstanceSet(InstanceSet node, w.ValueType expectedType) {
bool preserved = expectedType != voidMarker;
w.Local? temp;
Member? singleTarget = translator.singleTarget(node);
final useUncheckedEntry =
translator.canUseUncheckedEntry(node.receiver, node);
if (singleTarget != null) {
return _directSet(singleTarget, node.receiver, node.value,
preserved: preserved, useUncheckedEntry: useUncheckedEntry);
} else {
_virtualCall(
node,
node.interfaceTarget,
_VirtualCallKind.Set,
(signature) =>
translateExpression(node.receiver, signature.inputs.first),
(signature, _) {
w.ValueType paramType = signature.inputs.last;
translateExpression(node.value, paramType);
if (preserved) {
temp = addLocal(paramType);
b.local_tee(temp!);
}
}, useUncheckedEntry: useUncheckedEntry);
if (preserved) {
b.local_get(temp!);
return temp!.type;
} else {
return voidMarker;
}
}
}
w.ValueType _directSet(Member target, Expression receiver, Expression value,
{required bool preserved, required bool useUncheckedEntry}) {
w.Local? temp;
final Reference reference = translator.getFunctionEntry(
(target is Field)
? target.setterReference!
: (target as Procedure).reference,
uncheckedEntry: useUncheckedEntry);
final w.FunctionType targetFunctionType =
translator.signatureForDirectCall(reference);
final w.ValueType paramType = targetFunctionType.inputs.last;
translateExpression(receiver, targetFunctionType.inputs.first);
translateExpression(value, paramType);
if (preserved) {
temp = addLocal(paramType);
b.local_tee(temp);
}
call(reference);
if (preserved) {
b.local_get(temp!);
return temp.type;
} else {
return voidMarker;
}
}
@override
void visitFunctionDeclaration(FunctionDeclaration node) {
Capture? capture = closures.captures[node.variable];
bool locallyClosurized = closures.closurizedFunctions.contains(node);
if (capture != null || locallyClosurized) {
if (capture != null) {
b.local_get(capture.context.currentLocal);
}
w.StructType struct = _instantiateClosure(node.function);
if (locallyClosurized) {
w.Local local = addLocal(w.RefType.def(struct, nullable: false));
locals[node.variable] = local;
if (capture != null) {
b.local_tee(local);
} else {
b.local_set(local);
}
}
if (capture != null) {
b.struct_set(capture.context.struct, capture.fieldIndex);
}
}
}
@override
w.ValueType visitFunctionExpression(
FunctionExpression node, w.ValueType expectedType) {
w.StructType struct = _instantiateClosure(node.function);
return w.RefType.def(struct, nullable: false);
}
w.StructType _instantiateClosure(FunctionNode functionNode) {
Lambda lambda = closures.lambdas[functionNode]!;
ClosureImplementation closure = translator.getClosure(
functionNode,
lambda.function,
b.moduleBuilder,
ParameterInfo.fromLocalFunction(functionNode),
"closure wrapper at ${functionNode.location}");
return _pushClosure(
closure,
functionNode.computeFunctionType(Nullability.nonNullable),
() => _pushContext(functionNode));
}
w.StructType _pushClosure(ClosureImplementation closure,
DartType functionType, void Function() pushContext) {
w.StructType struct = closure.representation.closureStruct;
ClassInfo info = translator.closureInfo;
translator.functions.recordClassAllocation(info.classId);
b.pushObjectHeaderFields(translator, info);
pushContext();
translator.globals.readGlobal(b, closure.vtable);
types.makeType(this, functionType);
b.struct_new(struct);
return struct;
}
void _pushContext(FunctionNode functionNode) {
Context? context = closures.contexts[functionNode]?.parent;
if (context != null) {
assert(!context.isEmpty);
b.local_get(context.currentLocal);
if (context.currentLocal.type.nullable) {
b.ref_as_non_null();
}
} else {
translator.globals.readGlobal(
b,
translator
.getDummyValuesCollectorForModule(b.moduleBuilder)
.dummyStructGlobal); // Dummy context
}
}
@override
w.ValueType visitFunctionInvocation(
FunctionInvocation node, w.ValueType expectedType) {
w.ValueType? intrinsicResult =
intrinsifier.generateFunctionCallIntrinsic(node);
if (intrinsicResult != null) return intrinsicResult;
if (node.kind == FunctionAccessKind.Function ||
translator.dynamicModuleSupportEnabled) {
// Type of function is `Function`, without the argument types.
return visitDynamicInvocation(
DynamicInvocation(DynamicAccessKind.Dynamic, node.receiver, node.name,
node.arguments),
expectedType);
}
List<String> argNames = node.arguments.named.map((a) => a.name).toList()
..sort();
ClosureRepresentation? representation = translator.closureLayouter
.getClosureRepresentation(node.arguments.types.length,
node.arguments.positional.length, argNames);
if (representation == null) {
// This is a dynamic function call with a signature that matches no
// functions in the program.
b.unreachable();
return translator.topType;
}
final SingleClosureTarget? directClosureCall =
translator.singleClosureTarget(node, representation, typeContext);
if (directClosureCall != null) {
return _generateDirectClosureCall(
node, representation, directClosureCall);
}
return _generateClosureInvocation(node, representation);
}
w.ValueType _generateDirectClosureCall(FunctionInvocation node,
ClosureRepresentation representation, SingleClosureTarget closureTarget) {
final closureStruct = representation.closureStruct;
final closureStructRef = w.RefType.def(closureStruct, nullable: false);
final signature = closureTarget.signature;
final paramInfo = closureTarget.paramInfo;
final member = closureTarget.member;
final lambdaFunction = closureTarget.lambdaFunction;
if (lambdaFunction == null) {
if (paramInfo.takesContextOrReceiver) {
translateExpression(node.receiver, closureStructRef);
b.struct_get(closureStruct, FieldIndex.closureContext);
translator.convertType(b, closureContextFieldType, signature.inputs[0]);
_visitArguments(node.arguments, signature, paramInfo, 1);
} else {
_visitArguments(node.arguments, signature, paramInfo, 0);
}
return translator.outputOrVoid(call(translator
.getFunctionEntry(member.reference, uncheckedEntry: false)));
} else {
assert(paramInfo.takesContextOrReceiver);
translateExpression(node.receiver, closureStructRef);
b.struct_get(closureStruct, FieldIndex.closureContext);
translator.convertType(b, closureContextFieldType, signature.inputs[0]);
_visitArguments(node.arguments, signature, paramInfo, 1);
return translator
.outputOrVoid(translator.callFunction(lambdaFunction, b));
}
}
w.ValueType _generateClosureInvocation(
FunctionInvocation node, ClosureRepresentation representation) {
final closureStruct = representation.closureStruct;
// Evaluate receiver
w.Local closureLocal =
addLocal(w.RefType.def(closureStruct, nullable: false));
translateExpression(node.receiver, closureLocal.type);
b.local_tee(closureLocal);
b.struct_get(closureStruct, FieldIndex.closureContext);
// Type arguments
for (DartType typeArg in node.arguments.types) {
types.makeType(this, typeArg);
}
// Positional arguments
for (Expression arg in node.arguments.positional) {
translateExpression(arg, translator.topType);
}
// Named arguments
final List<String> argNames =
node.arguments.named.map((a) => a.name).toList()..sort();
final Map<String, w.Local> namedLocals = {};
for (final namedArg in node.arguments.named) {
final w.Local namedLocal = addLocal(translator.topType);
namedLocals[namedArg.name] = namedLocal;
translateExpression(namedArg.value, namedLocal.type);
b.local_set(namedLocal);
}
for (String name in argNames) {
b.local_get(namedLocals[name]!);
}
final int vtableFieldIndex = representation.fieldIndexForSignature(
node.arguments.positional.length, argNames);
final w.FunctionType functionType =
representation.vtableStruct.getVtableEntryAt(vtableFieldIndex);
// Call entry point in vtable
b.local_get(closureLocal);
b.struct_get(closureStruct, FieldIndex.closureVtable);
b.struct_get(representation.vtableStruct, vtableFieldIndex);
b.call_ref(functionType);
return translator.topType;
}
@override
w.ValueType visitLocalFunctionInvocation(
LocalFunctionInvocation node, w.ValueType expectedType) {
var decl = node.variable.parent as FunctionDeclaration;
Lambda lambda = closures.lambdas[decl.function]!;
_pushContext(decl.function);
Arguments arguments = node.arguments;
_visitArguments(arguments, lambda.function.type,
ParameterInfo.fromLocalFunction(decl.function), 1);
b.comment("Local call of ${decl.variable.name}");
translator.callFunction(lambda.function, b);
return translator.outputOrVoid(lambda.function.type.outputs);
}
@override
w.ValueType visitInstantiation(Instantiation node, w.ValueType expectedType) {
DartType type = dartTypeOf(node.expression);
if (type is FunctionType) {
int typeCount = type.typeParameters.length;
int posArgCount = type.positionalParameters.length;
List<String> argNames = type.namedParameters.map((a) => a.name).toList();
ClosureRepresentation representation = translator.closureLayouter
.getClosureRepresentation(typeCount, posArgCount, argNames)!;
// Operand closure
w.RefType closureType =
w.RefType.def(representation.closureStruct, nullable: false);
w.Local closureTemp = addLocal(closureType);
translateExpression(node.expression, closureType);
b.local_tee(closureTemp);
// Type arguments
for (DartType typeArg in node.typeArguments) {
types.makeType(this, typeArg);
}
// Instantiation function
final vtableIndex =
translator.closureLayouter.vtableInstantiationFunctionIndex;
final instantiationFunctionType =
representation.vtableStruct.getVtableEntryAt(vtableIndex);
b.local_get(closureTemp);
b.struct_get(representation.closureStruct, FieldIndex.closureVtable);
b.struct_get(representation.vtableStruct, vtableIndex);
// Call instantiation function
b.call_ref(instantiationFunctionType);
return instantiationFunctionType.outputs.single;
} else {
// Only other alternative is `NeverType`.
assert(type is NeverType);
b.unreachable();
return voidMarker;
}
}
@override
w.ValueType visitLogicalExpression(
LogicalExpression node, w.ValueType expectedType) {
_conditional(node, () => b.i32_const(1), () => b.i32_const(0),
const [w.NumType.i32]);
return w.NumType.i32;
}
@override
w.ValueType visitNot(Not node, w.ValueType expectedType) {
translateExpression(node.operand, w.NumType.i32);
b.i32_eqz();
return w.NumType.i32;
}
@override
w.ValueType visitConditionalExpression(
ConditionalExpression node, w.ValueType expectedType) {
_conditional(
node.condition,
() => translateExpression(node.then, expectedType),
() => translateExpression(node.otherwise, expectedType),
[if (expectedType != voidMarker) expectedType]);
return expectedType;
}
@override
w.ValueType visitNullCheck(NullCheck node, w.ValueType expectedType) {
w.ValueType operandType =
translator.translateType(dartTypeOf(node.operand));
w.ValueType nonNullOperandType = operandType.withNullability(false);
// In rare cases the operand is non-nullable but TFA doesn't optimize away
// the null check. If the operand is an unboxed type, the br_on_non_null
// would fail to compile.
if (!operandType.nullable) {
translateExpression(node.operand, operandType);
return nonNullOperandType;
}
w.Label nullCheckBlock = b.block(const [], [nonNullOperandType]);
translateExpression(node.operand, operandType);
// We lower a null check to a br_on_non_null, throwing a [TypeError] in
// the null case.
b.br_on_non_null(nullCheckBlock);
call(translator.throwNullCheckErrorWithCurrentStack.reference);
b.unreachable();
b.end();
return nonNullOperandType;
}
void _visitArguments(Arguments node, w.FunctionType signature,
ParameterInfo paramInfo, int signatureOffset) {
// Type arguments
for (int i = 0; i < node.types.length; i++) {
types.makeType(this, node.types[i]);
}
signatureOffset += node.types.length;
// Positional arguments
for (int i = 0; i < node.positional.length; i++) {
translateExpression(
node.positional[i], signature.inputs[signatureOffset + i]);
}
// Push default values for optional positional parameters.
for (int i = node.positional.length; i < paramInfo.positional.length; i++) {
final w.ValueType type = signature.inputs[signatureOffset + i];
instantiateConstant(paramInfo.positional[i]!, type);
}
// Named arguments. Store evaluated arguments in locals to be able to
// re-order them based on the `ParameterInfo`.
final Map<String, w.Local> namedLocals = {};
for (var namedArg in node.named) {
final w.ValueType type = signature
.inputs[signatureOffset + paramInfo.nameIndex[namedArg.name]!];
final w.Local namedLocal = addLocal(type);
namedLocals[namedArg.name] = namedLocal;
translateExpression(namedArg.value, namedLocal.type);
b.local_set(namedLocal);
}
// Re-order named arguments and push default values for optional named
// parameters.
for (String name in paramInfo.names) {
w.Local? namedLocal = namedLocals[name];
final w.ValueType type =
signature.inputs[signatureOffset + paramInfo.nameIndex[name]!];
if (namedLocal != null) {
b.local_get(namedLocal);
} else {
instantiateConstant(paramInfo.named[name]!, type);
}
}
}
@override
w.ValueType visitStringConcatenation(
StringConcatenation node, w.ValueType expectedType) {
bool isConstantString(Expression expr) =>
expr is StringLiteral ||
(expr is ConstantExpression && expr.constant is StringConstant);
String extractConstantString(Expression expr) {
if (expr is StringLiteral) {
return expr.value;
} else {
return ((expr as ConstantExpression).constant as StringConstant).value;
}
}
final expressions = node.expressions;
if (expressions.every(isConstantString)) {
StringBuffer result = StringBuffer();
for (final expr in expressions) {
result.write(extractConstantString(expr));
}
final expr = StringLiteral(result.toString());
return visitStringLiteral(expr, expectedType);
}
late final Procedure target;
// We have special cases for 1/2/3/4 arguments.
if (expressions.length <= 4) {
final nullableObjectType =
translator.translateType(translator.coreTypes.objectNullableRawType);
for (final expression in expressions) {
translateExpression(expression, nullableObjectType);
}
if (expressions.length == 1) {
target = translator.jsStringInterpolate1;
} else if (expressions.length == 2) {
target = translator.jsStringInterpolate2;
} else if (expressions.length == 3) {
target = translator.jsStringInterpolate3;
} else {
assert(expressions.length == 4);
target = translator.jsStringInterpolate4;
}
} else {
final nullableObjectType = translator.coreTypes.objectNullableRawType;
makeArrayFromExpressions(expressions, nullableObjectType);
target = translator.jsStringInterpolate;
}
return translator.outputOrVoid(call(target.reference));
}
@override
w.ValueType visitThrow(Throw node, w.ValueType expectedType) {
// Front-end wraps the argument with `as Object` when necessary, so we can
// assume non-nullable here.
assert(!dartTypeOf(node.expression).isPotentiallyNullable);
translateExpression(node.expression, translator.topTypeNonNullable);
call(translator.errorThrowWithCurrentStackTrace.reference);
b.unreachable();
return expectedType;
}
@override
w.ValueType visitRethrow(Rethrow node, w.ValueType expectedType) {
final exceptionLocals = tryBlockLocals.last;
b.local_get(exceptionLocals.exceptionLocal);
b.local_get(exceptionLocals.stackTraceLocal);
b.throw_(translator.getExceptionTag(b.moduleBuilder));
return expectedType;
}
@override
w.ValueType visitConstantExpression(
ConstantExpression node, w.ValueType expectedType) {
instantiateConstant(node.constant, expectedType);
return expectedType;
}
w.ModuleBuilder? _recognizeDeferredModuleGuard(Let let) {
if (!translator.options.enableDeferredLoading &&
!translator.options.enableMultiModuleStressTestMode) {
return null;
}
// TODO(http://dartbug.com/61764): Find better way to do this.
//
// If we have somewhere in the parent chain of [node] a
//
// let
// _ = checkLibraryIsLoadedFromLoadId(<id>)
// in
// <body>
//
// Then we know that the constant use in <body> can only happen after the
// given <id> was loaded, i.e. the constant use is deferred-load-guarded
// by <id>.
final init = let.variable.initializer;
if (init is StaticInvocation) {
final target = init.target;
if (target == translator.checkLibraryIsLoadedFromLoadId) {
final args = init.arguments.positional;
final loadId = (args[0] as IntLiteral).value;
return translator.moduleForLoadId(
enclosingMember.enclosingLibrary, loadId);
}
}
return null;
}
@override
w.ValueType visitNullLiteral(NullLiteral node, w.ValueType expectedType) {
instantiateConstant(NullConstant(), expectedType);
return expectedType;
}
@override
w.ValueType visitStringLiteral(StringLiteral node, w.ValueType expectedType) {
instantiateConstant(StringConstant(node.value), expectedType);
return expectedType;
}
@override
w.ValueType visitBoolLiteral(BoolLiteral node, w.ValueType expectedType) {
instantiateConstant(BoolConstant(node.value), expectedType);
return expectedType;
}
@override
w.ValueType visitIntLiteral(IntLiteral node, w.ValueType expectedType) {
instantiateConstant(IntConstant(node.value), expectedType);
return expectedType;
}
@override
w.ValueType visitDoubleLiteral(DoubleLiteral node, w.ValueType expectedType) {
instantiateConstant(DoubleConstant(node.value), expectedType);
return expectedType;
}
@override
w.ValueType visitListLiteral(ListLiteral node, w.ValueType expectedType) {
final useSharedCreator = types.isTypeConstant(node.typeArgument);
final passType = !useSharedCreator;
final passArray = node.expressions.isNotEmpty;
final targetReference = passArray
? translator.growableListFromWasmArray.reference
: translator.growableListEmpty.reference;
final target = useSharedCreator
? translator
.getPartialInstantiatorForModule(b.moduleBuilder)
.getOneTypeArgumentForwarder(targetReference, node.typeArgument,
'create${passArray ? '' : 'Empty'}List<${node.typeArgument}>')
: translator.functions.getFunction(targetReference);
if (passType) {
types.makeType(this, node.typeArgument);
}
if (passArray) {
makeArrayFromExpressions(node.expressions,
translator.coreTypes.objectRawType(Nullability.nullable));
}
translator.callFunction(target, b);
return target.type.outputs.single;
}
w.ValueType makeArrayFromExpressions(
List<Expression> expressions, InterfaceType elementType) {
return makeArray(
translator.arrayTypeForDartType(elementType, mutable: true),
expressions.length, (w.ValueType type, int i) {
translateExpression(expressions[i], type);
});
}
w.ValueType makeArray(w.ArrayType arrayType, int length,
void Function(w.ValueType, int) generateItem) {
return translator.makeArray(b, arrayType, length, generateItem);
}
@override
w.ValueType visitMapLiteral(MapLiteral node, w.ValueType expectedType) {
final useSharedCreator = types.isTypeConstant(node.keyType) &&
types.isTypeConstant(node.valueType);
final passTypes = !useSharedCreator;
final passArray = node.entries.isNotEmpty;
final targetReference = passArray
? translator.mapFromWasmArray.reference
: translator.mapFactory.reference;
final target = useSharedCreator
? translator
.getPartialInstantiatorForModule(b.moduleBuilder)
.getTwoTypeArgumentForwarder(
targetReference,
node.keyType,
node.valueType,
'create${passArray ? '' : 'Empty'}'
'Map<${node.keyType}, ${node.valueType}>')
: translator.functions.getFunction(targetReference);
if (passTypes) {
types.makeType(this, node.keyType);
types.makeType(this, node.valueType);
}
if (passArray) {
makeArray(translator.nullableObjectArrayType, 2 * node.entries.length,
(elementType, elementIndex) {
final index = elementIndex ~/ 2;
final entry = node.entries[index];
if (elementIndex % 2 == 0) {
translateExpression(entry.key, elementType);
} else {
translateExpression(entry.value, elementType);
}
});
}
translator.callFunction(target, b);
return target.type.outputs.single;
}
@override
w.ValueType visitSetLiteral(SetLiteral node, w.ValueType expectedType) {
final useSharedCreator = types.isTypeConstant(node.typeArgument);
final passType = !useSharedCreator;
final passArray = node.expressions.isNotEmpty;
final targetReference = passArray
? translator.setFromWasmArray.reference
: translator.setFactory.reference;
final target = useSharedCreator
? translator
.getPartialInstantiatorForModule(b.moduleBuilder)
.getOneTypeArgumentForwarder(targetReference, node.typeArgument,
'create${passArray ? '' : 'Empty'}Set<${node.typeArgument}>')
: translator.functions.getFunction(targetReference);
if (passType) {
types.makeType(this, node.typeArgument);
}
if (passArray) {
makeArrayFromExpressions(node.expressions,
translator.coreTypes.objectRawType(Nullability.nullable));
}
translator.callFunction(target, b);
return target.type.outputs.single;
}
@override
w.ValueType visitTypeLiteral(TypeLiteral node, w.ValueType expectedType) {
return types.makeType(this, node.type);
}
@override
w.ValueType visitIsExpression(IsExpression node, w.ValueType expectedType) {
final operandType = dartTypeOf(node.operand);
final boxedOperandType = operandType.isPotentiallyNullable
? translator.topType
: translator.topTypeNonNullable;
translateExpression(node.operand, boxedOperandType);
types.emitIsTest(this, node.type, operandType, node.location);
return w.NumType.i32;
}
@override
w.ValueType visitAsExpression(AsExpression node, w.ValueType expectedType) {
final isImplicitCheck =
(node.isTypeError || node.isCovarianceCheck || node.isForDynamic);
if (node.isUnchecked ||
(translator.options.omitImplicitTypeChecks && isImplicitCheck) ||
(translator.options.omitExplicitTypeChecks && !isImplicitCheck)) {
return translateExpression(node.operand, expectedType);
}
final operandType = dartTypeOf(node.operand);
final boxedOperandType = operandType.isPotentiallyNullable
? translator.topType
: translator.topTypeNonNullable;
translateExpression(node.operand, boxedOperandType);
return types.emitAsCheck(this, node.isCovarianceCheck, node.type,
operandType, boxedOperandType, node.location);
}
@override
w.ValueType visitLoadLibrary(LoadLibrary node, w.ValueType expectedType) {
throw UnsupportedError(
'LoadLibrary should be lowered by modular transformer.');
}
@override
w.ValueType visitCheckLibraryIsLoaded(
CheckLibraryIsLoaded node, w.ValueType expectedType) {
throw UnsupportedError(
'CheckLibraryIsLoaded should be lowered by modular transformer.');
}
/// Pushes the `_Type` object for a function or class type parameter to the
/// stack and returns the value type of the object.
w.ValueType instantiateTypeParameter(TypeParameter parameter) {
w.ValueType resultType;
w.Local? local = typeLocals[parameter];
Capture? capture = closures.captures[parameter];
if (local != null) {
b.local_get(local);
resultType = local.type;
} else if (capture != null) {
Capture capture = closures.captures[parameter]!;
b.local_get(capture.context.currentLocal);
b.struct_get(capture.context.struct, capture.fieldIndex);
resultType = capture.type;
} else {
Class cls = parameter.declaration as Class;
ClassInfo info = translator.classInfo[cls]!;
int fieldIndex = translator.typeParameterIndex[parameter]!;
visitThis(info.nonNullableType);
b.struct_get(info.struct, fieldIndex);
resultType = info.struct.fields[fieldIndex].type.unpacked;
}
translator.convertType(b, resultType, types.nonNullableTypeType);
return types.nonNullableTypeType;
}
@override
w.ValueType visitRecordLiteral(RecordLiteral node, w.ValueType expectedType) {
final ClassInfo recordClassInfo =
translator.getRecordClassInfo(node.recordType);
translator.functions.recordClassAllocation(recordClassInfo.classId);
b.pushObjectHeaderFields(translator, recordClassInfo);
for (Expression positional in node.positional) {
translateExpression(positional, translator.topType);
}
for (NamedExpression named in node.named) {
translateExpression(named.value, translator.topType);
}
b.struct_new(recordClassInfo.struct);
return recordClassInfo.nonNullableType;
}
@override
w.ValueType visitRecordIndexGet(
RecordIndexGet node, w.ValueType expectedType) {
final RecordShape recordShape = RecordShape.fromType(node.receiverType);
final ClassInfo recordClassInfo =
translator.getRecordClassInfo(node.receiverType);
translateExpression(node.receiver, translator.topTypeNonNullable);
b.ref_cast(w.RefType(recordClassInfo.struct, nullable: false));
b.struct_get(
recordClassInfo.struct, recordShape.getPositionalIndex(node.index));
return translator.topType;
}
@override
w.ValueType visitRecordNameGet(RecordNameGet node, w.ValueType expectedType) {
final RecordShape recordShape = RecordShape.fromType(node.receiverType);
final ClassInfo recordClassInfo =
translator.getRecordClassInfo(node.receiverType);
translateExpression(node.receiver, translator.topTypeNonNullable);
b.ref_cast(w.RefType(recordClassInfo.struct, nullable: false));
b.struct_get(recordClassInfo.struct, recordShape.getNameIndex(node.name));
return translator.topType;
}
@override
w.ValueType visitFileUriExpression(
FileUriExpression node, w.ValueType expectedType) {
return translateExpression(node.expression, expectedType);
}
/// Generate code that checks type of an argument against an expected type
/// and throws a `TypeError` on failure.
///
/// Expects a boxed object (whose type is to be checked) on the stack.
///
/// [argName] is used in the type error as the name of the argument that
/// doesn't match the expected type.
void _generateArgumentTypeCheck(
String argName,
w.RefType argumentType,
DartType testedAgainstType,
) {
if (translator.options.minify) {
// We don't need to include the name in the error message, so we can use
// the optimized `as` checks.
types.emitAsCheck(this, false, testedAgainstType,
translator.coreTypes.objectNullableRawType, argumentType);
b.drop();
} else {
final argLocal = b.addLocal(argumentType);
b.local_tee(argLocal);
types.emitIsTest(
this, testedAgainstType, translator.coreTypes.objectNullableRawType);
b.i32_eqz();
b.if_();
b.local_get(argLocal);
types.makeType(this, testedAgainstType);
_emitString(argName);
call(translator.stackTraceCurrent.reference);
call(translator.throwArgumentTypeCheckError.reference);
b.unreachable();
b.end();
}
}
void _generateTypeArgumentBoundCheck(
String argName,
w.Local typeLocal,
DartType bound,
) {
b.local_get(typeLocal);
final boundLocal = b.addLocal(translator.runtimeTypeType);
types.makeType(this, bound);
b.local_tee(boundLocal);
call(translator.isTypeSubtype.reference);
b.i32_eqz();
b.if_();
// Type check failed
b.local_get(typeLocal);
b.local_get(boundLocal);
_emitString(argName);
call(translator.stackTraceCurrent.reference);
call(translator.throwTypeArgumentBoundCheckError.reference);
b.unreachable();
b.end();
}
void _emitString(String str) => translateExpression(StringLiteral(str),
translator.translateType(translator.coreTypes.stringNonNullableRawType));
@override
void visitPatternSwitchStatement(PatternSwitchStatement node) {
// This node is internal to the front end and removed by the constant
// evaluator.
throw UnsupportedError("CodeGenerator.visitPatternSwitchStatement");
}
@override
void visitPatternVariableDeclaration(PatternVariableDeclaration node) {
// This node is internal to the front end and removed by the constant
// evaluator.
throw UnsupportedError("CodeGenerator.visitPatternVariableDeclaration");
}
@override
void visitIfCaseStatement(IfCaseStatement node) {
// This node is internal to the front end and removed by the constant
// evaluator.
throw UnsupportedError("CodeGenerator.visitIfCaseStatement");
}
void debugRuntimePrint(String s) {
final printFunction =
translator.functions.getFunction(translator.printToConsole.reference);
translator.constants.instantiateConstant(
b, StringConstant(s), printFunction.type.inputs[0]);
translator.callFunction(printFunction, b);
}
@override
void visitAuxiliaryStatement(AuxiliaryStatement node) {
throw UnsupportedError(
"Unsupported auxiliary statement $node (${node.runtimeType}).");
}
@override
void visitAuxiliaryInitializer(AuxiliaryInitializer node) {
throw UnsupportedError(
"Unsupported auxiliary initializer $node (${node.runtimeType}).");
}
void emitUnimplementedExternalError(Member member) {
b.comment("Unimplemented external member $member at ${member.location}");
if (member.isInstanceMember) {
b.local_get(paramLocals[0]);
} else {
b.ref_null(w.HeapType.none);
}
translator.constants.instantiateConstant(
b,
translator.symbols.methodSymbolFromName(member.name),
translator.classInfo[translator.symbolClass]!.nonNullableType);
call(translator
.noSuchMethodErrorThrowUnimplementedExternalMemberError.reference);
b.unreachable();
}
void instantiateConstant(Constant constant, w.ValueType expectedType) {
translator.constants.instantiateConstant(
b,
constant,
expectedType,
deferredModuleGuard: _activeDeferredLoadingGuard,
);
}
}
CodeGenerator getMemberCodeGenerator(Translator translator,
w.FunctionBuilder functionBuilder, Reference memberReference) {
final member = memberReference.asMember;
final asyncMarker = member.function?.asyncMarker ?? AsyncMarker.Sync;
final codeGen = getInlinableMemberCodeGenerator(
translator, asyncMarker, functionBuilder.type, memberReference);
if (codeGen != null) return codeGen;
final procedure = member as Procedure;
if (asyncMarker == AsyncMarker.SyncStar) {
return SyncStarProcedureCodeGenerator(
translator, functionBuilder, procedure);
}
assert(asyncMarker == AsyncMarker.Async);
return AsyncProcedureCodeGenerator(translator, functionBuilder, procedure);
}
CodeGenerator getLambdaCodeGenerator(Translator translator, Lambda lambda,
Member enclosingMember, Closures enclosingMemberClosures) {
final asyncMarker = lambda.functionNode.asyncMarker;
if (asyncMarker == AsyncMarker.Async) {
return AsyncLambdaCodeGenerator(
translator, enclosingMember, lambda, enclosingMemberClosures);
}
if (asyncMarker == AsyncMarker.SyncStar) {
return SyncStarLambdaCodeGenerator(
translator, enclosingMember, lambda, enclosingMemberClosures);
}
assert(asyncMarker == AsyncMarker.Sync);
return SynchronousLambdaCodeGenerator(
translator, enclosingMember, lambda, enclosingMemberClosures);
}
/// Returns a [CodeGenerator] for the given member iff that member can be
/// inlined.
CodeGenerator? getInlinableMemberCodeGenerator(Translator translator,
AsyncMarker asyncMarker, w.FunctionType functionType, Reference reference) {
final Member member = reference.asMember;
if (reference.isTearOffReference) {
return TearOffCodeGenerator(translator, functionType, member);
}
if (reference.isTypeCheckerReference) {
return TypeCheckerCodeGenerator(translator, functionType, member);
}
if (member is Constructor) {
if (reference.isConstructorBodyReference) {
return ConstructorCodeGenerator(translator, functionType, member);
} else if (reference.isInitializerReference) {
return InitializerListCodeGenerator(translator, functionType, member);
} else {
return ConstructorAllocatorCodeGenerator(
translator, functionType, member);
}
}
if (member is Field) {
if (member.isStatic) {
if (reference.isImplicitGetter || reference.isImplicitSetter) {
return StaticFieldImplicitAccessorCodeGenerator(
translator, functionType, member, reference.isImplicitGetter);
}
return StaticFieldInitializerCodeGenerator(
translator, functionType, member);
}
final useUncheckedEntry = reference.isUncheckedEntryReference;
return ImplicitFieldAccessorCodeGenerator(translator, functionType, member,
reference.isImplicitGetter, useUncheckedEntry);
}
if (member is Procedure && asyncMarker == AsyncMarker.Sync) {
return SynchronousProcedureCodeGenerator(
translator, functionType, member, reference.entryKind);
}
assert(
asyncMarker == AsyncMarker.SyncStar || asyncMarker == AsyncMarker.Async);
return null;
}
class SynchronousProcedureCodeGenerator extends AstCodeGenerator {
final Procedure member;
final EntryPoint kind;
SynchronousProcedureCodeGenerator(Translator translator,
w.FunctionType functionType, this.member, this.kind)
: super(translator, functionType, member) {
assert(
!translator.needToCheckTypesFor(member) || kind != EntryPoint.normal);
}
@override
void generateInternal() {
final source = member.enclosingComponent!.uriToSource[member.fileUri]!;
setSourceMapSourceAndFileOffset(source, member.fileOffset);
if (intrinsifier.generateMemberIntrinsic(
member.reference, functionType, paramLocals, returnLabel)) {
b.end();
return;
}
if (member.isExternal) {
emitUnimplementedExternalError(member);
b.end();
return;
}
closures = translator.getClosures(member);
switch (kind) {
case EntryPoint.normal:
b.comment('Normal Entry');
_makeNonMultiEntryPointFunction();
case EntryPoint.checked:
b.comment('Checked Entry');
_makeMultipleEntryPoint(true);
case EntryPoint.unchecked:
b.comment('Unchecked Entry');
_makeMultipleEntryPoint(false);
case EntryPoint.body:
b.comment('Body for Checked & Unchecked Entry');
_makeMultipleEntryPointSharedBody();
break;
}
}
void _makeMultipleEntryPoint(bool checked) {
final function = member.function;
final signature = translator.signatureForDirectCall(member.bodyReference);
if (checked) {
setupParametersForCheckedEntry(member);
} else {
setupParametersForUncheckedEntry(member);
}
int arg = 0;
visitThis(signature.inputs[arg++]);
for (final parameter in function.typeParameters) {
final r = instantiateTypeParameter(parameter);
translator.convertType(b, r, signature.inputs[arg++]);
}
for (final parameter in function.positionalParameters) {
final local = locals[parameter]!;
b.local_get(local);
translator.convertType(b, local.type, signature.inputs[arg++]);
}
for (final parameter in function.namedParameters) {
final local = locals[parameter]!;
b.local_get(local);
translator.convertType(b, local.type, signature.inputs[arg++]);
}
final outputs = call(member.bodyReference);
if (outputs.isNotEmpty) {
translator.convertType(b, outputs.single, functionType.outputs.single);
}
_returnFromFunction();
b.end();
}
void _makeMultipleEntryPointSharedBody() {
final function = member.function;
final typeParameters = function.typeParameters;
final positionals = function.positionalParameters;
final named = function.namedParameters;
int param = _initializeThis(member.reference);
for (int i = 0; i < typeParameters.length; i++) {
final typeParameter = typeParameters[i];
typeLocals[typeParameter] = paramLocals[param++];
}
void setupParameter(VariableDeclaration parameter) {
// The body may assign less precise types to the parameter variable than
// what the caller provides.
w.Local local = paramLocals[param++];
if (translator.typeOfCheckedParameterVariable(parameter) !=
parameter.type) {
final newLocal = addLocal(translator.translateType(parameter.type));
b.local_get(local);
translator.convertType(b, local.type, newLocal.type);
b.local_set(newLocal);
local = newLocal;
}
locals[parameter] = local;
}
for (int i = 0; i < positionals.length; i++) {
setupParameter(positionals[i]);
}
for (int i = 0; i < named.length; i++) {
setupParameter(named[i]);
}
setupContexts(member);
Statement? body = member.function.body;
if (body != null) {
translateStatement(body);
}
_implicitReturn();
b.end();
}
void _makeNonMultiEntryPointFunction() {
setupParametersForNormalEntry(member);
setupContexts(member);
Statement? body = member.function.body;
if (body != null) {
translateStatement(body);
}
_implicitReturn();
b.end();
return;
}
}
class TearOffCodeGenerator extends AstCodeGenerator {
final Member member;
TearOffCodeGenerator(
Translator translator, w.FunctionType functionType, this.member)
: super(translator, functionType, member);
@override
void generateInternal() {
// Initialize [Closures] without [Closures.captures]: [Closures.captures] is
// used by `makeType` below, when generating runtime types of type
// parameters of the function type, but the type parameters are not
// captured, always loaded from the `this` struct.
closures = translator.getClosures(member, findCaptures: false);
_initializeThis(member.reference);
Procedure procedure = member as Procedure;
DartType functionType = translator.getTearOffType(procedure);
ClosureImplementation closure =
translator.getTearOffClosure(procedure, b.moduleBuilder);
w.StructType struct = closure.representation.closureStruct;
ClassInfo info = translator.closureInfo;
translator.functions.recordClassAllocation(info.classId);
b.pushObjectHeaderFields(translator, info);
b.local_get(paramLocals[0]); // `this` as context
// The closure requires a struct value so box `this` if necessary.
translator.convertType(b, paramLocals[0].type,
struct.fields[FieldIndex.closureContext].type.unpacked);
translator.globals.readGlobal(b, closure.vtable);
types.makeType(this, functionType);
b.struct_new(struct);
b.end();
}
}
class TypeCheckerCodeGenerator extends AstCodeGenerator {
final Member member;
TypeCheckerCodeGenerator(
Translator translator, w.FunctionType functionType, this.member)
: super(translator, functionType, member);
@override
void generateInternal() {
// Initialize [Closures] without [Closures.captures]: Similar to
// [TearOffCodeGenerator], type parameters will be loaded from the `this`
// struct.
closures = translator.getClosures(member, findCaptures: false);
if (member is Field ||
(member is Procedure && (member as Procedure).isSetter)) {
_generateFieldSetterTypeCheckerMethod();
} else {
_generateProcedureTypeCheckerMethod();
}
}
/// Generate type checker method for a method.
///
/// This function will be called by an invocation forwarder in a dynamic
/// invocation to type check parameters before calling the actual method.
void _generateProcedureTypeCheckerMethod() {
final receiverLocal = paramLocals[0];
final typeArgsLocal = paramLocals[1];
final positionalArgsLocal = paramLocals[2];
final namedArgsLocal = paramLocals[3];
_initializeThis(member.reference);
final typeType =
translator.classInfo[translator.typeClass]!.nonNullableType;
final target =
translator.getFunctionEntry(member.reference, uncheckedEntry: false);
final targetParamInfo = translator.paramInfoForDirectCall(target);
final procedure = member as Procedure;
// Bind type parameters
final memberTypeParams = procedure.function.typeParameters;
assert(memberTypeParams.length == targetParamInfo.typeParamCount);
if (memberTypeParams.isNotEmpty) {
// Type argument list is either empty or have the right number of types
// (checked by the forwarder).
b.local_get(typeArgsLocal);
b.array_len();
b.i32_eqz();
b.if_([], List.generate(memberTypeParams.length, (_) => typeType));
// No type arguments passed, initialize with defaults
for (final typeParam in memberTypeParams) {
types.makeType(this, typeParam.defaultType);
}
b.else_();
for (int typeParamIdx = 0;
typeParamIdx < memberTypeParams.length;
typeParamIdx += 1) {
b.local_get(typeArgsLocal);
b.i32_const(typeParamIdx);
b.array_get(translator.typeArrayType);
}
b.end();
// Create locals for type parameters. These will be used by `makeType`
// below when generating types of parameters, for type checks, and when
// pushing the type parameters when calling the actual member.
for (int typeParamIdx = memberTypeParams.length - 1;
typeParamIdx >= 0;
typeParamIdx -= 1) {
final local = addLocal(typeType);
b.local_set(local);
typeLocals[memberTypeParams[typeParamIdx]] = local;
}
}
if (!translator.options.omitImplicitTypeChecks) {
// Check type parameter bounds
for (TypeParameter typeParameter in memberTypeParams) {
if (typeParameter.bound != translator.coreTypes.objectNullableRawType) {
_generateTypeArgumentBoundCheck(typeParameter.name!,
typeLocals[typeParameter]!, typeParameter.bound);
}
}
// Check positional argument types
final List<VariableDeclaration> memberPositionalParams =
procedure.function.positionalParameters;
for (int positionalParamIdx = 0;
positionalParamIdx < memberPositionalParams.length;
positionalParamIdx += 1) {
final param = memberPositionalParams[positionalParamIdx];
b.local_get(positionalArgsLocal);
b.i32_const(positionalParamIdx);
b.array_get(translator.nullableObjectArrayType);
_generateArgumentTypeCheck(param.name!, translator.topType, param.type);
}
// Check named argument types
final memberNamedParams = procedure.function.namedParameters;
/// Maps a named parameter in the member's signature to the parameter's
/// index in the array [namedArgsLocal].
int mapNamedParameterToArrayIndex(String name) {
int? idx;
for (int i = 0; i < targetParamInfo.names.length; i += 1) {
if (targetParamInfo.names[i] == name) {
idx = i;
break;
}
}
return idx!;
}
for (int namedParamIdx = 0;
namedParamIdx < memberNamedParams.length;
namedParamIdx += 1) {
final param = memberNamedParams[namedParamIdx];
b.local_get(namedArgsLocal);
b.i32_const(mapNamedParameterToArrayIndex(param.name!));
b.array_get(translator.nullableObjectArrayType);
_generateArgumentTypeCheck(param.name!, translator.topType, param.type);
}
}
// Argument types are as expected, call the member function
final w.FunctionType memberWasmFunctionType =
translator.signatureForDirectCall(target);
final List<w.ValueType> memberWasmInputs = memberWasmFunctionType.inputs;
b.local_get(receiverLocal);
translator.convertType(b, receiverLocal.type, memberWasmInputs[0]);
for (final typeParam in memberTypeParams) {
b.local_get(typeLocals[typeParam]!);
}
int memberParamIdx =
1 + targetParamInfo.typeParamCount; // skip receiver and type args
void pushArgument(w.Local listLocal, int listIdx, int wasmInputIdx) {
b.local_get(listLocal);
b.i32_const(listIdx);
b.array_get(translator.nullableObjectArrayType);
translator.convertType(
b, translator.topType, memberWasmInputs[wasmInputIdx]);
}
for (int positionalParamIdx = 0;
positionalParamIdx < targetParamInfo.positional.length;
positionalParamIdx += 1) {
pushArgument(positionalArgsLocal, positionalParamIdx, memberParamIdx);
memberParamIdx += 1;
}
for (int namedParamIdx = 0;
namedParamIdx < targetParamInfo.names.length;
namedParamIdx += 1) {
pushArgument(namedArgsLocal, namedParamIdx, memberParamIdx);
memberParamIdx += 1;
}
call(target);
translator.convertType(
b,
translator.outputOrVoid(memberWasmFunctionType.outputs),
translator.topType);
b.return_();
b.end();
}
/// Generate type checker method for a setter.
///
/// This function will be called by a setter forwarder in a dynamic set to
/// type check the setter argument before calling the actual setter.
void _generateFieldSetterTypeCheckerMethod() {
final receiverLocal = paramLocals[0];
final positionalArgLocal = paramLocals[1];
_initializeThis(member.reference);
final member_ = member;
DartType paramType;
if (member_ is Field) {
paramType = member_.type;
} else {
paramType = (member_ as Procedure).setterType;
}
if (!translator.options.omitImplicitTypeChecks) {
b.local_get(positionalArgLocal);
_generateArgumentTypeCheck(
member.name.text,
positionalArgLocal.type as w.RefType,
paramType,
);
}
ClassInfo info = translator.classInfo[member_.enclosingClass]!;
if (member_ is Field) {
int fieldIndex = translator.fieldIndex[member_]!;
b.local_get(receiverLocal);
translator.convertType(b, receiverLocal.type, info.nonNullableType);
b.local_get(positionalArgLocal);
translator.convertType(b, positionalArgLocal.type,
info.struct.fields[fieldIndex].type.unpacked);
b.struct_set(info.struct, fieldIndex);
} else {
final setterProcedure = member_ as Procedure;
final target = translator.getFunctionEntry(setterProcedure.reference,
uncheckedEntry: false);
final setterProcedureWasmType = translator.signatureForDirectCall(target);
final setterWasmInputs = setterProcedureWasmType.inputs;
assert(setterWasmInputs.length == 2);
b.local_get(receiverLocal);
translator.convertType(b, receiverLocal.type, setterWasmInputs[0]);
b.local_get(positionalArgLocal);
translator.convertType(b, positionalArgLocal.type, setterWasmInputs[1]);
call(target);
}
b.local_get(positionalArgLocal);
b.end(); // end function
}
}
class InitializerListCodeGenerator extends AstCodeGenerator {
final Constructor member;
InitializerListCodeGenerator(
Translator translator, w.FunctionType functionType, this.member)
: super(translator, functionType, member);
@override
void generateInternal() {
// Closures are built when constructor functions are added to worklist.
closures = translator.constructorClosures[member.reference]!;
final source = member.enclosingComponent!.uriToSource[member.fileUri]!;
setSourceMapSourceAndFileOffset(source, member.fileOffset);
if (member.isExternal) {
emitUnimplementedExternalError(member);
} 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 generateInitializerList() {
_setupInitializerListParametersAndContexts();
Class cls = member.enclosingClass;
ClassInfo info = translator.classInfo[cls]!;
List<w.Local> initializedFields = _generateInitializers(member);
bool containsSuperInitializer = false;
bool containsRedirectingInitializer = false;
for (Initializer initializer in member.initializers) {
if (initializer is SuperInitializer) {
containsSuperInitializer = true;
} else if (initializer is RedirectingInitializer) {
containsRedirectingInitializer = true;
}
}
if (cls.superclass != null && !containsRedirectingInitializer) {
// checks if a SuperInitializer was dropped because the constructor body
// throws an error
if (!containsSuperInitializer) {
b.unreachable();
return;
}
// checks if a FieldInitializer was dropped because the constructor body
// throws an error
for (Field field in info.cls!.fields) {
if (field.isInstanceMember && !fieldLocals.containsKey(field)) {
b.unreachable();
return;
}
}
}
// push constructor arguments
List<w.Local> constructorArgs =
_getConstructorArgumentLocals(member.reference, true);
for (w.Local arg in constructorArgs) {
b.local_get(arg);
}
// push reference to context
Context? context = closures.contexts[member];
if (context != null) {
assert(!context.isEmpty);
b.local_get(context.currentLocal);
}
// push initialized fields
for (w.Local field in initializedFields) {
b.local_get(field);
}
}
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) {
visitInitializer(initializer);
if (initializer is SuperInitializer) {
// Save super classes' fields to locals
ClassInfo superInfo = info.superInfo!;
for (w.ValueType outputType
in superInfo.getClassFieldTypes().reversed) {
w.Local local = addLocal(outputType);
b.local_set(local);
superclassFields.add(local);
}
} else if (initializer is RedirectingInitializer) {
// Save redirected classes' fields to locals
List<w.Local> redirectedFields = [];
for (w.ValueType outputType in info.getClassFieldTypes().reversed) {
w.Local local = addLocal(outputType);
b.local_set(local);
redirectedFields.add(local);
}
return redirectedFields.reversed.toList();
}
}
List<w.Local> typeFields = [];
for (TypeParameter typeParam in cls.typeParameters) {
TypeParameter? match = info.typeParameterMatch[typeParam];
if (match == null) {
// Type is not contained in super class' fields
typeFields.add(typeLocals[typeParam]!);
}
}
List<w.Local> orderedFieldLocals = Map.fromEntries(
fieldLocals.entries.toList()
..sort((x, y) => translator.fieldIndex[x.key]!
.compareTo(translator.fieldIndex[y.key]!)))
.values
.toList();
return superclassFields.reversed.toList() + typeFields + orderedFieldLocals;
}
}
class ConstructorAllocatorCodeGenerator extends AstCodeGenerator {
final Constructor member;
ConstructorAllocatorCodeGenerator(
Translator translator, w.FunctionType functionType, this.member)
: super(translator, functionType, member);
@override
void generateInternal() {
// Closures are built when constructor functions are added to worklist.
closures = translator.constructorClosures[member.reference]!;
final source = member.enclosingComponent!.uriToSource[member.fileUri]!;
setSourceMapSourceAndFileOffset(source, member.fileOffset);
generateConstructorAllocator();
}
// Generates a function for allocating an object. This calls the separate
// initializer list and constructor body methods, and allocates a struct for
// the object.
void generateConstructorAllocator() {
setupParameters(member.reference, isForwarder: true);
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
.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(0, numConstructorBodyArgs);
List<w.Local> constructorArguments = [];
for (w.ValueType argType in constructorArgTypes.reversed) {
w.Local local = addLocal(argType);
b.local_set(local);
constructorArguments.add(local);
}
// Set field values
b.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);
// Push context local if it is present
if (contextLocal != null) {
b.local_get(contextLocal);
}
// Push all constructor arguments
for (w.Local constructorArg in constructorArguments) {
b.local_get(constructorArg);
}
b.comment("Direct call of $member Constructor Body");
call(member.constructorBodyReference);
b.local_get(temp);
b.end();
}
}
class ConstructorCodeGenerator extends AstCodeGenerator {
final Constructor member;
ConstructorCodeGenerator(
Translator translator, w.FunctionType functionType, this.member)
: super(translator, functionType, member);
@override
void generateInternal() {
// Closures are built when constructor functions are added to worklist.
closures = translator.constructorClosures[member.reference]!;
final source = member.enclosingComponent!.uriToSource[member.fileUri]!;
setSourceMapSourceAndFileOffset(source, member.fileOffset);
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;
}
// Call super class' constructor body, or redirected constructor
for (Initializer initializer in member.initializers) {
if (initializer is SuperInitializer ||
initializer is RedirectingInitializer) {
Constructor target = initializer is SuperInitializer
? initializer.target
: (initializer as RedirectingInitializer).target;
Supertype? supersupertype = target.enclosingClass.supertype;
if (supersupertype == null) {
break;
}
int startIndex =
getStartIndexForSuperOrRedirectedConstructorArguments();
List<w.Local> superOrRedirectedConstructorArgs =
paramLocals.sublist(startIndex);
w.Local object = thisLocal!;
b.local_get(object);
for (w.Local local in superOrRedirectedConstructorArgs) {
b.local_get(local);
}
call(target.constructorBodyReference);
break;
}
}
Statement? body = member.function.body;
if (body != null) {
translateStatement(body);
}
b.end();
}
void _setupConstructorBodyParametersAndContexts() {
ParameterInfo paramInfo =
translator.paramInfoForDirectCall(member.constructorBodyReference);
// 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);
}
// 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;
_setupLocalParameters(member, paramInfo, parameterOffset, implicitParams);
allocateContext(member.function);
}
}
class StaticFieldInitializerCodeGenerator extends AstCodeGenerator {
final Field field;
StaticFieldInitializerCodeGenerator(
Translator translator, w.FunctionType functionType, this.field)
: super(translator, functionType, field);
@override
void generateInternal() {
final source = field.enclosingComponent!.uriToSource[field.fileUri]!;
setSourceMapSourceAndFileOffset(source, field.fileOffset);
// Static field initializer function
closures = translator.getClosures(field);
w.Global global = translator.globals.getGlobalForStaticField(field);
w.Global? flag = translator.globals.getGlobalInitializedFlag(field);
translateExpression(field.initializer!, global.type.type);
b.global_set(global);
if (flag != null) {
b.i32_const(1);
b.global_set(flag);
}
b.global_get(global);
translator.convertType(b, global.type.type, outputs.single);
b.end();
}
}
/// Will eagerly initialize a static field as part of the module's start
/// function.
class EagerStaticFieldInitializerCodeGenerator extends AstCodeGenerator {
final Field field;
final w.Global global;
EagerStaticFieldInitializerCodeGenerator(
Translator translator, this.field, this.global)
: super(translator, w.FunctionType([], []), field);
@override
void generateInternal() {
final source = field.enclosingComponent!.uriToSource[field.fileUri]!;
setSourceMapSourceAndFileOffset(source, field.fileOffset);
translateExpression(field.initializer!, global.type.type);
b.global_set(global);
}
}
class StaticFieldImplicitAccessorCodeGenerator extends AstCodeGenerator {
final Field field;
final bool isImplicitGetter;
StaticFieldImplicitAccessorCodeGenerator(Translator translator,
w.FunctionType functionType, this.field, this.isImplicitGetter)
: super(translator, functionType, field);
@override
void generateInternal() {
final global = translator.globals.getGlobalForStaticField(field);
final flag = translator.globals.getGlobalInitializedFlag(field);
if (isImplicitGetter) {
final initFunction =
translator.functions.getExistingFunction(field.fieldReference);
_generateGetter(global, flag, initFunction);
} else {
_generateSetter(global, flag);
}
b.end();
}
void _generateGetter(
w.Global global, w.Global? flag, w.BaseFunction? initFunction) {
if (initFunction == null) {
// Statically initialized
b.global_get(global);
} else {
if (flag != null) {
// Explicit initialization flag
b.global_get(flag);
b.if_(const [], [global.type.type]);
b.global_get(global);
b.else_();
translator.callFunction(initFunction, b);
b.end();
} else {
// Null signals uninitialized
w.Label block = b.block(const [], [initFunction.type.outputs.single]);
b.global_get(global);
b.br_on_non_null(block);
translator.callFunction(initFunction, b);
b.end();
}
}
}
void _generateSetter(w.Global global, w.Global? flag) {
b.local_get(paramLocals.single);
b.global_set(global);
if (flag != null) {
b.i32_const(1); // true
b.global_set(flag);
}
}
}
class ImplicitFieldAccessorCodeGenerator extends AstCodeGenerator {
final Field field;
final bool isImplicitGetter;
final bool useUncheckedEntry;
ImplicitFieldAccessorCodeGenerator(
Translator translator,
w.FunctionType functionType,
this.field,
this.isImplicitGetter,
this.useUncheckedEntry,
) : super(translator, functionType, field);
@override
void generateInternal() {
thisLocal = preciseThisLocal = paramLocals[0];
// Conceptually not needed for implicit accessors, but currently the code
// that instantiates types uses closure information to see whether a type
// parameter was captured (and loads it from context chain) or not (and
// loads it directly from `this`).
closures = translator.getClosures(field, findCaptures: false);
final source = field.enclosingComponent!.uriToSource[field.fileUri]!;
setSourceMapSourceAndFileOffset(source, field.fileOffset);
// Implicit getter or setter
w.StructType struct = translator.classInfo[field.enclosingClass!]!.struct;
w.RefType structType = w.RefType.def(struct, nullable: false);
int fieldIndex = translator.fieldIndex[field]!;
w.ValueType fieldType = struct.fields[fieldIndex].type.unpacked;
void getThis() {
w.Local thisLocal = paramLocals[0];
b.local_get(thisLocal);
translator.convertType(b, thisLocal.type, structType);
}
if (isImplicitGetter) {
// Implicit getter
getThis();
b.struct_get(struct, fieldIndex);
translator.convertType(b, fieldType, returnType);
} else {
// Implicit setter
w.Local valueLocal = paramLocals[1];
getThis();
if (translator.needToCheckTypesFor(field) &&
translator.needToCheckImplicitSetterValue(field,
uncheckedEntry: useUncheckedEntry)) {
final boxedType = field.type.isPotentiallyNullable
? translator.topType
: translator.topTypeNonNullable;
w.Local operand = valueLocal;
if (!operand.type.isSubtypeOf(boxedType)) {
final boxedOperand = addLocal(boxedType);
b.local_get(operand);
translator.convertType(b, operand.type, boxedOperand.type);
b.local_set(boxedOperand);
operand = boxedOperand;
}
b.local_get(operand);
_generateArgumentTypeCheck(
field.name.text,
operand.type as w.RefType,
field.type,
);
}
b.local_get(valueLocal);
translator.convertType(b, valueLocal.type, fieldType);
b.struct_set(struct, fieldIndex);
assert(functionType.outputs.isEmpty);
}
b.end();
}
}
class SynchronousLambdaCodeGenerator extends AstCodeGenerator {
final Lambda lambda;
final Closures enclosingMemberClosures;
SynchronousLambdaCodeGenerator(Translator translator, Member enclosingMember,
this.lambda, this.enclosingMemberClosures)
: super(translator, lambda.function.type, enclosingMember);
@override
void generateInternal() {
closures = enclosingMemberClosures;
setSourceMapSource(lambda.functionNodeSource);
assert(lambda.functionNode.asyncMarker != AsyncMarker.Async);
setupLambdaParametersAndContexts(lambda);
translateStatement(lambda.functionNode.body!);
_implicitReturn();
b.end();
}
}
class TryBlockFinalizer {
/// `br` target to run the finalizer
final w.Label label;
/// Whether the last finalizer in the chain should return. When this is
/// `false` the block won't be used, as the block is for running finalizers
/// when returning.
bool mustHandleReturn = false;
TryBlockFinalizer(this.label);
}
/// Holds information of a switch statement, to be used when doing a backward
/// jump to it
class SwitchBackwardJumpInfo {
/// Wasm local for the value of the switched expression. For example, in a
/// `switch` like:
///
/// ```
/// switch (expr) {
/// ...
/// }
/// ```
///
/// This local holds the value of `expr`.
///
/// This local is updated with a new value when doing backward jumps.
final w.Local switchValueLocal;
/// Label of the `loop` to use when doing backward jumps
final w.Label loopLabel;
/// When compiling a `default` case, label of the `loop` in the case body, to
/// use when doing backward jumps to the same case.
w.Label? defaultLoopLabel;
SwitchBackwardJumpInfo(this.switchValueLocal, this.loopLabel)
: defaultLoopLabel = null;
}
/// Info needed to represent a switch statement using a br_table instruction.
///
/// This is used for switches on integers and enums (using their indicies). We
/// map each option to an index in the jump table and then jump directly to the
/// target case. This is much faster than iteratively comparing each cases's
/// expression to the switch expression.
///
/// Sometimes switches over ranges of ints are sparse enough that a table would
/// bloat the code compared to the iterative comparison.
class BrTableInfo {
// At least 50% of the [min, max] must be occupied for us to use `br_table`.
static const double _minimumTableOccupancy = 0.5;
// This is the maximum size where it's always worth it to use a br_table.
// If the br_table is bigger than this then we start to check sparseness.
// Below this point we always accept the potential code size hit.
static const int _maxSparseSize = 50;
int get rangeSize => _rangeSize(minValue, maxValue);
final int minValue;
final int maxValue;
final void Function(w.Local switchExprLocal) _brTableExpr;
final Map<int, SwitchCase> caseMap;
BrTableInfo._(this.minValue, this.maxValue, this.caseMap, this._brTableExpr)
: assert(!_isTooSparse(minValue, maxValue, caseMap));
/// Heuristically validate whether the provided table would be too sparse and
/// if so return null. Otherwise return the expected table.
static BrTableInfo? build(Map<int, SwitchCase> caseMap,
void Function(w.Local switchExprLocal) brTableExpr,
{required int minValue, required int maxValue}) {
// Validate the table density and size is worth putting into a br_table.
if (_isTooSparse(minValue, maxValue, caseMap)) return null;
return BrTableInfo._(minValue, maxValue, caseMap, brTableExpr);
}
static bool _isTooSparse(int min, int max, Map<int, SwitchCase> caseMap) {
int rangeSize = _rangeSize(min, max);
return (caseMap.length / rangeSize) < _minimumTableOccupancy &&
rangeSize > _maxSparseSize;
}
static int _rangeSize(int min, int max) => max - min + 1;
void emitBrTableExpr(w.InstructionsBuilder b, w.Local switchExprLocal) {
_brTableExpr(switchExprLocal);
// Normalize on 0.
if (minValue != 0) {
b.i64_const(minValue);
b.i64_sub();
}
// Now that we've normalized on 0 it should be safe to switch to i32.
b.i32_wrap_i64();
}
}
class SwitchInfo {
/// Non-nullable Wasm type of the `switch` expression. Used when the
/// expression is not nullable, and after the null check.
late final w.ValueType nullableType;
/// Nullable Wasm type of the `switch` expression. Only used when the
/// expression is nullable.
late final w.ValueType nonNullableType;
/// Generates code that will br on [successLabel] if [switchExprLocal] has the
/// correct type for the case checks on this switch. Only set for switches
/// where the switch expression is dynamic.
void Function(w.Local switchExprLocal, w.Label successLabel)?
dynamicTypeGuard;
/// Generates code that compares value of a `case` expression with the
/// `switch` expression's value. Calls [pushCaseExpr] once.
late final void Function(
w.Local switchExprLocal, w.ValueType Function() pushCaseExpr) compare;
/// The `default: ...` case, if exists.
late final SwitchCase? defaultCase;
/// The `null: ...` case, if exists.
late final SwitchCase? nullCase;
/// Info needed to compile this switch statement into a wasm br_table. If null
/// this switch statement should not use a br_table and should use comparison
/// based case matching instead.
BrTableInfo? brTable;
SwitchInfo(AstCodeGenerator codeGen, SwitchStatement node) {
final translator = codeGen.translator;
final switchExprType = codeGen.dartTypeOf(node.expression);
final switchExprClass = translator.classForType(switchExprType);
bool check<L extends Expression, C extends Constant>() =>
node.cases.expand((c) => c.expressions).every((e) =>
e is L ||
e is NullLiteral ||
(e is ConstantExpression &&
((e.constant is C &&
(translator.hierarchy.isSubInterfaceOf(
translator.classForType(codeGen.dartTypeOf(e)),
switchExprClass))) ||
e.constant is NullConstant)));
// Type objects should be compared using `==` rather than identity even
// though the specification is not very clear about it. In language versions
// >=3.0 CFE would desugar such switches to a sequence of `if` statements
// using `==`, but for language versions <3.0 it would simply emit
// `SwitchStatement` and expect back-end to handle types specially if
// required. See #60375 for more details.
bool canInvokeTypeEquality() =>
translator.typeEnvironment.isSubtypeOf(
switchExprType, translator.coreTypes.typeNullableRawType) ||
node.cases.expand((c) => c.expressions).any((e) =>
translator.typeEnvironment.isSubtypeOf(codeGen.dartTypeOf(e),
translator.coreTypes.typeNonNullableRawType));
if (node.cases.every((c) =>
c.expressions.isEmpty && c.isDefault ||
c.expressions.every((e) =>
e is NullLiteral ||
e is ConstantExpression && e.constant is NullConstant))) {
// default-only switch
nonNullableType = w.RefType.eq(nullable: false);
nullableType = w.RefType.eq(nullable: true);
compare = (switchExprLocal, pushCaseExpr) =>
throw "Comparison in default-only switch";
} else if (canInvokeTypeEquality()) {
nonNullableType = translator.runtimeTypeType;
nullableType = translator.runtimeTypeTypeNullable;
compare = (switchExprLocal, pushCaseExpr) {
// Virtual call to `Type.==`.
codeGen._virtualCall(
node, translator.coreTypes.objectEquals, _VirtualCallKind.Call,
(functionType) {
codeGen.b.local_get(switchExprLocal);
}, (functionType, paramInfo) {
pushCaseExpr();
}, useUncheckedEntry: false);
};
} else if (switchExprType is DynamicType) {
// Per spec, compare with `<case expr> == <switch expr>`. For performance,
// if we know that the cases all have the same type, we call the case
// expression's `==` implementation directly (instead of virtually calling
// `Object.==`).
//
// Note: this could be improved by directly calling a different `==` in
// each of the cases based on the case value. For now we only directly
// call a `==` if all of the cases have a compatible type.
nonNullableType = translator.topTypeNonNullable;
nullableType = translator.topType;
final Member equalsMember;
if (check<BoolLiteral, BoolConstant>()) {
equalsMember = translator.boxedBoolEquals;
} else if (check<IntLiteral, IntConstant>()) {
equalsMember = translator.boxedIntEquals;
} else if (check<StringLiteral, StringConstant>()) {
equalsMember = translator.jsStringEquals;
} else {
compare = (switchExprLocal, pushCaseExpr) {
// Virtual call to `Object.==`.
codeGen._virtualCall(node, codeGen.translator.coreTypes.objectEquals,
_VirtualCallKind.Call, (functionType) {
codeGen.b.local_get(switchExprLocal);
}, (functionType, paramInfo) {
pushCaseExpr();
}, useUncheckedEntry: false);
};
_initializeSpecialCases(node);
return;
}
final equalsMemberSignature =
translator.signatureForDirectCall(equalsMember.reference);
// Per spec, `==` can't have type, or extra (optional) positional and
// named arguments. So we don't have to check `ParamInfo` for it and
// add missing optional parameters.
assert(equalsMemberSignature.inputs.length == 2);
dynamicTypeGuard = (switchExprLocal, successLabel) {
codeGen.b.local_get(switchExprLocal);
codeGen.b.br_on_cast(
successLabel,
switchExprLocal.type as w.RefType,
equalsMemberSignature.inputs[0]
.withNullability(switchExprLocal.type.nullable) as w.RefType);
codeGen.b.drop();
};
compare = (switchExprLocal, pushCaseExpr) {
final caseExprType = pushCaseExpr();
translator.convertType(
codeGen.b, caseExprType, equalsMemberSignature.inputs[0]);
codeGen.b.local_get(switchExprLocal);
translator.convertType(
codeGen.b, switchExprLocal.type, equalsMemberSignature.inputs[1]);
codeGen.call(equalsMember.reference);
};
} else if (check<BoolLiteral, BoolConstant>()) {
// bool switch
nonNullableType = w.NumType.i32;
nullableType =
translator.classInfo[translator.boxedBoolClass]!.nullableType;
compare = (switchExprLocal, pushCaseExpr) {
codeGen.b.local_get(switchExprLocal);
pushCaseExpr();
codeGen.b.i32_eq();
};
} else if (check<IntLiteral, IntConstant>()) {
// int switch
nonNullableType = w.NumType.i64;
nullableType =
translator.classInfo[translator.boxedIntClass]!.nullableType;
// Calculate the range covered by the cases and create the jump table.
int? minValue;
int? maxValue;
Map<int, SwitchCase> caseMap = {};
for (final c in node.cases) {
for (final e in c.expressions) {
if (e is NullLiteral ||
(e is ConstantExpression && e.constant is NullConstant)) {
// Null already handled above.
continue;
}
final value = e is IntLiteral
? e.value
: ((e as ConstantExpression).constant as IntConstant).value;
caseMap[value] = c;
if (minValue == null || value < minValue) minValue = value;
if (maxValue == null || value > maxValue) maxValue = value;
}
}
if (maxValue != null) {
brTable = BrTableInfo.build(
minValue: minValue!,
maxValue: maxValue,
caseMap, (switchExprLocal) {
codeGen.b.local_get(switchExprLocal);
});
}
// Provide a compare as a fallback in case the range is too sparse.
compare = (switchExprLocal, pushCaseExpr) {
codeGen.b.local_get(switchExprLocal);
pushCaseExpr();
codeGen.b.i64_eq();
};
} else if (check<StringLiteral, StringConstant>()) {
// String switch
nonNullableType = translator.stringType;
nullableType = translator.stringTypeNullable;
compare = (switchExprLocal, pushCaseExpr) {
codeGen.b.local_get(switchExprLocal);
pushCaseExpr();
codeGen.call(translator.jsStringEquals.reference);
};
} else if (switchExprClass.isEnum) {
// If this is an applicable switch over enums, create a jump table.
bool isValid = true;
var caseMap = <int, SwitchCase>{};
int? minIndex;
int? maxIndex;
outer:
for (final c in node.cases) {
for (final e in c.expressions) {
if (e is NullLiteral ||
(e is ConstantExpression && e.constant is NullConstant)) {
// Null already handled above.
continue;
}
if (e is! ConstantExpression) {
isValid = false;
break outer;
}
final constant = e.constant;
if (constant is! InstanceConstant) {
isValid = false;
break outer;
}
if (constant.classNode != switchExprClass) {
isValid = false;
break outer;
}
final enumIndex =
(constant.fieldValues[translator.enumIndexField.fieldReference]
as IntConstant)
.value;
caseMap[enumIndex] = c;
if (maxIndex == null || enumIndex > maxIndex) maxIndex = enumIndex;
if (minIndex == null || enumIndex < minIndex) minIndex = enumIndex;
}
}
if (isValid && maxIndex != null) {
brTable = BrTableInfo.build(
minValue: minIndex!,
maxValue: maxIndex,
caseMap, (switchExprLocal) {
codeGen.b.local_get(switchExprLocal);
codeGen.call(translator.enumIndexField.getterReference);
});
}
if (brTable == null) {
// Object identity switch
nonNullableType = translator.topTypeNonNullable;
nullableType = translator.topType;
} else {
nonNullableType =
translator.classInfo[switchExprClass]!.nonNullableType;
nullableType = translator.classInfo[switchExprClass]!.nullableType;
}
// Set compare anyway for state machine handling
compare = (switchExprLocal, pushCaseExpr) {
codeGen.b.local_get(switchExprLocal);
pushCaseExpr();
codeGen.call(translator.coreTypes.identicalProcedure.reference);
};
} else {
// Object identity switch
nonNullableType = translator.topTypeNonNullable;
nullableType = translator.topType;
compare = (switchExprLocal, pushCaseExpr) {
codeGen.b.local_get(switchExprLocal);
pushCaseExpr();
codeGen.call(translator.coreTypes.identicalProcedure.reference);
};
}
_initializeSpecialCases(node);
}
void _initializeSpecialCases(SwitchStatement node) {
// Special cases
defaultCase = node.cases
.cast<SwitchCase?>()
.firstWhere((c) => c!.isDefault, orElse: () => null);
nullCase = node.cases.cast<SwitchCase?>().firstWhere(
(c) => c!.expressions.any((e) =>
e is NullLiteral ||
e is ConstantExpression && e.constant is NullConstant),
orElse: () => null);
}
}
enum _VirtualCallKind {
Get,
Set,
Call;
@override
String toString() {
return switch (this) {
_VirtualCallKind.Get => "get",
_VirtualCallKind.Set => "set",
_VirtualCallKind.Call => "call"
};
}
bool get isGetter => this == _VirtualCallKind.Get;
bool get isSetter => this == _VirtualCallKind.Set;
}
extension MacroAssembler on w.InstructionsBuilder {
/// If the given [outputs] of a call contain bottom types then we will emit an
/// `unreachable` instruction.
///
/// This can help wasm compilers / wasm runtimes to optimize things more as
/// they know control flow has ended here.
List<w.ValueType> emitUnreachableIfNoResult(List<w.ValueType> outputs) {
for (int i = 0; i < outputs.length; ++i) {
final output = outputs[i];
if (output is w.RefType &&
output.heapType == w.HeapType.none &&
!output.nullable) {
unreachable();
break;
}
}
return outputs;
}
/// Switches on the [pushBranchExpression] and calls [handleCase] for each
/// case or [handleDefault] for the default case.
///
/// Assumes that [handleCase] and [handleDefault] will push [outputs] on the
/// stack.
///
/// Leaves [outputs] on the stack.
void emitDenseTableBranch(
List<w.ValueType> outputs,
int n,
void Function() pushBranchExpression,
void Function(int) handleCase,
void Function() handleDefault) {
final done = block([], outputs);
final defaultCase = block();
final labelStack = <w.Label>[];
for (int i = 0; i < n; ++i) {
labelStack.add(block());
}
pushBranchExpression();
br_table(labelStack, defaultCase);
for (int i = n - 1; i >= 0; --i) {
end();
handleCase(i);
br(done);
}
end(); // defaultCase
handleDefault();
end(); // done
}
void incrementingLoop(
{required void Function() pushStart,
required void Function() pushLimit,
required void Function(w.Local) genBody,
int step = 1}) {
final endLoop = block();
final limitVar = addLocal(w.NumType.i32);
final loopVar = addLocal(w.NumType.i32);
pushLimit();
local_set(limitVar);
pushStart();
local_set(loopVar);
final loopLabel = loop();
local_get(loopVar);
local_get(limitVar);
i32_ge_u();
br_if(endLoop);
genBody(loopVar);
local_get(loopVar);
i32_const(step);
i32_add();
local_set(loopVar);
br(loopLabel);
end();
end();
}
/// [ref Array] [ref Array] -> [ref Array]
///
/// Takes the two arrays on the stack and concatenates them into a single
/// array. They both must have the same type provided as [arrayRefType].
/// Uses [pushDefaultElement] as the filler element that holds space in the
/// array until values are copied over.
void concatenateWasmArrays(w.ArrayType arrayType,
{required void Function(
w.InstructionsBuilder b, w.Local oldArray, w.Local newArray)
pushDefaultElement}) {
final arrayRefType = w.RefType(arrayType, nullable: false);
final newArray = addLocal(arrayRefType);
final oldArray = addLocal(arrayRefType);
final newArrayLen = addLocal(w.NumType.i32);
final oldArrayLen = addLocal(w.NumType.i32);
final joinedArray = addLocal(arrayRefType);
local_set(newArray);
local_set(oldArray);
pushDefaultElement(this, oldArray, newArray);
local_get(newArray);
array_len();
local_set(newArrayLen);
local_get(oldArray);
array_len();
local_tee(oldArrayLen);
local_get(newArrayLen);
i32_add();
array_new(arrayType);
local_tee(joinedArray);
i32_const(0);
local_get(oldArray);
i32_const(0);
local_get(oldArrayLen);
array_copy(arrayType, arrayType);
local_get(joinedArray);
local_get(oldArrayLen);
local_get(newArray);
i32_const(0);
local_get(newArrayLen);
array_copy(arrayType, arrayType);
local_get(joinedArray);
end();
}
/// `[i32] -> [i32]`
///
/// Consumes a `i32` class ID, leaves an `i32` as `bool` for whether
/// the class ID is in the given list of ranges.
void emitClassIdRangeCheck(List<Range> ranges) {
final rangeValues = ranges.map((r) => (range: r, value: null)).toList();
classIdSearch<Null>(rangeValues, [w.NumType.i32], (_) {
i32_const(1);
}, () {
i32_const(0);
});
}
/// `[i32] -> [outputs]`
///
/// Consumes a `i32` class ID and checks whether it lies within one of the
/// given [ranges] using a linear or binary search.
///
/// The [ranges] have to be non-empty, non-overlapping and sorted.
///
/// Calls [match] on a matching value and [miss] if provided and no match was
/// found.
///
/// Assumes [match] and [miss] leave [outputs] on the stack.
void classIdSearch<T>(
List<({Range range, T value})> ranges,
List<w.ValueType> outputs,
void Function(T) match,
void Function()? miss) {
final bool linearSearch = ranges.length <= 3;
if (traceEnabled) {
comment('Class id ${linearSearch ? 'linear' : 'binary'} search:');
for (final (:range, :value) in ranges) {
comment(' - $range -> $value');
}
}
if (linearSearch) {
_linearClassIdSearch<T>(ranges, outputs, match, miss);
} else {
_binaryClassIdSearch<T>(ranges, outputs, match, miss);
}
}
void _binaryClassIdSearch<T>(
List<({Range range, T value})> ranges,
List<w.ValueType> outputs,
void Function(T) match,
void Function()? miss) {
assert(ranges.isNotEmpty || miss != null);
if (miss != null && ranges.isEmpty) {
drop();
miss();
return;
}
w.Local classId = addLocal(w.NumType.i32);
local_set(classId);
final done = block([], outputs);
final fail = block();
void search(int left, int right, Range searchArea) {
if (left == right) {
final entry = ranges[left];
final range = entry.range;
assert(searchArea.containsRange(range));
if (miss == null || range.containsRange(searchArea)) {
match(entry.value);
br(done);
return;
}
local_get(classId);
if (range.length == 1) {
i32_const(range.start);
i32_eq();
} else {
if (searchArea.end <= range.end) {
i32_const(range.start);
i32_ge_u();
} else if (range.start <= searchArea.start) {
i32_const(range.end);
i32_le_u();
} else {
i32_const(range.start);
i32_sub();
i32_const(range.length);
i32_lt_u();
}
}
if_();
match(entry.value);
br(done);
end();
br(fail);
return;
}
final mid = (left + right) ~/ 2;
final midRange = ranges[mid].range;
local_get(classId);
i32_const(midRange.end);
i32_le_u();
if_();
search(left, mid, Range(searchArea.start, midRange.end));
end();
search(mid + 1, right, Range(midRange.end + 1, searchArea.end));
}
search(0, ranges.length - 1, Range(0, 0xffffffff));
end(); // fail
if (miss != null) {
miss();
br(done);
} else {
unreachable();
}
end(); // done
}
void _linearClassIdSearch<T>(
List<({Range range, T value})> ranges,
List<w.ValueType> outputs,
void Function(T) match,
void Function()? miss) {
assert(ranges.isNotEmpty || miss != null);
if (miss != null && ranges.isEmpty) {
drop();
miss();
return;
}
w.Local classId = addLocal(w.NumType.i32);
local_set(classId);
final done = block([], outputs);
for (final (:range, :value) in ranges) {
local_get(classId);
i32_const(range.start);
if (range.length == 1) {
i32_eq();
} else {
i32_sub();
i32_const(range.length);
i32_lt_u();
}
if_();
match(value);
br(done);
end();
}
if (miss != null) {
miss();
br(done);
} else {
unreachable();
}
end(); // done
}
/// `[ref _Closure] -> [i32]`
///
/// Given a closure reference returns whether the closure is an
/// instantiation.
void emitInstantiationClosureCheck(Translator translator) {
ref_cast(w.RefType(translator.closureLayouter.closureBaseStruct,
nullable: false));
struct_get(translator.closureLayouter.closureBaseStruct,
FieldIndex.closureContext);
ref_test(w.RefType(
translator.closureLayouter.instantiationContextBaseStruct,
nullable: false));
}
/// `[ref _Closure] -> [ref #ClosureBase]`
///
/// Given an instantiation closure returns the instantiated closure.
void emitGetInstantiatedClosure(Translator translator) {
// instantiation.context
ref_cast(w.RefType(translator.closureLayouter.closureBaseStruct,
nullable: false));
struct_get(translator.closureLayouter.closureBaseStruct,
FieldIndex.closureContext);
// instantiation.context.inner
ref_cast(w.RefType(
translator.closureLayouter.instantiationContextBaseStruct,
nullable: false));
struct_get(translator.closureLayouter.instantiationContextBaseStruct,
FieldIndex.instantiationContextInner);
}
/// `[ref _Closure] -> [i32]`
///
/// Given a closure returns whether the closure is a tear-off.
void emitTearOffCheck(Translator translator) {
ref_cast(w.RefType(translator.closureLayouter.closureBaseStruct,
nullable: false));
struct_get(translator.closureLayouter.closureBaseStruct,
FieldIndex.closureContext);
ref_test(translator.topTypeNonNullable);
}
/// `[ref _Closure] -> [ref #Top]`
///
/// Given a closure returns the receiver of the closure.
void emitGetTearOffReceiver(Translator translator) {
ref_cast(w.RefType(translator.closureLayouter.closureBaseStruct,
nullable: false));
struct_get(translator.closureLayouter.closureBaseStruct,
FieldIndex.closureContext);
ref_cast(translator.topTypeNonNullable);
}
/// `[ref _Closure] -> [ref Any]
///
/// Given a closure returns the vtable of the closure.
void emitGetClosureVtable(Translator translator) {
ref_cast(w.RefType(translator.closureLayouter.closureBaseStruct,
nullable: false));
struct_get(
translator.closureLayouter.closureBaseStruct, FieldIndex.closureVtable);
}
/// Will restore all context locals and `this` from a suspend state.
void restoreSuspendStateContext(
w.Local suspendStateLocal,
w.StructType suspendStateStruct,
int suspendStateContextField,
Closures closures,
Context? context,
w.Local? thisLocal,
{FunctionNode? cloneContextFor}) {
if (context != null) {
assert(!context.isEmpty);
local_get(suspendStateLocal);
struct_get(suspendStateStruct, suspendStateContextField);
ref_cast(context.currentLocal.type as w.RefType);
local_set(context.currentLocal);
if (context.owner == cloneContextFor) {
context.currentLocal =
cloneFunctionLevelContext(closures, context, cloneContextFor!);
}
restoreThisAndContextChain(context, thisLocal);
}
}
/// Will restore the parent context chain and `this` (if captured)
///
/// Assumes the innermost context is already loaded.
void restoreThisAndContextChain(
Context innermostContext, w.Local? thisLocal) {
bool restoredThis = false;
Context? context = innermostContext;
while (context != null) {
if (context.containsThis) {
assert(!restoredThis);
local_get(context.currentLocal);
struct_get(context.struct, context.thisFieldIndex);
ref_as_non_null();
local_set(thisLocal!);
restoredThis = true;
}
final parent = context.parent;
if (parent != null) {
assert(!parent.isEmpty);
local_get(context.currentLocal);
struct_get(context.struct, context.parentFieldIndex);
ref_as_non_null();
local_set(parent.currentLocal);
}
context = parent;
}
}
/// Clones the [context] and returns a local to the clone it.
///
/// It is assumed that the context is a function-level context.
w.Local cloneFunctionLevelContext(
Closures closures, Context context, FunctionNode functionNode) {
final w.Local srcContext = context.currentLocal;
final w.Local destContext = addLocal(context.currentLocal.type);
struct_new_default(context.struct);
local_set(destContext);
void copyCapture(TreeNode node) {
Capture? capture = closures.captures[node];
if (capture != null) {
assert(capture.context == context);
local_get(destContext);
local_get(srcContext);
struct_get(context.struct, capture.fieldIndex);
struct_set(context.struct, capture.fieldIndex);
}
}
if (context.containsThis) {
local_get(destContext);
local_get(srcContext);
struct_get(context.struct, context.thisFieldIndex);
struct_set(context.struct, context.thisFieldIndex);
}
if (context.parent != null) {
local_get(destContext);
local_get(srcContext);
struct_get(context.struct, context.parentFieldIndex);
struct_set(context.struct, context.parentFieldIndex);
}
functionNode.positionalParameters.forEach(copyCapture);
functionNode.namedParameters.forEach(copyCapture);
functionNode.typeParameters.forEach(copyCapture);
return destContext;
}
List<w.ValueType> invoke(CallTarget target, {bool forceInline = false}) {
if (target.supportsInlining && (target.shouldInline || forceInline)) {
final List<w.Local> inlinedLocals =
target.signature.inputs.map((t) => addLocal(t)).toList();
for (w.Local local in inlinedLocals.reversed) {
local_set(local);
}
final w.Label callBlock = block(const [], target.signature.outputs);
comment('Inlined ${target.name}');
target.inliningCodeGen.generate(this, inlinedLocals, callBlock);
} else {
comment('Direct call to ${target.name}');
call(target.function);
}
return emitUnreachableIfNoResult(target.signature.outputs);
}
/// Pushes fields common to all Dart objects (class id, id hash).
void pushObjectHeaderFields(Translator translator, ClassInfo classInfo) {
pushClassIdToStack(translator, classInfo.classId);
i32_const(initialIdentityHash);
}
void pushClassIdToStack(Translator translator, ClassId classId) {
switch (classId) {
case AbsoluteClassId():
i32_const(classId.value);
case RelativeClassId():
i32_const(classId.relativeValue);
translator.pushModuleId(this);
translator.callReference(translator.globalizeClassId.reference, this);
}
}
void loadClassId(Translator translator, w.ValueType receiverType) {
assert(!receiverType.nullable);
assert(receiverType.isSubtypeOf(translator.topTypeNonNullable));
struct_get(
translator.classInfoCollector.topInfo.struct, FieldIndex.classId);
}
}
/// A call target that may be called with a direct call or may be inlined.
abstract class CallTarget {
/// The wasm signature of the call target (that may be called or inlined).
final w.FunctionType signature;
CallTarget(this.signature);
/// Whether this call target supports inlining.
bool get supportsInlining => false;
/// Whether we should inline (different call targets may have semantic
/// knowledge about how big the body would be and whether we should inline or
/// not).
bool get shouldInline => false;
/// The code generator to use for inlining the body.
CodeGenerator get inliningCodeGen => throw 'No inlining support (yet).';
/// The name of this target
///
/// The inliner can use this to emit comments for the inlined target.
String get name;
/// The wasm target function to call.
///
/// This should only be accessed if caller intents to call it, as it will
/// enqueue the function in the compilation queue.
w.BaseFunction get function;
}
class AstCallTarget extends CallTarget {
final Translator _translator;
final Reference _reference;
AstCallTarget(super.signature, this._translator, this._reference);
@override
String get name => _translator.functions.getFunctionName(_reference);
@override
bool get supportsInlining => _translator.supportsInlining(_reference);
@override
bool get shouldInline => _translator.shouldInline(_reference, signature);
@override
CodeGenerator get inliningCodeGen => getInlinableMemberCodeGenerator(
_translator, AsyncMarker.Sync, signature, _reference)!;
@override
w.BaseFunction get function => _translator.functions.getFunction(_reference);
}
bool guardCanMatchJSException(Translator translator, DartType guard) {
if (guard is DynamicType) {
return true;
}
if (guard is InterfaceType) {
return translator.hierarchy
.isSubInterfaceOf(translator.javaScriptErrorClass, guard.classNode);
}
if (guard is TypeParameterType) {
return guardCanMatchJSException(translator, guard.bound);
}
return false;
}