mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 00:09:49 +00:00
35e35f5786
Default parameter values are implemented in dart2wasm via caller-side substitution. When the same parameter has different default values across different implementations within the same selector, a special sentinel value is passed by the caller, and the callee checks for this value and substitutes the actual default value. Change-Id: I8235145f93c2aee7e9ef603456380253b836fcef Cq-Include-Trybots: luci.dart.try:dart2wasm-linux-x64-d8-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/259040 Commit-Queue: Aske Simon Christensen <askesc@google.com> Reviewed-by: Joshua Litt <joshualitt@google.com>
2436 lines
81 KiB
Dart
2436 lines
81 KiB
Dart
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
import 'package:dart2wasm/class_info.dart';
|
|
import 'package:dart2wasm/closures.dart';
|
|
import 'package:dart2wasm/dispatch_table.dart';
|
|
import 'package:dart2wasm/intrinsics.dart';
|
|
import 'package:dart2wasm/param_info.dart';
|
|
import 'package:dart2wasm/reference_extensions.dart';
|
|
import 'package:dart2wasm/translator.dart';
|
|
import 'package:dart2wasm/types.dart';
|
|
|
|
import 'package:kernel/ast.dart';
|
|
import 'package:kernel/type_environment.dart';
|
|
|
|
import 'package:wasm_builder/wasm_builder.dart' as w;
|
|
|
|
/// 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 [wrap]
|
|
/// method, which emits appropriate conversion code if the produced type is not
|
|
/// a subtype of the expected type.
|
|
class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
|
|
implements InitializerVisitor<void>, StatementVisitor<void> {
|
|
final Translator translator;
|
|
final w.DefinedFunction function;
|
|
final Reference reference;
|
|
late final List<w.Local> paramLocals;
|
|
final w.Label? returnLabel;
|
|
|
|
late final Intrinsifier intrinsifier;
|
|
late final StaticTypeContext typeContext;
|
|
late final w.Instructions b;
|
|
|
|
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 = {};
|
|
final List<TryBlockFinalizer> finalizers = [];
|
|
final List<w.Label> tryLabels = [];
|
|
final Map<LabeledStatement, w.Label> labels = {};
|
|
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.
|
|
///
|
|
/// The [paramLocals] and [returnLabel] parameters can be used to generate
|
|
/// code for an inlined function by specifying the locals containing the
|
|
/// parameters (instead of the function inputs) and the label to jump to on
|
|
/// return (instead of emitting a `return` instruction).
|
|
CodeGenerator(this.translator, this.function, this.reference,
|
|
{List<w.Local>? paramLocals, this.returnLabel}) {
|
|
this.paramLocals = paramLocals ?? function.locals;
|
|
intrinsifier = Intrinsifier(this);
|
|
typeContext = StaticTypeContext(member, translator.typeEnvironment);
|
|
b = function.body;
|
|
}
|
|
|
|
w.Module get m => translator.m;
|
|
|
|
Member get member => reference.asMember;
|
|
|
|
w.ValueType get returnType => translator
|
|
.outputOrVoid(returnLabel?.targetTypes ?? function.type.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) {
|
|
return function.addLocal(type);
|
|
}
|
|
|
|
DartType dartTypeOf(Expression exp) {
|
|
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
|
|
void defaultInitializer(Initializer node) {
|
|
unimplemented(node, node.runtimeType, const []);
|
|
}
|
|
|
|
@override
|
|
w.ValueType defaultExpression(Expression node, w.ValueType expectedType) {
|
|
unimplemented(node, node.runtimeType, [expectedType]);
|
|
return expectedType;
|
|
}
|
|
|
|
@override
|
|
void defaultStatement(Statement node) {
|
|
unimplemented(node, node.runtimeType, const []);
|
|
}
|
|
|
|
/// Generate code for the member.
|
|
void generate() {
|
|
// Build closure information.
|
|
closures = Closures(this);
|
|
|
|
Member member = this.member;
|
|
|
|
if (reference.isTearOffReference) {
|
|
return generateTearOffGetter(member as Procedure);
|
|
}
|
|
|
|
if (intrinsifier.generateMemberIntrinsic(
|
|
reference, function, paramLocals, returnLabel)) {
|
|
b.end();
|
|
return;
|
|
}
|
|
|
|
if (member.isExternal) {
|
|
final text =
|
|
"Unimplemented external member $member at ${member.location}";
|
|
print(text);
|
|
b.comment(text);
|
|
b.unreachable();
|
|
b.end();
|
|
return;
|
|
}
|
|
|
|
if (member is Field) {
|
|
if (member.isStatic) {
|
|
return generateStaticFieldInitializer(member);
|
|
} else {
|
|
return generateImplicitAccessor(member);
|
|
}
|
|
}
|
|
|
|
if (member.function!.asyncMarker == AsyncMarker.Async &&
|
|
!reference.isAsyncInnerReference) {
|
|
// Generate the async wrapper function, i.e. the function that gets
|
|
// called when an async function is called. The inner function, containing
|
|
// the body of the async function, is marked as an async inner reference
|
|
// and is generated separately.
|
|
Procedure procedure = member as Procedure;
|
|
w.BaseFunction inner =
|
|
translator.functions.getFunction(procedure.asyncInnerReference);
|
|
int parameterOffset = _initializeThis(member);
|
|
return generateAsyncWrapper(procedure.function, inner, parameterOffset);
|
|
}
|
|
|
|
return generateBody(member);
|
|
}
|
|
|
|
void generateTearOffGetter(Procedure procedure) {
|
|
ClosureImplementation closure = translator.getTearOffClosure(procedure);
|
|
w.StructType struct = closure.representation.closureStruct;
|
|
|
|
ClassInfo info = translator.classInfo[translator.functionClass]!;
|
|
translator.functions.allocateClass(info.classId);
|
|
|
|
b.i32_const(info.classId);
|
|
b.i32_const(initialIdentityHash);
|
|
b.local_get(paramLocals[0]);
|
|
b.global_get(closure.vtable);
|
|
b.struct_new(struct);
|
|
b.end();
|
|
}
|
|
|
|
void generateStaticFieldInitializer(Field field) {
|
|
// Static field initializer function
|
|
assert(reference == field.fieldReference);
|
|
closures.findCaptures(field);
|
|
closures.collectContexts(field);
|
|
closures.buildContexts();
|
|
|
|
w.Global global = translator.globals.getGlobal(field);
|
|
w.Global? flag = translator.globals.getGlobalInitializedFlag(field);
|
|
wrap(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(
|
|
function, global.type.type, function.type.outputs.single);
|
|
b.end();
|
|
}
|
|
|
|
void generateImplicitAccessor(Field field) {
|
|
// Implicit getter or setter
|
|
w.StructType struct = translator.classInfo[field.enclosingClass!]!.struct;
|
|
int fieldIndex = translator.fieldIndex[field]!;
|
|
w.ValueType fieldType = struct.fields[fieldIndex].type.unpacked;
|
|
|
|
void getThis() {
|
|
w.Local thisLocal = paramLocals[0];
|
|
w.RefType structType = w.RefType.def(struct, nullable: false);
|
|
b.local_get(thisLocal);
|
|
translator.convertType(function, thisLocal.type, structType);
|
|
}
|
|
|
|
if (reference.isImplicitGetter) {
|
|
// Implicit getter
|
|
getThis();
|
|
b.struct_get(struct, fieldIndex);
|
|
translator.convertType(function, fieldType, returnType);
|
|
} else {
|
|
// Implicit setter
|
|
w.Local valueLocal = paramLocals[1];
|
|
getThis();
|
|
b.local_get(valueLocal);
|
|
translator.convertType(function, valueLocal.type, fieldType);
|
|
b.struct_set(struct, fieldIndex);
|
|
}
|
|
b.end();
|
|
}
|
|
|
|
/// Generate the async wrapper for an async function and its associated
|
|
/// stub function.
|
|
///
|
|
/// The async wrapper is the outer function that gets called when the async
|
|
/// function is called. It bundles up the arguments to the function into an
|
|
/// arguments struct along with a reference to the stub function.
|
|
///
|
|
/// This struct is passed to the async helper, which allocates a new stack and
|
|
/// calls the stub function on that stack.
|
|
///
|
|
/// The stub function unwraps the arguments from the struct and calls the
|
|
/// inner function, containing the implementation of the async function.
|
|
void generateAsyncWrapper(
|
|
FunctionNode functionNode, w.BaseFunction inner, int parameterOffset) {
|
|
w.DefinedFunction stub =
|
|
m.addFunction(translator.functions.asyncStubFunctionType);
|
|
w.BaseFunction asyncHelper =
|
|
translator.functions.getFunction(translator.asyncHelper.reference);
|
|
|
|
w.Instructions stubBody = stub.body;
|
|
w.Local stubArguments = stub.locals[0];
|
|
w.Local stubStack = stub.locals[1];
|
|
|
|
// Set up the type parameter to local mapping, in case a type parameter is
|
|
// used in the return type.
|
|
int paramIndex = parameterOffset;
|
|
for (TypeParameter typeParam in functionNode.typeParameters) {
|
|
typeLocals[typeParam] = paramLocals[paramIndex++];
|
|
}
|
|
|
|
// Push the type argument to the async helper, specifying the type argument
|
|
// of the returned `Future`.
|
|
DartType returnType = functionNode.returnType;
|
|
DartType innerType = returnType is InterfaceType &&
|
|
returnType.classNode == translator.coreTypes.futureClass
|
|
? returnType.typeArguments.single
|
|
: const DynamicType();
|
|
types.makeType(this, innerType);
|
|
|
|
// Create struct for stub reference and arguments
|
|
w.StructType baseStruct = translator.functions.asyncStubBaseStruct;
|
|
w.StructType argsStruct = m.addStructType("${function.functionName} (args)",
|
|
fields: baseStruct.fields, superType: baseStruct);
|
|
|
|
// Push stub reference
|
|
w.Global stubGlobal = translator.makeFunctionRef(stub);
|
|
b.global_get(stubGlobal);
|
|
|
|
// Transfer function arguments to inner
|
|
w.Local argsLocal =
|
|
stub.addLocal(w.RefType.def(argsStruct, nullable: false));
|
|
stubBody.local_get(stubArguments);
|
|
translator.convertType(stub, stubArguments.type, argsLocal.type);
|
|
stubBody.local_set(argsLocal);
|
|
int arity = function.type.inputs.length;
|
|
for (int i = 0; i < arity; i++) {
|
|
int fieldIndex = argsStruct.fields.length;
|
|
w.ValueType type = function.locals[i].type;
|
|
argsStruct.fields.add(w.FieldType(type, mutable: false));
|
|
b.local_get(function.locals[i]);
|
|
stubBody.local_get(argsLocal);
|
|
stubBody.struct_get(argsStruct, fieldIndex);
|
|
}
|
|
b.struct_new(argsStruct);
|
|
|
|
// Call async helper
|
|
b.call(asyncHelper);
|
|
translator.convertType(function, asyncHelper.type.outputs.single,
|
|
function.type.outputs.single);
|
|
b.end();
|
|
|
|
// Call inner function from stub
|
|
stubBody.local_get(stubStack);
|
|
stubBody.call(inner);
|
|
translator.convertType(
|
|
stub, inner.type.outputs.single, stub.type.outputs.single);
|
|
stubBody.end();
|
|
}
|
|
|
|
void generateBody(Member member) {
|
|
ParameterInfo paramInfo = translator.paramInfoFor(reference);
|
|
int parameterOffset = _initializeThis(member);
|
|
int implicitParams = parameterOffset + paramInfo.typeParamCount;
|
|
|
|
void setupParamLocal(
|
|
VariableDeclaration variable, int index, Constant? defaultValue) {
|
|
w.Local local = paramLocals[implicitParams + index];
|
|
locals[variable] = local;
|
|
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.
|
|
b.local_get(local);
|
|
translator.constants.instantiateConstant(
|
|
function, b, ParameterInfo.defaultValueSentinel, local.type);
|
|
b.ref_eq();
|
|
b.if_();
|
|
wrap(variable.initializer!, local.type);
|
|
b.local_set(local);
|
|
b.end();
|
|
}
|
|
}
|
|
|
|
List<VariableDeclaration> positional =
|
|
member.function!.positionalParameters;
|
|
for (int i = 0; i < positional.length; i++) {
|
|
setupParamLocal(positional[i], i, paramInfo.positional[i]);
|
|
}
|
|
List<VariableDeclaration> named = member.function!.namedParameters;
|
|
for (var param in named) {
|
|
setupParamLocal(
|
|
param, paramInfo.nameIndex[param.name]!, paramInfo.named[param.name]);
|
|
}
|
|
List<TypeParameter> typeParameters = member is Constructor
|
|
? member.enclosingClass.typeParameters
|
|
: member.function!.typeParameters;
|
|
for (int i = 0; i < typeParameters.length; i++) {
|
|
typeLocals[typeParameters[i]] = paramLocals[parameterOffset + i];
|
|
}
|
|
|
|
// 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));
|
|
b.local_get(local);
|
|
translator.convertType(function, local.type, newLocal.type);
|
|
b.local_set(newLocal);
|
|
locals[parameter] = newLocal;
|
|
}
|
|
});
|
|
|
|
closures.findCaptures(member);
|
|
closures.collectContexts(member);
|
|
if (member is Constructor) {
|
|
for (Field field in member.enclosingClass.fields) {
|
|
if (field.isInstanceMember && field.initializer != null) {
|
|
closures.collectContexts(field.initializer!,
|
|
container: member.function);
|
|
}
|
|
}
|
|
}
|
|
closures.buildContexts();
|
|
|
|
allocateContext(member.function!);
|
|
captureParameters();
|
|
|
|
if (member is Constructor) {
|
|
Class cls = member.enclosingClass;
|
|
ClassInfo info = translator.classInfo[cls]!;
|
|
for (TypeParameter typeParam in cls.typeParameters) {
|
|
b.local_get(thisLocal!);
|
|
b.local_get(typeLocals[typeParam]!);
|
|
b.struct_set(info.struct, translator.typeParameterIndex[typeParam]!);
|
|
}
|
|
for (Field field in cls.fields) {
|
|
if (field.isInstanceMember &&
|
|
field.initializer != null &&
|
|
field.type is! VoidType) {
|
|
int fieldIndex = translator.fieldIndex[field]!;
|
|
b.local_get(thisLocal!);
|
|
wrap(
|
|
field.initializer!, info.struct.fields[fieldIndex].type.unpacked);
|
|
b.struct_set(info.struct, fieldIndex);
|
|
}
|
|
}
|
|
for (Initializer initializer in member.initializers) {
|
|
visitInitializer(initializer);
|
|
}
|
|
}
|
|
|
|
Statement? body = member.function!.body;
|
|
if (body != null) {
|
|
visitStatement(body);
|
|
}
|
|
_implicitReturn();
|
|
b.end();
|
|
}
|
|
|
|
/// Generate code for the body of a lambda.
|
|
w.DefinedFunction generateLambda(Lambda lambda, Closures closures) {
|
|
// Initialize closure information from enclosing member.
|
|
this.closures = closures;
|
|
|
|
FunctionNode functionNode = lambda.functionNode;
|
|
_initializeContextLocals(functionNode);
|
|
|
|
if (functionNode.asyncMarker == AsyncMarker.Async &&
|
|
lambda.function == function) {
|
|
w.DefinedFunction inner =
|
|
translator.functions.addAsyncInnerFunctionFor(function);
|
|
generateAsyncWrapper(functionNode, inner, 1);
|
|
return CodeGenerator(translator, inner, reference)
|
|
.generateLambda(lambda, closures);
|
|
}
|
|
|
|
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();
|
|
|
|
visitStatement(functionNode.body!);
|
|
_implicitReturn();
|
|
b.end();
|
|
|
|
return function;
|
|
}
|
|
|
|
/// 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(Member member) {
|
|
bool hasThis = member.isInstanceMember || member is Constructor;
|
|
if (hasThis) {
|
|
Class cls = member.enclosingClass!;
|
|
ClassInfo info = translator.classInfo[cls]!;
|
|
thisLocal = paramLocals[0];
|
|
assert(!thisLocal!.type.nullable);
|
|
w.RefType thisType = info.nonNullableType;
|
|
if (translator.needsConversion(paramLocals[0].type, thisType) &&
|
|
!(cls == translator.objectInfo.cls ||
|
|
cls == translator.ffiPointerClass ||
|
|
translator.isFfiCompound(cls) ||
|
|
translator.isWasmType(cls))) {
|
|
preciseThisLocal = addLocal(thisType);
|
|
b.local_get(paramLocals[0]);
|
|
b.ref_cast(info.struct);
|
|
b.local_set(preciseThisLocal!);
|
|
} else {
|
|
preciseThisLocal = paramLocals[0];
|
|
}
|
|
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(FunctionNode functionNode) {
|
|
Context? context = closures.contexts[functionNode]?.parent;
|
|
if (context != null) {
|
|
b.local_get(paramLocals[0]);
|
|
b.ref_cast(context.struct);
|
|
while (true) {
|
|
w.Local contextLocal =
|
|
addLocal(w.RefType.def(context!.struct, nullable: false));
|
|
context.currentLocal = contextLocal;
|
|
if (context.parent != null || context.containsThis) {
|
|
b.local_tee(contextLocal);
|
|
} else {
|
|
b.local_set(contextLocal);
|
|
}
|
|
if (context.parent == null) break;
|
|
|
|
b.struct_get(context.struct, context.parentFieldIndex);
|
|
b.ref_as_non_null();
|
|
context = context.parent!;
|
|
}
|
|
if (context.containsThis) {
|
|
thisLocal = addLocal(context
|
|
.struct.fields[context.thisFieldIndex].type.unpacked
|
|
.withNullability(false));
|
|
preciseThisLocal = thisLocal;
|
|
b.struct_get(context.struct, context.thisFieldIndex);
|
|
b.ref_as_non_null();
|
|
b.local_set(thisLocal!);
|
|
}
|
|
}
|
|
}
|
|
|
|
void _implicitReturn() {
|
|
if (function.type.outputs.isNotEmpty) {
|
|
w.ValueType returnType = function.type.outputs[0];
|
|
if (returnType is w.RefType && returnType.nullable) {
|
|
// Dart body may have an implicit return null.
|
|
b.ref_null(returnType.heapType);
|
|
} else {
|
|
// This point is unreachable, but the Wasm validator still expects the
|
|
// stack to contain a value matching the Wasm function return type.
|
|
b.block(const [], function.type.outputs);
|
|
b.comment("Unreachable implicit return");
|
|
b.unreachable();
|
|
b.end();
|
|
}
|
|
}
|
|
}
|
|
|
|
void allocateContext(TreeNode node) {
|
|
Context? context = closures.contexts[node];
|
|
if (context != null && !context.isEmpty) {
|
|
w.Local contextLocal =
|
|
addLocal(w.RefType.def(context.struct, nullable: false));
|
|
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(function, 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(function, 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) {
|
|
wrap(
|
|
StringLiteral(expected),
|
|
translator
|
|
.translateType(translator.coreTypes.stringNonNullableRawType));
|
|
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 wrap(Expression node, w.ValueType expectedType) {
|
|
try {
|
|
w.ValueType resultType = node.accept1(this, expectedType);
|
|
translator.convertType(function, resultType, expectedType);
|
|
return expectedType;
|
|
} catch (_) {
|
|
_printLocation(node);
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
void visitStatement(Statement node) {
|
|
try {
|
|
node.accept(this);
|
|
} catch (_) {
|
|
_printLocation(node);
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
w.ValueType call(Reference target) {
|
|
w.BaseFunction targetFunction = translator.functions.getFunction(target);
|
|
if (translator.shouldInline(target)) {
|
|
List<w.Local> inlinedLocals =
|
|
targetFunction.type.inputs.map((t) => addLocal(t)).toList();
|
|
for (w.Local local in inlinedLocals.reversed) {
|
|
b.local_set(local);
|
|
}
|
|
w.Label block = b.block(const [], targetFunction.type.outputs);
|
|
b.comment("Inlined ${target.asMember}");
|
|
CodeGenerator(translator, function, target,
|
|
paramLocals: inlinedLocals, returnLabel: block)
|
|
.generate();
|
|
} else {
|
|
String access =
|
|
target.isGetter ? "get" : (target.isSetter ? "set" : "call");
|
|
b.comment("Direct $access of '${target.asMember}'");
|
|
b.call(targetFunction);
|
|
}
|
|
return translator.outputOrVoid(targetFunction.type.outputs);
|
|
}
|
|
|
|
@override
|
|
void visitInvalidInitializer(InvalidInitializer node) {}
|
|
|
|
@override
|
|
void visitAssertInitializer(AssertInitializer node) {}
|
|
|
|
@override
|
|
void visitLocalInitializer(LocalInitializer node) {
|
|
visitStatement(node.variable);
|
|
}
|
|
|
|
@override
|
|
void visitFieldInitializer(FieldInitializer node) {
|
|
Class cls = (node.parent as Constructor).enclosingClass;
|
|
w.StructType struct = translator.classInfo[cls]!.struct;
|
|
int fieldIndex = translator.fieldIndex[node.field]!;
|
|
|
|
b.local_get(thisLocal!);
|
|
wrap(node.value, struct.fields[fieldIndex].type.unpacked);
|
|
b.struct_set(struct, fieldIndex);
|
|
}
|
|
|
|
@override
|
|
void visitRedirectingInitializer(RedirectingInitializer node) {
|
|
Class cls = (node.parent as Constructor).enclosingClass;
|
|
b.local_get(thisLocal!);
|
|
for (TypeParameter typeParam in cls.typeParameters) {
|
|
types.makeType(
|
|
this, TypeParameterType(typeParam, Nullability.nonNullable));
|
|
}
|
|
_visitArguments(
|
|
node.arguments, node.targetReference, 1 + cls.typeParameters.length);
|
|
call(node.targetReference);
|
|
}
|
|
|
|
@override
|
|
void visitSuperInitializer(SuperInitializer node) {
|
|
Supertype? supertype =
|
|
(node.parent as Constructor).enclosingClass.supertype;
|
|
if (supertype?.classNode.superclass == null) {
|
|
return;
|
|
}
|
|
b.local_get(thisLocal!);
|
|
for (DartType typeArg in supertype!.typeArguments) {
|
|
types.makeType(this, typeArg);
|
|
}
|
|
_visitArguments(node.arguments, node.targetReference,
|
|
1 + supertype.typeArguments.length);
|
|
call(node.targetReference);
|
|
}
|
|
|
|
@override
|
|
void visitBlock(Block node) {
|
|
for (Statement statement in node.statements) {
|
|
visitStatement(statement);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void visitLabeledStatement(LabeledStatement node) {
|
|
w.Label label = b.block();
|
|
labels[node] = label;
|
|
visitStatement(node.body);
|
|
labels.remove(node);
|
|
b.end();
|
|
}
|
|
|
|
@override
|
|
void visitBreakStatement(BreakStatement node) {
|
|
b.br(labels[node.target]!);
|
|
}
|
|
|
|
@override
|
|
void visitVariableDeclaration(VariableDeclaration node) {
|
|
if (node.type is VoidType) {
|
|
if (node.initializer != null) {
|
|
wrap(node.initializer!, voidMarker);
|
|
}
|
|
return;
|
|
}
|
|
w.ValueType type = translateType(node.type);
|
|
w.Local? local;
|
|
Capture? capture = closures.captures[node];
|
|
if (capture == null || !capture.written) {
|
|
local = addLocal(type);
|
|
locals[node] = local;
|
|
}
|
|
if (node.initializer != null) {
|
|
if (capture != null) {
|
|
w.ValueType expectedType = capture.written ? capture.type : local!.type;
|
|
b.local_get(capture.context.currentLocal);
|
|
wrap(node.initializer!, expectedType);
|
|
if (!capture.written) {
|
|
b.local_tee(local!);
|
|
}
|
|
b.struct_set(capture.context.struct, capture.fieldIndex);
|
|
} else {
|
|
wrap(node.initializer!, local!.type);
|
|
b.local_set(local);
|
|
}
|
|
} else if (local != null && !local.type.defaultable) {
|
|
// Uninitialized variable
|
|
translator.globals.instantiateDummyValue(b, local.type);
|
|
b.local_set(local);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void visitEmptyStatement(EmptyStatement node) {}
|
|
|
|
@override
|
|
void visitAssertStatement(AssertStatement node) {}
|
|
|
|
@override
|
|
void visitAssertBlock(AssertBlock node) {}
|
|
|
|
@override
|
|
void visitTryCatch(TryCatch node) {
|
|
// It is not valid dart to have a try without a catch.
|
|
assert(node.catches.isNotEmpty);
|
|
|
|
// We lower a [TryCatch] to a wasm try block. If there are any [Catch]
|
|
// nodes, we generate a single wasm catch instruction, and dispatch at
|
|
// runtime based on the type of the caught exception.
|
|
w.Label try_ = b.try_();
|
|
visitStatement(node.body);
|
|
b.br(try_);
|
|
|
|
// Insert a catch instruction which will catch any thrown Dart
|
|
// exceptions.
|
|
// Note: We must wait to add the try block to the [tryLabels] stack until
|
|
// after we have visited the body of the try. This is to handle the case of
|
|
// a rethrow nested within a try nested within a catch, that is we need the
|
|
// rethrow to target the last try block with a catch.
|
|
tryLabels.add(try_);
|
|
b.catch_(translator.exceptionTag);
|
|
|
|
// 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.RefType stackTrace = translator.stackTraceInfo.nonNullableType;
|
|
w.Local thrownStackTrace = addLocal(stackTrace);
|
|
b.local_set(thrownStackTrace);
|
|
|
|
w.RefType exception = translator.topInfo.nonNullableType;
|
|
w.Local thrownException = addLocal(exception);
|
|
b.local_set(thrownException);
|
|
|
|
// 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
|
|
// execute the body of the catch.
|
|
for (Catch catch_ in node.catches) {
|
|
w.Label catchBlock = b.block();
|
|
DartType guard = catch_.guard;
|
|
|
|
// Only emit the type test if the guard is not [Object].
|
|
if (guard != translator.coreTypes.objectNonNullableRawType) {
|
|
b.local_get(thrownException);
|
|
types.emitTypeTest(
|
|
this, guard, translator.coreTypes.objectNonNullableRawType, node);
|
|
b.i32_eqz();
|
|
b.br_if(catchBlock);
|
|
}
|
|
|
|
// If there is an exception declaration, create a local corresponding to
|
|
// the catch's exception [VariableDeclaration] node.
|
|
VariableDeclaration? exceptionDeclaration = catch_.exception;
|
|
if (exceptionDeclaration != null) {
|
|
w.Local guardedException = addLocal(exception);
|
|
locals[exceptionDeclaration] = guardedException;
|
|
b.local_get(thrownException);
|
|
b.local_set(guardedException);
|
|
}
|
|
|
|
// If there is a stack trace declaration, then create a local
|
|
// corresponding to the catch's stack trace [VariableDeclaration] node.
|
|
VariableDeclaration? stackTraceDeclaration = catch_.stackTrace;
|
|
if (stackTraceDeclaration != null) {
|
|
w.Local guardedStackTrace = addLocal(stackTrace);
|
|
locals[stackTraceDeclaration] = guardedStackTrace;
|
|
b.local_get(thrownStackTrace);
|
|
b.local_set(guardedStackTrace);
|
|
}
|
|
visitStatement(catch_.body);
|
|
|
|
// Jump out of the try entirely if we enter any catch block.
|
|
b.br(try_);
|
|
b.end(); // end catchBlock.
|
|
}
|
|
|
|
// We insert a rethrow just before the end of the try block to handle the
|
|
// case where none of the catch blocks catch the type of the thrown
|
|
// exception.
|
|
b.rethrow_(try_);
|
|
tryLabels.removeLast();
|
|
b.end(); // end try_.
|
|
}
|
|
|
|
@override
|
|
void visitTryFinally(TryFinally node) {
|
|
// We lower a [TryFinally] to three nested blocks, and we emit the finalizer
|
|
// up to three times. 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. Finally, 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.
|
|
w.Label tryFinallyBlock = b.block();
|
|
w.Label finalizerBlock = b.block();
|
|
finalizers.add(TryBlockFinalizer(finalizerBlock));
|
|
w.Label tryBlock = b.try_();
|
|
visitStatement(node.body);
|
|
bool mustHandleReturn = finalizers.removeLast().mustHandleReturn;
|
|
b.br(tryBlock);
|
|
b.catch_(translator.exceptionTag);
|
|
visitStatement(node.finalizer);
|
|
b.rethrow_(tryBlock);
|
|
b.end(); // end tryBlock.
|
|
visitStatement(node.finalizer);
|
|
b.br(tryFinallyBlock);
|
|
b.end(); // end finalizerBlock.
|
|
if (mustHandleReturn) {
|
|
visitStatement(node.finalizer);
|
|
if (finalizers.isNotEmpty) {
|
|
b.br(finalizers.last.label);
|
|
} else {
|
|
if (returnValueLocal != null) {
|
|
b.local_get(returnValueLocal!);
|
|
translator.convertType(function, returnValueLocal!.type, returnType);
|
|
}
|
|
_returnFromFunction();
|
|
}
|
|
}
|
|
b.end(); // end tryFinallyBlock.
|
|
}
|
|
|
|
@override
|
|
void visitExpressionStatement(ExpressionStatement node) {
|
|
wrap(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 {
|
|
wrap(condition!, w.NumType.i32);
|
|
if (negated) {
|
|
b.i32_eqz();
|
|
}
|
|
b.br_if(target);
|
|
}
|
|
}
|
|
|
|
void _conditional(Expression condition, void then(), void otherwise()?,
|
|
List<w.ValueType> result) {
|
|
if (!_hasLogicalOperator(condition)) {
|
|
// Simple condition
|
|
wrap(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,
|
|
() => visitStatement(node.then),
|
|
node.otherwise != null ? () => visitStatement(node.otherwise!) : null,
|
|
const []);
|
|
}
|
|
|
|
@override
|
|
void visitDoStatement(DoStatement node) {
|
|
w.Label loop = b.loop();
|
|
allocateContext(node);
|
|
visitStatement(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();
|
|
_branchIf(node.condition, block, negated: true);
|
|
allocateContext(node);
|
|
visitStatement(node.body);
|
|
b.br(loop);
|
|
b.end();
|
|
b.end();
|
|
}
|
|
|
|
@override
|
|
void visitForStatement(ForStatement node) {
|
|
Context? context = closures.contexts[node];
|
|
allocateContext(node);
|
|
for (VariableDeclaration variable in node.variables) {
|
|
visitStatement(variable);
|
|
}
|
|
w.Label block = b.block();
|
|
w.Label loop = b.loop();
|
|
_branchIf(node.condition, block, negated: true);
|
|
visitStatement(node.body);
|
|
|
|
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) {
|
|
wrap(update, voidMarker);
|
|
}
|
|
b.br(loop);
|
|
b.end();
|
|
b.end();
|
|
}
|
|
|
|
@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) {
|
|
wrap(expression, returnType);
|
|
} else {
|
|
translator.convertType(function, 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 (finalizers.isNotEmpty) {
|
|
for (TryBlockFinalizer finalizer in finalizers) {
|
|
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));
|
|
b.local_set(returnValueLocal!);
|
|
}
|
|
b.br(finalizers.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) {
|
|
wrap(node.expression, voidMarker);
|
|
return;
|
|
}
|
|
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));
|
|
|
|
// Identify kind of switch. One of `nullableType` or `nonNullableType` will
|
|
// be the type for Wasm local that holds the switch value.
|
|
w.ValueType nullableType;
|
|
w.ValueType nonNullableType;
|
|
void Function() compare;
|
|
if (check<BoolLiteral, BoolConstant>()) {
|
|
// bool switch
|
|
nonNullableType = w.NumType.i32;
|
|
nullableType =
|
|
translator.classInfo[translator.boxedBoolClass]!.nullableType;
|
|
compare = () => b.i32_eq();
|
|
} else if (check<IntLiteral, IntConstant>()) {
|
|
// int switch
|
|
nonNullableType = w.NumType.i64;
|
|
nullableType =
|
|
translator.classInfo[translator.boxedIntClass]!.nullableType;
|
|
compare = () => b.i64_eq();
|
|
} else if (check<StringLiteral, StringConstant>()) {
|
|
// String switch
|
|
nonNullableType =
|
|
translator.classInfo[translator.stringBaseClass]!.nonNullableType;
|
|
nullableType = nonNullableType.withNullability(true);
|
|
compare = () => call(translator.stringEquals.reference);
|
|
} else {
|
|
// Object switch
|
|
assert(check<InvalidExpression, Constant>());
|
|
nonNullableType = w.RefType.eq(nullable: false);
|
|
nullableType = w.RefType.eq(nullable: true);
|
|
compare = () => b.ref_eq();
|
|
}
|
|
|
|
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(nonNullableType);
|
|
w.Local? switchValueNullableLocal =
|
|
isNullable ? addLocal(nullableType) : null;
|
|
|
|
// Initialize switch value local
|
|
wrap(node.expression, isNullable ? nullableType : nonNullableType);
|
|
b.local_set(
|
|
isNullable ? switchValueNullableLocal! : switchValueNonNullableLocal);
|
|
|
|
// Special cases
|
|
SwitchCase? defaultCase = node.cases
|
|
.cast<SwitchCase?>()
|
|
.firstWhere((c) => c!.isDefault, orElse: () => null);
|
|
|
|
SwitchCase? nullCase = node.cases.cast<SwitchCase?>().firstWhere(
|
|
(c) => c!.expressions.any((e) =>
|
|
e is NullLiteral ||
|
|
e is ConstantExpression && e.constant is NullConstant),
|
|
orElse: () => null);
|
|
|
|
// 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(
|
|
function, nullableType.withNullability(false), nonNullableType);
|
|
b.local_set(switchValueNonNullableLocal);
|
|
}
|
|
|
|
// 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 {
|
|
wrap(exp, nonNullableType);
|
|
b.local_get(switchValueNonNullableLocal);
|
|
compare();
|
|
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();
|
|
}
|
|
|
|
visitStatement(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
|
|
wrap(targetValue, targetInfo.switchValueLocal.type);
|
|
b.local_set(targetInfo.switchValueLocal);
|
|
b.br(targetInfo.loopLabel);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void visitYieldStatement(YieldStatement node) => defaultStatement(node);
|
|
|
|
@override
|
|
w.ValueType visitAwaitExpression(
|
|
AwaitExpression node, w.ValueType expectedType) {
|
|
w.BaseFunction awaitHelper =
|
|
translator.functions.getFunction(translator.awaitHelper.reference);
|
|
|
|
// The stack for the suspension is the last parameter to the function.
|
|
w.Local stack = function.locals[function.type.inputs.length - 1];
|
|
assert(stack.type == translator.functions.asyncStackType);
|
|
|
|
wrap(node.operand, translator.topInfo.nullableType);
|
|
b.local_get(stack);
|
|
b.call(awaitHelper);
|
|
|
|
return translator.topInfo.nullableType;
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitBlockExpression(
|
|
BlockExpression node, w.ValueType expectedType) {
|
|
visitStatement(node.body);
|
|
return wrap(node.value, expectedType);
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitLet(Let node, w.ValueType expectedType) {
|
|
visitStatement(node.variable);
|
|
return wrap(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) &&
|
|
preciseThisType.isSubtypeOf(expectedType)) {
|
|
b.local_get(preciseThisLocal!);
|
|
return preciseThisType;
|
|
} else {
|
|
b.local_get(thisLocal!);
|
|
return thisType;
|
|
}
|
|
}
|
|
|
|
@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.allocateClass(info.classId);
|
|
w.Local temp = addLocal(info.nonNullableType);
|
|
b.struct_new_default(info.struct);
|
|
b.local_tee(temp);
|
|
b.local_get(temp);
|
|
b.i32_const(info.classId);
|
|
b.struct_set(info.struct, FieldIndex.classId);
|
|
_visitArguments(node.arguments, node.targetReference, 1);
|
|
call(node.targetReference);
|
|
if (expectedType != voidMarker) {
|
|
b.local_get(temp);
|
|
return temp.type;
|
|
} else {
|
|
return voidMarker;
|
|
}
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitStaticInvocation(
|
|
StaticInvocation node, w.ValueType expectedType) {
|
|
w.ValueType? intrinsicResult = intrinsifier.generateStaticIntrinsic(node);
|
|
if (intrinsicResult != null) return intrinsicResult;
|
|
|
|
_visitArguments(node.arguments, node.targetReference, 0);
|
|
return call(node.targetReference);
|
|
}
|
|
|
|
Member _lookupSuperTarget(Member interfaceTarget, {required bool setter}) {
|
|
return translator.hierarchy.getDispatchTarget(
|
|
member.enclosingClass!.superclass!, interfaceTarget.name,
|
|
setter: setter)!;
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitSuperMethodInvocation(
|
|
SuperMethodInvocation node, w.ValueType expectedType) {
|
|
Reference target =
|
|
_lookupSuperTarget(node.interfaceTarget, setter: false).reference;
|
|
w.BaseFunction targetFunction = translator.functions.getFunction(target);
|
|
w.ValueType receiverType = targetFunction.type.inputs.first;
|
|
visitThis(receiverType);
|
|
_visitArguments(node.arguments, target, 1);
|
|
return call(target);
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitInstanceInvocation(
|
|
InstanceInvocation node, w.ValueType expectedType) {
|
|
w.ValueType? intrinsicResult = intrinsifier.generateInstanceIntrinsic(node);
|
|
if (intrinsicResult != null) return intrinsicResult;
|
|
|
|
Procedure target = node.interfaceTarget;
|
|
if (node.kind == InstanceAccessKind.Object) {
|
|
switch (target.name.text) {
|
|
case "toString":
|
|
late w.Label done;
|
|
w.ValueType resultType = _virtualCall(node, target, (signature) {
|
|
done = b.block(const [], signature.outputs);
|
|
w.Label nullString = b.block();
|
|
wrap(node.receiver, translator.topInfo.nullableType);
|
|
b.br_on_null(nullString);
|
|
}, (_) {
|
|
_visitArguments(node.arguments, node.interfaceTargetReference, 1);
|
|
}, getter: false, setter: false);
|
|
b.br(done);
|
|
b.end();
|
|
wrap(StringLiteral("null"), resultType);
|
|
b.end();
|
|
return resultType;
|
|
default:
|
|
unimplemented(node, "Nullable invocation of ${target.name.text}",
|
|
[if (expectedType != voidMarker) expectedType]);
|
|
return expectedType;
|
|
}
|
|
}
|
|
Member? singleTarget = translator.singleTarget(node);
|
|
if (singleTarget != null) {
|
|
w.BaseFunction targetFunction =
|
|
translator.functions.getFunction(singleTarget.reference);
|
|
wrap(node.receiver, targetFunction.type.inputs.first);
|
|
_visitArguments(node.arguments, node.interfaceTargetReference, 1);
|
|
return call(singleTarget.reference);
|
|
}
|
|
return _virtualCall(node, target,
|
|
(signature) => wrap(node.receiver, signature.inputs.first), (_) {
|
|
_visitArguments(node.arguments, node.interfaceTargetReference, 1);
|
|
}, getter: false, setter: false);
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitDynamicInvocation(
|
|
DynamicInvocation node, w.ValueType expectedType) {
|
|
// Handle dynamic 'call' seperately.
|
|
if (node.name.text == "call") {
|
|
return _functionCall(node.receiver, node.arguments);
|
|
}
|
|
return translator.dynamics.emitDynamicCall(this, node);
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitEqualsCall(EqualsCall node, w.ValueType expectedType) {
|
|
w.ValueType? intrinsicResult = intrinsifier.generateEqualsIntrinsic(node);
|
|
if (intrinsicResult != null) return intrinsicResult;
|
|
|
|
Member? singleTarget = translator.singleTarget(node);
|
|
if (singleTarget == translator.coreTypes.objectEquals) {
|
|
// Plain reference comparison
|
|
wrap(node.left, w.RefType.eq(nullable: true));
|
|
wrap(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.topInfo.typeWithNullability(leftNullable);
|
|
w.RefType rightType =
|
|
translator.topInfo.typeWithNullability(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();
|
|
}
|
|
wrap(node.left, leftLocal.type);
|
|
b.local_set(leftLocal);
|
|
wrap(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();
|
|
}
|
|
}
|
|
|
|
if (singleTarget != null) {
|
|
left();
|
|
right();
|
|
call(singleTarget.reference);
|
|
} else {
|
|
_virtualCall(node, node.interfaceTarget, left, right,
|
|
getter: false, setter: false);
|
|
}
|
|
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) {
|
|
wrap(node.expression, const w.RefType.any(nullable: true));
|
|
b.ref_is_null();
|
|
return w.NumType.i32;
|
|
}
|
|
|
|
w.ValueType _virtualCall(
|
|
TreeNode node,
|
|
Member interfaceTarget,
|
|
void pushReceiver(w.FunctionType signature),
|
|
void pushArguments(w.FunctionType signature),
|
|
{required bool getter,
|
|
required bool setter}) {
|
|
SelectorInfo selector = translator.dispatchTable.selectorForTarget(
|
|
interfaceTarget.referenceAs(getter: getter, setter: setter));
|
|
assert(selector.name == interfaceTarget.name.text);
|
|
|
|
pushReceiver(selector.signature);
|
|
|
|
int? offset = selector.offset;
|
|
if (offset == null) {
|
|
// Singular target or unreachable call
|
|
assert(selector.targetCount <= 1);
|
|
if (selector.targetCount == 1) {
|
|
pushArguments(selector.signature);
|
|
return call(selector.singularTarget!);
|
|
} else {
|
|
b.comment("Virtual call of ${selector.name} with no targets"
|
|
" at ${node.location}");
|
|
b.drop();
|
|
b.block(const [], selector.signature.outputs);
|
|
b.unreachable();
|
|
b.end();
|
|
return translator.outputOrVoid(selector.signature.outputs);
|
|
}
|
|
}
|
|
|
|
// Receiver is already on stack.
|
|
w.Local receiverVar = addLocal(selector.signature.inputs.first);
|
|
assert(!receiverVar.type.nullable);
|
|
b.local_tee(receiverVar);
|
|
pushArguments(selector.signature);
|
|
|
|
if (options.polymorphicSpecialization) {
|
|
_polymorphicSpecialization(selector, receiverVar);
|
|
} else {
|
|
String access = getter ? "get" : (setter ? "set" : "call");
|
|
b.comment("Instance $access of '${selector.name}'");
|
|
b.local_get(receiverVar);
|
|
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
|
|
if (offset != 0) {
|
|
b.i32_const(offset);
|
|
b.i32_add();
|
|
}
|
|
b.call_indirect(selector.signature, translator.dispatchTable.wasmTable);
|
|
|
|
translator.functions.activateSelector(selector);
|
|
}
|
|
|
|
return translator.outputOrVoid(selector.signature.outputs);
|
|
}
|
|
|
|
void _polymorphicSpecialization(SelectorInfo selector, w.Local receiver) {
|
|
Map<int, Reference> implementations = Map.from(selector.targets);
|
|
implementations.removeWhere((id, target) => target.asMember.isAbstract);
|
|
|
|
w.Local idVar = addLocal(w.NumType.i32);
|
|
b.local_get(receiver);
|
|
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
|
|
b.local_set(idVar);
|
|
|
|
w.Label block =
|
|
b.block(selector.signature.inputs, selector.signature.outputs);
|
|
calls:
|
|
while (Set.from(implementations.values).length > 1) {
|
|
for (int id in implementations.keys) {
|
|
Reference target = implementations[id]!;
|
|
if (implementations.values.where((t) => t == target).length == 1) {
|
|
// Single class id implements method.
|
|
b.local_get(idVar);
|
|
b.i32_const(id);
|
|
b.i32_eq();
|
|
b.if_(selector.signature.inputs, selector.signature.inputs);
|
|
call(target);
|
|
b.br(block);
|
|
b.end();
|
|
implementations.remove(id);
|
|
continue calls;
|
|
}
|
|
}
|
|
// Find class id that separates remaining classes in two.
|
|
List<int> sorted = implementations.keys.toList()..sort();
|
|
int pivotId = sorted.firstWhere(
|
|
(id) => implementations[id] != implementations[sorted.first]);
|
|
// Fail compilation if no such id exists.
|
|
assert(sorted.lastWhere(
|
|
(id) => implementations[id] != implementations[pivotId]) ==
|
|
pivotId - 1);
|
|
Reference target = implementations[sorted.first]!;
|
|
b.local_get(idVar);
|
|
b.i32_const(pivotId);
|
|
b.i32_lt_u();
|
|
b.if_(selector.signature.inputs, selector.signature.inputs);
|
|
call(target);
|
|
b.br(block);
|
|
b.end();
|
|
for (int id in sorted) {
|
|
if (id == pivotId) break;
|
|
implementations.remove(id);
|
|
}
|
|
continue calls;
|
|
}
|
|
// Call remaining implementation.
|
|
Reference target = implementations.values.first;
|
|
call(target);
|
|
b.end();
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitVariableGet(VariableGet node, w.ValueType expectedType) {
|
|
// Return `void` for a void [VariableGet].
|
|
if (node.variable.type is VoidType) {
|
|
return voidMarker;
|
|
}
|
|
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) {
|
|
// Return `void` for a void [VariableSet].
|
|
if (node.variable.type is VoidType) {
|
|
return wrap(node.value, voidMarker);
|
|
}
|
|
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);
|
|
wrap(node.value, capture.type);
|
|
if (preserved) {
|
|
w.Local temp = addLocal(translateType(node.variable.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}";
|
|
}
|
|
wrap(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;
|
|
|
|
Member target = node.target;
|
|
if (target is Field) {
|
|
return translator.globals.readGlobal(b, target);
|
|
} else {
|
|
return call(target.reference);
|
|
}
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitStaticTearOff(StaticTearOff node, w.ValueType expectedType) {
|
|
translator.constants.instantiateConstant(
|
|
function, b, StaticTearOffConstant(node.target), expectedType);
|
|
return expectedType;
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitStaticSet(StaticSet node, w.ValueType expectedType) {
|
|
bool preserved = expectedType != voidMarker;
|
|
Member target = node.target;
|
|
if (target is Field) {
|
|
w.Global global = translator.globals.getGlobal(target);
|
|
wrap(node.value, global.type.type);
|
|
b.global_set(global);
|
|
if (preserved) {
|
|
b.global_get(global);
|
|
return global.type.type;
|
|
} else {
|
|
return voidMarker;
|
|
}
|
|
} else {
|
|
w.BaseFunction targetFunction =
|
|
translator.functions.getFunction(target.reference);
|
|
w.ValueType paramType = targetFunction.type.inputs.single;
|
|
wrap(node.value, paramType);
|
|
w.Local? temp;
|
|
if (preserved) {
|
|
temp = addLocal(paramType);
|
|
b.local_tee(temp);
|
|
}
|
|
call(target.reference);
|
|
if (preserved) {
|
|
b.local_get(temp!);
|
|
return temp.type;
|
|
} else {
|
|
return voidMarker;
|
|
}
|
|
}
|
|
}
|
|
|
|
@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),
|
|
() => visitThis(w.RefType.data(nullable: false)));
|
|
return w.RefType.def(closureStruct, nullable: false);
|
|
}
|
|
return _directGet(target, ThisExpression(), () => null);
|
|
}
|
|
|
|
@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);
|
|
}
|
|
|
|
@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, (signature) {
|
|
doneLabel = b.block(const [], signature.outputs);
|
|
w.Label nullLabel = b.block();
|
|
wrap(node.receiver, translator.topInfo.nullableType);
|
|
b.br_on_null(nullLabel);
|
|
}, (_) {}, getter: true, setter: false);
|
|
b.br(doneLabel);
|
|
b.end(); // nullLabel
|
|
switch (target.name.text) {
|
|
case "hashCode":
|
|
b.i64_const(2011);
|
|
break;
|
|
case "runtimeType":
|
|
case "_runtimeType":
|
|
wrap(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) {
|
|
return _directGet(singleTarget, node.receiver,
|
|
() => intrinsifier.generateInstanceGetterIntrinsic(node));
|
|
} else {
|
|
return _virtualCall(node, target,
|
|
(signature) => wrap(node.receiver, signature.inputs.first), (_) {},
|
|
getter: true, setter: false);
|
|
}
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitDynamicGet(DynamicGet node, w.ValueType expectedType) {
|
|
return translator.dynamics.emitDynamicCall(this, node);
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitDynamicSet(DynamicSet node, w.ValueType expectedType) {
|
|
return translator.dynamics.emitDynamicCall(this, node);
|
|
}
|
|
|
|
w.ValueType _directGet(
|
|
Member target, Expression receiver, w.ValueType? Function() intrinsify) {
|
|
w.ValueType? intrinsicResult = intrinsify();
|
|
if (intrinsicResult != null) return intrinsicResult;
|
|
|
|
if (target is Field) {
|
|
ClassInfo info = translator.classInfo[target.enclosingClass]!;
|
|
int fieldIndex = translator.fieldIndex[target]!;
|
|
w.ValueType receiverType = info.nullableType;
|
|
w.ValueType fieldType = info.struct.fields[fieldIndex].type.unpacked;
|
|
wrap(receiver, receiverType);
|
|
b.struct_get(info.struct, fieldIndex);
|
|
return fieldType;
|
|
} else {
|
|
// Instance call of getter
|
|
assert(target is Procedure && target.isGetter);
|
|
w.BaseFunction targetFunction =
|
|
translator.functions.getFunction(target.reference);
|
|
wrap(receiver, targetFunction.type.inputs.single);
|
|
return call(target.reference);
|
|
}
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitInstanceTearOff(
|
|
InstanceTearOff node, w.ValueType expectedType) {
|
|
return _virtualCall(node, node.interfaceTarget,
|
|
(signature) => wrap(node.receiver, signature.inputs.first), (_) {},
|
|
getter: true, setter: false);
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitInstanceSet(InstanceSet node, w.ValueType expectedType) {
|
|
bool preserved = expectedType != voidMarker;
|
|
w.Local? temp;
|
|
Member? singleTarget = translator.singleTarget(node);
|
|
if (singleTarget != null) {
|
|
return _directSet(singleTarget, node.receiver, node.value,
|
|
preserved: preserved);
|
|
} else {
|
|
_virtualCall(node, node.interfaceTarget,
|
|
(signature) => wrap(node.receiver, signature.inputs.first),
|
|
(signature) {
|
|
w.ValueType paramType = signature.inputs.last;
|
|
wrap(node.value, paramType);
|
|
if (preserved) {
|
|
temp = addLocal(paramType);
|
|
b.local_tee(temp!);
|
|
}
|
|
}, getter: false, setter: true);
|
|
if (preserved) {
|
|
b.local_get(temp!);
|
|
return temp!.type;
|
|
} else {
|
|
return voidMarker;
|
|
}
|
|
}
|
|
}
|
|
|
|
w.ValueType _directSet(Member target, Expression receiver, Expression value,
|
|
{required bool preserved}) {
|
|
w.Local? temp;
|
|
if (target is Field) {
|
|
ClassInfo info = translator.classInfo[target.enclosingClass]!;
|
|
int fieldIndex = translator.fieldIndex[target]!;
|
|
w.ValueType receiverType = info.nullableType;
|
|
w.ValueType fieldType = info.struct.fields[fieldIndex].type.unpacked;
|
|
wrap(receiver, receiverType);
|
|
wrap(value, fieldType);
|
|
if (preserved) {
|
|
temp = addLocal(fieldType);
|
|
b.local_tee(temp);
|
|
}
|
|
b.struct_set(info.struct, fieldIndex);
|
|
} else {
|
|
w.BaseFunction targetFunction =
|
|
translator.functions.getFunction(target.reference);
|
|
w.ValueType paramType = targetFunction.type.inputs.last;
|
|
wrap(receiver, targetFunction.type.inputs.first);
|
|
wrap(value, paramType);
|
|
if (preserved) {
|
|
temp = addLocal(paramType);
|
|
b.local_tee(temp);
|
|
}
|
|
call(target.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,
|
|
ParameterInfo.fromLocalFunction(functionNode),
|
|
"closure wrapper at ${functionNode.location}");
|
|
return _pushClosure(closure, () => _pushContext(functionNode));
|
|
}
|
|
|
|
w.StructType _pushClosure(ClosureImplementation closure, void pushContext()) {
|
|
w.StructType struct = closure.representation.closureStruct;
|
|
|
|
ClassInfo info = translator.classInfo[translator.functionClass]!;
|
|
translator.functions.allocateClass(info.classId);
|
|
|
|
b.i32_const(info.classId);
|
|
b.i32_const(initialIdentityHash);
|
|
pushContext();
|
|
b.global_get(closure.vtable);
|
|
b.struct_new(struct);
|
|
|
|
return struct;
|
|
}
|
|
|
|
void _pushContext(FunctionNode functionNode) {
|
|
Context? context = closures.contexts[functionNode]?.parent;
|
|
if (context != null) {
|
|
b.local_get(context.currentLocal);
|
|
assert(!context.currentLocal.type.nullable);
|
|
} else {
|
|
b.global_get(translator.globals.dummyGlobal); // Dummy context
|
|
}
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitFunctionInvocation(
|
|
FunctionInvocation node, w.ValueType expectedType) {
|
|
w.ValueType? intrinsicResult =
|
|
intrinsifier.generateFunctionCallIntrinsic(node);
|
|
if (intrinsicResult != null) return intrinsicResult;
|
|
|
|
return _functionCall(node.receiver, node.arguments);
|
|
}
|
|
|
|
w.ValueType _functionCall(Expression receiver, Arguments arguments) {
|
|
int typeCount = arguments.types.length;
|
|
int posArgCount = arguments.positional.length;
|
|
List<String> argNames = arguments.named.map((a) => a.name).toList()..sort();
|
|
ClosureRepresentation? representation = translator.closureLayouter
|
|
.getClosureRepresentation(typeCount, posArgCount, argNames);
|
|
if (representation == null) {
|
|
// This is a dynamic function call with a signature that matches no
|
|
// functions in the program.
|
|
b.unreachable();
|
|
return translator.topInfo.nullableType;
|
|
}
|
|
|
|
// Evaluate receiver
|
|
w.StructType struct = representation.closureStruct;
|
|
w.Local temp = addLocal(w.RefType.def(struct, nullable: false));
|
|
wrap(receiver, temp.type);
|
|
b.local_tee(temp);
|
|
b.struct_get(struct, FieldIndex.closureContext);
|
|
|
|
// Type arguments
|
|
for (DartType typeArg in arguments.types) {
|
|
types.makeType(this, typeArg);
|
|
}
|
|
|
|
// Positional arguments
|
|
for (Expression arg in arguments.positional) {
|
|
wrap(arg, translator.topInfo.nullableType);
|
|
}
|
|
|
|
// Named arguments
|
|
final Map<String, w.Local> namedLocals = {};
|
|
for (final namedArg in arguments.named) {
|
|
final w.Local namedLocal = addLocal(translator.topInfo.nullableType);
|
|
namedLocals[namedArg.name] = namedLocal;
|
|
wrap(namedArg.value, namedLocal.type);
|
|
b.local_set(namedLocal);
|
|
}
|
|
for (String name in argNames) {
|
|
b.local_get(namedLocals[name]!);
|
|
}
|
|
|
|
// Call entry point in vtable
|
|
int vtableIndex =
|
|
representation.fieldIndexForSignature(posArgCount, argNames);
|
|
b.local_get(temp);
|
|
b.struct_get(struct, FieldIndex.closureVtable);
|
|
b.struct_get(representation.vtableStruct, vtableIndex);
|
|
b.call_ref();
|
|
return translator.topInfo.nullableType;
|
|
}
|
|
|
|
@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;
|
|
visitArgumentsLists(arguments.positional, lambda.function.type,
|
|
ParameterInfo.fromLocalFunction(decl.function), 1,
|
|
typeArguments: arguments.types, named: arguments.named);
|
|
b.comment("Local call of ${decl.variable.name}");
|
|
b.call(lambda.function);
|
|
return translator.outputOrVoid(lambda.function.type.outputs);
|
|
}
|
|
|
|
@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) {
|
|
wrap(node.operand, w.NumType.i32);
|
|
b.i32_eqz();
|
|
return w.NumType.i32;
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitConditionalExpression(
|
|
ConditionalExpression node, w.ValueType expectedType) {
|
|
_conditional(
|
|
node.condition,
|
|
() => wrap(node.then, expectedType),
|
|
() => wrap(node.otherwise, expectedType),
|
|
[if (expectedType != voidMarker) expectedType]);
|
|
return expectedType;
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitNullCheck(NullCheck node, w.ValueType expectedType) {
|
|
return _nullCheck(node.operand, translator.throwNullCheckError);
|
|
}
|
|
|
|
w.ValueType _nullCheck(Expression operand, Procedure errorProcedure) {
|
|
w.ValueType operandType = translator.translateType(dartTypeOf(operand));
|
|
w.ValueType nonNullOperandType = operandType.withNullability(false);
|
|
w.Label nullCheckBlock = b.block(const [], [nonNullOperandType]);
|
|
wrap(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.stackTraceCurrent.reference);
|
|
call(errorProcedure.reference);
|
|
b.unreachable();
|
|
b.end();
|
|
return nonNullOperandType;
|
|
}
|
|
|
|
void visitArgumentsLists(List<Expression> positional,
|
|
w.FunctionType signature, ParameterInfo paramInfo, int signatureOffset,
|
|
{List<DartType> typeArguments = const [],
|
|
List<NamedExpression> named = const [],
|
|
bool requiresTypeChecks = false}) {
|
|
for (int i = 0; i < typeArguments.length; i++) {
|
|
types.makeType(this, typeArguments[i]);
|
|
}
|
|
signatureOffset += typeArguments.length;
|
|
for (int i = 0; i < positional.length; i++) {
|
|
wrap(positional[i], signature.inputs[signatureOffset + i]);
|
|
}
|
|
// Default values for positional parameters
|
|
for (int i = positional.length; i < paramInfo.positional.length; i++) {
|
|
final w.ValueType type = signature.inputs[signatureOffset + i];
|
|
translator.constants
|
|
.instantiateConstant(function, b, paramInfo.positional[i]!, type);
|
|
}
|
|
// Named arguments
|
|
final Map<String, w.Local> namedLocals = {};
|
|
for (var namedArg in named) {
|
|
final w.ValueType type = signature
|
|
.inputs[signatureOffset + paramInfo.nameIndex[namedArg.name]!];
|
|
final w.Local namedLocal = addLocal(type);
|
|
namedLocals[namedArg.name] = namedLocal;
|
|
wrap(namedArg.value, namedLocal.type);
|
|
b.local_set(namedLocal);
|
|
}
|
|
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(function, b, paramInfo.named[name]!, type);
|
|
}
|
|
}
|
|
}
|
|
|
|
void _visitArguments(Arguments node, Reference target, int signatureOffset) {
|
|
final w.FunctionType signature = translator.signatureFor(target);
|
|
final ParameterInfo paramInfo = translator.paramInfoFor(target);
|
|
visitArgumentsLists(node.positional, signature, paramInfo, signatureOffset,
|
|
typeArguments: node.types, named: node.named);
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitStringConcatenation(
|
|
StringConcatenation node, w.ValueType expectedType) {
|
|
makeListFromExpressions(node.expressions,
|
|
InterfaceType(translator.stringBaseClass, Nullability.nonNullable));
|
|
return call(translator.stringInterpolate.reference);
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitThrow(Throw node, w.ValueType expectedType) {
|
|
if (dartTypeOf(node.expression).isPotentiallyNullable) {
|
|
_nullCheck(node.expression, translator.throwThrowNullError);
|
|
} else {
|
|
wrap(node.expression, translator.topInfo.nonNullableType);
|
|
}
|
|
call(translator.stackTraceCurrent.reference);
|
|
|
|
// At this point, we have the exception and the current stack trace on the
|
|
// stack, so just throw them using the exception tag.
|
|
b.throw_(translator.exceptionTag);
|
|
return expectedType;
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitRethrow(Rethrow node, w.ValueType expectedType) {
|
|
b.rethrow_(tryLabels.last);
|
|
return expectedType;
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitInstantiation(Instantiation node, w.ValueType expectedType) {
|
|
throw "Not supported: Generic function instantiation at ${node.location}";
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitConstantExpression(
|
|
ConstantExpression node, w.ValueType expectedType) {
|
|
translator.constants
|
|
.instantiateConstant(function, b, node.constant, expectedType);
|
|
return expectedType;
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitNullLiteral(NullLiteral node, w.ValueType expectedType) {
|
|
translator.constants
|
|
.instantiateConstant(function, b, NullConstant(), expectedType);
|
|
return expectedType;
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitStringLiteral(StringLiteral node, w.ValueType expectedType) {
|
|
translator.constants.instantiateConstant(
|
|
function, b, StringConstant(node.value), expectedType);
|
|
return expectedType;
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitBoolLiteral(BoolLiteral node, w.ValueType expectedType) {
|
|
b.i32_const(node.value ? 1 : 0);
|
|
return w.NumType.i32;
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitIntLiteral(IntLiteral node, w.ValueType expectedType) {
|
|
b.i64_const(node.value);
|
|
return w.NumType.i64;
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitDoubleLiteral(DoubleLiteral node, w.ValueType expectedType) {
|
|
b.f64_const(node.value);
|
|
return w.NumType.f64;
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitListLiteral(ListLiteral node, w.ValueType expectedType) {
|
|
return makeListFromExpressions(node.expressions, node.typeArgument,
|
|
isGrowable: true);
|
|
}
|
|
|
|
/// Takes a List class, a type argument, a function which will be called for
|
|
/// each item in the list with the expected type of the element, and a list
|
|
/// length, and creates a Dart List on the stack.
|
|
w.ValueType makeList(DartType typeArg, int length,
|
|
void Function(w.ValueType, int) generateItem,
|
|
{bool isGrowable = false}) {
|
|
Class cls = isGrowable
|
|
? translator.growableListClass
|
|
: translator.fixedLengthListClass;
|
|
ClassInfo info = translator.classInfo[cls]!;
|
|
translator.functions.allocateClass(info.classId);
|
|
w.RefType refType = info.struct.fields.last.type.unpacked as w.RefType;
|
|
w.ArrayType arrayType = refType.heapType as w.ArrayType;
|
|
w.ValueType elementType = arrayType.elementType.type.unpacked;
|
|
|
|
b.i32_const(info.classId);
|
|
b.i32_const(initialIdentityHash);
|
|
types.makeType(this, typeArg);
|
|
b.i64_const(length);
|
|
if (options.lazyConstants) {
|
|
// Avoid array.init instruction in lazy constants mode
|
|
b.i32_const(length);
|
|
b.array_new_default(arrayType);
|
|
if (length > 0) {
|
|
w.Local arrayLocal = addLocal(refType.withNullability(false));
|
|
b.local_set(arrayLocal);
|
|
for (int i = 0; i < length; i++) {
|
|
b.local_get(arrayLocal);
|
|
b.i32_const(i);
|
|
generateItem(elementType, i);
|
|
b.array_set(arrayType);
|
|
}
|
|
b.local_get(arrayLocal);
|
|
}
|
|
} else {
|
|
for (int i = 0; i < length; i++) {
|
|
generateItem(elementType, i);
|
|
}
|
|
b.array_new_fixed(arrayType, length);
|
|
}
|
|
b.struct_new(info.struct);
|
|
|
|
return info.nonNullableType;
|
|
}
|
|
|
|
w.ValueType makeListFromExpressions(
|
|
List<Expression> expressions, DartType typeArg,
|
|
{bool isGrowable = false}) =>
|
|
makeList(typeArg, expressions.length,
|
|
(w.ValueType elementType, int i) => wrap(expressions[i], elementType),
|
|
isGrowable: isGrowable);
|
|
|
|
@override
|
|
w.ValueType visitMapLiteral(MapLiteral node, w.ValueType expectedType) {
|
|
w.BaseFunction mapFactory =
|
|
translator.functions.getFunction(translator.mapFactory.reference);
|
|
w.ValueType factoryReturnType = mapFactory.type.outputs.single;
|
|
types.makeType(this, node.keyType);
|
|
types.makeType(this, node.valueType);
|
|
b.call(mapFactory);
|
|
if (node.entries.isEmpty) {
|
|
return factoryReturnType;
|
|
}
|
|
w.BaseFunction mapPut =
|
|
translator.functions.getFunction(translator.mapPut.reference);
|
|
w.ValueType putReceiverType = mapPut.type.inputs[0];
|
|
w.ValueType putKeyType = mapPut.type.inputs[1];
|
|
w.ValueType putValueType = mapPut.type.inputs[2];
|
|
w.Local mapLocal = addLocal(putReceiverType);
|
|
translator.convertType(function, factoryReturnType, mapLocal.type);
|
|
b.local_set(mapLocal);
|
|
for (MapLiteralEntry entry in node.entries) {
|
|
b.local_get(mapLocal);
|
|
wrap(entry.key, putKeyType);
|
|
wrap(entry.value, putValueType);
|
|
b.call(mapPut);
|
|
}
|
|
b.local_get(mapLocal);
|
|
return mapLocal.type;
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitSetLiteral(SetLiteral node, w.ValueType expectedType) {
|
|
w.BaseFunction setFactory =
|
|
translator.functions.getFunction(translator.setFactory.reference);
|
|
w.ValueType factoryReturnType = setFactory.type.outputs.single;
|
|
types.makeType(this, node.typeArgument);
|
|
b.call(setFactory);
|
|
if (node.expressions.isEmpty) {
|
|
return factoryReturnType;
|
|
}
|
|
w.BaseFunction setAdd =
|
|
translator.functions.getFunction(translator.setAdd.reference);
|
|
w.ValueType addReceiverType = setAdd.type.inputs[0];
|
|
w.ValueType addKeyType = setAdd.type.inputs[1];
|
|
w.Local setLocal = addLocal(addReceiverType);
|
|
translator.convertType(function, factoryReturnType, setLocal.type);
|
|
b.local_set(setLocal);
|
|
for (Expression element in node.expressions) {
|
|
b.local_get(setLocal);
|
|
wrap(element, addKeyType);
|
|
b.call(setAdd);
|
|
b.drop();
|
|
}
|
|
b.local_get(setLocal);
|
|
return setLocal.type;
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitTypeLiteral(TypeLiteral node, w.ValueType expectedType) {
|
|
return types.makeType(this, node.type);
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitIsExpression(IsExpression node, w.ValueType expectedType) {
|
|
wrap(node.operand, translator.topInfo.nullableType);
|
|
types.emitTypeTest(this, node.type, dartTypeOf(node.operand), node);
|
|
return w.NumType.i32;
|
|
}
|
|
|
|
@override
|
|
w.ValueType visitAsExpression(AsExpression node, w.ValueType expectedType) {
|
|
w.Label asCheckBlock = b.block();
|
|
wrap(node.operand, translator.topInfo.nullableType);
|
|
w.Local operand = addLocal(translator.topInfo.nullableType);
|
|
b.local_tee(operand);
|
|
|
|
// We lower an `as` expression to a type test, throwing a [TypeError] if
|
|
// the type test fails.
|
|
types.emitTypeTest(this, node.type, dartTypeOf(node.operand), node);
|
|
b.br_if(asCheckBlock);
|
|
b.local_get(operand);
|
|
types.makeType(this, node.type);
|
|
call(translator.stackTraceCurrent.reference);
|
|
call(translator.throwAsCheckError.reference);
|
|
b.unreachable();
|
|
b.end();
|
|
b.local_get(operand);
|
|
return operand.type;
|
|
}
|
|
|
|
w.ValueType instantiateTypeParameter(TypeParameter parameter) {
|
|
w.ValueType resultType;
|
|
if (parameter.parent is FunctionNode) {
|
|
// Type argument to function
|
|
w.Local? local = typeLocals[parameter];
|
|
if (local != null) {
|
|
b.local_get(local);
|
|
resultType = local.type;
|
|
} else {
|
|
Capture capture = closures.captures[parameter]!;
|
|
b.local_get(capture.context.currentLocal);
|
|
b.struct_get(capture.context.struct, capture.fieldIndex);
|
|
resultType = capture.type;
|
|
}
|
|
} else {
|
|
// Type argument of class
|
|
Class cls = parameter.parent 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;
|
|
}
|
|
final w.ValueType nonNullableTypeType =
|
|
translator.classInfo[translator.typeClass]!.nonNullableType;
|
|
translator.convertType(function, resultType, nonNullableTypeType);
|
|
return nonNullableTypeType;
|
|
}
|
|
}
|
|
|
|
class TryBlockFinalizer {
|
|
final w.Label label;
|
|
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;
|
|
}
|