blob: 86b1cc38e1069117948f8ea639355888c96025ef [file] [log] [blame]
// 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>
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) {
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.module)
.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) {
translator.constants.instantiateConstant(
b,
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);
translator.constants.instantiateConstant(
b,
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.module));
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.module));
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();
}
// 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);
}
@override
w.ValueType visitLet(Let node, w.ValueType expectedType) {
translateStatement(node.variable);
return translateExpression(node.body, expectedType);
}
@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.text;
final forwarder = translator
.getDynamicForwardersForModule(b.module)
.getDynamicInvocationForwarder(memberName);
// Evaluate receiver
translateExpression(receiver, translator.topType);
final nullableReceiverLocal = addLocal(translator.topType);
b.local_set(nullableReceiverLocal);
// Evaluate type arguments.
final typeArgsLocal = addLocal(
makeArray(translator.typeArrayType, typeArguments.length,
(elementType, elementIdx) {
translator.types.makeType(this, typeArguments[elementIdx]);
}));
b.local_set(typeArgsLocal);
// Evaluate positional arguments
final positionalArgsLocal = addLocal(makeArray(
translator.nullableObjectArrayType, positionalArguments.length,
(elementType, elementIdx) {
translateExpression(positionalArguments[elementIdx], elementType);
}));
b.local_set(positionalArgsLocal);
// 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 List<MapEntry<String, w.Local>> namedArgumentLocals = [];
for (final namedArgument in namedArguments) {
translateExpression(namedArgument.value, translator.topType);
final argumentLocal = addLocal(translator.topType);
b.local_set(argumentLocal);
namedArgumentLocals.add(MapEntry(namedArgument.name, argumentLocal));
}
namedArgumentLocals.sort((e1, e2) => e1.key.compareTo(e2.key));
// Create named argument array
final namedArgsLocal = addLocal(
makeArray(translator.nullableObjectArrayType, namedArguments.length * 2,
(elementType, elementIdx) {
if (elementIdx % 2 == 0) {
final name = namedArgumentLocals[elementIdx ~/ 2].key;
final w.ValueType symbolValueType =
translator.classInfo[translator.symbolClass]!.nonNullableType;
translator.constants.instantiateConstant(
b, SymbolConstant(name, null), symbolValueType);
} else {
final local = namedArgumentLocals[elementIdx ~/ 2].value;
b.local_get(local);
}
}));
b.local_set(namedArgsLocal);
final nullBlock = b.block([], [translator.topTypeNonNullable]);
b.local_get(nullableReceiverLocal);
b.br_on_non_null(nullBlock);
// Throw `NoSuchMethodError`. Normally this needs to happen via instance
// invocation of `noSuchMethod` (done in [_callNoSuchMethod]), but we don't
// have a `Null` class in dart2wasm so we throw directly.
b.local_get(nullableReceiverLocal);
createInvocationObject(translator, b, memberName, typeArgsLocal,
positionalArgsLocal, namedArgsLocal);
call(translator.noSuchMethodErrorThrowWithInvocation.reference);
b.unreachable();
b.end(); // nullBlock
b.local_get(typeArgsLocal);
b.local_get(positionalArgsLocal);
b.local_get(namedArgsLocal);
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.module)
.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) {
translator.constants.instantiateConstant(
b, 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.module),
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.text;
final forwarder = translator
.getDynamicForwardersForModule(b.module)
.getDynamicGetForwarder(memberName);
// Evaluate receiver
translateExpression(receiver, translator.topType);
final nullableReceiverLocal = addLocal(translator.topType);
b.local_set(nullableReceiverLocal);
final nullBlock = b.block([], [translator.topTypeNonNullable]);
b.local_get(nullableReceiverLocal);
b.br_on_non_null(nullBlock);
// Throw `NoSuchMethodError`. Normally this needs to happen via instance
// invocation of `noSuchMethod` (done in [_callNoSuchMethod]), but we don't
// have a `Null` class in dart2wasm so we throw directly.
b.local_get(nullableReceiverLocal);
createGetterInvocationObject(translator, b, memberName);
call(translator.noSuchMethodErrorThrowWithInvocation.reference);
b.unreachable();
b.end(); // nullBlock
// 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.text;
final forwarder = translator
.getDynamicForwardersForModule(b.module)
.getDynamicSetForwarder(memberName);
// Evaluate receiver
translateExpression(receiver, translator.topType);
final nullableReceiverLocal = addLocal(translator.topType);
b.local_set(nullableReceiverLocal);
// Evaluate positional arg
translateExpression(value, translator.topType);
final positionalArgLocal = addLocal(translator.topType);
b.local_set(positionalArgLocal);
final nullBlock = b.block([], [translator.topTypeNonNullable]);
b.local_get(nullableReceiverLocal);
b.br_on_non_null(nullBlock);
// Throw `NoSuchMethodError`. Normally this needs to happen via instance
// invocation of `noSuchMethod` (done in [_callNoSuchMethod]), but we don't
// have a `Null` class in dart2wasm so we throw directly.
b.local_get(nullableReceiverLocal);
createSetterInvocationObject(translator, b, memberName, positionalArgLocal);
call(translator.noSuchMethodErrorThrowWithInvocation.reference);
b.unreachable();
b.end(); // nullBlock
// Call set forwarder
b.local_get(positionalArgLocal);
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.module,
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.module)
.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.getVtableFieldType(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
b.local_get(closureTemp);
b.struct_get(representation.closureStruct, FieldIndex.closureVtable);
b.struct_get(
representation.vtableStruct, FieldIndex.vtableInstantiationFunction);
// Call instantiation function
b.call_ref(representation.instantiationFunctionType);
return representation.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];
translator.constants
.instantiateConstant(b, 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 {
translator.constants
.instantiateConstant(b, 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.module));
return expectedType;
}
@override
w.ValueType visitConstantExpression(
ConstantExpression node, w.ValueType expectedType) {
translator.constants.instantiateConstant(b, node.constant, expectedType);
return expectedType;
}
@override
w.ValueType visitNullLiteral(NullLiteral node, w.ValueType expectedType) {
translator.constants.instantiateConstant(b, NullConstant(), expectedType);
return expectedType;
}
@override
w.ValueType visitStringLiteral(StringLiteral node, w.ValueType expectedType) {
translator.constants
.instantiateConstant(b, StringConstant(node.value), expectedType);
return expectedType;
}
@override
w.ValueType visitBoolLiteral(BoolLiteral node, w.ValueType expectedType) {
translator.constants
.instantiateConstant(b, BoolConstant(node.value), expectedType);
return expectedType;
}
@override
w.ValueType visitIntLiteral(IntLiteral node, w.ValueType expectedType) {
translator.constants
.instantiateConstant(b, IntConstant(node.value), expectedType);
return expectedType;
}
@override
w.ValueType visitDoubleLiteral(DoubleLiteral node, w.ValueType expectedType) {
translator.constants
.instantiateConstant(b, 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.module)
.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.module)
.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.module)
.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,
SymbolConstant(member.name.text, null),
translator.classInfo[translator.symbolClass]!.nonNullableType);
call(translator
.noSuchMethodErrorThrowUnimplementedExternalMemberError.reference);
b.unreachable();
}
}
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.module);
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;
}
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;
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 || e.constant is NullConstant) &&
(translator.hierarchy.isSubInterfaceOf(
translator.classForType(codeGen.dartTypeOf(e)),
switchExprClass))));
// 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,
SubtypeCheckMode.withNullabilities) ||
node.cases.expand((c) => c.expressions).any((e) =>
translator.typeEnvironment.isSubtypeOf(
codeGen.dartTypeOf(e),
translator.coreTypes.typeNonNullableRawType,
SubtypeCheckMode.withNullabilities));
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 `Object.==`.
codeGen._virtualCall(
node, translator.coreTypes.objectEquals, _VirtualCallKind.Call,
(functionType) {
codeGen.b.local_get(switchExprLocal);
}, (functionType, paramInfo) {
pushCaseExpr();
}, useUncheckedEntry: false);
};
} else if (switchExprType is DynamicType) {
// Object equality switch
nonNullableType = translator.topTypeNonNullable;
nullableType = translator.topType;
// Per spec, compare with `<case expr> == <switch expr>`.
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 {
equalsMember = translator.coreTypes.identicalProcedure;
}
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;
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 {
// Object identity switch
nonNullableType = translator.topTypeNonNullable;
nullableType = translator.topType;
compare = (switchExprLocal, pushCaseExpr) {
codeGen.b.local_get(switchExprLocal);
pushCaseExpr();
codeGen.call(translator.coreTypes.identicalProcedure.reference);
};
}
// 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;
}
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;
}