| // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| import 'dart:typed_data'; | |
| import 'package:dart2wasm/class_info.dart'; | |
| import 'package:dart2wasm/closures.dart'; | |
| import 'package:dart2wasm/code_generator.dart'; | |
| import 'package:dart2wasm/constants.dart'; | |
| import 'package:dart2wasm/dispatch_table.dart'; | |
| import 'package:dart2wasm/functions.dart'; | |
| import 'package:dart2wasm/globals.dart'; | |
| import 'package:dart2wasm/param_info.dart'; | |
| import 'package:dart2wasm/reference_extensions.dart'; | |
| import 'package:kernel/ast.dart'; | |
| import 'package:kernel/class_hierarchy.dart' | |
| show ClassHierarchy, ClassHierarchySubtypes, ClosedWorldClassHierarchy; | |
| import 'package:kernel/core_types.dart'; | |
| import 'package:kernel/src/printer.dart'; | |
| import 'package:kernel/type_environment.dart'; | |
| import 'package:vm/metadata/direct_call.dart'; | |
| import 'package:wasm_builder/wasm_builder.dart' as w; | |
| /// Options controlling the translation. | |
| class TranslatorOptions { | |
| bool exportAll = false; | |
| bool inlining = false; | |
| int inliningLimit = 3; | |
| bool lazyConstants = false; | |
| bool localNullability = false; | |
| bool nameSection = true; | |
| bool nominalTypes = true; | |
| bool parameterNullability = true; | |
| bool polymorphicSpecialization = false; | |
| bool printKernel = false; | |
| bool printWasm = false; | |
| bool runtimeTypes = false; | |
| bool stringDataSegments = false; | |
| List<int>? watchPoints = null; | |
| bool get useRttGlobals => runtimeTypes && !nominalTypes; | |
| } | |
| typedef CodeGenCallback = void Function(w.Instructions); | |
| /// The main entry point for the translation from kernel to Wasm and the hub for | |
| /// all global state in the compiler. | |
| /// | |
| /// This class also contains utility methods for types and code generation used | |
| /// throughout the compiler. | |
| class Translator { | |
| // Options for the translation. | |
| final TranslatorOptions options; | |
| // Kernel input and context. | |
| final Component component; | |
| final List<Library> libraries; | |
| final CoreTypes coreTypes; | |
| final TypeEnvironment typeEnvironment; | |
| final ClosedWorldClassHierarchy hierarchy; | |
| late final ClassHierarchySubtypes subtypes; | |
| // Classes and members referenced specifically by the compiler. | |
| late final Class wasmTypesBaseClass; | |
| late final Class wasmArrayBaseClass; | |
| late final Class wasmAnyRefClass; | |
| late final Class wasmEqRefClass; | |
| late final Class wasmDataRefClass; | |
| late final Class boxedBoolClass; | |
| late final Class boxedIntClass; | |
| late final Class boxedDoubleClass; | |
| late final Class functionClass; | |
| late final Class listBaseClass; | |
| late final Class fixedLengthListClass; | |
| late final Class growableListClass; | |
| late final Class immutableListClass; | |
| late final Class immutableMapClass; | |
| late final Class hashFieldBaseClass; | |
| late final Class stringBaseClass; | |
| late final Class oneByteStringClass; | |
| late final Class twoByteStringClass; | |
| late final Class typeClass; | |
| late final Class stackTraceClass; | |
| late final Class ffiCompoundClass; | |
| late final Class ffiPointerClass; | |
| late final Class typedListBaseClass; | |
| late final Class typedListClass; | |
| late final Class typedListViewClass; | |
| late final Class byteDataViewClass; | |
| late final Class typeErrorClass; | |
| late final Procedure stackTraceCurrent; | |
| late final Procedure stringEquals; | |
| late final Procedure stringInterpolate; | |
| late final Procedure throwNullCheckError; | |
| late final Procedure mapFactory; | |
| late final Procedure mapPut; | |
| late final Procedure immutableMapIndexNullable; | |
| late final Map<Class, w.StorageType> builtinTypes; | |
| late final Map<w.ValueType, Class> boxedClasses; | |
| // Other parts of the global compiler state. | |
| late final ClassInfoCollector classInfoCollector; | |
| late final DispatchTable dispatchTable; | |
| late final Globals globals; | |
| late final Constants constants; | |
| late final FunctionCollector functions; | |
| // Information about the program used and updated by the various phases. | |
| final List<ClassInfo> classes = []; | |
| final Map<Class, ClassInfo> classInfo = {}; | |
| final Map<w.HeapType, ClassInfo> classForHeapType = {}; | |
| final Map<Field, int> fieldIndex = {}; | |
| final Map<TypeParameter, int> typeParameterIndex = {}; | |
| final Map<Reference, ParameterInfo> staticParamInfo = {}; | |
| late Procedure mainFunction; | |
| late final w.Module m; | |
| late final w.DefinedFunction initFunction; | |
| late final w.ValueType voidMarker; | |
| // Lazily create exception tag if used. | |
| late final w.Tag exceptionTag = createExceptionTag(); | |
| // Lazily import FFI memory if used. | |
| late final w.Memory ffiMemory = m.importMemory("ffi", "memory", 0); | |
| // Caches for when identical source constructs need a common representation. | |
| final Map<w.StorageType, w.ArrayType> arrayTypeCache = {}; | |
| final Map<int, w.StructType> functionTypeCache = {}; | |
| final Map<w.StructType, int> functionTypeParameterCount = {}; | |
| final Map<int, w.DefinedGlobal> functionTypeRtt = {}; | |
| final Map<w.DefinedFunction, w.DefinedGlobal> functionRefCache = {}; | |
| final Map<Procedure, w.DefinedFunction> tearOffFunctionCache = {}; | |
| ClassInfo get topInfo => classes[0]; | |
| ClassInfo get objectInfo => classInfo[coreTypes.objectClass]!; | |
| ClassInfo get stackTraceInfo => classInfo[stackTraceClass]!; | |
| Translator(this.component, this.coreTypes, this.typeEnvironment, this.options) | |
| : libraries = component.libraries, | |
| hierarchy = | |
| ClassHierarchy(component, coreTypes) as ClosedWorldClassHierarchy { | |
| subtypes = hierarchy.computeSubtypesInformation(); | |
| classInfoCollector = ClassInfoCollector(this); | |
| dispatchTable = DispatchTable(this); | |
| functions = FunctionCollector(this); | |
| Class Function(String) makeLookup(String libraryName) { | |
| Library library = | |
| component.libraries.firstWhere((l) => l.name == libraryName); | |
| return (name) => library.classes.firstWhere((c) => c.name == name); | |
| } | |
| Class Function(String) lookupCore = makeLookup("dart.core"); | |
| Class Function(String) lookupCollection = makeLookup("dart.collection"); | |
| Class Function(String) lookupFfi = makeLookup("dart.ffi"); | |
| Class Function(String) lookupTypedData = makeLookup("dart.typed_data"); | |
| Class Function(String) lookupWasm = makeLookup("dart.wasm"); | |
| wasmTypesBaseClass = lookupWasm("_WasmBase"); | |
| wasmArrayBaseClass = lookupWasm("_WasmArray"); | |
| wasmAnyRefClass = lookupWasm("WasmAnyRef"); | |
| wasmEqRefClass = lookupWasm("WasmEqRef"); | |
| wasmDataRefClass = lookupWasm("WasmDataRef"); | |
| boxedBoolClass = lookupCore("_BoxedBool"); | |
| boxedIntClass = lookupCore("_BoxedInt"); | |
| boxedDoubleClass = lookupCore("_BoxedDouble"); | |
| functionClass = lookupCore("_Function"); | |
| fixedLengthListClass = lookupCore("_List"); | |
| listBaseClass = lookupCore("_ListBase"); | |
| growableListClass = lookupCore("_GrowableList"); | |
| immutableListClass = lookupCore("_ImmutableList"); | |
| immutableMapClass = lookupCollection("_WasmImmutableLinkedHashMap"); | |
| hashFieldBaseClass = lookupCollection("_HashFieldBase"); | |
| stringBaseClass = lookupCore("_StringBase"); | |
| oneByteStringClass = lookupCore("_OneByteString"); | |
| twoByteStringClass = lookupCore("_TwoByteString"); | |
| typeClass = lookupCore("_Type"); | |
| stackTraceClass = lookupCore("StackTrace"); | |
| ffiCompoundClass = lookupFfi("_Compound"); | |
| ffiPointerClass = lookupFfi("Pointer"); | |
| typeErrorClass = lookupCore("_TypeError"); | |
| typedListBaseClass = lookupTypedData("_TypedListBase"); | |
| typedListClass = lookupTypedData("_TypedList"); | |
| typedListViewClass = lookupTypedData("_TypedListView"); | |
| byteDataViewClass = lookupTypedData("_ByteDataView"); | |
| stackTraceCurrent = | |
| stackTraceClass.procedures.firstWhere((p) => p.name.text == "current"); | |
| stringEquals = | |
| stringBaseClass.procedures.firstWhere((p) => p.name.text == "=="); | |
| stringInterpolate = stringBaseClass.procedures | |
| .firstWhere((p) => p.name.text == "_interpolate"); | |
| throwNullCheckError = typeErrorClass.procedures | |
| .firstWhere((p) => p.name.text == "_throwNullCheckError"); | |
| mapFactory = lookupCollection("LinkedHashMap").procedures.firstWhere( | |
| (p) => p.kind == ProcedureKind.Factory && p.name.text == "_default"); | |
| mapPut = lookupCollection("_CompactLinkedCustomHashMap") | |
| .superclass! // _LinkedHashMapMixin<K, V> | |
| .procedures | |
| .firstWhere((p) => p.name.text == "[]="); | |
| immutableMapIndexNullable = lookupCollection("_HashAbstractImmutableBase") | |
| .procedures | |
| .firstWhere((p) => p.name.text == "_indexNullable"); | |
| builtinTypes = { | |
| coreTypes.boolClass: w.NumType.i32, | |
| coreTypes.intClass: w.NumType.i64, | |
| coreTypes.doubleClass: w.NumType.f64, | |
| wasmAnyRefClass: w.RefType.any(nullable: false), | |
| wasmEqRefClass: w.RefType.eq(nullable: false), | |
| wasmDataRefClass: w.RefType.data(nullable: false), | |
| boxedBoolClass: w.NumType.i32, | |
| boxedIntClass: w.NumType.i64, | |
| boxedDoubleClass: w.NumType.f64, | |
| lookupWasm("WasmI8"): w.PackedType.i8, | |
| lookupWasm("WasmI16"): w.PackedType.i16, | |
| lookupWasm("WasmI32"): w.NumType.i32, | |
| lookupWasm("WasmI64"): w.NumType.i64, | |
| lookupWasm("WasmF32"): w.NumType.f32, | |
| lookupWasm("WasmF64"): w.NumType.f64, | |
| ffiPointerClass: w.NumType.i32, | |
| }; | |
| boxedClasses = { | |
| w.NumType.i32: boxedBoolClass, | |
| w.NumType.i64: boxedIntClass, | |
| w.NumType.f64: boxedDoubleClass, | |
| }; | |
| } | |
| Uint8List translate() { | |
| m = w.Module(watchPoints: options.watchPoints); | |
| voidMarker = w.RefType.def(w.StructType("void"), nullable: true); | |
| classInfoCollector.collect(); | |
| functions.collectImportsAndExports(); | |
| mainFunction = | |
| libraries.first.procedures.firstWhere((p) => p.name.text == "main"); | |
| functions.addExport(mainFunction.reference, "main"); | |
| initFunction = m.addFunction(functionType(const [], const []), "#init"); | |
| m.startFunction = initFunction; | |
| globals = Globals(this); | |
| constants = Constants(this); | |
| dispatchTable.build(); | |
| functions.initialize(); | |
| while (functions.worklist.isNotEmpty) { | |
| Reference reference = functions.worklist.removeLast(); | |
| Member member = reference.asMember; | |
| var function = | |
| functions.getExistingFunction(reference) as w.DefinedFunction; | |
| String canonicalName = "$member"; | |
| if (reference.isSetter) { | |
| canonicalName = "$canonicalName="; | |
| } else if (reference.isGetter || reference.isTearOffReference) { | |
| int dot = canonicalName.indexOf('.'); | |
| canonicalName = canonicalName.substring(0, dot + 1) + | |
| '=' + | |
| canonicalName.substring(dot + 1); | |
| } | |
| canonicalName = member.enclosingLibrary == libraries.first | |
| ? canonicalName | |
| : "${member.enclosingLibrary.importUri} $canonicalName"; | |
| String? exportName = functions.exports[reference]; | |
| if (options.printKernel || options.printWasm) { | |
| if (exportName != null) { | |
| print("#${function.index}: $canonicalName (exported as $exportName)"); | |
| } else { | |
| print("#${function.index}: $canonicalName"); | |
| } | |
| print(member.function | |
| ?.computeFunctionType(Nullability.nonNullable) | |
| .toStringInternal()); | |
| } | |
| if (options.printKernel) { | |
| if (member is Constructor) { | |
| Class cls = member.enclosingClass; | |
| for (Field field in cls.fields) { | |
| if (field.isInstanceMember && field.initializer != null) { | |
| print("${field.name}: ${field.initializer}"); | |
| } | |
| } | |
| for (Initializer initializer in member.initializers) { | |
| print(initializer); | |
| } | |
| } | |
| Statement? body = member.function?.body; | |
| if (body != null) { | |
| print(body); | |
| } | |
| if (!options.printWasm) print(""); | |
| } | |
| if (exportName != null) { | |
| m.exportFunction(exportName, function); | |
| } else if (options.exportAll) { | |
| m.exportFunction(canonicalName, function); | |
| } | |
| var codeGen = CodeGenerator(this, function, reference); | |
| codeGen.generate(); | |
| if (options.printWasm) { | |
| print(function.type); | |
| print(function.body.trace); | |
| } | |
| for (Lambda lambda in codeGen.closures.lambdas.values) { | |
| CodeGenerator(this, lambda.function, reference) | |
| .generateLambda(lambda, codeGen.closures); | |
| _printFunction(lambda.function, "$canonicalName (closure)"); | |
| } | |
| } | |
| dispatchTable.output(); | |
| constants.finalize(); | |
| initFunction.body.end(); | |
| for (ConstantInfo info in constants.constantInfo.values) { | |
| w.DefinedFunction? function = info.function; | |
| if (function != null) { | |
| _printFunction(function, info.constant); | |
| } else { | |
| if (options.printWasm) { | |
| print("Global #${info.global.index}: ${info.constant}"); | |
| print(info.global.initializer.trace); | |
| } | |
| } | |
| } | |
| if (options.lazyConstants) { | |
| _printFunction(constants.oneByteStringFunction, "makeOneByteString"); | |
| _printFunction(constants.twoByteStringFunction, "makeTwoByteString"); | |
| } | |
| _printFunction(initFunction, "init"); | |
| return m.encode(emitNameSection: options.nameSection); | |
| } | |
| void _printFunction(w.DefinedFunction function, Object name) { | |
| if (options.printWasm) { | |
| print("#${function.index}: $name"); | |
| print(function.body.trace); | |
| } | |
| } | |
| Class classForType(DartType type) { | |
| return type is InterfaceType | |
| ? type.classNode | |
| : type is TypeParameterType | |
| ? classForType(type.bound) | |
| : coreTypes.objectClass; | |
| } | |
| /// Creates a [Tag] for a void [FunctionType] with two parameters, | |
| /// a [topInfo.nonNullableType] parameter to hold an exception, and a | |
| /// [stackTraceInfo.nonNullableType] to hold a stack trace. This single | |
| /// exception tag is used to throw and catch all Dart exceptions. | |
| w.Tag createExceptionTag() { | |
| w.FunctionType tagType = functionType( | |
| [topInfo.nonNullableType, stackTraceInfo.nonNullableType], const []); | |
| w.Tag tag = m.addTag(tagType); | |
| return tag; | |
| } | |
| w.ValueType translateType(DartType type) { | |
| w.StorageType wasmType = translateStorageType(type); | |
| if (wasmType is w.ValueType) return wasmType; | |
| throw "Packed types are only allowed in arrays and fields"; | |
| } | |
| bool _hasSuperclass(Class cls, Class superclass) { | |
| while (cls.superclass != null) { | |
| cls = cls.superclass!; | |
| if (cls == superclass) return true; | |
| } | |
| return false; | |
| } | |
| bool isWasmType(Class cls) => _hasSuperclass(cls, wasmTypesBaseClass); | |
| bool isFfiCompound(Class cls) => _hasSuperclass(cls, ffiCompoundClass); | |
| w.StorageType typeForInfo(ClassInfo info, bool nullable, | |
| {bool ensureBoxed = false}) { | |
| Class? cls = info.cls; | |
| if (cls != null) { | |
| w.StorageType? builtin = builtinTypes[cls]; | |
| if (builtin != null) { | |
| if (!nullable && (!ensureBoxed || cls == ffiPointerClass)) { | |
| return builtin; | |
| } | |
| if (isWasmType(cls)) { | |
| if (builtin.isPrimitive) throw "Wasm numeric types can't be nullable"; | |
| return (builtin as w.RefType).withNullability(true); | |
| } | |
| if (cls == ffiPointerClass) throw "FFI types can't be nullable"; | |
| Class? boxedClass = boxedClasses[builtin]; | |
| if (boxedClass != null) { | |
| info = classInfo[boxedClass]!; | |
| } | |
| } else if (isFfiCompound(cls)) { | |
| if (nullable) throw "FFI types can't be nullable"; | |
| return w.NumType.i32; | |
| } | |
| } | |
| return w.RefType.def(info.repr.struct, | |
| nullable: !options.parameterNullability || nullable); | |
| } | |
| w.StorageType translateStorageType(DartType type) { | |
| if (type is InterfaceType) { | |
| if (type.classNode.superclass == wasmArrayBaseClass) { | |
| DartType elementType = type.typeArguments.single; | |
| return w.RefType.def(arrayTypeForDartType(elementType), | |
| nullable: false); | |
| } | |
| return typeForInfo( | |
| classInfo[type.classNode]!, type.isPotentiallyNullable); | |
| } | |
| if (type is DynamicType) { | |
| return topInfo.nullableType; | |
| } | |
| if (type is NullType) { | |
| return topInfo.nullableType; | |
| } | |
| if (type is NeverType) { | |
| return topInfo.nullableType; | |
| } | |
| if (type is VoidType) { | |
| return voidMarker; | |
| } | |
| if (type is TypeParameterType) { | |
| return translateStorageType(type.isPotentiallyNullable | |
| ? type.bound.withDeclaredNullability(type.nullability) | |
| : type.bound); | |
| } | |
| if (type is FutureOrType) { | |
| return topInfo.nullableType; | |
| } | |
| if (type is FunctionType) { | |
| if (type.requiredParameterCount != type.positionalParameters.length || | |
| type.namedParameters.isNotEmpty) { | |
| throw "Function types with optional parameters not supported: $type"; | |
| } | |
| return w.RefType.def(closureStructType(type.requiredParameterCount), | |
| nullable: | |
| !options.parameterNullability || type.isPotentiallyNullable); | |
| } | |
| throw "Unsupported type ${type.runtimeType}"; | |
| } | |
| w.ArrayType arrayTypeForDartType(DartType type) { | |
| while (type is TypeParameterType) type = type.bound; | |
| return wasmArrayType( | |
| translateStorageType(type), type.toText(defaultAstTextStrategy)); | |
| } | |
| w.ArrayType wasmArrayType(w.StorageType type, String name) { | |
| return arrayTypeCache.putIfAbsent( | |
| type, () => arrayType("Array<$name>", elementType: w.FieldType(type))); | |
| } | |
| w.StructType closureStructType(int parameterCount) { | |
| return functionTypeCache.putIfAbsent(parameterCount, () { | |
| ClassInfo info = classInfo[functionClass]!; | |
| w.StructType struct = structType("Function$parameterCount", | |
| fields: info.struct.fields, superType: info.struct); | |
| assert(struct.fields.length == FieldIndex.closureFunction); | |
| struct.fields.add(w.FieldType( | |
| w.RefType.def(closureFunctionType(parameterCount), nullable: false), | |
| mutable: false)); | |
| if (options.useRttGlobals) { | |
| functionTypeRtt[parameterCount] = | |
| classInfoCollector.makeRtt(struct, info); | |
| } | |
| functionTypeParameterCount[struct] = parameterCount; | |
| return struct; | |
| }); | |
| } | |
| w.FunctionType closureFunctionType(int parameterCount) { | |
| return functionType([ | |
| w.RefType.data(), | |
| ...List<w.ValueType>.filled(parameterCount, topInfo.nullableType) | |
| ], [ | |
| topInfo.nullableType | |
| ]); | |
| } | |
| int parameterCountForFunctionStruct(w.HeapType heapType) { | |
| return functionTypeParameterCount[heapType]!; | |
| } | |
| w.DefinedGlobal makeFunctionRef(w.DefinedFunction f) { | |
| return functionRefCache.putIfAbsent(f, () { | |
| w.DefinedGlobal global = m.addGlobal( | |
| w.GlobalType(w.RefType.def(f.type, nullable: false), mutable: false)); | |
| global.initializer.ref_func(f); | |
| global.initializer.end(); | |
| return global; | |
| }); | |
| } | |
| w.DefinedFunction getTearOffFunction(Procedure member) { | |
| return tearOffFunctionCache.putIfAbsent(member, () { | |
| assert(member.kind == ProcedureKind.Method); | |
| FunctionNode functionNode = member.function; | |
| int parameterCount = functionNode.requiredParameterCount; | |
| if (functionNode.positionalParameters.length != parameterCount || | |
| functionNode.namedParameters.isNotEmpty) { | |
| throw "Not supported: Tear-off with optional parameters" | |
| " at ${member.location}"; | |
| } | |
| if (functionNode.typeParameters.isNotEmpty) { | |
| throw "Not supported: Tear-off with type parameters" | |
| " at ${member.location}"; | |
| } | |
| w.FunctionType memberSignature = signatureFor(member.reference); | |
| w.FunctionType closureSignature = closureFunctionType(parameterCount); | |
| int signatureOffset = member.isInstanceMember ? 1 : 0; | |
| assert(memberSignature.inputs.length == signatureOffset + parameterCount); | |
| assert(closureSignature.inputs.length == 1 + parameterCount); | |
| w.DefinedFunction function = | |
| m.addFunction(closureSignature, "$member (tear-off)"); | |
| w.BaseFunction target = functions.getFunction(member.reference); | |
| w.Instructions b = function.body; | |
| for (int i = 0; i < memberSignature.inputs.length; i++) { | |
| w.Local paramLocal = function.locals[(1 - signatureOffset) + i]; | |
| b.local_get(paramLocal); | |
| convertType(function, paramLocal.type, memberSignature.inputs[i]); | |
| } | |
| b.call(target); | |
| convertType(function, outputOrVoid(target.type.outputs), | |
| outputOrVoid(closureSignature.outputs)); | |
| b.end(); | |
| return function; | |
| }); | |
| } | |
| w.ValueType typeForLocal(w.ValueType type) { | |
| return options.localNullability ? type : type.withNullability(true); | |
| } | |
| w.ValueType outputOrVoid(List<w.ValueType> outputs) { | |
| return outputs.isEmpty ? voidMarker : outputs.single; | |
| } | |
| bool needsConversion(w.ValueType from, w.ValueType to) { | |
| return (from == voidMarker) ^ (to == voidMarker) || !from.isSubtypeOf(to); | |
| } | |
| void convertType( | |
| w.DefinedFunction function, w.ValueType from, w.ValueType to) { | |
| w.Instructions b = function.body; | |
| if (from == voidMarker || to == voidMarker) { | |
| if (from != voidMarker) { | |
| b.drop(); | |
| return; | |
| } | |
| if (to != voidMarker) { | |
| if (to is w.RefType && to.nullable) { | |
| // This can happen when a void method has its return type overridden to | |
| // return a value, in which case the selector signature will have a | |
| // non-void return type to encompass all possible return values. | |
| b.ref_null(to.heapType); | |
| } else { | |
| // This only happens in invalid but unreachable code produced by the | |
| // TFA dead-code elimination. | |
| b.comment("Non-nullable void conversion"); | |
| b.unreachable(); | |
| } | |
| return; | |
| } | |
| } | |
| if (!from.isSubtypeOf(to)) { | |
| if (from is! w.RefType && to is w.RefType) { | |
| // Boxing | |
| ClassInfo info = classInfo[boxedClasses[from]!]!; | |
| assert(info.struct.isSubtypeOf(to.heapType)); | |
| w.Local temp = function.addLocal(from); | |
| b.local_set(temp); | |
| b.i32_const(info.classId); | |
| b.local_get(temp); | |
| struct_new(b, info); | |
| } else if (from is w.RefType && to is! w.RefType) { | |
| // Unboxing | |
| ClassInfo info = classInfo[boxedClasses[to]!]!; | |
| if (!from.heapType.isSubtypeOf(info.struct)) { | |
| // Cast to box type | |
| if (!from.heapType.isSubtypeOf(w.HeapType.data)) { | |
| b.ref_as_data(); | |
| } | |
| ref_cast(b, info); | |
| } | |
| b.struct_get(info.struct, FieldIndex.boxValue); | |
| } else if (from.withNullability(false).isSubtypeOf(to)) { | |
| // Null check | |
| b.ref_as_non_null(); | |
| } else { | |
| // Downcast | |
| var heapType = (to as w.RefType).heapType; | |
| ClassInfo? info = classForHeapType[heapType]; | |
| if (from.nullable && !to.nullable) { | |
| b.ref_as_non_null(); | |
| } | |
| if (!(from as w.RefType).heapType.isSubtypeOf(w.HeapType.data)) { | |
| b.ref_as_data(); | |
| } | |
| ref_cast( | |
| b, | |
| info ?? | |
| (heapType.isSubtypeOf(classInfo[functionClass]!.struct) | |
| ? parameterCountForFunctionStruct(heapType) | |
| : heapType)); | |
| } | |
| } | |
| } | |
| w.FunctionType signatureFor(Reference target) { | |
| Member member = target.asMember; | |
| if (member.isInstanceMember) { | |
| return dispatchTable.selectorForTarget(target).signature; | |
| } else { | |
| return functions.getFunction(target).type; | |
| } | |
| } | |
| ParameterInfo paramInfoFor(Reference target) { | |
| Member member = target.asMember; | |
| if (member.isInstanceMember) { | |
| return dispatchTable.selectorForTarget(target).paramInfo; | |
| } else { | |
| return staticParamInfo.putIfAbsent( | |
| target, () => ParameterInfo.fromMember(target)); | |
| } | |
| } | |
| Member? singleTarget(TreeNode node) { | |
| DirectCallMetadataRepository metadata = | |
| component.metadata[DirectCallMetadataRepository.repositoryTag] | |
| as DirectCallMetadataRepository; | |
| return metadata.mapping[node]?.target; | |
| } | |
| bool shouldInline(Reference target) { | |
| if (!options.inlining) return false; | |
| Member member = target.asMember; | |
| if (member is Field) return true; | |
| Statement? body = member.function!.body; | |
| return body != null && | |
| NodeCounter().countNodes(body) <= options.inliningLimit; | |
| } | |
| T? getPragma<T>(Annotatable node, String name, [T? defaultvalue]) { | |
| for (Expression annotation in node.annotations) { | |
| if (annotation is ConstantExpression) { | |
| Constant constant = annotation.constant; | |
| if (constant is InstanceConstant) { | |
| if (constant.classNode == coreTypes.pragmaClass) { | |
| Constant? nameConstant = | |
| constant.fieldValues[coreTypes.pragmaName.fieldReference]; | |
| if (nameConstant is StringConstant && nameConstant.value == name) { | |
| Object? value = | |
| constant.fieldValues[coreTypes.pragmaOptions.fieldReference]; | |
| if (value is PrimitiveConstant<T>) { | |
| return value.value; | |
| } | |
| return value as T? ?? defaultvalue; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| return null; | |
| } | |
| // Wrappers for type creation to abstract over equi-recursive versus nominal | |
| // typing. The given supertype is ignored when nominal types are disabled, | |
| // and a suitable default is inserted when nominal types are enabled. | |
| w.FunctionType functionType( | |
| Iterable<w.ValueType> inputs, Iterable<w.ValueType> outputs, | |
| {w.HeapType? superType}) { | |
| return m.addFunctionType(inputs, outputs, | |
| superType: options.nominalTypes ? superType ?? w.HeapType.func : null); | |
| } | |
| w.StructType structType(String name, | |
| {Iterable<w.FieldType>? fields, w.HeapType? superType}) { | |
| return m.addStructType(name, | |
| fields: fields, | |
| superType: options.nominalTypes ? superType ?? w.HeapType.data : null); | |
| } | |
| w.ArrayType arrayType(String name, | |
| {w.FieldType? elementType, w.HeapType? superType}) { | |
| return m.addArrayType(name, | |
| elementType: elementType, | |
| superType: options.nominalTypes ? superType ?? w.HeapType.data : null); | |
| } | |
| // Wrappers for object allocation and cast instructions to abstract over | |
| // RTT-based and static versions of the instructions. | |
| // The [type] parameter taken by the methods is either a [ClassInfo] (to use | |
| // the RTT for the class), an [int] (to use the RTT for the closure struct | |
| // corresponding to functions with that number of parameters) or a | |
| // [w.DataType] (to use the canonical RTT for the type). | |
| void struct_new(w.Instructions b, Object type) { | |
| if (options.runtimeTypes) { | |
| final struct = _emitRtt(b, type) as w.StructType; | |
| b.struct_new_with_rtt(struct); | |
| } else { | |
| b.struct_new(_targetType(type) as w.StructType); | |
| } | |
| } | |
| void struct_new_default(w.Instructions b, Object type) { | |
| if (options.runtimeTypes) { | |
| final struct = _emitRtt(b, type) as w.StructType; | |
| b.struct_new_default_with_rtt(struct); | |
| } else { | |
| b.struct_new_default(_targetType(type) as w.StructType); | |
| } | |
| } | |
| void array_new(w.Instructions b, w.ArrayType type) { | |
| if (options.runtimeTypes) { | |
| b.rtt_canon(type); | |
| b.array_new_with_rtt(type); | |
| } else { | |
| b.array_new(type); | |
| } | |
| } | |
| void array_new_default(w.Instructions b, w.ArrayType type) { | |
| if (options.runtimeTypes) { | |
| b.rtt_canon(type); | |
| b.array_new_default_with_rtt(type); | |
| } else { | |
| b.array_new_default(type); | |
| } | |
| } | |
| void array_init(w.Instructions b, w.ArrayType type, int length) { | |
| if (options.runtimeTypes) { | |
| b.rtt_canon(type); | |
| b.array_init(type, length); | |
| } else { | |
| b.array_init_static(type, length); | |
| } | |
| } | |
| void array_init_from_data( | |
| w.Instructions b, w.ArrayType type, w.DataSegment data) { | |
| if (options.runtimeTypes) { | |
| b.rtt_canon(type); | |
| b.array_init_from_data(type, data); | |
| } else { | |
| b.array_init_from_data_static(type, data); | |
| } | |
| } | |
| void ref_test(w.Instructions b, Object type) { | |
| if (options.runtimeTypes) { | |
| _emitRtt(b, type); | |
| b.ref_test(); | |
| } else { | |
| b.ref_test_static(_targetType(type)); | |
| } | |
| } | |
| void ref_cast(w.Instructions b, Object type) { | |
| if (options.runtimeTypes) { | |
| _emitRtt(b, type); | |
| b.ref_cast(); | |
| } else { | |
| b.ref_cast_static(_targetType(type)); | |
| } | |
| } | |
| void br_on_cast(w.Instructions b, w.Label label, Object type) { | |
| if (options.runtimeTypes) { | |
| _emitRtt(b, type); | |
| b.br_on_cast(label); | |
| } else { | |
| b.br_on_cast_static(label, _targetType(type)); | |
| } | |
| } | |
| void br_on_cast_fail(w.Instructions b, w.Label label, Object type) { | |
| if (options.runtimeTypes) { | |
| _emitRtt(b, type); | |
| b.br_on_cast_fail(label); | |
| } else { | |
| b.br_on_cast_static_fail(label, _targetType(type)); | |
| } | |
| } | |
| w.DefType _emitRtt(w.Instructions b, Object type) { | |
| if (type is ClassInfo) { | |
| if (options.nominalTypes) { | |
| b.rtt_canon(type.struct); | |
| } else { | |
| b.global_get(type.rtt); | |
| } | |
| return type.struct; | |
| } else if (type is int) { | |
| int parameterCount = type; | |
| w.StructType struct = closureStructType(parameterCount); | |
| if (options.nominalTypes) { | |
| b.rtt_canon(struct); | |
| } else { | |
| w.DefinedGlobal rtt = functionTypeRtt[parameterCount]!; | |
| b.global_get(rtt); | |
| } | |
| return struct; | |
| } else { | |
| b.rtt_canon(type as w.DataType); | |
| return type; | |
| } | |
| } | |
| w.DefType _targetType(Object type) => type is ClassInfo | |
| ? type.struct | |
| : type is int | |
| ? closureStructType(type) | |
| : type as w.DefType; | |
| } | |
| class NodeCounter extends Visitor<void> with VisitorVoidMixin { | |
| int count = 0; | |
| int countNodes(Node node) { | |
| count = 0; | |
| node.accept(this); | |
| return count; | |
| } | |
| @override | |
| void defaultNode(Node node) { | |
| count++; | |
| node.visitChildren(this); | |
| } | |
| } |