| // 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: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; |
| |
| 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 = {}; |
| |
| /// 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; |
| } |
| |
| 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; |
| |
| w.ValueType translateType(DartType type) => translator.translateType(type); |
| |
| w.Local addLocal(w.ValueType type) { |
| return function.addLocal(translator.typeForLocal(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 body of the member. |
| void generate() { |
| closures = Closures(this); |
| |
| Member member = this.member; |
| |
| if (reference.isTearOffReference) { |
| // Tear-off getter |
| w.DefinedFunction closureFunction = |
| translator.getTearOffFunction(member as Procedure); |
| |
| int parameterCount = member.function.requiredParameterCount; |
| w.DefinedGlobal global = translator.makeFunctionRef(closureFunction); |
| |
| 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(global); |
| translator.struct_new(b, parameterCount); |
| b.end(); |
| return; |
| } |
| |
| 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) { |
| // Static field initializer function |
| assert(reference == member.fieldReference); |
| closures.findCaptures(member); |
| closures.collectContexts(member); |
| closures.buildContexts(); |
| |
| w.Global global = translator.globals.getGlobal(member); |
| w.Global? flag = translator.globals.getGlobalInitializedFlag(member); |
| wrap(member.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(); |
| return; |
| } |
| |
| // Implicit getter or setter |
| w.StructType struct = |
| translator.classInfo[member.enclosingClass!]!.struct; |
| int fieldIndex = translator.fieldIndex[member]!; |
| w.ValueType fieldType = struct.fields[fieldIndex].type.unpacked; |
| |
| void getThis() { |
| w.Local thisLocal = paramLocals[0]; |
| w.RefType structType = w.RefType.def(struct, nullable: true); |
| 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(); |
| return; |
| } |
| |
| ParameterInfo paramInfo = translator.paramInfoFor(reference); |
| bool hasThis = member.isInstanceMember || member is Constructor; |
| int typeParameterOffset = hasThis ? 1 : 0; |
| int implicitParams = typeParameterOffset + paramInfo.typeParamCount; |
| List<VariableDeclaration> positional = |
| member.function!.positionalParameters; |
| for (int i = 0; i < positional.length; i++) { |
| locals[positional[i]] = paramLocals[implicitParams + i]; |
| } |
| List<VariableDeclaration> named = member.function!.namedParameters; |
| for (var param in named) { |
| locals[param] = |
| paramLocals[implicitParams + paramInfo.nameIndex[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[typeParameterOffset + i]; |
| } |
| |
| closures.findCaptures(member); |
| |
| if (hasThis) { |
| Class cls = member.enclosingClass!; |
| ClassInfo info = translator.classInfo[cls]!; |
| thisLocal = paramLocals[0]; |
| w.RefType thisType = info.nonNullableType; |
| if (translator.needsConversion(paramLocals[0].type, thisType) && |
| !(cls == translator.ffiPointerClass || |
| translator.isFfiCompound(cls))) { |
| preciseThisLocal = addLocal(thisType); |
| b.local_get(paramLocals[0]); |
| translator.ref_cast(b, info); |
| b.local_set(preciseThisLocal!); |
| } else { |
| preciseThisLocal = paramLocals[0]; |
| } |
| } |
| |
| 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) { |
| 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) { |
| initializer.accept(this); |
| } |
| } |
| |
| member.function!.body?.accept(this); |
| _implicitReturn(); |
| b.end(); |
| } |
| |
| /// Generate code for the body of a lambda. |
| void generateLambda(Lambda lambda, Closures closures) { |
| this.closures = closures; |
| |
| final int implicitParams = 1; |
| List<VariableDeclaration> positional = |
| lambda.functionNode.positionalParameters; |
| for (int i = 0; i < positional.length; i++) { |
| locals[positional[i]] = paramLocals[implicitParams + i]; |
| } |
| |
| Context? context = closures.contexts[lambda.functionNode]?.parent; |
| if (context != null) { |
| b.local_get(paramLocals[0]); |
| translator.ref_cast(b, 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); |
| if (options.localNullability) { |
| b.ref_as_non_null(); |
| } |
| context = context.parent!; |
| } |
| if (context.containsThis) { |
| thisLocal = addLocal( |
| context.struct.fields[context.thisFieldIndex].type.unpacked); |
| preciseThisLocal = thisLocal; |
| b.struct_get(context.struct, context.thisFieldIndex); |
| b.local_set(thisLocal!); |
| } |
| } |
| allocateContext(lambda.functionNode); |
| captureParameters(); |
| |
| lambda.functionNode.body!.accept(this); |
| _implicitReturn(); |
| b.end(); |
| } |
| |
| void _implicitReturn() { |
| if (function.type.outputs.length > 0) { |
| 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; |
| translator.struct_new_default(b, 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); |
| } |
| }); |
| } |
| |
| /// 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) { |
| w.ValueType resultType = node.accept1(this, expectedType); |
| translator.convertType(function, resultType, expectedType); |
| return expectedType; |
| } |
| |
| 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) { |
| node.variable.accept(this); |
| } |
| |
| @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!); |
| if (options.parameterNullability && thisLocal!.type.nullable) { |
| b.ref_as_non_null(); |
| } |
| for (TypeParameter typeParam in cls.typeParameters) { |
| _makeType(TypeParameterType(typeParam, Nullability.nonNullable), node); |
| } |
| _visitArguments(node.arguments, node.targetReference, 1); |
| _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!); |
| if (options.parameterNullability && thisLocal!.type.nullable) { |
| b.ref_as_non_null(); |
| } |
| for (DartType typeArg in supertype!.typeArguments) { |
| _makeType(typeArg, node); |
| } |
| _visitArguments(node.arguments, node.targetReference, |
| 1 + supertype.typeArguments.length); |
| _call(node.targetReference); |
| } |
| |
| @override |
| void visitBlock(Block node) { |
| for (Statement statement in node.statements) { |
| statement.accept(this); |
| } |
| } |
| |
| @override |
| void visitLabeledStatement(LabeledStatement node) { |
| w.Label label = b.block(); |
| labels[node] = label; |
| node.body.accept(this); |
| 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_(); |
| node.body.accept(this); |
| 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); |
| emitTypeTest( |
| 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); |
| } |
| catch_.body.accept(this); |
| |
| // 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_(); |
| node.body.accept(this); |
| bool mustHandleReturn = finalizers.removeLast().mustHandleReturn; |
| b.br(tryBlock); |
| b.catch_(translator.exceptionTag); |
| node.finalizer.accept(this); |
| b.rethrow_(tryBlock); |
| b.end(); // end tryBlock. |
| node.finalizer.accept(this); |
| b.br(tryFinallyBlock); |
| b.end(); // end finalizerBlock. |
| if (mustHandleReturn) { |
| node.finalizer.accept(this); |
| 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, |
| () => node.then.accept(this), |
| node.otherwise != null ? () => node.otherwise!.accept(this) : null, |
| const []); |
| } |
| |
| @override |
| void visitDoStatement(DoStatement node) { |
| w.Label loop = b.loop(); |
| allocateContext(node); |
| node.body.accept(this); |
| _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); |
| node.body.accept(this); |
| 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) { |
| variable.accept(this); |
| } |
| w.Label block = b.block(); |
| w.Label loop = b.loop(); |
| _branchIf(node.condition, block, negated: true); |
| node.body.accept(this); |
| if (node.variables.any((v) => closures.captures.containsKey(v))) { |
| w.Local oldContext = context!.currentLocal; |
| allocateContext(node); |
| w.Local newContext = context.currentLocal; |
| for (VariableDeclaration variable in node.variables) { |
| Capture? capture = closures.captures[variable]; |
| if (capture != null) { |
| b.local_get(oldContext); |
| b.struct_get(context.struct, capture.fieldIndex); |
| b.local_get(newContext); |
| b.struct_set(context.struct, capture.fieldIndex); |
| } |
| } |
| } else { |
| allocateContext(node); |
| } |
| 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) { |
| returnValueLocal ??= addLocal(returnType); |
| b.local_set(returnValueLocal!); |
| } |
| b.br(finalizers.last.label); |
| } else { |
| _returnFromFunction(); |
| } |
| } |
| |
| @override |
| void visitSwitchStatement(SwitchStatement node) { |
| 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 |
| w.ValueType valueType; |
| w.ValueType nullableType; |
| void Function() compare; |
| if (check<BoolLiteral, BoolConstant>()) { |
| // bool switch |
| valueType = w.NumType.i32; |
| nullableType = |
| translator.classInfo[translator.boxedBoolClass]!.nullableType; |
| compare = () => b.i32_eq(); |
| } else if (check<IntLiteral, IntConstant>()) { |
| // int switch |
| valueType = w.NumType.i64; |
| nullableType = |
| translator.classInfo[translator.boxedIntClass]!.nullableType; |
| compare = () => b.i64_eq(); |
| } else if (check<StringLiteral, StringConstant>()) { |
| // String switch |
| valueType = |
| translator.classInfo[translator.stringBaseClass]!.nonNullableType; |
| nullableType = valueType.withNullability(true); |
| compare = () => _call(translator.stringEquals.reference); |
| } else { |
| // Object switch |
| assert(check<InvalidExpression, InstanceConstant>()); |
| valueType = w.RefType.eq(nullable: false); |
| nullableType = w.RefType.eq(nullable: true); |
| compare = () => b.ref_eq(); |
| } |
| w.Local valueLocal = addLocal(valueType); |
| |
| // 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); |
| |
| // 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 |
| bool isNullable = dartTypeOf(node.expression).isPotentiallyNullable; |
| if (isNullable) { |
| w.Label nullLabel = nullCase != null |
| ? switchLabels[nullCase]! |
| : defaultCase != null |
| ? switchLabels[defaultCase]! |
| : doneLabel; |
| wrap(node.expression, nullableType); |
| b.br_on_null(nullLabel); |
| translator.convertType( |
| function, nullableType.withNullability(false), valueType); |
| } else { |
| assert(nullCase == null); |
| wrap(node.expression, valueType); |
| } |
| b.local_set(valueLocal); |
| |
| // 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, valueType); |
| b.local_get(valueLocal); |
| translator.convertType(function, valueLocal.type, valueType); |
| compare(); |
| b.br_if(switchLabels[c]!); |
| } |
| } |
| } |
| w.Label defaultLabel = |
| defaultCase != null ? switchLabels[defaultCase]! : doneLabel; |
| b.br(defaultLabel); |
| |
| // Emit case bodies |
| for (SwitchCase c in node.cases) { |
| switchLabels.remove(c); |
| b.end(); |
| c.body.accept(this); |
| b.br(doneLabel); |
| } |
| b.end(); |
| } |
| |
| @override |
| void visitContinueSwitchStatement(ContinueSwitchStatement node) { |
| w.Label? label = switchLabels[node.target]; |
| if (label != null) { |
| b.br(label); |
| } else { |
| throw "Not supported: Backward jump to switch case at ${node.location}"; |
| } |
| } |
| |
| @override |
| void visitYieldStatement(YieldStatement node) => defaultStatement(node); |
| |
| @override |
| w.ValueType visitBlockExpression( |
| BlockExpression node, w.ValueType expectedType) { |
| node.body.accept(this); |
| return wrap(node.value, expectedType); |
| } |
| |
| @override |
| w.ValueType visitLet(Let node, w.ValueType expectedType) { |
| node.variable.accept(this); |
| 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.withNullability(false); |
| w.ValueType preciseThisType = preciseThisLocal!.type.withNullability(false); |
| if (!thisType.isSubtypeOf(expectedType) && |
| preciseThisType.isSubtypeOf(expectedType)) { |
| b.local_get(preciseThisLocal!); |
| return preciseThisLocal!.type; |
| } else { |
| b.local_get(thisLocal!); |
| return thisLocal!.type; |
| } |
| } |
| |
| @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); |
| translator.struct_new_default(b, info); |
| b.local_tee(temp); |
| b.local_get(temp); |
| b.i32_const(info.classId); |
| b.struct_set(info.struct, FieldIndex.classId); |
| if (options.parameterNullability && temp.type.nullable) { |
| b.ref_as_non_null(); |
| } |
| _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; |
| w.ValueType thisType = _visitThis(receiverType); |
| translator.convertType(function, thisType, 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) { |
| if (node.name.text != "call") { |
| _unimplemented(node, "Dynamic invocation of ${node.name.text}", |
| [if (expectedType != voidMarker) expectedType]); |
| return expectedType; |
| } |
| return _functionCall( |
| node.arguments.positional.length, node.receiver, node.arguments); |
| } |
| |
| @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!); |
| } else if (leftLocal.type.nullable) { |
| b.ref_as_non_null(); |
| } |
| } |
| |
| void right([_]) { |
| b.local_get(rightLocal); |
| if (rightLocal.type.nullable) { |
| 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()); |
| 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); |
| b.local_tee(receiverVar); |
| if (options.parameterNullability && receiverVar.type.nullable) { |
| b.ref_as_non_null(); |
| } |
| 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.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) { |
| 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); |
| 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); |
| wrap(node.value, targetFunction.type.inputs.single); |
| w.Local? temp; |
| if (preserved) { |
| temp = addLocal(translateType(dartTypeOf(node.value))); |
| 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) { |
| throw "Not supported: Super tear-off at ${node.location}"; |
| } |
| 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": |
| 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) { |
| // Provisional implementation of dynamic get which assumes the getter |
| // is present (otherwise it traps or calls something random) and |
| // does not support tearoffs. This is sufficient to handle the |
| // dynamic .length calls in the core libraries. |
| |
| SelectorInfo selector = |
| translator.dispatchTable.selectorForDynamicName(node.name.text); |
| |
| // Evaluate receiver |
| wrap(node.receiver, selector.signature.inputs.first); |
| w.Local receiverVar = addLocal(selector.signature.inputs.first); |
| b.local_tee(receiverVar); |
| if (options.parameterNullability && receiverVar.type.nullable) { |
| b.ref_as_non_null(); |
| } |
| |
| // Dispatch table call |
| b.comment("Dynamic get of '${selector.name}'"); |
| int offset = selector.offset!; |
| 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.functions.activateSelector(selector); |
| |
| return translator.outputOrVoid(selector.signature.outputs); |
| } |
| |
| 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); |
| translator.convertType(function, temp.type, paramType); |
| } |
| _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) { |
| int parameterCount = functionNode.requiredParameterCount; |
| Lambda lambda = closures.lambdas[functionNode]!; |
| w.DefinedGlobal global = translator.makeFunctionRef(lambda.function); |
| |
| ClassInfo info = translator.classInfo[translator.functionClass]!; |
| translator.functions.allocateClass(info.classId); |
| w.StructType struct = translator.closureStructType(parameterCount); |
| |
| b.i32_const(info.classId); |
| b.i32_const(initialIdentityHash); |
| _pushContext(functionNode); |
| b.global_get(global); |
| translator.struct_new(b, parameterCount); |
| |
| return struct; |
| } |
| |
| void _pushContext(FunctionNode functionNode) { |
| Context? context = closures.contexts[functionNode]?.parent; |
| if (context != null) { |
| b.local_get(context.currentLocal); |
| if (context.currentLocal.type.nullable) { |
| b.ref_as_non_null(); |
| } |
| } else { |
| b.global_get(translator.globals.dummyGlobal); // Dummy context |
| } |
| } |
| |
| @override |
| w.ValueType visitFunctionInvocation( |
| FunctionInvocation node, w.ValueType expectedType) { |
| FunctionType functionType = node.functionType!; |
| int parameterCount = functionType.requiredParameterCount; |
| return _functionCall(parameterCount, node.receiver, node.arguments); |
| } |
| |
| w.ValueType _functionCall( |
| int parameterCount, Expression receiver, Arguments arguments) { |
| w.StructType struct = translator.closureStructType(parameterCount); |
| w.Local temp = addLocal(w.RefType.def(struct, nullable: false)); |
| wrap(receiver, temp.type); |
| b.local_tee(temp); |
| b.struct_get(struct, FieldIndex.closureContext); |
| for (Expression arg in arguments.positional) { |
| wrap(arg, translator.topInfo.nullableType); |
| } |
| b.local_get(temp); |
| b.struct_get(struct, FieldIndex.closureFunction); |
| b.call_ref(); |
| return translator.topInfo.nullableType; |
| } |
| |
| @override |
| w.ValueType visitLocalFunctionInvocation( |
| LocalFunctionInvocation node, w.ValueType expectedType) { |
| var decl = node.variable.parent as FunctionDeclaration; |
| _pushContext(decl.function); |
| for (Expression arg in node.arguments.positional) { |
| wrap(arg, translator.topInfo.nullableType); |
| } |
| Lambda lambda = closures.lambdas[decl.function]!; |
| b.comment("Local call of ${decl.variable.name}"); |
| b.call(lambda.function); |
| return translator.topInfo.nullableType; |
| } |
| |
| @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) { |
| w.ValueType operandType = |
| translator.translateType(dartTypeOf(node.operand)); |
| w.ValueType nonNullOperandType = operandType.withNullability(false); |
| w.Label nullCheckBlock = b.block(const [], [nonNullOperandType]); |
| wrap(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.stackTraceCurrent.reference); |
| _call(translator.throwNullCheckError.reference); |
| b.unreachable(); |
| b.end(); |
| return nonNullOperandType; |
| } |
| |
| void _visitArguments(Arguments node, Reference target, int signatureOffset) { |
| final w.FunctionType signature = translator.signatureFor(target); |
| final ParameterInfo paramInfo = translator.paramInfoFor(target); |
| for (int i = 0; i < node.types.length; i++) { |
| _makeType(node.types[i], node); |
| } |
| signatureOffset += node.types.length; |
| for (int i = 0; i < node.positional.length; i++) { |
| wrap(node.positional[i], signature.inputs[signatureOffset + i]); |
| } |
| // Default values for positional parameters |
| for (int i = node.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 node.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); |
| translator.convertType(function, namedLocal.type, type); |
| } else { |
| translator.constants |
| .instantiateConstant(function, b, paramInfo.named[name]!, type); |
| } |
| } |
| } |
| |
| @override |
| w.ValueType visitStringConcatenation( |
| StringConcatenation node, w.ValueType expectedType) { |
| _makeList( |
| node.expressions, |
| translator.fixedLengthListClass, |
| InterfaceType(translator.stringBaseClass, Nullability.nonNullable), |
| node); |
| return _call(translator.stringInterpolate.reference); |
| } |
| |
| @override |
| w.ValueType visitThrow(Throw node, w.ValueType expectedType) { |
| 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 _makeList(node.expressions, translator.growableListClass, |
| node.typeArgument, node); |
| } |
| |
| w.ValueType _makeList(List<Expression> expressions, Class cls, |
| DartType typeArg, TreeNode node) { |
| 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; |
| int length = expressions.length; |
| |
| b.i32_const(info.classId); |
| b.i32_const(initialIdentityHash); |
| _makeType(typeArg, node); |
| b.i64_const(length); |
| if (options.lazyConstants) { |
| // Avoid array.init instruction in lazy constants mode |
| b.i32_const(length); |
| translator.array_new_default(b, 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); |
| wrap(expressions[i], elementType); |
| b.array_set(arrayType); |
| } |
| b.local_get(arrayLocal); |
| if (arrayLocal.type.nullable) { |
| b.ref_as_non_null(); |
| } |
| } |
| } else { |
| for (Expression expression in expressions) { |
| wrap(expression, elementType); |
| } |
| translator.array_init(b, arrayType, length); |
| } |
| translator.struct_new(b, info); |
| |
| return info.nonNullableType; |
| } |
| |
| @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; |
| _makeType(node.keyType, node); |
| _makeType(node.valueType, node); |
| 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); |
| translator.convertType(function, mapLocal.type, putReceiverType); |
| wrap(entry.key, putKeyType); |
| wrap(entry.value, putValueType); |
| b.call(mapPut); |
| } |
| b.local_get(mapLocal); |
| return mapLocal.type; |
| } |
| |
| @override |
| w.ValueType visitTypeLiteral(TypeLiteral node, w.ValueType expectedType) { |
| return _makeType(node.type, node); |
| } |
| |
| w.ValueType _makeType(DartType type, TreeNode node) { |
| w.ValueType typeType = |
| translator.classInfo[translator.typeClass]!.nullableType; |
| if (_isTypeConstant(type)) { |
| return wrap(ConstantExpression(TypeLiteralConstant(type)), typeType); |
| } |
| if (type is TypeParameterType) { |
| if (type.parameter.parent is FunctionNode) { |
| // Type argument to function |
| w.Local? local = typeLocals[type.parameter]; |
| if (local != null) { |
| b.local_get(local); |
| return local.type; |
| } else { |
| _unimplemented( |
| node, "Type parameter access inside lambda", [typeType]); |
| return typeType; |
| } |
| } |
| // Type argument of class |
| Class cls = type.parameter.parent as Class; |
| ClassInfo info = translator.classInfo[cls]!; |
| int fieldIndex = translator.typeParameterIndex[type.parameter]!; |
| w.ValueType thisType = _visitThis(info.nullableType); |
| translator.convertType(function, thisType, info.nullableType); |
| b.struct_get(info.struct, fieldIndex); |
| return typeType; |
| } |
| ClassInfo info = translator.classInfo[translator.typeClass]!; |
| translator.functions.allocateClass(info.classId); |
| if (type is FutureOrType) { |
| // TODO(askesc): Have an actual representation of FutureOr types |
| b.ref_null(info.nullableType.heapType); |
| return info.nullableType; |
| } |
| if (type is! InterfaceType) { |
| _unimplemented(node, type, [info.nullableType]); |
| return info.nullableType; |
| } |
| ClassInfo typeInfo = translator.classInfo[type.classNode]!; |
| w.ValueType typeListExpectedType = info.struct.fields[3].type.unpacked; |
| b.i32_const(info.classId); |
| b.i32_const(initialIdentityHash); |
| b.i64_const(typeInfo.classId); |
| if (type.typeArguments.isEmpty) { |
| b.global_get(translator.constants.emptyTypeList); |
| translator.convertType(function, |
| translator.constants.emptyTypeList.type.type, typeListExpectedType); |
| } else if (type.typeArguments.every(_isTypeConstant)) { |
| ListConstant typeArgs = ListConstant( |
| InterfaceType(translator.typeClass, Nullability.nonNullable), |
| type.typeArguments.map((t) => TypeLiteralConstant(t)).toList()); |
| translator.constants |
| .instantiateConstant(function, b, typeArgs, typeListExpectedType); |
| } else { |
| w.ValueType listType = _makeList( |
| type.typeArguments.map((t) => TypeLiteral(t)).toList(), |
| translator.fixedLengthListClass, |
| InterfaceType(translator.typeClass, Nullability.nonNullable), |
| node); |
| translator.convertType(function, listType, typeListExpectedType); |
| } |
| translator.struct_new(b, info); |
| return info.nullableType; |
| } |
| |
| bool _isTypeConstant(DartType type) { |
| return type is DynamicType || |
| type is VoidType || |
| type is NeverType || |
| type is NullType || |
| type is FunctionType || |
| type is InterfaceType && type.typeArguments.every(_isTypeConstant); |
| } |
| |
| @override |
| w.ValueType visitIsExpression(IsExpression node, w.ValueType expectedType) { |
| wrap(node.operand, translator.topInfo.nullableType); |
| emitTypeTest(node.type, dartTypeOf(node.operand), node); |
| return w.NumType.i32; |
| } |
| |
| /// Test value against a Dart type. Expects the value on the stack as a |
| /// (ref null #Top) and leaves the result on the stack as an i32. |
| void emitTypeTest(DartType type, DartType operandType, TreeNode node) { |
| if (type is! InterfaceType) { |
| // TODO(askesc): Implement type test for remaining types |
| print("Not implemented: Type test with non-interface type $type" |
| " at ${node.location}"); |
| b.drop(); |
| b.i32_const(1); |
| return; |
| } |
| bool isNullable = operandType.isPotentiallyNullable; |
| w.Label? resultLabel; |
| if (isNullable) { |
| // Store operand in a temporary variable, since Binaryen does not support |
| // block inputs. |
| w.Local operand = addLocal(translator.topInfo.nullableType); |
| b.local_set(operand); |
| resultLabel = b.block(const [], const [w.NumType.i32]); |
| w.Label nullLabel = b.block(const [], const []); |
| b.local_get(operand); |
| b.br_on_null(nullLabel); |
| } |
| if (type.typeArguments.any((t) => t is! DynamicType)) { |
| // If the tested-against type as an instance of the static operand type |
| // has the same type arguments as the static operand type, it is not |
| // necessary to test the type arguments. |
| Class cls = translator.classForType(operandType); |
| InterfaceType? base = translator.hierarchy |
| .getTypeAsInstanceOf(type, cls, member.enclosingLibrary) |
| ?.withDeclaredNullability(operandType.declaredNullability); |
| if (base != operandType) { |
| print("Not implemented: Type test with type arguments" |
| " at ${node.location}"); |
| } |
| } |
| List<Class> concrete = translator.subtypes |
| .getSubtypesOf(type.classNode) |
| .where((c) => !c.isAbstract) |
| .toList(); |
| if (concrete.isEmpty) { |
| b.drop(); |
| b.i32_const(0); |
| } else if (concrete.length == 1) { |
| ClassInfo info = translator.classInfo[concrete.single]!; |
| b.struct_get(translator.topInfo.struct, FieldIndex.classId); |
| b.i32_const(info.classId); |
| b.i32_eq(); |
| } else { |
| w.Local idLocal = addLocal(w.NumType.i32); |
| b.struct_get(translator.topInfo.struct, FieldIndex.classId); |
| b.local_set(idLocal); |
| w.Label done = b.block(const [], const [w.NumType.i32]); |
| b.i32_const(1); |
| for (Class cls in concrete) { |
| ClassInfo info = translator.classInfo[cls]!; |
| b.i32_const(info.classId); |
| b.local_get(idLocal); |
| b.i32_eq(); |
| b.br_if(done); |
| } |
| b.drop(); |
| b.i32_const(0); |
| b.end(); // done |
| } |
| if (isNullable) { |
| b.br(resultLabel!); |
| b.end(); // nullLabel |
| b.i32_const(type.declaredNullability == Nullability.nullable ? 1 : 0); |
| b.end(); // resultLabel |
| } |
| } |
| |
| @override |
| w.ValueType visitAsExpression(AsExpression node, w.ValueType expectedType) { |
| // TODO(joshualitt): Emit type test and throw exception on failure |
| return wrap(node.operand, expectedType); |
| } |
| } |
| |
| class TryBlockFinalizer { |
| final w.Label label; |
| bool mustHandleReturn = false; |
| |
| TryBlockFinalizer(this.label); |
| } |