| // 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:kernel/ast.dart'; |
| import 'package:kernel/class_hierarchy.dart' |
| show ClassHierarchy, ClassHierarchySubtypes, ClosedWorldClassHierarchy; |
| import 'package:kernel/core_types.dart'; |
| import 'package:kernel/library_index.dart'; |
| import 'package:kernel/src/printer.dart'; |
| import 'package:kernel/type_environment.dart'; |
| import 'package:vm/metadata/direct_call.dart'; |
| import 'package:vm/metadata/inferred_type.dart'; |
| import 'package:vm/metadata/unboxing_info.dart'; |
| import 'package:wasm_builder/wasm_builder.dart' as w; |
| |
| import 'class_info.dart'; |
| import 'closures.dart'; |
| import 'code_generator.dart'; |
| import 'constants.dart'; |
| import 'deferred_loading.dart'; |
| import 'dispatch_table.dart'; |
| import 'dynamic_forwarders.dart'; |
| import 'functions.dart'; |
| import 'globals.dart'; |
| import 'kernel_nodes.dart'; |
| import 'param_info.dart'; |
| import 'records.dart'; |
| import 'reference_extensions.dart'; |
| import 'static_dispatch_table.dart'; |
| import 'tags.dart'; |
| import 'types.dart'; |
| import 'util.dart' as util; |
| |
| /// Options controlling the translation. |
| class TranslatorOptions { |
| bool enableAsserts = false; |
| bool importSharedMemory = false; |
| bool inlining = true; |
| bool jsCompatibility = false; |
| bool omitImplicitTypeChecks = false; |
| bool omitExplicitTypeChecks = false; |
| bool omitBoundsChecks = false; |
| bool polymorphicSpecialization = false; |
| bool printKernel = false; |
| bool printWasm = false; |
| bool minify = false; |
| bool verifyTypeChecks = false; |
| bool verbose = false; |
| bool enableExperimentalFfi = false; |
| bool enableExperimentalWasmInterop = false; |
| bool generateSourceMaps = true; |
| bool enableDeferredLoading = false; |
| bool enableMultiModuleStressTestMode = false; |
| int inliningLimit = 0; |
| int? sharedMemoryMaxPages; |
| List<int> watchPoints = []; |
| } |
| |
| /// 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 with KernelNodes { |
| // Options for the translation. |
| final TranslatorOptions options; |
| |
| // Kernel input and context. |
| @override |
| final Component component; |
| final List<Library> libraries; |
| final CoreTypes coreTypes; |
| late final TypeEnvironment typeEnvironment; |
| final ClosedWorldClassHierarchy hierarchy; |
| late final ClassHierarchySubtypes subtypes; |
| |
| // TFA-inferred metadata. |
| late final Map<TreeNode, DirectCallMetadata> directCallMetadata = |
| (component.metadata[DirectCallMetadataRepository.repositoryTag] |
| as DirectCallMetadataRepository) |
| .mapping; |
| late final Map<TreeNode, InferredType> inferredTypeMetadata = |
| (component.metadata[InferredTypeMetadataRepository.repositoryTag] |
| as InferredTypeMetadataRepository) |
| .mapping; |
| late final Map<TreeNode, InferredType> inferredArgTypeMetadata = |
| (component.metadata[InferredArgTypeMetadataRepository.repositoryTag] |
| as InferredArgTypeMetadataRepository) |
| .mapping; |
| late final Map<TreeNode, InferredType> inferredReturnTypeMetadata = |
| (component.metadata[InferredReturnTypeMetadataRepository.repositoryTag] |
| as InferredReturnTypeMetadataRepository) |
| .mapping; |
| late final Map<TreeNode, UnboxingInfoMetadata> unboxingInfoMetadata = |
| (component.metadata[UnboxingInfoMetadataRepository.repositoryTag] |
| as UnboxingInfoMetadataRepository) |
| .mapping; |
| |
| // Other parts of the global compiler state. |
| @override |
| final LibraryIndex index; |
| late final ClosureLayouter closureLayouter; |
| late final ClassInfoCollector classInfoCollector; |
| late final StaticDispatchTables staticTablesPerType; |
| late final DispatchTable dispatchTable; |
| late final Globals globals; |
| late final Constants constants; |
| late final Types types; |
| late final ExceptionTag exceptionTag; |
| late final CompilationQueue compilationQueue; |
| late final FunctionCollector functions; |
| |
| // Information about the program used and updated by the various phases. |
| |
| /// [ClassInfo]s of classes in the compilation unit and the [ClassInfo] for |
| /// the `#Top` struct. Indexed by class ID. Entries added by |
| /// [ClassInfoCollector]. |
| late final List<ClassInfo> classes; |
| |
| /// Same as [classes] but ordered such that info for class at index I |
| /// will have class info for superlass/superinterface at <I). |
| late final List<ClassInfo> classesSupersFirst; |
| |
| late final ClassIdNumbering classIdNumbering; |
| |
| /// [ClassInfo]s of classes in the compilation unit. Entries added by |
| /// [ClassInfoCollector]. |
| final Map<Class, ClassInfo> classInfo = {}; |
| |
| /// Internalized strings to move to the JS runtime |
| final List<String> internalizedStringsForJSRuntime = []; |
| final Map<(w.ModuleBuilder, String), w.Global> _internalizedStringGlobals = |
| {}; |
| |
| final Map<w.HeapType, ClassInfo> classForHeapType = {}; |
| final Map<Field, int> fieldIndex = {}; |
| final Map<TypeParameter, int> typeParameterIndex = {}; |
| final Map<Reference, ParameterInfo> staticParamInfo = {}; |
| final Map<Field, w.Table> _declaredFieldTables = {}; |
| late final WasmTableImporter _importedFieldTables = |
| WasmTableImporter(this, 'fieldTable'); |
| final Set<Member> membersContainingInnerFunctions = {}; |
| final Set<Member> membersBeingGenerated = {}; |
| final Map<Reference, Closures> constructorClosures = {}; |
| late final w.FunctionBuilder initFunction; |
| late final w.ValueType voidMarker; |
| // Lazily import FFI memory if used. |
| late final w.Memory ffiMemory = mainModule.memories.import("ffi", "memory", |
| options.importSharedMemory, 0, options.sharedMemoryMaxPages); |
| |
| /// Maps record shapes to the record class for the shape. Classes generated |
| /// by `record_class_generator` library. |
| final Map<RecordShape, Class> recordClasses; |
| |
| // Caches for when identical source constructs need a common representation. |
| final Map<w.StorageType, w.ArrayType> immutableArrayTypeCache = {}; |
| final Map<w.StorageType, w.ArrayType> mutableArrayTypeCache = {}; |
| final Map<w.BaseFunction, w.Global> functionRefCache = {}; |
| final Map<Procedure, ClosureImplementation> tearOffFunctionCache = {}; |
| |
| final Map<FunctionNode, ClosureImplementation> closureImplementations = {}; |
| |
| // Some convenience accessors for commonly used values. |
| late final ClassInfo topInfo = classes[0]; |
| late final ClassInfo objectInfo = classInfo[coreTypes.objectClass]!; |
| late final ClassInfo closureInfo = classInfo[closureClass]!; |
| late final ClassInfo stackTraceInfo = classInfo[stackTraceClass]!; |
| late final ClassInfo recordInfo = classInfo[coreTypes.recordClass]!; |
| late final w.ArrayType typeArrayType = arrayTypeForDartType( |
| InterfaceType(typeClass, Nullability.nonNullable), |
| mutable: true); |
| late final w.ArrayType listArrayType = (classInfo[listBaseClass]! |
| .struct |
| .fields[FieldIndex.listArray] |
| .type as w.RefType) |
| .heapType as w.ArrayType; |
| late final w.ArrayType nullableObjectArrayType = arrayTypeForDartType( |
| coreTypes.objectRawType(Nullability.nullable), |
| mutable: true); |
| late final w.RefType typeArrayTypeRef = |
| w.RefType.def(typeArrayType, nullable: false); |
| late final w.RefType nullableObjectArrayTypeRef = |
| w.RefType.def(nullableObjectArrayType, nullable: false); |
| |
| final Map<w.ModuleBuilder, PartialInstantiator> _partialInstantiators = {}; |
| PartialInstantiator getPartialInstantiatorForModule(w.ModuleBuilder module) { |
| return _partialInstantiators[module] ??= PartialInstantiator(this, module); |
| } |
| |
| final Map<w.ModuleBuilder, PolymorphicDispatchers> _polymorphicDispatchers = |
| {}; |
| PolymorphicDispatchers getPolymorphicDispatchersForModule( |
| w.ModuleBuilder module) { |
| return _polymorphicDispatchers[module] ??= |
| PolymorphicDispatchers(this, module); |
| } |
| |
| final Map<w.ModuleBuilder, DynamicForwarders> _dynamicForwarders = {}; |
| DynamicForwarders getDynamicForwardersForModule(w.ModuleBuilder module) { |
| return _dynamicForwarders[module] ??= DynamicForwarders(this, module); |
| } |
| |
| final Map<w.ModuleBuilder, DummyValuesCollector> _dummyValueCollectors = {}; |
| DummyValuesCollector getDummyValuesCollectorForModule( |
| w.ModuleBuilder module) { |
| return _dummyValueCollectors[module] ??= DummyValuesCollector(this, module); |
| } |
| |
| /// Dart types that have specialized Wasm representations. |
| late final Map<Class, w.StorageType> builtinTypes = { |
| coreTypes.boolClass: w.NumType.i32, |
| coreTypes.intClass: w.NumType.i64, |
| coreTypes.doubleClass: w.NumType.f64, |
| boxedBoolClass: w.NumType.i32, |
| boxedIntClass: w.NumType.i64, |
| boxedDoubleClass: w.NumType.f64, |
| wasmI8Class: w.PackedType.i8, |
| wasmI16Class: w.PackedType.i16, |
| wasmI32Class: w.NumType.i32, |
| wasmI64Class: w.NumType.i64, |
| wasmF32Class: w.NumType.f32, |
| wasmF64Class: w.NumType.f64, |
| wasmAnyRefClass: const w.RefType.any(nullable: false), |
| wasmExternRefClass: const w.RefType.extern(nullable: false), |
| wasmFuncRefClass: const w.RefType.func(nullable: false), |
| wasmEqRefClass: const w.RefType.eq(nullable: false), |
| wasmStructRefClass: const w.RefType.struct(nullable: false), |
| wasmArrayRefClass: const w.RefType.array(nullable: false), |
| }; |
| |
| /// The box classes corresponding to each of the value types. |
| late final Map<w.ValueType, Class> boxedClasses = { |
| w.NumType.i32: boxedBoolClass, |
| w.NumType.i64: boxedIntClass, |
| w.NumType.f64: boxedDoubleClass, |
| }; |
| |
| /// Classes whose identity hash code is their hash code rather than the |
| /// identity hash code field in the struct. Each implementation class maps to |
| /// the class containing the implementation of its `hashCode` getter. |
| late final Map<Class, Class> valueClasses = { |
| boxedIntClass: boxedIntClass, |
| boxedDoubleClass: boxedDoubleClass, |
| boxedBoolClass: coreTypes.boolClass, |
| if (!options.jsCompatibility) ...{ |
| oneByteStringClass: stringBaseClass, |
| twoByteStringClass: stringBaseClass |
| }, |
| if (options.jsCompatibility) ...{jsStringClass: jsStringClass}, |
| }; |
| |
| /// Type for vtable entries for dynamic calls. These entries are used in |
| /// dynamic invocations and `Function.apply`. |
| late final w.FunctionType dynamicCallVtableEntryFunctionType = |
| typesBuilder.defineFunction([ |
| // Closure |
| w.RefType.def(closureLayouter.closureBaseStruct, nullable: false), |
| |
| // Type arguments |
| typeArrayTypeRef, |
| |
| // Positional arguments |
| nullableObjectArrayTypeRef, |
| |
| // Named arguments, represented as array of symbol and object pairs |
| nullableObjectArrayTypeRef, |
| ], [ |
| topInfo.nullableType |
| ]); |
| |
| /// Type of a dynamic invocation forwarder function. |
| late final w.FunctionType dynamicInvocationForwarderFunctionType = |
| typesBuilder.defineFunction([ |
| // Receiver |
| topInfo.nonNullableType, |
| |
| // Type arguments |
| typeArrayTypeRef, |
| |
| // Positional arguments |
| nullableObjectArrayTypeRef, |
| |
| // Named arguments, represented as array of symbol and object pairs |
| nullableObjectArrayTypeRef, |
| ], [ |
| topInfo.nullableType |
| ]); |
| |
| /// Type of a dynamic get forwarder function. |
| late final w.FunctionType dynamicGetForwarderFunctionType = |
| typesBuilder.defineFunction([ |
| // Receiver |
| topInfo.nonNullableType, |
| ], [ |
| topInfo.nullableType |
| ]); |
| |
| /// Type of a dynamic set forwarder function. |
| late final w.FunctionType dynamicSetForwarderFunctionType = |
| typesBuilder.defineFunction([ |
| // Receiver |
| topInfo.nonNullableType, |
| |
| // Positional argument |
| topInfo.nullableType, |
| ], [ |
| topInfo.nullableType |
| ]); |
| |
| // Module predicates and helpers |
| final ModuleOutputData _moduleOutputData; |
| Iterable<w.ModuleBuilder> get modules => _builderToOutput.keys; |
| w.ModuleBuilder get mainModule => |
| _outputToBuilder[_moduleOutputData.mainModule]!; |
| w.TypesBuilder get typesBuilder => mainModule.types; |
| final Map<ModuleOutput, w.ModuleBuilder> _outputToBuilder = {}; |
| final Map<w.ModuleBuilder, ModuleOutput> _builderToOutput = {}; |
| bool get hasMultipleModules => _moduleOutputData.hasMultipleModules; |
| |
| w.ModuleBuilder moduleForReference(Reference reference) => |
| _outputToBuilder[_moduleOutputData.moduleForReference(reference)]!; |
| |
| String nameForModule(w.ModuleBuilder module) => |
| _builderToOutput[module]!.moduleImportName; |
| |
| bool isMainModule(w.ModuleBuilder module) => _builderToOutput[module]!.isMain; |
| |
| /// Maps compiled members to their [Closures], with capture information. |
| final Map<Member, Closures> _memberClosures = {}; |
| |
| Closures getClosures(Member member, {bool findCaptures = true}) => |
| findCaptures |
| ? _memberClosures.putIfAbsent( |
| member, () => Closures(this, member, findCaptures: true)) |
| : Closures(this, member, findCaptures: false); |
| |
| Translator(this.component, this.coreTypes, this.index, this.recordClasses, |
| this._moduleOutputData, this.options) |
| : libraries = component.libraries, |
| hierarchy = |
| ClassHierarchy(component, coreTypes) as ClosedWorldClassHierarchy { |
| typeEnvironment = TypeEnvironment(coreTypes, hierarchy); |
| subtypes = hierarchy.computeSubtypesInformation(); |
| closureLayouter = ClosureLayouter(this); |
| classInfoCollector = ClassInfoCollector(this); |
| staticTablesPerType = StaticDispatchTables(this); |
| dispatchTable = DispatchTable(this); |
| compilationQueue = CompilationQueue(); |
| functions = FunctionCollector(this); |
| types = Types(this); |
| exceptionTag = ExceptionTag(this); |
| } |
| |
| void _initLoadLibraryImportMap() { |
| final mapEntries = <MapLiteralEntry>[]; |
| _moduleOutputData.generateModuleImportMap().forEach((libName, importMap) { |
| final subMapEntries = <MapLiteralEntry>[]; |
| importMap.forEach((importName, moduleNames) { |
| subMapEntries.add(MapLiteralEntry(StringLiteral(importName), |
| ListLiteral([...moduleNames.map(StringLiteral.new)]))); |
| }); |
| mapEntries.add( |
| MapLiteralEntry(StringLiteral(libName), MapLiteral(subMapEntries))); |
| }); |
| final stringClass = |
| options.jsCompatibility ? jsStringClass : stringBaseClass; |
| loadLibraryImportMap.function.body = ReturnStatement(MapLiteral(mapEntries, |
| keyType: InterfaceType(stringClass, Nullability.nonNullable), |
| valueType: InterfaceType(coreTypes.mapNonNullableRawType.classNode, |
| Nullability.nonNullable, [ |
| InterfaceType(stringClass, Nullability.nonNullable), |
| InterfaceType(stringClass, Nullability.nonNullable) |
| ]))); |
| loadLibraryImportMap.isExternal = false; |
| } |
| |
| void _initModules(Uri Function(String moduleName)? sourceMapUrlGenerator) { |
| for (final outputModule in _moduleOutputData.modules) { |
| // `moduleName` is the suffix appended to the filename which is the empty |
| // string for the main module. `moduleImportName` provides a non-empty |
| // name for every module. We provide the former to generate source map |
| // uris and the latter to fill the NameSection of the module. |
| final builder = w.ModuleBuilder(outputModule.moduleImportName, |
| sourceMapUrlGenerator?.call(outputModule.moduleName), |
| parent: outputModule.isMain ? null : mainModule, |
| watchPoints: options.watchPoints); |
| _outputToBuilder[outputModule] = builder; |
| _builderToOutput[builder] = outputModule; |
| } |
| } |
| |
| Map<ModuleOutput, w.Module> translate( |
| Uri Function(String moduleName)? sourceMapUrlGenerator) { |
| _initLoadLibraryImportMap(); |
| _initModules(sourceMapUrlGenerator); |
| voidMarker = w.RefType.def(w.StructType("void"), nullable: true); |
| |
| closureLayouter.collect(); |
| classInfoCollector.collect(); |
| |
| initFunction = mainModule.functions |
| .define(typesBuilder.defineFunction(const [], const []), "#init"); |
| mainModule.functions.start = initFunction; |
| |
| globals = Globals(this); |
| constants = Constants(this); |
| |
| dispatchTable.build(); |
| |
| functions.initialize(); |
| while (!compilationQueue.isEmpty) { |
| final task = compilationQueue.pop(); |
| task.run(this, options.printKernel, options.printWasm); |
| } |
| |
| constructorClosures.clear(); |
| dispatchTable.output(); |
| staticTablesPerType.outputTables(); |
| initFunction.body.end(); |
| |
| for (ConstantInfo info in constants.constantInfo.values) { |
| w.BaseFunction? function = info.function; |
| if (function != null) { |
| _printFunction(function, info.constant); |
| } else { |
| if (options.printWasm) { |
| print("Global #${info.global.name}: ${info.constant}"); |
| final global = info.global; |
| if (global is w.GlobalBuilder) { |
| print(global.initializer.trace); |
| } |
| } |
| } |
| } |
| _printFunction(initFunction, "init"); |
| |
| final result = <ModuleOutput, w.Module>{}; |
| _outputToBuilder.forEach((outputModule, builder) { |
| result[outputModule] = builder.build(); |
| }); |
| return result; |
| } |
| |
| void _printFunction(w.BaseFunction function, Object name) { |
| if (options.printWasm) { |
| print("#${function.name}: $name"); |
| final f = function; |
| if (f is w.FunctionBuilder) { |
| print(f.body.trace); |
| } |
| } |
| } |
| |
| /// Gets the function associated with [reference] and calls its using |
| /// [callFunction]. |
| List<w.ValueType> callReference( |
| Reference reference, w.InstructionsBuilder b) { |
| return callFunction(functions.getFunction(reference), b); |
| } |
| |
| late final WasmFunctionImporter _importedFunctions = |
| WasmFunctionImporter(this, 'func'); |
| |
| /// Generates a set of instructions to call [function] adding indirection |
| /// if the call crosses a module boundary. Calls the function directly if it |
| /// is local. Imports the function and calls it directly if is in the main |
| /// module. Otherwise does an indirect call through the static dispatch table. |
| List<w.ValueType> callFunction( |
| w.BaseFunction function, w.InstructionsBuilder b) { |
| final targetModule = function.enclosingModule; |
| // TODO(natebiggs): Consider inlining function body in some scenarios. |
| if (targetModule == b.module) { |
| b.call(function); |
| } else if (isMainModule(targetModule)) { |
| final importedFunction = _importedFunctions.get(function, b.module); |
| b.call(importedFunction); |
| } else { |
| final staticTable = staticTablesPerType.getTableForType(function.type); |
| b.i32_const(staticTable.indexForFunction(function)); |
| b.table_get(staticTable.getWasmTable(b.module)); |
| b.ref_as_non_null(); |
| b.call_ref(function.type); |
| } |
| return function.type.outputs; |
| } |
| |
| void callDispatchTable(w.InstructionsBuilder b, SelectorInfo selector) { |
| // TODO(natebiggs): Handle dispatch to dynamic module overrideable members. |
| b.struct_get(topInfo.struct, FieldIndex.classId); |
| if (selector.offset! != 0) { |
| b.i32_const(selector.offset!); |
| b.i32_add(); |
| } |
| b.call_indirect(selector.signature, dispatchTable.getWasmTable(b.module)); |
| functions.recordSelectorUse(selector); |
| } |
| |
| Class classForType(DartType type) { |
| return type is InterfaceType |
| ? type.classNode |
| : type is TypeParameterType |
| ? classForType(type.bound) |
| : coreTypes.objectClass; |
| } |
| |
| /// Compute the runtime type of a tear-off. This is the signature of the |
| /// method with the types of all covariant parameters replaced by `Object?`. |
| FunctionType getTearOffType(Procedure method) { |
| assert(method.kind == ProcedureKind.Method); |
| final FunctionType staticType = method.getterType as FunctionType; |
| |
| final positionalParameters = List.of(staticType.positionalParameters); |
| assert(positionalParameters.length == |
| method.function.positionalParameters.length); |
| |
| final namedParameters = List.of(staticType.namedParameters); |
| assert(namedParameters.length == method.function.namedParameters.length); |
| |
| for (int i = 0; i < positionalParameters.length; i++) { |
| final param = method.function.positionalParameters[i]; |
| if (param.isCovariantByDeclaration || param.isCovariantByClass) { |
| positionalParameters[i] = coreTypes.objectNullableRawType; |
| } |
| } |
| |
| for (int i = 0; i < namedParameters.length; i++) { |
| final param = method.function.namedParameters[i]; |
| if (param.isCovariantByDeclaration || param.isCovariantByClass) { |
| namedParameters[i] = NamedType( |
| namedParameters[i].name, coreTypes.objectNullableRawType, |
| isRequired: namedParameters[i].isRequired); |
| } |
| } |
| |
| return FunctionType( |
| positionalParameters, staticType.returnType, Nullability.nonNullable, |
| namedParameters: namedParameters, |
| typeParameters: staticType.typeParameters, |
| requiredParameterCount: staticType.requiredParameterCount); |
| } |
| |
| /// Get the exception tag reference for [module]. |
| w.Tag getExceptionTag(w.ModuleBuilder module) => |
| exceptionTag.getExceptionTag(module); |
| |
| w.ValueType translateType(DartType type) { |
| w.StorageType wasmType = translateStorageType(type); |
| if (wasmType is w.ValueType) return wasmType; |
| |
| // We represent the packed i8/i16 types as zero-extended i32 type. |
| // Dart code can currently only obtain them via loading from packed arrays |
| // and only use them for storing into packed arrays (there are no |
| // conversion or other operations on WasmI8/WasmI16). |
| if (wasmType is w.PackedType) return w.NumType.i32; |
| throw "Cannot translate $type to wasm type."; |
| } |
| |
| bool _hasSuperclass(Class cls, Class superclass) { |
| while (cls.superclass != null) { |
| cls = cls.superclass!; |
| if (cls == superclass) return true; |
| } |
| return false; |
| } |
| |
| bool isWasmType(Class cls) => |
| cls == wasmTypesBaseClass || _hasSuperclass(cls, wasmTypesBaseClass); |
| |
| w.StorageType translateStorageType(DartType type) { |
| bool nullable = type.isPotentiallyNullable; |
| if (type is InterfaceType) { |
| Class cls = type.classNode; |
| |
| // Abstract `Function`? |
| if (cls == coreTypes.functionClass) { |
| return w.RefType.def(closureLayouter.closureBaseStruct, |
| nullable: nullable); |
| } |
| |
| // Wasm array? |
| if (cls == wasmArrayClass) { |
| DartType elementType = type.typeArguments.single; |
| return w.RefType.def(arrayTypeForDartType(elementType, mutable: true), |
| nullable: nullable); |
| } |
| |
| // Immutable Wasm array? |
| if (cls == immutableWasmArrayClass) { |
| DartType elementType = type.typeArguments.single; |
| return w.RefType.def(arrayTypeForDartType(elementType, mutable: false), |
| nullable: nullable); |
| } |
| |
| // Wasm function? |
| if (cls == wasmFunctionClass) { |
| DartType functionType = type.typeArguments.single; |
| if (functionType is! FunctionType) { |
| throw "The type argument of a WasmFunction must be a function type"; |
| } |
| if (functionType.typeParameters.isNotEmpty || |
| functionType.namedParameters.isNotEmpty || |
| functionType.requiredParameterCount != |
| functionType.positionalParameters.length) { |
| throw "A WasmFunction can't have optional/type parameters"; |
| } |
| DartType returnType = functionType.returnType; |
| bool voidReturn = returnType is InterfaceType && |
| returnType.classNode == wasmVoidClass; |
| List<w.ValueType> inputs = [ |
| for (DartType type in functionType.positionalParameters) |
| translateType(type) |
| ]; |
| List<w.ValueType> outputs = [ |
| if (!voidReturn) translateType(functionType.returnType) |
| ]; |
| w.FunctionType wasmType = typesBuilder.defineFunction(inputs, outputs); |
| return w.RefType.def(wasmType, nullable: nullable); |
| } |
| |
| // Other built-in type? |
| w.StorageType? builtin = builtinTypes[cls]; |
| if (builtin != null) { |
| if (!nullable) { |
| return builtin; |
| } |
| if (isWasmType(cls)) { |
| if (builtin.isPrimitive) throw "Wasm numeric types can't be nullable"; |
| return (builtin as w.RefType).withNullability(nullable); |
| } |
| final boxedBuiltin = classInfo[boxedClasses[builtin]!]!; |
| return nullable |
| ? boxedBuiltin.nullableType |
| : boxedBuiltin.nonNullableType; |
| } |
| |
| // Regular class. |
| return classInfo[cls]!.repr.typeWithNullability(nullable); |
| } |
| if (type is DynamicType || type is VoidType) { |
| return topInfo.nullableType; |
| } |
| if (type is NullType || type is NeverType) { |
| return const w.RefType.none(nullable: true); |
| } |
| if (type is TypeParameterType) { |
| return translateStorageType(nullable |
| ? type.bound.withDeclaredNullability(Nullability.nullable) |
| : type.bound); |
| } |
| if (type is IntersectionType) { |
| return translateStorageType(type.left); |
| } |
| if (type is FutureOrType) { |
| return topInfo.typeWithNullability(nullable); |
| } |
| if (type is FunctionType) { |
| ClosureRepresentation? representation = |
| closureLayouter.getClosureRepresentation( |
| type.typeParameters.length, |
| type.positionalParameters.length, |
| type.namedParameters.map((p) => p.name).toList()); |
| return w.RefType.def( |
| representation != null |
| ? representation.closureStruct |
| : classInfo[typeClass]!.struct, |
| nullable: nullable); |
| } |
| if (type is ExtensionType) { |
| return translateStorageType(type.extensionTypeErasure); |
| } |
| if (type is RecordType) { |
| return getRecordClassInfo(type).typeWithNullability(nullable); |
| } |
| throw "Unsupported type ${type.runtimeType}"; |
| } |
| |
| w.ArrayType arrayTypeForDartType(DartType type, {required bool mutable}) { |
| while (type is TypeParameterType) { |
| type = type.bound; |
| } |
| return wasmArrayType( |
| translateStorageType(type), type.toText(defaultAstTextStrategy), |
| mutable: mutable); |
| } |
| |
| w.ArrayType wasmArrayType(w.StorageType type, String name, |
| {bool mutable = true}) { |
| final cache = mutable ? mutableArrayTypeCache : immutableArrayTypeCache; |
| return cache.putIfAbsent( |
| type, |
| () => typesBuilder.defineArray( |
| "${mutable ? '' : 'Immutable'}Array<$name>", |
| elementType: w.FieldType(type, mutable: mutable))); |
| } |
| |
| /// Translate a Dart type as it should appear on parameters and returns of |
| /// imported and exported functions. All wasm types are allowed on the interop |
| /// boundary, but in order to be compatible with the `--closed-world` mode of |
| /// Binaryen, we coerce all reference types to abstract reference types |
| /// (`anyref`, `funcref` or `externref`). |
| /// This function can be called before the class info is built. |
| w.ValueType translateExternalType(DartType type) { |
| final bool isPotentiallyNullable = type.isPotentiallyNullable; |
| if (type is InterfaceType) { |
| Class cls = type.classNode; |
| if (cls == wasmFuncRefClass || cls == wasmFunctionClass) { |
| return w.RefType.func(nullable: isPotentiallyNullable); |
| } |
| if (cls == wasmExternRefClass) { |
| return w.RefType.extern(nullable: isPotentiallyNullable); |
| } |
| if (cls == wasmArrayRefClass) { |
| return w.RefType.array(nullable: isPotentiallyNullable); |
| } |
| if (cls == wasmArrayClass) { |
| final elementType = |
| translateExternalStorageType(type.typeArguments.single); |
| return w.RefType.def( |
| wasmArrayType(elementType, '$elementType', mutable: true), |
| nullable: isPotentiallyNullable); |
| } |
| if (!isPotentiallyNullable) { |
| w.StorageType? builtin = builtinTypes[cls]; |
| if (builtin != null && builtin.isPrimitive) { |
| return builtin as w.ValueType; |
| } |
| } |
| } |
| // TODO(joshualitt): We'd like to use the potential nullability here too, |
| // but unfortunately this seems to break things. |
| return w.RefType.any(nullable: true); |
| } |
| |
| w.StorageType translateExternalStorageType(DartType type) { |
| if (type is InterfaceType) { |
| final cls = type.classNode; |
| if (isWasmType(cls)) { |
| final isNullable = type.isPotentiallyNullable; |
| final w.StorageType? builtin = builtinTypes[cls]; |
| if (builtin != null) { |
| if (!isNullable) return builtin; |
| if (builtin.isPrimitive) throw "Wasm numeric types can't be nullable"; |
| return (builtin as w.RefType).withNullability(isNullable); |
| } |
| } |
| } |
| return translateExternalType(type) as w.RefType; |
| } |
| |
| /// Creates a global reference to [f] in its [w.BaseFunction.enclosingModule]. |
| w.Global makeFunctionRef(w.BaseFunction f) { |
| return functionRefCache.putIfAbsent(f, () { |
| final global = f.enclosingModule.globals.define( |
| w.GlobalType(w.RefType.def(f.type, nullable: false), mutable: false)); |
| global.initializer.ref_func(f); |
| global.initializer.end(); |
| return global; |
| }); |
| } |
| |
| ClosureImplementation getTearOffClosure(Procedure member) { |
| return tearOffFunctionCache.putIfAbsent(member, () { |
| assert(member.kind == ProcedureKind.Method); |
| w.BaseFunction target = functions.getFunction(member.reference); |
| return getClosure(member.function, target, |
| paramInfoForDirectCall(member.reference), "$member tear-off"); |
| }); |
| } |
| |
| ClosureImplementation getClosure(FunctionNode functionNode, |
| w.BaseFunction target, ParameterInfo paramInfo, String name) { |
| // We compile a block multiple times in try-catch, to catch Dart exceptions |
| // and then again to catch JS exceptions. We may also ask for |
| // `ClosureImplementation` for a local function multiple times as we see |
| // direct calls to the closure (in TFA direct-call metadata). Avoid |
| // recompiling the closures in these cases by caching implementations. |
| // |
| // Note that every `FunctionNode` passed to this method will have one |
| // `ParameterInfo` for them. For local functions, the `ParameterInfo` will |
| // be the one generated by `ParameterInfo.fromLocalFunction`, for others it |
| // will be the value returned by `paramInfoForDirectCall`. So the key for |
| // this cache can be just `FunctionNode`, instead of `(FunctionNode, |
| // ParameterInfo)`. |
| final existingImplementation = closureImplementations[functionNode]; |
| if (existingImplementation != null) { |
| return existingImplementation; |
| } |
| |
| final targetModule = target.enclosingModule; |
| |
| // Look up the closure representation for the signature. |
| int typeCount = functionNode.typeParameters.length; |
| int positionalCount = functionNode.positionalParameters.length; |
| final List<VariableDeclaration> namedParamsSorted = |
| functionNode.namedParameters.toList() |
| ..sort((p1, p2) => p1.name!.compareTo(p2.name!)); |
| List<String> names = namedParamsSorted.map((p) => p.name!).toList(); |
| assert(typeCount == paramInfo.typeParamCount); |
| assert(positionalCount <= paramInfo.positional.length); |
| assert(names.length <= paramInfo.named.length); |
| assert(target.type.inputs.length == |
| (paramInfo.takesContextOrReceiver ? 1 : 0) + |
| paramInfo.typeParamCount + |
| paramInfo.positional.length + |
| paramInfo.named.length); |
| ClosureRepresentation representation = closureLayouter |
| .getClosureRepresentation(typeCount, positionalCount, names)!; |
| assert(representation.vtableStruct.fields.length == |
| representation.vtableBaseIndex + |
| (1 + positionalCount) + |
| representation.nameCombinations.length); |
| |
| List<w.BaseFunction> functions = []; |
| |
| bool canBeCalledWith(int posArgCount, List<String> argNames) { |
| if (posArgCount < functionNode.requiredParameterCount) { |
| return false; |
| } |
| |
| int namedArgIdx = 0, namedParamIdx = 0; |
| while (namedArgIdx < argNames.length && |
| namedParamIdx < namedParamsSorted.length) { |
| int comp = argNames[namedArgIdx] |
| .compareTo(namedParamsSorted[namedParamIdx].name!); |
| if (comp < 0) { |
| // Unexpected named argument passed |
| return false; |
| } else if (comp > 0) { |
| if (namedParamsSorted[namedParamIdx].isRequired) { |
| // Required named parameter not passed |
| return false; |
| } else { |
| // Optional named parameter not passed |
| namedParamIdx++; |
| continue; |
| } |
| } else { |
| // Expected required or optional named parameter passed |
| namedArgIdx++; |
| namedParamIdx++; |
| } |
| } |
| |
| if (namedArgIdx < argNames.length) { |
| // Unexpected named argument(s) passed |
| return false; |
| } |
| |
| while (namedParamIdx < namedParamsSorted.length) { |
| if (namedParamsSorted[namedParamIdx++].isRequired) { |
| // Required named parameter not passed |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| w.BaseFunction makeTrampoline( |
| w.FunctionType signature, int posArgCount, List<String> argNames) { |
| final trampoline = |
| targetModule.functions.define(signature, "$name trampoline"); |
| compilationQueue.add(CompilationTask( |
| trampoline, |
| _ClosureTrampolineGenerator(this, trampoline, target, typeCount, |
| posArgCount, argNames, paramInfo))); |
| return trampoline; |
| } |
| |
| w.BaseFunction makeDynamicCallEntry() { |
| final function = targetModule.functions.define( |
| dynamicCallVtableEntryFunctionType, "$name dynamic call entry"); |
| compilationQueue.add(CompilationTask( |
| function, |
| _ClosureDynamicEntryGenerator( |
| this, functionNode, target, paramInfo, name, function))); |
| return function; |
| } |
| |
| void fillVtableEntry( |
| w.InstructionsBuilder ib, int posArgCount, List<String> argNames) { |
| int fieldIndex = representation.vtableBaseIndex + functions.length; |
| assert(fieldIndex == |
| representation.fieldIndexForSignature(posArgCount, argNames)); |
| w.FunctionType signature = representation.getVtableFieldType(fieldIndex); |
| w.BaseFunction function = canBeCalledWith(posArgCount, argNames) |
| ? makeTrampoline(signature, posArgCount, argNames) |
| : getDummyValuesCollectorForModule(ib.module) |
| .getDummyFunction(signature); |
| functions.add(function); |
| ib.ref_func(function); |
| } |
| |
| final vtable = targetModule.globals.define(w.GlobalType( |
| w.RefType.def(representation.vtableStruct, nullable: false), |
| mutable: false)); |
| final ib = vtable.initializer; |
| final dynamicCallEntry = makeDynamicCallEntry(); |
| ib.ref_func(dynamicCallEntry); |
| if (representation.isGeneric) { |
| ib.ref_func(representation |
| .instantiationTypeComparisonFunctionForModule(ib.module)); |
| ib.ref_func( |
| representation.instantiationTypeHashFunctionForModule(ib.module)); |
| ib.ref_func(representation.instantiationFunctionForModule(ib.module)); |
| } |
| for (int posArgCount = 0; posArgCount <= positionalCount; posArgCount++) { |
| fillVtableEntry(ib, posArgCount, const []); |
| } |
| for (NameCombination nameCombination in representation.nameCombinations) { |
| fillVtableEntry(ib, positionalCount, nameCombination.names); |
| } |
| ib.struct_new(representation.vtableStruct); |
| ib.end(); |
| |
| final implementation = ClosureImplementation(representation, functions, |
| dynamicCallEntry, vtable, targetModule, paramInfo); |
| closureImplementations[functionNode] = implementation; |
| return implementation; |
| } |
| |
| 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.InstructionsBuilder b, w.ValueType from, w.ValueType to) { |
| if (from == voidMarker || to == voidMarker) { |
| if (from != voidMarker) { |
| b.drop(); |
| return; |
| } |
| if (to != voidMarker) { |
| // This can happen e.g. when a `return;` is guaranteed to be never taken |
| // but TFA didn't remove the dead code. In that case we synthesize a |
| // dummy value. |
| getDummyValuesCollectorForModule(b.module).instantiateDummyValue(b, to); |
| return; |
| } |
| } |
| |
| if (!from.isSubtypeOf(to)) { |
| if (from is w.RefType && to is w.RefType) { |
| if (from.withNullability(false).isSubtypeOf(to)) { |
| // Null check |
| b.ref_as_non_null(); |
| } else { |
| // Downcast |
| b.ref_cast(to); |
| } |
| } else if (to is w.RefType) { |
| // Boxing |
| Class cls = boxedClasses[from]!; |
| ClassInfo info = classInfo[cls]!; |
| assert(info.struct.isSubtypeOf(to.heapType), |
| '${info.struct} is not a subtype of ${to.heapType}'); |
| |
| if (cls == boxedBoolClass) { |
| final constantType = w.RefType(info.struct, nullable: false); |
| b.if_([], [constantType]); |
| constants.instantiateConstant(b, BoolConstant(true), constantType); |
| b.else_(); |
| constants.instantiateConstant(b, BoolConstant(false), constantType); |
| b.end(); |
| return; |
| } |
| |
| w.Local temp = b.addLocal(from); |
| b.local_set(temp); |
| b.i32_const(info.classId); |
| b.local_get(temp); |
| b.struct_new(info.struct); |
| } else if (from is w.RefType) { |
| // Unboxing |
| ClassInfo info = classInfo[boxedClasses[to]!]!; |
| if (!from.heapType.isSubtypeOf(info.struct)) { |
| // Cast to box type |
| b.ref_cast(info.nonNullableType); |
| } |
| b.struct_get(info.struct, FieldIndex.boxValue); |
| } else { |
| if (options.omitExplicitTypeChecks || options.omitImplicitTypeChecks) { |
| b.unreachable(); |
| } else { |
| throw "Conversion between non-reference types (from $from to $to)"; |
| } |
| } |
| } |
| } |
| |
| w.FunctionType signatureForDispatchTableCall(Reference target) { |
| assert(target.asMember.isInstanceMember); |
| return dispatchTable.selectorForTarget(target).signature; |
| } |
| |
| ParameterInfo paramInfoForDispatchTableCall(Reference target) { |
| assert(target.asMember.isInstanceMember); |
| return dispatchTable.selectorForTarget(target).paramInfo; |
| } |
| |
| AstCallTarget directCallTarget(Reference target, bool useUncheckedEntry) { |
| final signature = signatureForDirectCall(target); |
| return AstCallTarget(signature, this, target, useUncheckedEntry); |
| } |
| |
| w.FunctionType signatureForDirectCall(Reference target) { |
| if (target.asMember.isInstanceMember) { |
| final selector = dispatchTable.selectorForTarget(target); |
| if (selector.targetSet.contains(target)) { |
| return selector.signature; |
| } |
| } |
| return functions.getFunctionType(target); |
| } |
| |
| ParameterInfo paramInfoForDirectCall(Reference target) { |
| if (target.asMember.isInstanceMember) { |
| final selector = dispatchTable.selectorForTarget(target); |
| if (selector.targetSet.contains(target)) { |
| return selector.paramInfo; |
| } |
| } |
| return staticParamInfo.putIfAbsent( |
| target, () => ParameterInfo.fromMember(target)); |
| } |
| |
| w.ValueType preciseThisFor(Member member, {bool nullable = false}) { |
| assert(member.isInstanceMember || member is Constructor); |
| |
| Class cls = member.enclosingClass!; |
| final w.StorageType? builtin = builtinTypes[cls]; |
| final boxClass = boxedClasses[builtin]; |
| if (boxClass != null) { |
| // We represent `this` as an unboxed type. |
| if (!nullable) return builtin as w.ValueType; |
| // Otherwise we use [boxClass] to represent `this`. |
| cls = boxClass; |
| } |
| final representationClassInfo = classInfo[cls]!.repr; |
| return nullable |
| ? representationClassInfo.nullableType |
| : representationClassInfo.nonNullableType; |
| } |
| |
| /// Get the Wasm table declared by [field], or `null` if [field] is not a |
| /// declaration of a Wasm table. |
| /// |
| /// This function participates in tree shaking in the sense that if it's |
| /// never called for a particular table declaration, that table is not added |
| /// to the output module. |
| w.Table? getTable(w.ModuleBuilder module, Field field) { |
| DartType fieldType = field.type; |
| if (fieldType is! InterfaceType || fieldType.classNode != wasmTableClass) { |
| return null; |
| } |
| final mainTable = _declaredFieldTables.putIfAbsent(field, () { |
| w.RefType elementType = |
| translateType(fieldType.typeArguments.single) as w.RefType; |
| Expression sizeExp = (field.initializer as ConstructorInvocation) |
| .arguments |
| .positional |
| .single; |
| if (sizeExp is StaticGet && sizeExp.target is Field) { |
| sizeExp = (sizeExp.target as Field).initializer!; |
| } |
| int size = sizeExp is ConstantExpression |
| ? (sizeExp.constant as IntConstant).value |
| : (sizeExp as IntLiteral).value; |
| return mainModule.tables.define(elementType, size); |
| }); |
| |
| return _importedFieldTables.get(mainTable, module); |
| } |
| |
| Member? singleTarget(TreeNode node) { |
| return directCallMetadata[node]?.targetMember; |
| } |
| |
| /// Direct call information of a [FunctionInvocation] based on TFA's direct |
| /// call metadata. |
| SingleClosureTarget? singleClosureTarget(FunctionInvocation node, |
| ClosureRepresentation representation, StaticTypeContext typeContext) { |
| final (Member, int)? directClosureCall = |
| directCallMetadata[node]?.targetClosure; |
| |
| if (directClosureCall == null) { |
| return null; |
| } |
| |
| // To avoid using the `Null` class, avoid devirtualizing to `Null` members. |
| // `noSuchMethod` is also not allowed as `Null` inherits it. |
| if (directClosureCall.$1.enclosingClass == coreTypes.deprecatedNullClass || |
| directClosureCall.$1 == objectNoSuchMethod) { |
| return null; |
| } |
| |
| final member = directClosureCall.$1; |
| final closureId = directClosureCall.$2; |
| |
| if (closureId == 0) { |
| // The member is called as a closure (tear-off). We'll generate a direct |
| // call to the member. |
| final lambdaDartType = |
| member.function!.computeFunctionType(Nullability.nonNullable); |
| |
| // Check that type of the receiver is a subtype of |
| if (!typeEnvironment.isSubtypeOf( |
| lambdaDartType, |
| node.receiver.getStaticType(typeContext), |
| SubtypeCheckMode.withNullabilities)) { |
| return null; |
| } |
| |
| return SingleClosureTarget._( |
| member, |
| paramInfoForDirectCall(member.reference), |
| signatureForDirectCall(member.reference), |
| null, |
| ); |
| } else { |
| // A closure in the member is called. |
| final Closures enclosingMemberClosures = |
| getClosures(member, findCaptures: true); |
| final Lambda lambda = enclosingMemberClosures.lambdas.values |
| .firstWhere((lambda) => lambda.index == closureId - 1); |
| final FunctionType lambdaDartType = |
| lambda.functionNode.computeFunctionType(Nullability.nonNullable); |
| final w.BaseFunction lambdaFunction = |
| functions.getLambdaFunction(lambda, member, enclosingMemberClosures); |
| |
| if (!typeEnvironment.isSubtypeOf( |
| lambdaDartType, |
| node.receiver.getStaticType(typeContext), |
| SubtypeCheckMode.withNullabilities)) { |
| return null; |
| } |
| |
| return SingleClosureTarget._( |
| member, |
| ParameterInfo.fromLocalFunction(lambda.functionNode), |
| lambdaFunction.type, |
| lambdaFunction, |
| ); |
| } |
| } |
| |
| bool canSkipImplicitCheck(VariableDeclaration node) { |
| return inferredArgTypeMetadata[node]?.skipCheck ?? false; |
| } |
| |
| bool canUseUncheckedEntry(InstanceInvocationExpression node) { |
| if (node is ThisExpression) return true; |
| return inferredTypeMetadata[node]?.skipCheck ?? false; |
| } |
| |
| DartType typeOfParameterVariable(VariableDeclaration node, bool isRequired) { |
| // We have a guarantee that inferred types are correct. |
| final inferredType = _inferredTypeOfParameterVariable(node); |
| if (inferredType != null) { |
| return isRequired |
| ? inferredType |
| : inferredType.withDeclaredNullability(Nullability.nullable); |
| } |
| |
| final isCovariant = |
| node.isCovariantByDeclaration || node.isCovariantByClass; |
| if (isCovariant) { |
| // The type argument of a static type is not required to conform |
| // to the bounds of the type variable. Thus, any object can be |
| // passed to a parameter that is covariant by class. |
| return coreTypes.objectNullableRawType; |
| } |
| |
| return node.type; |
| } |
| |
| DartType typeOfReturnValue(Member member) { |
| if (member is Field) return typeOfField(member); |
| |
| return _inferredTypeOfReturnValue(member) ?? member.function!.returnType; |
| } |
| |
| DartType typeOfField(Field node) { |
| assert(!node.isLate); |
| return _inferredTypeOfField(node) ?? node.type; |
| } |
| |
| w.ValueType translateTypeOfParameter( |
| VariableDeclaration node, bool isRequired) { |
| return translateType(typeOfParameterVariable(node, isRequired)); |
| } |
| |
| w.ValueType translateTypeOfReturnValue(Member node) { |
| return translateType(typeOfReturnValue(node)); |
| } |
| |
| w.ValueType translateTypeOfField(Field node) { |
| return translateType(typeOfField(node)); |
| } |
| |
| w.ValueType translateTypeOfLocalVariable(VariableDeclaration node) { |
| return translateType(_inferredTypeOfLocalVariable(node) ?? node.type); |
| } |
| |
| DartType? _inferredTypeOfParameterVariable(VariableDeclaration node) { |
| return _filterInferredType(node.type, inferredArgTypeMetadata[node]); |
| } |
| |
| DartType? _inferredTypeOfReturnValue(Member node) { |
| return _filterInferredType( |
| node.function!.returnType, inferredReturnTypeMetadata[node]); |
| } |
| |
| DartType? _inferredTypeOfField(Field node) { |
| return _filterInferredType(node.type, inferredTypeMetadata[node]); |
| } |
| |
| DartType? _inferredTypeOfLocalVariable(VariableDeclaration node) { |
| InferredType? inferredType = inferredTypeMetadata[node]; |
| if (node.isFinal) { |
| inferredType ??= inferredTypeMetadata[node.initializer]; |
| } |
| return _filterInferredType(node.type, inferredType); |
| } |
| |
| DartType? _filterInferredType( |
| DartType defaultType, InferredType? inferredType) { |
| if (inferredType == null) return null; |
| |
| // To check whether [inferredType] is more precise than [defaultType] we |
| // require it (for now) to be an interface type. |
| if (defaultType is! InterfaceType) return null; |
| |
| final concreteClass = inferredType.concreteClass; |
| if (concreteClass == null) return null; |
| // TFA doesn't know how dart2wasm represents closures |
| if (concreteClass == closureClass) return null; |
| // The WasmFunction<>/WasmArray<>/WasmTable<> types need concrete type |
| // arguments. |
| if (concreteClass == wasmFunctionClass) return null; |
| if (concreteClass == wasmArrayClass) return null; |
| if (concreteClass == wasmTableClass) return null; |
| |
| // If the TFA inferred class is the same as the [defaultType] we prefer the |
| // latter as it has the correct type arguments. |
| if (concreteClass == defaultType.classNode) return null; |
| |
| // Sometimes we get inferred types that violate soundness (and would result |
| // in a runtime error, e.g. in a dynamic invocation forwarder passing an |
| // object of incorrect type to a target). |
| if (!hierarchy.isSubInterfaceOf(concreteClass, defaultType.classNode)) { |
| return null; |
| } |
| |
| final typeParameters = concreteClass.typeParameters; |
| final typeArguments = typeParameters.isEmpty |
| ? const <DartType>[] |
| : List<DartType>.filled(typeParameters.length, const DynamicType()); |
| final nullability = |
| inferredType.nullable ? Nullability.nullable : Nullability.nonNullable; |
| return InterfaceType(concreteClass, nullability, typeArguments); |
| } |
| |
| bool shouldInline( |
| Reference target, w.FunctionType signature, bool useUncheckedEntry) { |
| if (!options.inlining) return false; |
| |
| final member = target.asMember; |
| if (getPragma<bool>(member, "wasm:never-inline", true) == true) { |
| return false; |
| } |
| if (getPragma<bool>(member, "wasm:prefer-inline", true) == true) { |
| return true; |
| } |
| if (member is Field) return true; |
| if (target.isInitializerReference) return true; |
| |
| final function = member.function!; |
| if (function.body == null) return false; |
| |
| // We never want to inline throwing functions (as they are slow paths). |
| if (member is Procedure && member.function.returnType is NeverType) { |
| return false; |
| } |
| |
| final nodeCount = |
| NodeCounter(options.omitImplicitTypeChecks || useUncheckedEntry) |
| .countNodes(member); |
| |
| // Special cases for iterator inlining: |
| // class ... implements Iterable<T> { |
| // Iterator<T> get iterator => FooIterator(...) |
| // } |
| // class ... implements Iterator<T> { |
| // T get current => _current as E; |
| // } |
| final klass = member.enclosingClass; |
| if (klass != null) { |
| final name = member.name.text; |
| if (name == 'iterator' && nodeCount <= 20) { |
| if (typeEnvironment.isSubtypeOf( |
| klass.getThisType(coreTypes, Nullability.nonNullable), |
| coreTypes.iterableRawType(Nullability.nonNullable), |
| SubtypeCheckMode.ignoringNullabilities)) { |
| return true; |
| } |
| } |
| if (name == 'current' && nodeCount <= 5) { |
| if (typeEnvironment.isSubtypeOf( |
| klass.getThisType(coreTypes, Nullability.nonNullable), |
| coreTypes.iteratorRawType(Nullability.nonNullable), |
| SubtypeCheckMode.ignoringNullabilities)) { |
| return true; |
| } |
| } |
| } |
| |
| // If we think the overhead of pushing arguments is around the same as the |
| // body itself, we always inline. |
| if (nodeCount <= signature.inputs.length) return true; |
| |
| return nodeCount <= options.inliningLimit; |
| } |
| |
| bool supportsInlining(Reference target) { |
| final Member member = target.asMember; |
| if (membersContainingInnerFunctions.contains(member)) return false; |
| if (membersBeingGenerated.contains(member)) { |
| // Guard against recursive inlining. |
| // Though we allow inlining calls to constructor initializer & body |
| // functions while generating the constructor. |
| if (!target.isInitializerReference && |
| !target.isConstructorBodyReference) { |
| return false; |
| } |
| } |
| if (member is Field) return true; |
| if (member.function!.asyncMarker != AsyncMarker.Sync) return false; |
| return true; |
| } |
| |
| T? getPragma<T>(Annotatable node, String name, [T? defaultValue]) { |
| return util.getPragma(coreTypes, node, name, defaultValue: defaultValue); |
| } |
| |
| w.ValueType makeArray(w.InstructionsBuilder b, w.ArrayType arrayType, |
| int length, void Function(w.ValueType, int) generateItem) { |
| final w.ValueType elementType = arrayType.elementType.type.unpacked; |
| final arrayTypeRef = w.RefType.def(arrayType, nullable: false); |
| |
| if (length > maxArrayNewFixedLength) { |
| assert(arrayType.elementType.mutable); |
| // Too long for `array.new_fixed`. Set elements individually. |
| b.i32_const(length); |
| b.array_new_default(arrayType); |
| if (length > 0) { |
| final w.Local arrayLocal = b.addLocal(arrayTypeRef); |
| b.local_set(arrayLocal); |
| for (int i = 0; i < length; i++) { |
| b.local_get(arrayLocal); |
| b.i32_const(i); |
| generateItem(elementType, i); |
| b.array_set(arrayType); |
| } |
| b.local_get(arrayLocal); |
| } |
| } else { |
| for (int i = 0; i < length; i++) { |
| generateItem(elementType, i); |
| } |
| b.array_new_fixed(arrayType, length); |
| } |
| return arrayTypeRef; |
| } |
| |
| /// Indexes a Dart `WasmListBase` on the stack. |
| void indexList(w.InstructionsBuilder b, |
| void Function(w.InstructionsBuilder b) pushIndex) { |
| getListBaseArray(b); |
| pushIndex(b); |
| b.array_get(nullableObjectArrayType); |
| } |
| |
| /// Pushes a Dart `List`'s length onto the stack as `i32`. |
| void getListLength(w.InstructionsBuilder b) { |
| ClassInfo info = classInfo[listBaseClass]!; |
| b.struct_get(info.struct, FieldIndex.listLength); |
| b.i32_wrap_i64(); |
| } |
| |
| /// Get the `WasmListBase._data` field of type `WasmArray<Object?>`. |
| void getListBaseArray(w.InstructionsBuilder b) { |
| ClassInfo info = classInfo[listBaseClass]!; |
| b.struct_get(info.struct, FieldIndex.listArray); |
| } |
| |
| ClassInfo getRecordClassInfo(RecordType recordType) => |
| classInfo[recordClasses[RecordShape.fromType(recordType)]!]!; |
| |
| w.Global getInternalizedStringGlobal(w.ModuleBuilder module, String s) { |
| w.Global? internalizedString = _internalizedStringGlobals[(module, s)]; |
| if (internalizedString != null) { |
| return internalizedString; |
| } |
| final i = internalizedStringsForJSRuntime.length; |
| internalizedString = module.globals.import('s', '$i', |
| w.GlobalType(w.RefType.extern(nullable: true), mutable: false)); |
| _internalizedStringGlobals[(module, s)] = internalizedString; |
| internalizedStringsForJSRuntime.add(s); |
| return internalizedString; |
| } |
| } |
| |
| class CompilationQueue { |
| final List<CompilationTask> _pending = []; |
| |
| bool get isEmpty => _pending.isEmpty; |
| void add(CompilationTask entry) => _pending.add(entry); |
| CompilationTask pop() => _pending.removeLast(); |
| } |
| |
| class CompilationTask { |
| final w.FunctionBuilder function; |
| final CodeGenerator _codeGenerator; |
| |
| CompilationTask(this.function, this._codeGenerator); |
| |
| void run(Translator translator, bool printKernel, bool printWasm) { |
| _codeGenerator.generate(function.body, function.locals.toList(), null); |
| if (printWasm) { |
| print("#${function.name} (synthetic)"); |
| print(function.type); |
| print(function.body.trace); |
| } |
| } |
| } |
| |
| // Compilation task for AST. |
| class AstCompilationTask extends CompilationTask { |
| final Reference reference; |
| |
| AstCompilationTask(super.function, super._createCodeGenerator, this.reference) |
| : super(); |
| |
| @override |
| void run(Translator translator, bool printKernel, bool printWasm) { |
| final member = reference.asMember; |
| |
| final codeGen = getMemberCodeGenerator(translator, function, reference); |
| codeGen.generate(function.body, function.locals.toList(), null); |
| |
| if (printKernel || printWasm) { |
| final (:name, :exportName) = _getNames(translator); |
| |
| String header = "#${function.name}: $name"; |
| if (exportName != null) { |
| header = "$header (exported as $exportName)"; |
| } |
| if (reference.isTypeCheckerReference) { |
| header = "$header (type checker)"; |
| } |
| print(header); |
| print(member.function |
| ?.computeFunctionType(Nullability.nonNullable) |
| .toStringInternal()); |
| } |
| if (printKernel && !reference.isTypeCheckerReference) { |
| 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 (!printWasm) print(""); |
| } |
| |
| if (printWasm) { |
| print(function.type); |
| print(function.body.trace); |
| } |
| } |
| |
| ({String name, String? exportName}) _getNames(Translator translator) { |
| final member = reference.asMember; |
| 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 == |
| translator.component.mainMethod!.enclosingLibrary |
| ? canonicalName |
| : "${member.enclosingLibrary.importUri} $canonicalName"; |
| |
| return ( |
| name: canonicalName, |
| exportName: translator.functions.getExportName(reference) |
| ); |
| } |
| } |
| |
| class _ClosureTrampolineGenerator implements CodeGenerator { |
| final Translator translator; |
| final w.FunctionBuilder trampoline; |
| final w.BaseFunction target; |
| final int typeCount; |
| final int posArgCount; |
| final List<String> argNames; |
| final ParameterInfo paramInfo; |
| |
| _ClosureTrampolineGenerator(this.translator, this.trampoline, this.target, |
| this.typeCount, this.posArgCount, this.argNames, this.paramInfo); |
| |
| @override |
| void generate(w.InstructionsBuilder b, List<w.Local> paramLocals, |
| w.Label? returnLabel) { |
| assert(returnLabel == null); |
| |
| int targetIndex = 0; |
| if (paramInfo.takesContextOrReceiver) { |
| w.Local receiver = trampoline.locals[0]; |
| b.local_get(receiver); |
| translator.convertType( |
| b, receiver.type, target.type.inputs[targetIndex++]); |
| } |
| int argIndex = 1; |
| for (int i = 0; i < typeCount; i++) { |
| b.local_get(trampoline.locals[argIndex++]); |
| targetIndex++; |
| } |
| for (int i = 0; i < paramInfo.positional.length; i++) { |
| if (i < posArgCount) { |
| w.Local arg = trampoline.locals[argIndex++]; |
| b.local_get(arg); |
| translator.convertType(b, arg.type, target.type.inputs[targetIndex++]); |
| } else { |
| translator.constants.instantiateConstant( |
| b, paramInfo.positional[i]!, target.type.inputs[targetIndex++]); |
| } |
| } |
| int argNameIndex = 0; |
| for (int i = 0; i < paramInfo.names.length; i++) { |
| String argName = paramInfo.names[i]; |
| if (argNameIndex < argNames.length && argNames[argNameIndex] == argName) { |
| w.Local arg = trampoline.locals[argIndex++]; |
| b.local_get(arg); |
| translator.convertType(b, arg.type, target.type.inputs[targetIndex++]); |
| argNameIndex++; |
| } else { |
| translator.constants.instantiateConstant( |
| b, paramInfo.named[argName]!, target.type.inputs[targetIndex++]); |
| } |
| } |
| assert(argIndex == trampoline.type.inputs.length); |
| assert(targetIndex == target.type.inputs.length); |
| assert(argNameIndex == argNames.length); |
| |
| translator.callFunction(target, b); |
| |
| translator.convertType(b, translator.outputOrVoid(target.type.outputs), |
| translator.outputOrVoid(trampoline.type.outputs)); |
| b.end(); |
| } |
| } |
| |
| /// Similar to [_ClosureTrampolineGenerator], but generates dynamic call |
| /// entries. |
| class _ClosureDynamicEntryGenerator implements CodeGenerator { |
| final Translator translator; |
| final FunctionNode functionNode; |
| final w.BaseFunction target; |
| final ParameterInfo paramInfo; |
| final String name; |
| final w.FunctionBuilder function; |
| |
| _ClosureDynamicEntryGenerator(this.translator, this.functionNode, this.target, |
| this.paramInfo, this.name, this.function); |
| |
| @override |
| void generate(w.InstructionsBuilder b, List<w.Local> paramLocals, |
| w.Label? returnLabel) { |
| assert(returnLabel == null); |
| |
| final b = function.body; |
| |
| final int typeCount = functionNode.typeParameters.length; |
| |
| final closureLocal = function.locals[0]; |
| final typeArgsListLocal = function.locals[1]; |
| final posArgsListLocal = function.locals[2]; |
| final namedArgsListLocal = function.locals[3]; |
| |
| final positionalRequired = |
| paramInfo.positional.where((arg) => arg == null).length; |
| final positionalTotal = paramInfo.positional.length; |
| |
| // At this point the shape and type checks passed. We have right number |
| // of type arguments in the list, but optional positional and named |
| // parameters may be missing. |
| |
| final targetInputs = target.type.inputs; |
| int inputIdx = 0; |
| |
| // Push context or receiver |
| if (paramInfo.takesContextOrReceiver) { |
| final closureBaseType = w.RefType.def( |
| translator.closureLayouter.closureBaseStruct, |
| nullable: false); |
| final closureContextType = w.RefType.struct(nullable: false); |
| |
| // Get context, downcast it to expected type |
| b.local_get(closureLocal); |
| translator.convertType(b, closureLocal.type, closureBaseType); |
| b.struct_get(translator.closureLayouter.closureBaseStruct, |
| FieldIndex.closureContext); |
| translator.convertType(b, closureContextType, targetInputs[inputIdx]); |
| inputIdx += 1; |
| } |
| |
| // Push type arguments |
| for (int typeIdx = 0; typeIdx < typeCount; typeIdx += 1) { |
| b.local_get(typeArgsListLocal); |
| b.i32_const(typeIdx); |
| b.array_get(translator.typeArrayType); |
| translator.convertType( |
| b, translator.topInfo.nullableType, targetInputs[inputIdx]); |
| inputIdx += 1; |
| } |
| |
| // Push positional arguments |
| for (int posIdx = 0; posIdx < positionalTotal; posIdx += 1) { |
| if (posIdx < positionalRequired) { |
| // Shape check passed, argument must be passed |
| b.local_get(posArgsListLocal); |
| b.i32_const(posIdx); |
| b.array_get(translator.nullableObjectArrayType); |
| } else { |
| // Argument may be missing |
| b.i32_const(posIdx); |
| b.local_get(posArgsListLocal); |
| b.array_len(); |
| b.i32_lt_u(); |
| b.if_([], [translator.topInfo.nullableType]); |
| b.local_get(posArgsListLocal); |
| b.i32_const(posIdx); |
| b.array_get(translator.nullableObjectArrayType); |
| b.else_(); |
| translator.constants.instantiateConstant( |
| b, paramInfo.positional[posIdx]!, translator.topInfo.nullableType); |
| b.end(); |
| } |
| translator.convertType( |
| b, translator.topInfo.nullableType, targetInputs[inputIdx]); |
| inputIdx += 1; |
| } |
| |
| // Push named arguments |
| |
| Expression? initializerForNamedParamInMember(String paramName) { |
| for (int i = 0; i < functionNode.namedParameters.length; i += 1) { |
| if (functionNode.namedParameters[i].name == paramName) { |
| return functionNode.namedParameters[i].initializer; |
| } |
| } |
| return null; |
| } |
| |
| final namedArgValueIndexLocal = b |
| .addLocal(translator.classInfo[translator.boxedIntClass]!.nullableType); |
| |
| for (String paramName in paramInfo.names) { |
| final Constant? paramInfoDefaultValue = paramInfo.named[paramName]; |
| final Expression? functionNodeDefaultValue = |
| initializerForNamedParamInMember(paramName); |
| |
| // Get passed value |
| b.local_get(namedArgsListLocal); |
| translator.constants.instantiateConstant( |
| b, |
| SymbolConstant(paramName, null), |
| translator.classInfo[translator.symbolClass]!.nonNullableType); |
| translator.callReference(translator.getNamedParameterIndex.reference, b); |
| b.local_set(namedArgValueIndexLocal); |
| |
| if (functionNodeDefaultValue == null && paramInfoDefaultValue == null) { |
| // Shape check passed, parameter must be passed |
| b.local_get(namedArgsListLocal); |
| b.local_get(namedArgValueIndexLocal); |
| translator.convertType(b, namedArgValueIndexLocal.type, w.NumType.i64); |
| b.i32_wrap_i64(); |
| b.array_get(translator.nullableObjectArrayType); |
| translator.convertType( |
| b, |
| translator.nullableObjectArrayType.elementType.type.unpacked, |
| target.type.inputs[inputIdx]); |
| } else { |
| // Parameter may not be passed. |
| b.local_get(namedArgValueIndexLocal); |
| b.ref_is_null(); |
| b.if_([], [translator.topInfo.nullableType]); |
| if (functionNodeDefaultValue != null) { |
| // Used by the member, has a default value |
| translator.constants.instantiateConstant( |
| b, |
| (functionNodeDefaultValue as ConstantExpression).constant, |
| translator.topInfo.nullableType); |
| } else { |
| // Not used by the member |
| translator.constants.instantiateConstant( |
| b, |
| paramInfoDefaultValue!, |
| translator.topInfo.nullableType, |
| ); |
| } |
| b.else_(); // value index not null |
| b.local_get(namedArgsListLocal); |
| b.local_get(namedArgValueIndexLocal); |
| translator.convertType(b, namedArgValueIndexLocal.type, w.NumType.i64); |
| b.i32_wrap_i64(); |
| b.array_get(translator.nullableObjectArrayType); |
| b.end(); |
| translator.convertType( |
| b, translator.topInfo.nullableType, targetInputs[inputIdx]); |
| } |
| inputIdx += 1; |
| } |
| |
| translator.callFunction(target, b); |
| |
| translator.convertType(b, translator.outputOrVoid(target.type.outputs), |
| translator.outputOrVoid(function.type.outputs)); |
| |
| b.end(); // end function |
| } |
| } |
| |
| class NodeCounter extends VisitorDefault<void> with VisitorVoidMixin { |
| final bool omitCovarianceChecks; |
| int count = 0; |
| |
| NodeCounter(this.omitCovarianceChecks); |
| |
| int countNodes(Member member) { |
| count = 0; |
| if (member is Constructor) { |
| count += 2; // object creation overhead |
| for (final init in member.initializers) { |
| init.accept(this); |
| } |
| for (final field in member.enclosingClass.fields) { |
| field.initializer?.accept(this); |
| } |
| } |
| |
| final function = member.function!; |
| if (!omitCovarianceChecks) { |
| for (final parameter in function.positionalParameters) { |
| if (parameter.isCovariantByDeclaration || |
| parameter.isCovariantByClass) { |
| count++; |
| } |
| } |
| } |
| for (final parameter in function.positionalParameters) { |
| if (!omitCovarianceChecks) { |
| if (parameter.isCovariantByDeclaration || |
| parameter.isCovariantByClass) { |
| count++; |
| } |
| } |
| if (!parameter.isRequired) count++; |
| } |
| |
| function.body?.accept(this); |
| return count; |
| } |
| |
| // We only count tree nodes and do not recurse into things that aren't part of |
| // the tree (e.g. constants, variable types, ...) |
| |
| @override |
| void defaultTreeNode(TreeNode node) { |
| count++; |
| node.visitChildren(this); |
| } |
| |
| // The following AST nodes do not actually emit any code, so we don't count |
| // those nodes but we recurse into children that do emit code and therefore |
| // should count. |
| |
| @override |
| void visitBlock(Block node) { |
| node.visitChildren(this); |
| } |
| |
| @override |
| void visitEmptyStatement(EmptyStatement node) { |
| node.visitChildren(this); |
| } |
| |
| @override |
| void visitLabeledStatement(LabeledStatement node) { |
| node.visitChildren(this); |
| } |
| |
| @override |
| void visitBlockExpression(BlockExpression node) { |
| node.visitChildren(this); |
| } |
| |
| @override |
| void visitExpressionStatement(ExpressionStatement node) { |
| node.visitChildren(this); |
| } |
| |
| @override |
| void visitArguments(Arguments node) { |
| count += node.types.length; |
| node.visitChildren(this); |
| } |
| |
| @override |
| void visitNamedExpression(NamedExpression node) { |
| node.visitChildren(this); |
| } |
| } |
| |
| /// Creates forwarders for generic functions where the caller passes a constant |
| /// type argument. |
| /// |
| /// Let's say we have |
| /// |
| /// foo<T>(args) => ...; |
| /// |
| /// and 3 call sites |
| /// |
| /// foo<int>(args) |
| /// foo<int>(args) |
| /// foo<double>(args) |
| /// |
| /// the callsites can instead call a forwarder |
| /// |
| /// fooInt(args) |
| /// fooInt(args) |
| /// fooDouble(args) |
| /// |
| /// fooInt(args) => foo<int>(args) |
| /// fooDouble(args) => foo<double>(args) |
| /// |
| /// This saves code size on the call site. |
| class PartialInstantiator { |
| final Translator translator; |
| final w.ModuleBuilder callingModule; |
| |
| final Map<(Reference, DartType), w.BaseFunction> _oneTypeArgument = {}; |
| final Map<(Reference, DartType, DartType), w.BaseFunction> _twoTypeArguments = |
| {}; |
| |
| PartialInstantiator(this.translator, this.callingModule); |
| |
| w.BaseFunction getOneTypeArgumentForwarder( |
| Reference target, DartType type, String name) { |
| assert(translator.types.isTypeConstant(type)); |
| |
| return _oneTypeArgument.putIfAbsent((target, type), () { |
| final wasmTarget = translator.functions.getFunction(target); |
| |
| final function = callingModule.functions.define( |
| translator.typesBuilder.defineFunction( |
| [...wasmTarget.type.inputs.skip(1)], |
| wasmTarget.type.outputs, |
| ), |
| name); |
| final b = function.body; |
| translator.constants.instantiateConstant( |
| b, TypeLiteralConstant(type), translator.types.nonNullableTypeType); |
| for (int i = 1; i < wasmTarget.type.inputs.length; ++i) { |
| b.local_get(b.locals[i - 1]); |
| } |
| translator.callFunction(wasmTarget, b); |
| b.return_(); |
| b.end(); |
| |
| return function; |
| }); |
| } |
| |
| w.BaseFunction getTwoTypeArgumentForwarder( |
| Reference target, DartType type1, DartType type2, String name) { |
| assert(translator.types.isTypeConstant(type1)); |
| assert(translator.types.isTypeConstant(type2)); |
| |
| return _twoTypeArguments.putIfAbsent((target, type1, type2), () { |
| final wasmTarget = translator.functions.getFunction(target); |
| |
| final function = callingModule.functions.define( |
| translator.typesBuilder.defineFunction( |
| [...wasmTarget.type.inputs.skip(2)], |
| wasmTarget.type.outputs, |
| ), |
| name); |
| final b = function.body; |
| translator.constants.instantiateConstant( |
| b, TypeLiteralConstant(type1), translator.types.nonNullableTypeType); |
| translator.constants.instantiateConstant( |
| b, TypeLiteralConstant(type2), translator.types.nonNullableTypeType); |
| for (int i = 2; i < wasmTarget.type.inputs.length; ++i) { |
| b.local_get(b.locals[i - 2]); |
| } |
| translator.callFunction(wasmTarget, b); |
| b.return_(); |
| b.end(); |
| |
| return function; |
| }); |
| } |
| } |
| |
| class PolymorphicDispatchers { |
| final Translator translator; |
| final w.ModuleBuilder callingModule; |
| final cache = <SelectorInfo, PolymorphicDispatcherCallTarget>{}; |
| |
| PolymorphicDispatchers(this.translator, this.callingModule); |
| |
| CallTarget getPolymorphicDispatcher(SelectorInfo selector) { |
| assert(selector.targetRanges.length > 1); |
| return cache.putIfAbsent(selector, () { |
| return PolymorphicDispatcherCallTarget( |
| translator, selector, callingModule); |
| }); |
| } |
| } |
| |
| class PolymorphicDispatcherCallTarget extends CallTarget { |
| final Translator translator; |
| final SelectorInfo selector; |
| final w.ModuleBuilder callingModule; |
| |
| PolymorphicDispatcherCallTarget( |
| this.translator, this.selector, this.callingModule) |
| : super(selector.signature); |
| |
| @override |
| String get name => '${selector.name} (polymorphic dispatcher)'; |
| |
| @override |
| bool get supportsInlining => true; |
| |
| @override |
| bool get shouldInline => selector.staticDispatchRanges.length <= 2; |
| |
| @override |
| CodeGenerator get inliningCodeGen => |
| PolymorphicDispatcherCodeGenerator(translator, selector); |
| |
| @override |
| late final w.BaseFunction function = (() { |
| final function = callingModule.functions.define( |
| translator.typesBuilder |
| .defineFunction(signature.inputs, signature.outputs), |
| name); |
| translator.compilationQueue.add(CompilationTask(function, inliningCodeGen)); |
| return function; |
| })(); |
| } |
| |
| class PolymorphicDispatcherCodeGenerator implements CodeGenerator { |
| final Translator translator; |
| final SelectorInfo selector; |
| |
| PolymorphicDispatcherCodeGenerator(this.translator, this.selector); |
| |
| @override |
| void generate(w.InstructionsBuilder b, List<w.Local> paramLocals, |
| w.Label? returnLabel) { |
| final signature = selector.signature; |
| |
| final targetRanges = selector.staticDispatchRanges |
| .map((entry) => (range: entry.range, value: entry.target)) |
| .toList(); |
| |
| final bool needFallback = |
| selector.targetRanges.length > selector.staticDispatchRanges.length; |
| |
| void emitDirectCall(Reference target) { |
| for (int i = 0; i < signature.inputs.length; ++i) { |
| b.local_get(paramLocals[i]); |
| } |
| translator.callReference(target, b); |
| } |
| |
| void emitDispatchTableCall() { |
| for (int i = 0; i < signature.inputs.length; ++i) { |
| b.local_get(paramLocals[i]); |
| } |
| b.local_get(paramLocals[0]); |
| translator.callDispatchTable(b, selector); |
| } |
| |
| b.local_get(paramLocals[0]); |
| b.struct_get(translator.topInfo.struct, FieldIndex.classId); |
| b.classIdSearch(targetRanges, signature.outputs, emitDirectCall, |
| needFallback ? emitDispatchTableCall : null); |
| |
| if (returnLabel != null) { |
| b.br(returnLabel); |
| } else { |
| b.return_(); |
| } |
| b.end(); |
| } |
| } |
| |
| class DummyValuesCollector { |
| final w.ModuleBuilder module; |
| final Translator translator; |
| |
| final Map<w.FunctionType, w.BaseFunction> _dummyFunctions = {}; |
| final Map<w.HeapType, w.Global> _dummyValues = {}; |
| late final w.Global dummyStructGlobal; |
| |
| DummyValuesCollector(this.translator, this.module) { |
| _init(); |
| } |
| |
| void _init() { |
| w.StructType structType = |
| translator.typesBuilder.defineStruct("#DummyStruct"); |
| final dummyStructGlobalInit = module.globals.define( |
| w.GlobalType(w.RefType.struct(nullable: false), mutable: false)); |
| final ib = dummyStructGlobalInit.initializer; |
| ib.struct_new(structType); |
| ib.end(); |
| _dummyValues[w.HeapType.any] = dummyStructGlobalInit; |
| _dummyValues[w.HeapType.eq] = dummyStructGlobalInit; |
| _dummyValues[w.HeapType.struct] = dummyStructGlobalInit; |
| dummyStructGlobal = dummyStructGlobalInit; |
| } |
| |
| w.Global? _prepareDummyValue(w.ModuleBuilder module, w.ValueType type) { |
| if (type is w.RefType && !type.nullable) { |
| w.HeapType heapType = type.heapType; |
| return _dummyValues.putIfAbsent(heapType, () { |
| if (heapType is w.DefType) { |
| if (heapType is w.StructType) { |
| for (w.FieldType field in heapType.fields) { |
| _prepareDummyValue(module, field.type.unpacked); |
| } |
| final global = |
| module.globals.define(w.GlobalType(type, mutable: false)); |
| final ib = global.initializer; |
| for (w.FieldType field in heapType.fields) { |
| instantiateDummyValue(ib, field.type.unpacked); |
| } |
| ib.struct_new(heapType); |
| ib.end(); |
| return global; |
| } else if (heapType is w.ArrayType) { |
| final global = |
| module.globals.define(w.GlobalType(type, mutable: false)); |
| final ib = global.initializer; |
| ib.array_new_fixed(heapType, 0); |
| ib.end(); |
| return global; |
| } else if (heapType is w.FunctionType) { |
| final global = |
| module.globals.define(w.GlobalType(type, mutable: false)); |
| final ib = global.initializer; |
| ib.ref_func(getDummyFunction(heapType)); |
| ib.end(); |
| return global; |
| } |
| } |
| throw 'Unexpected heapType: $heapType'; |
| }); |
| } |
| |
| return null; |
| } |
| |
| /// Produce a dummy value of any Wasm type. For non-nullable reference types, |
| /// the value is constructed in a global initializer, and the instantiation |
| /// of the value merely reads the global. |
| void instantiateDummyValue(w.InstructionsBuilder b, w.ValueType type) { |
| switch (type) { |
| case w.NumType.i32: |
| b.i32_const(0); |
| break; |
| case w.NumType.i64: |
| b.i64_const(0); |
| break; |
| case w.NumType.f32: |
| b.f32_const(0); |
| break; |
| case w.NumType.f64: |
| b.f64_const(0); |
| break; |
| default: |
| if (type is w.RefType) { |
| w.HeapType heapType = type.heapType; |
| if (type.nullable) { |
| b.ref_null(heapType.bottomType); |
| } else { |
| translator.globals |
| .readGlobal(b, _prepareDummyValue(b.module, type)!); |
| } |
| } else { |
| throw "Unsupported global type $type ($type)"; |
| } |
| break; |
| } |
| } |
| |
| /// Provide a dummy function with the given signature. Used for empty entries |
| /// in vtables and for dummy values of function reference type. |
| w.BaseFunction getDummyFunction(w.FunctionType type) { |
| return _dummyFunctions.putIfAbsent(type, () { |
| final function = module.functions.define(type, "#dummy function $type"); |
| final b = function.body; |
| b.unreachable(); |
| b.end(); |
| return function; |
| }); |
| } |
| |
| /// Returns whether the given function was provided by [getDummyFunction]. |
| bool isDummyFunction(w.BaseFunction function) { |
| return _dummyFunctions[function.type] == function; |
| } |
| } |
| |
| abstract class _WasmImporter<T extends w.Exportable> { |
| final Translator _translator; |
| final String _exportPrefix; |
| final Map<T, Map<w.ModuleBuilder, T>> _map = {}; |
| |
| _WasmImporter(this._translator, this._exportPrefix); |
| |
| T _import(w.ModuleBuilder importingModule, T definition, String moduleName, |
| String importName); |
| |
| Iterable<T> get imports => _map.values.expand((v) => v.values); |
| |
| T get(T key, w.ModuleBuilder module) { |
| if (key.enclosingModule == module) return key; |
| |
| final innerMap = _map.putIfAbsent(key, () { |
| key.enclosingModule.exports.export('$_exportPrefix${_map.length}', key); |
| return {}; |
| }); |
| return innerMap.putIfAbsent(module, () { |
| return _import(module, key, |
| _translator.nameForModule(key.enclosingModule), key.exportedName); |
| }); |
| } |
| } |
| |
| class WasmFunctionImporter extends _WasmImporter<w.BaseFunction> { |
| WasmFunctionImporter(super._translator, super._exportPrefix); |
| |
| @override |
| w.BaseFunction _import(w.ModuleBuilder importingModule, |
| w.BaseFunction definition, String moduleName, String importName) { |
| return importingModule.functions |
| .import(moduleName, importName, definition.type); |
| } |
| } |
| |
| class WasmGlobalImporter extends _WasmImporter<w.Global> { |
| WasmGlobalImporter(super._translator, super._exportPrefix); |
| |
| @override |
| w.Global _import(w.ModuleBuilder importingModule, w.Global definition, |
| String moduleName, String importName) { |
| return importingModule.globals |
| .import(moduleName, importName, definition.type); |
| } |
| } |
| |
| class WasmTableImporter extends _WasmImporter<w.Table> { |
| WasmTableImporter(super._translator, super._exportPrefix); |
| |
| @override |
| w.Table _import(w.ModuleBuilder importingModule, w.Table definition, |
| String moduleName, String importName) { |
| return importingModule.tables.import(moduleName, importName, |
| definition.type, definition.minSize, definition.maxSize); |
| } |
| } |
| |
| class WasmTagImporter extends _WasmImporter<w.Tag> { |
| WasmTagImporter(super._translator, super._exportPrefix); |
| |
| @override |
| w.Tag _import(w.ModuleBuilder importingModule, w.Tag definition, |
| String moduleName, String importName) { |
| return importingModule.tags.import(moduleName, importName, definition.type); |
| } |
| } |
| |
| class SingleClosureTarget { |
| /// When `lambdaFunction` is null, the member being directly called. Otherwise |
| /// the enclosing member of the closure being called. |
| final Member member; |
| |
| /// [ParameterInfo] specifying how to compile arguments to the closure or |
| /// member. |
| final ParameterInfo paramInfo; |
| |
| /// Wasm function type that goes along with the [paramInfo] for compiling |
| /// arguments. |
| final w.FunctionType signature; |
| |
| /// If the callee is a local function or function expression (intead of a |
| /// member), this Wasm function for it. |
| final w.BaseFunction? lambdaFunction; |
| |
| SingleClosureTarget._( |
| this.member, this.paramInfo, this.signature, this.lambdaFunction); |
| } |