| // 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:kernel/ast.dart'; |
| import 'package:kernel/core_types.dart'; |
| import 'package:kernel/type_algebra.dart' |
| show FunctionTypeInstantiator, substitute; |
| import 'package:wasm_builder/wasm_builder.dart' as w; |
| |
| import 'class_info.dart'; |
| import 'closures.dart'; |
| import 'code_generator.dart'; |
| import 'dynamic_modules.dart'; |
| import 'param_info.dart'; |
| import 'translator.dart'; |
| import 'types.dart'; |
| |
| const int maxArrayNewFixedLength = 10000; |
| |
| /// For testing we can force all constant uses to be resolved later on (instead |
| /// of resolving some of them during codegen phase when we know to place them to |
| /// the main module). |
| const bool forceDelayedConstantDefinition = false; |
| |
| class ConstantDefinition { |
| final w.ModuleBuilder module; |
| final w.Global global; |
| final w.BaseFunction? _initFunction; |
| |
| ConstantDefinition(this.module, this.global, this._initFunction); |
| |
| bool get isLazy => _initFunction != null; |
| } |
| |
| class ConstantInfo { |
| static const int canBeEagerBit = 1 << 0; |
| static const int needsRuntimeCanonicalizationBit = 1 << 1; |
| static const int exportByMainAppBit = 1 << 2; |
| |
| final Constant constant; |
| final List<ConstantInfo> children; |
| final ConstantCodeGeneratorLazy _forceLazy; |
| final int _bits; |
| final w.RefType type; |
| final ConstantCodeGenerator _codeGen; |
| ConstantDefinition? _definition; |
| |
| ConstantInfo( |
| this.constant, |
| this.children, |
| this._forceLazy, |
| bool canBeEager, |
| bool needsRuntimeCanonicalization, |
| bool exportByMainApp, |
| this.type, |
| this._codeGen) |
| : _bits = (canBeEager ? canBeEagerBit : 0) | |
| (needsRuntimeCanonicalization |
| ? needsRuntimeCanonicalizationBit |
| : 0) | |
| (exportByMainApp ? exportByMainAppBit : 0); |
| |
| /// Whether the [constant] can be made eager (i.e. non lazy). |
| /// |
| /// If `true` then it will depend on into which modules the constant and it's |
| /// transitive closure are placed in. If they are all e.g. placed in the main |
| /// module then the constant will be non-lazy. If they are placed across |
| /// modules the constant may still become lazy. |
| bool get canBeEager => (_bits & canBeEagerBit) != 0; |
| |
| /// Whether this constant needs runtime canonicalization. If it does, the |
| /// constant definition will be lazy. |
| bool get needsRuntimeCanonicalization => |
| (_bits & needsRuntimeCanonicalizationBit) != 0; |
| |
| /// Whether the main app was compiled with dynamic module support and exposes |
| /// this constant via an export. |
| bool get exportByMainApp => (_bits & exportByMainAppBit) != 0; |
| |
| void printInitializer(void Function(w.BaseFunction) printFunction, |
| void Function(w.Global) printLazyInitializer) { |
| final definition = _definition; |
| if (definition != null) { |
| final initFunction = definition._initFunction; |
| if (initFunction != null) { |
| printFunction(initFunction); |
| } else { |
| printLazyInitializer(definition.global); |
| } |
| } |
| } |
| |
| void setDefinition(ConstantDefinition definition) { |
| assert(_definition == null); |
| _definition = definition; |
| } |
| } |
| |
| typedef ConstantCodeGenerator = void Function( |
| ConstantInfo, w.InstructionsBuilder, bool isLazy); |
| typedef ConstantCodeGeneratorLazy = bool Function( |
| ConstantInfo, w.ModuleBuilder); |
| |
| /// Handles the creation of Dart constants. |
| /// |
| /// Each (non-trivial) constant is assigned to a Wasm global. Multiple |
| /// occurrences of the same constant use the same global. |
| /// |
| /// When possible, the constant is contained within the global initializer, |
| /// meaning the constant is initialized eagerly during module initialization. |
| /// If this would exceed built-in Wasm limits (in particular the maximum length |
| /// for `array.new_fixed`), the constant is lazy, meaning that the global starts |
| /// out uninitialized, and every use of the constant checks the global to see if |
| /// it has been initialized and calls an initialization function otherwise. |
| /// A constant is also forced to be lazy if any sub-constants (e.g. elements of |
| /// a constant list) are lazy. |
| class Constants { |
| final Translator translator; |
| final Map<Constant, ConstantInfo> constantInfo = {}; |
| w.DataSegmentBuilder? int32Segment; |
| late final ClassInfo typeInfo = translator.classInfo[translator.typeClass]!; |
| |
| late final _constantAccessor = _ConstantAccessor(translator); |
| |
| final Map<DartType, InstanceConstant> _loweredTypeConstants = {}; |
| late final BoolConstant _cachedTrueConstant = BoolConstant(true); |
| late final BoolConstant _cachedFalseConstant = BoolConstant(false); |
| late final InstanceConstant _cachedDynamicType = |
| _makeTopTypeConstant(const DynamicType()); |
| late final InstanceConstant _cachedVoidType = |
| _makeTopTypeConstant(const VoidType()); |
| late final InstanceConstant _cachedNeverType = |
| _makeBottomTypeConstant(const NeverType.nonNullable()); |
| late final InstanceConstant _cachedNullType = |
| _makeBottomTypeConstant(const NullType()); |
| late final InstanceConstant _cachedNullableObjectType = |
| _makeTopTypeConstant(coreTypes.objectRawType(Nullability.nullable)); |
| late final InstanceConstant _cachedNonNullableObjectType = |
| _makeTopTypeConstant(coreTypes.objectRawType(Nullability.nonNullable)); |
| late final InstanceConstant _cachedNullableFunctionType = |
| _makeAbstractFunctionTypeConstant( |
| coreTypes.functionRawType(Nullability.nullable)); |
| late final InstanceConstant _cachedNonNullableFunctionType = |
| _makeAbstractFunctionTypeConstant( |
| coreTypes.functionRawType(Nullability.nonNullable)); |
| late final InstanceConstant _cachedNullableRecordType = |
| _makeAbstractRecordTypeConstant( |
| coreTypes.recordRawType(Nullability.nullable)); |
| late final InstanceConstant _cachedNonNullableRecordType = |
| _makeAbstractRecordTypeConstant( |
| coreTypes.recordRawType(Nullability.nonNullable)); |
| |
| bool currentlyCreating = false; |
| |
| Constants(this.translator); |
| |
| // All constant constructs should go in the main module. |
| Types get types => translator.types; |
| CoreTypes get coreTypes => translator.coreTypes; |
| |
| Constant makeWasmI32(int value) { |
| return InstanceConstant(translator.wasmI32Class.reference, const [], |
| {translator.wasmI32Value.fieldReference: IntConstant(value)}); |
| } |
| |
| // Used as an indicator for interface types that the enclosed class ID must be |
| // globalized on instantiation. Resolves to a normal _InterfaceType. |
| static final Class _relativeInterfaceTypeIndicator = |
| Class(name: '', fileUri: Uri()); |
| |
| /// Makes a `WasmArray<_Type>` [InstanceConstant]. |
| InstanceConstant makeTypeArray(Iterable<DartType> types) { |
| return makeArrayOf( |
| translator.typeType, types.map(_lowerTypeToConstant).toList()); |
| } |
| |
| /// Makes a `_NamedParameter` [InstanceConstant]. |
| InstanceConstant makeNamedParameterConstant(NamedType n) { |
| return InstanceConstant( |
| translator.namedParameterClass.reference, const [], { |
| translator.namedParameterNameField.fieldReference: |
| translator.symbols.symbolForNamedParameter(n.name), |
| translator.namedParameterTypeField.fieldReference: |
| _lowerTypeToConstant(n.type), |
| translator.namedParameterIsRequiredField.fieldReference: |
| BoolConstant(n.isRequired), |
| }); |
| } |
| |
| /// Creates a `WasmArray<_NamedParameter>` to be used as field of |
| /// `_FunctionType`. |
| InstanceConstant makeNamedParametersArray(FunctionType type) => makeArrayOf( |
| translator.namedParameterType, |
| [for (final n in type.namedParameters) makeNamedParameterConstant(n)]); |
| |
| /// Creates a `WasmArray<T>` with the given [Constant]s |
| InstanceConstant makeArrayOf( |
| InterfaceType elementType, List<Constant> entries, |
| {bool mutable = true}) => |
| InstanceConstant( |
| mutable |
| ? translator.wasmArrayClass.reference |
| : translator.immutableWasmArrayClass.reference, |
| [ |
| elementType, |
| ], |
| { |
| mutable |
| ? translator.wasmArrayValueField.fieldReference |
| : translator.immutableWasmArrayValueField.fieldReference: |
| ListConstant(elementType, entries), |
| }); |
| |
| /// Ensure that the constant has a Wasm global assigned. |
| /// |
| /// Sub-constants must have Wasm globals assigned before the global for the |
| /// composite constant is assigned, since global initializers can only refer |
| /// to earlier globals. |
| ConstantInfo? ensureConstant(Constant constant) { |
| return ConstantCreator(this).ensureConstant(constant); |
| } |
| |
| /// Whether the constant can be accessed eagerly (i.e. is non lazy) in a |
| /// global initializer. |
| /// |
| /// If the constant can be eager then it'll be immediatly placed in the main |
| /// module and return we return `true`. Otherwise we return `false`. |
| bool tryInstantiateEagerlyFrom(w.ModuleBuilder usingModule, Constant constant, |
| w.ValueType expectedType) { |
| if (_constantAccessor.constantIsAlwaysEager(constant)) { |
| return true; |
| } |
| |
| final info = ensureConstant(constant); |
| if (info == null) return false; |
| |
| final baseModule = translator.isDynamicSubmodule |
| ? translator.dynamicSubmodule |
| : translator.mainModule; |
| |
| var definition = info._definition; |
| if (definition == null && info.canBeEager && usingModule == baseModule) { |
| // It can be eager and we want to use it from the base module, so let's |
| // define it there. |
| // |
| // If the usage is in a deferred module then we could guarantee it to be |
| // eager by placing in the base module as well, but it would make it |
| // bigger, so we don't do it. |
| definition = |
| _constantAccessor._defineConstantInModuleRecursive(baseModule, info); |
| } |
| |
| if (definition != null && !definition.isLazy) { |
| if (definition.module == usingModule) return true; |
| if (definition.module == baseModule) return true; |
| if (definition.module == translator.mainModule) return true; |
| } |
| |
| return false; |
| } |
| |
| /// Defines the constants from main application in the fake main application |
| /// module. |
| /// |
| /// NOTE: We do not recurse into the DAG of the given [constant]: |
| /// |
| /// * a sub-constant (directly or indirectly) referred to by [constant] may |
| /// also be exported, in which case the caller will call |
| /// [defineMainAppConstant] for it. |
| /// |
| /// * if the dynamic module creates a constant that is structurally equal to |
| /// a non-exported constant from the main app, then it's going to be |
| /// runtime canonicalized. |
| /// |
| void defineMainAppConstant( |
| Constant constant, String globalName, String? initializerName) { |
| assert(translator.isDynamicSubmodule); |
| final type = constant.accept(TypeOfConstantVisitor(translator)); |
| final children = const <ConstantInfo>[]; |
| final guaranteedNonLazy = initializerName == null; |
| final needsRuntimeCanonicalization = false; |
| final exportByMainApp = true; |
| final info = ConstantInfo( |
| constant, |
| children, |
| (_, __) { |
| throw StateError( |
| 'Should not try to generate code for imported constant'); |
| }, |
| guaranteedNonLazy, |
| needsRuntimeCanonicalization, |
| exportByMainApp, |
| type, |
| (_, __, ___) { |
| throw StateError( |
| 'Should not try to generate code for imported constant'); |
| }); |
| constantInfo[constant] = info; |
| _constantAccessor.defineMainAppDefinition( |
| info, globalName, initializerName); |
| } |
| |
| /// Emit code to push a constant onto the stack. |
| void instantiateConstant( |
| w.InstructionsBuilder b, Constant constant, w.ValueType expectedType, |
| {w.ModuleBuilder? deferredModuleGuard}) { |
| if (expectedType == translator.voidMarker) return; |
| ConstantInstantiator(this, b, expectedType, deferredModuleGuard) |
| .instantiate(constant); |
| } |
| |
| InstanceConstant _lowerTypeToConstant(DartType type) { |
| return _loweredTypeConstants[type] ??= _lowerTypeConstantImpl(type); |
| } |
| |
| InstanceConstant _lowerTypeConstantImpl(DartType type) { |
| return switch (type) { |
| DynamicType() => _cachedDynamicType, |
| VoidType() => _cachedVoidType, |
| NeverType() => _cachedNeverType, |
| NullType() => _cachedNullType, |
| InterfaceType(classNode: var c) when c == coreTypes.objectClass => |
| type.nullability == Nullability.nullable |
| ? _cachedNullableObjectType |
| : _cachedNonNullableObjectType, |
| InterfaceType(classNode: var c) when c == coreTypes.functionClass => |
| type.nullability == Nullability.nullable |
| ? _cachedNullableFunctionType |
| : _cachedNonNullableFunctionType, |
| InterfaceType(classNode: var c) when c == coreTypes.recordClass => |
| type.nullability == Nullability.nullable |
| ? _cachedNullableRecordType |
| : _cachedNonNullableRecordType, |
| InterfaceType() => _makeInterfaceTypeConstant(type), |
| FutureOrType() => _makeFutureOrTypeConstant(type), |
| FunctionType() => _makeFunctionTypeConstant(type), |
| TypeParameterType() => _makeTypeParameterTypeConstant(type), |
| StructuralParameterType() => _makeStructuralParameterTypeConstant(type), |
| ExtensionType() => _lowerTypeToConstant(type.extensionTypeErasure), |
| RecordType() => _makeRecordTypeConstant(type), |
| IntersectionType() => throw 'Unexpected DartType: $type', |
| TypedefType() => throw 'Unexpected DartType: $type', |
| AuxiliaryType() => throw 'Unexpected DartType: $type', |
| InvalidType() => throw 'Unexpected DartType: $type', |
| // ignore: unreachable_switch_case |
| ExperimentalType() => throw 'Unexpected DartType: $type', |
| }; |
| } |
| |
| InstanceConstant _makeTypeParameterTypeConstant(TypeParameterType type) { |
| final int environmentIndex = |
| types.interfaceTypeEnvironment.lookup(type.parameter); |
| return _makeTypeConstant( |
| translator.interfaceTypeParameterTypeClass, type.nullability, { |
| translator.interfaceTypeParameterTypeEnvironmentIndexField.fieldReference: |
| IntConstant(environmentIndex), |
| }); |
| } |
| |
| InstanceConstant _makeStructuralParameterTypeConstant( |
| StructuralParameterType type) { |
| final int index = types.getFunctionTypeParameterIndex(type.parameter); |
| return _makeTypeConstant( |
| translator.functionTypeParameterTypeClass, type.nullability, { |
| translator.functionTypeParameterTypeIndexField.fieldReference: |
| IntConstant(index), |
| }); |
| } |
| |
| InstanceConstant _makeInterfaceTypeConstant(InterfaceType type) { |
| final wrappedClassId = |
| translator.classIdNumbering.classIds[type.classNode]!; |
| final (typeClass, classId) = switch (wrappedClassId) { |
| RelativeClassId() => ( |
| _relativeInterfaceTypeIndicator, |
| wrappedClassId.relativeValue |
| ), |
| AbsoluteClassId() => ( |
| translator.interfaceTypeClass, |
| wrappedClassId.value |
| ), |
| }; |
| // If the class ID is relative we will detect that when the constant is |
| // emitted and adjust it accordingly. |
| return _makeTypeConstant(typeClass, type.nullability, { |
| translator.interfaceTypeClassIdField.fieldReference: makeWasmI32(classId), |
| translator.interfaceTypeTypeArguments.fieldReference: |
| makeTypeArray(type.typeArguments), |
| }); |
| } |
| |
| InstanceConstant _makeFutureOrTypeConstant(FutureOrType type) { |
| return _makeTypeConstant(translator.futureOrTypeClass, type.nullability, { |
| translator.futureOrTypeTypeArgumentField.fieldReference: |
| _lowerTypeToConstant(type.typeArgument), |
| }); |
| } |
| |
| InstanceConstant _makeRecordTypeConstant(RecordType type) { |
| final fieldTypes = makeTypeArray([ |
| ...type.positional, |
| ...type.named.map((named) => named.type), |
| ]); |
| final names = makeArrayOf(coreTypes.stringNonNullableRawType, |
| type.named.map((t) => StringConstant(t.name)).toList(), |
| mutable: false); |
| return _makeTypeConstant(translator.recordTypeClass, type.nullability, { |
| translator.recordTypeFieldTypesField.fieldReference: fieldTypes, |
| translator.recordTypeNamesField.fieldReference: names, |
| }); |
| } |
| |
| InstanceConstant _makeFunctionTypeConstant(FunctionType type) { |
| final typeParameterOffset = |
| IntConstant(types.computeFunctionTypeParameterOffset(type)); |
| final typeParameterBoundsConstant = |
| makeTypeArray(type.typeParameters.map((p) => p.bound)); |
| final typeParameterDefaultsConstant = |
| makeTypeArray(type.typeParameters.map((p) => p.defaultType)); |
| final returnTypeConstant = _lowerTypeToConstant(type.returnType); |
| final positionalParametersConstant = |
| makeTypeArray(type.positionalParameters); |
| final requiredParameterCountConstant = |
| IntConstant(type.requiredParameterCount); |
| final namedParametersConstant = makeNamedParametersArray(type); |
| return _makeTypeConstant(translator.functionTypeClass, type.nullability, { |
| translator.functionTypeTypeParameterOffsetField.fieldReference: |
| typeParameterOffset, |
| translator.functionTypeTypeParameterBoundsField.fieldReference: |
| typeParameterBoundsConstant, |
| translator.functionTypeTypeParameterDefaultsField.fieldReference: |
| typeParameterDefaultsConstant, |
| translator.functionTypeReturnTypeField.fieldReference: returnTypeConstant, |
| translator.functionTypePositionalParametersField.fieldReference: |
| positionalParametersConstant, |
| translator.functionTypeRequiredParameterCountField.fieldReference: |
| requiredParameterCountConstant, |
| translator.functionTypeTypeParameterNamedParamsField.fieldReference: |
| namedParametersConstant, |
| }); |
| } |
| |
| InstanceConstant _makeTopTypeConstant(DartType type) { |
| assert(type is VoidType || |
| type is DynamicType || |
| type is InterfaceType && type.classNode == coreTypes.objectClass); |
| return _makeTypeConstant(translator.topTypeClass, type.nullability, { |
| translator.topTypeKindField.fieldReference: |
| IntConstant(types.topTypeKind(type)), |
| }); |
| } |
| |
| InstanceConstant _makeAbstractFunctionTypeConstant(InterfaceType type) { |
| assert(coreTypes.functionClass == type.classNode); |
| return _makeTypeConstant( |
| translator.abstractFunctionTypeClass, type.nullability, {}); |
| } |
| |
| InstanceConstant _makeAbstractRecordTypeConstant(InterfaceType type) { |
| assert(coreTypes.recordClass == type.classNode); |
| return _makeTypeConstant( |
| translator.abstractRecordTypeClass, type.nullability, {}); |
| } |
| |
| InstanceConstant _makeBottomTypeConstant(DartType type) { |
| assert(type is NeverType || |
| type is NullType || |
| type is InterfaceType && types.isSpecializedClass(type.classNode)); |
| return _makeTypeConstant(translator.bottomTypeClass, type.nullability, {}); |
| } |
| |
| InstanceConstant _makeTypeConstant(Class classNode, Nullability nullability, |
| Map<Reference, Constant> fieldValues) { |
| fieldValues[translator.typeIsDeclaredNullableField.fieldReference] = |
| nullability == Nullability.nullable |
| ? _cachedTrueConstant |
| : _cachedFalseConstant; |
| return InstanceConstant(classNode.reference, const [], fieldValues); |
| } |
| } |
| |
| class ConstantInstantiator extends ConstantVisitor<w.ValueType> |
| with ConstantVisitorDefaultMixin<w.ValueType> { |
| final Constants constants; |
| final w.InstructionsBuilder b; |
| final w.ValueType expectedType; |
| final w.ModuleBuilder? deferredModuleGuard; |
| |
| ConstantInstantiator( |
| this.constants, this.b, this.expectedType, this.deferredModuleGuard); |
| |
| Translator get translator => constants.translator; |
| |
| void instantiate(Constant constant) { |
| w.ValueType resultType = constant.accept(this); |
| if (translator.needsConversion(resultType, expectedType)) { |
| if (expectedType == const w.RefType.extern(nullable: true)) { |
| assert(resultType.isSubtypeOf(w.RefType.any(nullable: true))); |
| b.extern_convert_any(); |
| } else { |
| // This only happens in invalid but unreachable code produced by the |
| // TFA dead-code elimination. |
| b.comment("Constant in incompatible context (constant: $constant, " |
| "expectedType: $expectedType, resultType: $resultType)"); |
| b.unreachable(); |
| } |
| } |
| } |
| |
| @override |
| w.ValueType defaultConstant(Constant constant) { |
| return constants._constantAccessor |
| .loadConstant(b, constant, deferredModuleGuard); |
| } |
| |
| @override |
| w.ValueType visitUnevaluatedConstant(UnevaluatedConstant constant) { |
| if (constant == ParameterInfo.defaultValueSentinel) { |
| // Instantiate a sentinel value specific to the parameter type. |
| w.ValueType sentinelType = expectedType.withNullability(false); |
| assert(sentinelType is w.RefType, |
| "Default value sentinel for unboxed parameter"); |
| translator |
| .getDummyValuesCollectorForModule(b.moduleBuilder) |
| .instantiateDummyValue(b, sentinelType); |
| return sentinelType; |
| } |
| return super.visitUnevaluatedConstant(constant); |
| } |
| |
| @override |
| w.ValueType visitNullConstant(NullConstant node) { |
| if (expectedType == w.RefType.func(nullable: true)) { |
| b.ref_null((expectedType as w.RefType).heapType); |
| return expectedType; |
| } |
| b.ref_null(w.HeapType.none); |
| return const w.RefType.none(nullable: true); |
| } |
| |
| @override |
| w.ValueType visitBoolConstant(BoolConstant constant) { |
| if (expectedType is w.RefType) return defaultConstant(constant); |
| b.i32_const(constant.value ? 1 : 0); |
| return w.NumType.i32; |
| } |
| |
| @override |
| w.ValueType visitIntConstant(IntConstant constant) { |
| if (expectedType is w.RefType) return defaultConstant(constant); |
| if (expectedType == w.NumType.i32) { |
| b.i32_const(constant.value); |
| return w.NumType.i32; |
| } |
| b.i64_const(constant.value); |
| return w.NumType.i64; |
| } |
| |
| @override |
| w.ValueType visitDoubleConstant(DoubleConstant constant) { |
| if (expectedType is w.RefType) return defaultConstant(constant); |
| b.f64_const(constant.value); |
| return w.NumType.f64; |
| } |
| |
| @override |
| w.ValueType visitInstanceConstant(InstanceConstant constant) { |
| if (constant.classNode == translator.wasmI32Class) { |
| int value = (constant.fieldValues.values.single as IntConstant).value; |
| b.i32_const(value); |
| return w.NumType.i32; |
| } |
| if (constant.classNode == translator.wasmI64Class) { |
| int value = (constant.fieldValues.values.single as IntConstant).value; |
| b.i64_const(value); |
| return w.NumType.i64; |
| } |
| if (constant.classNode == translator.wasmF32Class) { |
| double value = |
| (constant.fieldValues.values.single as DoubleConstant).value; |
| b.f32_const(value); |
| return w.NumType.f32; |
| } |
| if (constant.classNode == translator.wasmF64Class) { |
| double value = |
| (constant.fieldValues.values.single as DoubleConstant).value; |
| b.f64_const(value); |
| return w.NumType.f64; |
| } |
| return super.visitInstanceConstant(constant); |
| } |
| } |
| |
| class ConstantCreator extends ConstantVisitor<ConstantInfo?> |
| with ConstantVisitorDefaultMixin<ConstantInfo?> { |
| final Constants constants; |
| |
| ConstantCreator(this.constants); |
| |
| Translator get translator => constants.translator; |
| Types get types => translator.types; |
| |
| Constant get _uninitializedHashBaseIndexConstant => |
| (translator.uninitializedHashBaseIndex.initializer as ConstantExpression) |
| .constant; |
| |
| ConstantInfo? ensureConstant(Constant constant) { |
| // To properly canonicalize type literal constants, we normalize the |
| // type before canonicalization. |
| if (constant is TypeLiteralConstant) { |
| DartType type = types.normalize(constant.type); |
| constant = constants._lowerTypeToConstant(type); |
| } |
| |
| ConstantInfo? info = constants.constantInfo[constant]; |
| if (info == null) { |
| info = constant.accept(this); |
| if (info != null) { |
| assert(info.constant.accept(TypeOfConstantVisitor(translator)) == |
| info.type); |
| constants.constantInfo[constant] = info; |
| } |
| } |
| return info; |
| } |
| |
| ConstantInfo createConstant( |
| Constant constant, |
| List<ConstantInfo> childConstants, |
| w.RefType type, |
| ConstantCodeGenerator generator, |
| {required bool canBeEager, |
| ConstantCodeGeneratorLazy? forceLazyConstant}) { |
| assert(!type.nullable); |
| |
| bool exportByMainApp = false; |
| bool needsRuntimeCanonicalization = false; |
| if (translator.dynamicModuleSupportEnabled) { |
| if (!translator.isDynamicSubmodule) { |
| // This is main app compilation which allows loading dynamic modules at |
| // runtime. We may have to export the constant. |
| exportByMainApp = |
| constant.accept(_ConstantDynamicModuleSharedChecker(translator)); |
| } else { |
| // This is a dynamic module compilation. |
| // |
| // If the constant isn't module specific, we need to canonicalize it at |
| // runtime. |
| assert(!(translator.dynamicModuleConstants?.constantNames |
| .containsKey(constant) ?? |
| false)); |
| needsRuntimeCanonicalization = |
| constant.accept(_ConstantDynamicModuleSharedChecker(translator)); |
| } |
| } |
| canBeEager = canBeEager && |
| !needsRuntimeCanonicalization && |
| childConstants.every((c) => c.canBeEager); |
| |
| return ConstantInfo( |
| constant, |
| childConstants, |
| forceLazyConstant ?? (_, __) => false, |
| canBeEager, |
| needsRuntimeCanonicalization, |
| exportByMainApp, |
| type, |
| generator); |
| } |
| |
| @override |
| ConstantInfo? defaultConstant(Constant constant) => null; |
| |
| @override |
| ConstantInfo? visitBoolConstant(BoolConstant constant) { |
| ClassInfo info = translator.classInfo[translator.boxedBoolClass]!; |
| return createConstant(constant, const [], info.nonNullableType, |
| canBeEager: true, (_, b, __) { |
| b.i32_const((info.classId as AbsoluteClassId).value); |
| b.i32_const(constant.value ? 1 : 0); |
| b.struct_new(info.struct); |
| }); |
| } |
| |
| @override |
| ConstantInfo? visitIntConstant(IntConstant constant) { |
| ClassInfo info = translator.classInfo[translator.boxedIntClass]!; |
| return createConstant(constant, const [], info.nonNullableType, |
| canBeEager: true, (_, b, __) { |
| b.i32_const((info.classId as AbsoluteClassId).value); |
| b.i64_const(constant.value); |
| b.struct_new(info.struct); |
| }); |
| } |
| |
| @override |
| ConstantInfo? visitDoubleConstant(DoubleConstant constant) { |
| ClassInfo info = translator.classInfo[translator.boxedDoubleClass]!; |
| return createConstant(constant, const [], info.nonNullableType, |
| canBeEager: true, (_, b, __) { |
| b.i32_const((info.classId as AbsoluteClassId).value); |
| b.f64_const(constant.value); |
| b.struct_new(info.struct); |
| }); |
| } |
| |
| @override |
| ConstantInfo? visitStringConstant(StringConstant constant) { |
| ClassInfo info = translator.classInfo[translator.jsStringClass]!; |
| return createConstant(constant, const [], info.nonNullableType, |
| canBeEager: true, (_, b, __) { |
| b.pushObjectHeaderFields(translator, info); |
| translator.globals.readGlobal( |
| b, |
| translator.getInternalizedStringGlobal( |
| b.moduleBuilder, constant.value)); |
| b.struct_new(info.struct); |
| }); |
| } |
| |
| @override |
| ConstantInfo? visitInstanceConstant(InstanceConstant constant) { |
| Class cls = constant.classNode; |
| bool isRelativeInterfaceType = false; |
| if (cls == translator.wasmArrayClass) { |
| return _makeWasmArrayLiteral(constant, mutable: true); |
| } |
| if (cls == translator.immutableWasmArrayClass) { |
| return _makeWasmArrayLiteral(constant, mutable: false); |
| } |
| if (cls == translator.wasmI32Class) { |
| return null; |
| } |
| |
| if (cls == Constants._relativeInterfaceTypeIndicator) { |
| cls = translator.interfaceTypeClass; |
| constant = InstanceConstant( |
| cls.reference, constant.typeArguments, constant.fieldValues); |
| isRelativeInterfaceType = true; |
| } |
| |
| ClassInfo info = translator.classInfo[cls]!; |
| translator.functions.recordClassAllocation(info.classId); |
| w.RefType type = info.nonNullableType; |
| |
| // Collect sub-constants for field values. |
| int fieldCount = info.struct.fields.length; |
| List<Constant?> subConstants = List.filled(fieldCount, null); |
| // Relative class IDs will get adjusted at runtime based on the local |
| // class ID base for the enclosing module. This must be done lazily |
| // since the global is not const. |
| bool lazy = isRelativeInterfaceType; |
| final childConstants = <ConstantInfo>[]; |
| constant.fieldValues.forEach((reference, subConstant) { |
| final field = reference.asField; |
| int index = translator.fieldIndex[field]!; |
| assert(subConstants[index] == null); |
| subConstants[index] = subConstant; |
| final info = ensureConstant(subConstant); |
| if (info != null) { |
| childConstants.add(info); |
| } |
| }); |
| |
| // Collect sub-constants for type arguments. |
| Map<TypeParameter, DartType> substitution = {}; |
| List<DartType> args = constant.typeArguments; |
| while (true) { |
| for (int i = 0; i < cls.typeParameters.length; i++) { |
| TypeParameter parameter = cls.typeParameters[i]; |
| DartType arg = substitute(args[i], substitution); |
| substitution[parameter] = arg; |
| int index = translator.typeParameterIndex[parameter]!; |
| Constant typeArgConstant = constants._lowerTypeToConstant(arg); |
| subConstants[index] = typeArgConstant; |
| final info = ensureConstant(typeArgConstant); |
| if (info != null) { |
| childConstants.add(info); |
| } |
| } |
| Supertype? supertype = cls.supertype; |
| if (supertype == null) break; |
| cls = supertype.classNode; |
| args = supertype.typeArguments; |
| } |
| |
| // If the class ID is relative then it needs to be globalized when |
| // initializing the object which is a non-const operation. |
| lazy |= info.classId is RelativeClassId; |
| |
| return createConstant(constant, childConstants, type, canBeEager: !lazy, |
| (_, b, __) { |
| b.pushObjectHeaderFields(translator, info); |
| for (int i = FieldIndex.objectFieldBase; i < fieldCount; i++) { |
| Constant subConstant = subConstants[i]!; |
| constants.instantiateConstant( |
| b, subConstant, info.struct.fields[i].type.unpacked); |
| if (isRelativeInterfaceType && i == FieldIndex.interfaceTypeClassId) { |
| assert(translator.isDynamicSubmodule); |
| translator.pushModuleId(b); |
| translator.callReference(translator.globalizeClassId.reference, b); |
| } |
| } |
| b.struct_new(info.struct); |
| }); |
| } |
| |
| ConstantInfo? _makeWasmArrayLiteral(InstanceConstant constant, |
| {required bool mutable}) { |
| w.ArrayType arrayType = translator |
| .arrayTypeForDartType(constant.typeArguments.single, mutable: mutable); |
| w.ValueType elementType = arrayType.elementType.type.unpacked; |
| |
| List<Constant> elements = |
| (constant.fieldValues.values.single as ListConstant).entries; |
| final tooLargeForArrayNewFixed = elements.length > maxArrayNewFixedLength; |
| final childConstants = <ConstantInfo>[]; |
| for (Constant element in elements) { |
| final info = ensureConstant(element); |
| if (info != null) { |
| childConstants.add(info); |
| } |
| } |
| |
| if (tooLargeForArrayNewFixed && !mutable) { |
| throw Exception('Cannot allocate immutable wasm array of size ' |
| '$tooLargeForArrayNewFixed'); |
| } |
| |
| return createConstant( |
| constant, childConstants, w.RefType.def(arrayType, nullable: false), |
| canBeEager: !tooLargeForArrayNewFixed, (_, b, __) { |
| if (tooLargeForArrayNewFixed) { |
| // We use WasmArray<WasmI32> for some RTT data structures. Those arrays |
| // can get rather large and cross the 10k limit. |
| // |
| // If so, we prefer to initialize the array from data section over |
| // emitting a *lot* of code to store individual array elements. |
| // |
| // This can be a little bit larger than individual array stores, but the |
| // data section will compress better, so for app.wasm.gz it'a a win and |
| // will cause much faster validation & faster initialization. |
| if (arrayType.elementType.type == w.NumType.i32) { |
| // Initialize array contents from passive data segment. |
| final w.DataSegmentBuilder segment = |
| constants.int32Segment ??= b.moduleBuilder.dataSegments.define(); |
| |
| final field = translator.wasmI32Value.fieldReference; |
| |
| final list = Uint32List(elements.length); |
| for (int i = 0; i < list.length; ++i) { |
| // The constant is a `const WasmI32 {WasmI32._value: <XXX>}` |
| final constant = elements[i] as InstanceConstant; |
| assert(constant.classNode == translator.wasmI32Class); |
| list[i] = (constant.fieldValues[field] as IntConstant).value; |
| } |
| final offset = segment.length; |
| segment.append(list.buffer.asUint8List()); |
| b.i32_const(offset); |
| b.i32_const(elements.length); |
| b.array_new_data(arrayType, segment); |
| return; |
| } |
| |
| // We will initialize the array with one of the elements (using |
| // `array.new`) and update the fields. |
| // |
| // For the initial element pick the one that occurs the most to save |
| // some work when the array has duplicates. |
| final Map<Constant, int> occurrences = {}; |
| for (final element in elements) { |
| occurrences.update(element, (i) => i + 1, ifAbsent: () => 1); |
| } |
| |
| var initialElement = elements[0]; |
| var initialElementOccurrences = 1; |
| for (final entry in occurrences.entries) { |
| if (entry.value > initialElementOccurrences) { |
| initialElementOccurrences = entry.value; |
| initialElement = entry.key; |
| } |
| } |
| |
| w.Local arrayLocal = |
| b.addLocal(w.RefType.def(arrayType, nullable: false)); |
| constants.instantiateConstant(b, initialElement, elementType); |
| b.i32_const(elements.length); |
| b.array_new(arrayType); |
| b.local_set(arrayLocal); |
| |
| for (int i = 0; i < elements.length;) { |
| // If it's the same as initial element, nothing to do. |
| final value = elements[i++]; |
| if (value == initialElement) continue; |
| |
| // Find out how many times the current element repeats. |
| final int startInclusive = i - 1; |
| while (i < elements.length && elements[i] == value) { |
| i++; |
| } |
| final int endExclusive = i; |
| final int count = endExclusive - startInclusive; |
| |
| b.local_get(arrayLocal); |
| b.i32_const(startInclusive); |
| constants.instantiateConstant(b, value, elementType); |
| if (count > 1) { |
| b.i32_const(count); |
| b.array_fill(arrayType); |
| } else { |
| b.array_set(arrayType); |
| } |
| } |
| b.local_get(arrayLocal); |
| } else { |
| for (Constant element in elements) { |
| constants.instantiateConstant(b, element, elementType); |
| } |
| b.array_new_fixed(arrayType, elements.length); |
| } |
| }); |
| } |
| |
| @override |
| ConstantInfo? visitListConstant(ListConstant constant) { |
| final instanceConstant = |
| InstanceConstant(translator.immutableListClass.reference, [ |
| constant.typeArgument, |
| ], { |
| translator.listBaseLengthField.fieldReference: |
| IntConstant(constant.entries.length), |
| translator.listBaseDataField.fieldReference: |
| InstanceConstant(translator.wasmArrayClass.reference, [ |
| translator.coreTypes.objectNullableRawType |
| ], { |
| translator.wasmArrayValueField.fieldReference: ListConstant( |
| translator.coreTypes.objectNullableRawType, constant.entries) |
| }), |
| }); |
| return ensureConstant(instanceConstant); |
| } |
| |
| @override |
| ConstantInfo? visitMapConstant(MapConstant constant) { |
| final listElements = List.generate(constant.entries.length * 2, (i) { |
| ConstantMapEntry entry = constant.entries[i >> 1]; |
| return i.isEven ? entry.key : entry.value; |
| }); |
| |
| final instanceConstant = |
| InstanceConstant(translator.immutableMapClass.reference, [ |
| constant.keyType, |
| constant.valueType |
| ], { |
| // _index = _uninitializedHashBaseIndex |
| translator.hashFieldBaseIndexField.fieldReference: |
| _uninitializedHashBaseIndexConstant, |
| |
| // _hashMask |
| translator.hashFieldBaseHashMaskField.fieldReference: IntConstant(0), |
| |
| // _data |
| translator.hashFieldBaseDataField.fieldReference: |
| InstanceConstant(translator.wasmArrayClass.reference, [ |
| translator.coreTypes.objectNullableRawType |
| ], { |
| translator.wasmArrayValueField.fieldReference: ListConstant( |
| translator.coreTypes.objectNullableRawType, listElements) |
| }), |
| |
| // _usedData |
| translator.hashFieldBaseUsedDataField.fieldReference: |
| IntConstant(listElements.length), |
| |
| // _deletedKeys |
| translator.hashFieldBaseDeletedKeysField.fieldReference: IntConstant(0), |
| }); |
| |
| return ensureConstant(instanceConstant); |
| } |
| |
| @override |
| ConstantInfo? visitSetConstant(SetConstant constant) { |
| final instanceConstant = |
| InstanceConstant(translator.immutableSetClass.reference, [ |
| constant.typeArgument |
| ], { |
| // _index = _uninitializedHashBaseIndex |
| translator.hashFieldBaseIndexField.fieldReference: |
| _uninitializedHashBaseIndexConstant, |
| |
| // _hashMask |
| translator.hashFieldBaseHashMaskField.fieldReference: IntConstant(0), |
| |
| // _data |
| translator.hashFieldBaseDataField.fieldReference: |
| InstanceConstant(translator.wasmArrayClass.reference, [ |
| translator.coreTypes.objectNullableRawType |
| ], { |
| translator.wasmArrayValueField.fieldReference: ListConstant( |
| translator.coreTypes.objectNullableRawType, constant.entries) |
| }), |
| |
| // _usedData |
| translator.hashFieldBaseUsedDataField.fieldReference: |
| IntConstant(constant.entries.length), |
| |
| // _deletedKeys |
| translator.hashFieldBaseDeletedKeysField.fieldReference: IntConstant(0), |
| }); |
| |
| return ensureConstant(instanceConstant); |
| } |
| |
| @override |
| ConstantInfo? visitStaticTearOffConstant(StaticTearOffConstant constant) { |
| Procedure member = constant.targetReference.asProcedure; |
| |
| final functionTypeConstant = |
| constants._lowerTypeToConstant(translator.getTearOffType(member)); |
| final functionTypeInfo = ensureConstant(functionTypeConstant)!; |
| final childConstants = [functionTypeInfo]; |
| |
| // Ensure we enqueue the closure body function for compilation. |
| // |
| // Once we define the constant in a certain module we may be in link phase |
| // and have passed the codegen phase and we cannot codegen arbitrary |
| // functions in link phase anymore. |
| final owningModule = translator.isDynamicSubmodule |
| ? translator.dynamicSubmodule |
| : translator.moduleForReference(constant.targetReference); |
| final closure = translator.getTearOffClosure(member, owningModule); |
| final closureClassInfo = translator.closureInfo; |
| translator.functions.recordClassAllocation(closureClassInfo.classId); |
| |
| // We have a constraint here: The code for the torn off method is in a |
| // specific module. The vtable for that closure can (currently) not be setup |
| // lazily. That means the tear-off constant can only be an eager constant |
| // if we have a guarantee that it will be placed in the same module that can |
| // either import the vtable or it's the same module as vtable. |
| // |
| // Now it's possible that a constant composed of this one is used by |
| // different modules. Our constant placement algorithm will then put that |
| // one into a shared module (currently the main module) - which will make it |
| // lazy as the vtable resides in a module that hasn't been loaded yet at |
| // main module instantiation time. |
| // |
| // So currently we only can guarantee it to be eager if the module owning |
| // the vtable is the main module - or we compile a dynamic module. |
| final canBeEager = |
| owningModule == translator.mainModule || translator.isDynamicSubmodule; |
| |
| final closureType = |
| w.RefType.def(closure.representation.closureStruct, nullable: false); |
| return createConstant(constant, childConstants, closureType, |
| canBeEager: canBeEager, forceLazyConstant: (cinfo, m) { |
| final constantModule = m.module; |
| final vtableModule = closure.vtable.enclosingModule; |
| return constantModule != vtableModule && |
| vtableModule != translator.mainModule.module; |
| }, (cinfo, b, __) { |
| // The dummy struct must be declared before the constant global so that the |
| // constant's initializer can reference it. |
| final dummyStructGlobal = translator |
| .getDummyValuesCollectorForModule(b.moduleBuilder) |
| .dummyStructGlobal; |
| |
| b.pushObjectHeaderFields(translator, closureClassInfo); |
| translator.globals.readGlobal(b, dummyStructGlobal); // Dummy context |
| translator.globals.readGlobal(b, closure.vtable); |
| constants.instantiateConstant( |
| b, functionTypeInfo.constant, types.nonNullableTypeType); |
| b.struct_new(closure.representation.closureStruct); |
| }); |
| } |
| |
| @override |
| ConstantInfo? visitInstantiationConstant(InstantiationConstant constant) { |
| final tearOffConstant = constant.tearOffConstant as TearOffConstant; |
| final tearOffProcedure = tearOffConstant.target as Procedure; |
| final tearOffFunctionType = translator.getTearOffType(tearOffProcedure); |
| |
| final functionTypeInfo = ensureConstant(constants._lowerTypeToConstant( |
| FunctionTypeInstantiator.instantiate( |
| tearOffFunctionType, constant.types)))!; |
| final tearOffConstantInfo = ensureConstant(tearOffConstant)!; |
| final typeConstantInfos = <ConstantInfo>[]; |
| for (final type in constant.types) { |
| typeConstantInfos |
| .add(ensureConstant(constants._lowerTypeToConstant(type))!); |
| } |
| final typeArgsArrayConstantInfo = |
| ensureConstant(constants.makeTypeArray(constant.types))!; |
| |
| // Ensure we enqueue the closure body function for compilation. |
| // |
| // Once we define the constant in a certain module we may be in link phase |
| // and have passed the codegen phase and we cannot codegen arbitrary |
| // functions in link phase anymore. |
| final owningModule = translator.isDynamicSubmodule |
| ? translator.dynamicSubmodule |
| : translator.moduleForReference(tearOffProcedure.reference); |
| final tearOffClosure = |
| translator.getTearOffClosure(tearOffProcedure, owningModule); |
| final closureClassInfo = translator.closureInfo; |
| translator.functions.recordClassAllocation(closureClassInfo.classId); |
| |
| final childConstants = [ |
| functionTypeInfo, |
| tearOffConstantInfo, |
| typeArgsArrayConstantInfo, |
| ...typeConstantInfos |
| ]; |
| |
| final function = tearOffConstant.function; |
| final positionalCount = function.positionalParameters.length; |
| final names = function.namedParameters.map((p) => p.name!).toList(); |
| final instantiationOfTearOffRepresentation = translator.closureLayouter |
| .getClosureRepresentation(0, positionalCount, names)!; |
| final tearOffRepresentation = tearOffClosure.representation; |
| final closureStruct = instantiationOfTearOffRepresentation.closureStruct; |
| final closureType = w.RefType.def(closureStruct, nullable: false); |
| |
| return createConstant(constant, childConstants, closureType, |
| canBeEager: true, (info, b, isLazy) { |
| final targetModule = b.moduleBuilder; |
| |
| w.BaseFunction makeDynamicCallEntry() { |
| final function = targetModule.functions.define( |
| translator.dynamicCallVtableEntryFunctionType, |
| "dynamic call entry"); |
| |
| final b = function.body; |
| |
| final closureLocal = function.locals[0]; |
| final typeArgsListLocal = function.locals[1]; // empty |
| final posArgsListLocal = function.locals[2]; |
| final namedArgsListLocal = function.locals[3]; |
| |
| b.local_get(closureLocal); |
| constants.instantiateConstant( |
| b, typeArgsArrayConstantInfo.constant, typeArgsListLocal.type); |
| b.local_get(posArgsListLocal); |
| b.local_get(namedArgsListLocal); |
| translator.callFunction(tearOffClosure.dynamicCallEntry, b); |
| b.end(); |
| |
| return function; |
| } |
| |
| void declareAndAddRefFunc(w.BaseFunction function) { |
| // If the constant is lazy the body will be in a function rather than a |
| // global. In order for a function to use a ref.func, the function must |
| // be declared in a global (or via the element section). |
| if (isLazy) { |
| final global = b.moduleBuilder.globals |
| .define(w.GlobalType(w.RefType(function.type, nullable: false))); |
| global.initializer |
| ..ref_func(function) |
| ..end(); |
| b.global_get(global); |
| } else { |
| b.ref_func(function); |
| } |
| } |
| |
| w.BaseFunction makeTrampoline( |
| w.FunctionType signature, w.BaseFunction tearOffFunction) { |
| assert(tearOffFunction.type.inputs.length == |
| signature.inputs.length + typeConstantInfos.length); |
| final function = b.moduleBuilder.functions |
| .define(signature, "instantiation constant trampoline"); |
| final b2 = function.body; |
| b2.local_get(function.locals[0]); |
| for (final type in typeConstantInfos) { |
| constants.instantiateConstant( |
| b2, type.constant, translator.topTypeNonNullable); |
| } |
| for (int i = 1; i < signature.inputs.length; i++) { |
| b2.local_get(function.locals[i]); |
| } |
| translator.callFunction(tearOffFunction, b2); |
| b2.end(); |
| return function; |
| } |
| |
| void fillVtableEntry(int posArgCount, NameCombination nameCombination) { |
| final fieldIndex = instantiationOfTearOffRepresentation |
| .fieldIndexForSignature(posArgCount, nameCombination.names); |
| final signature = |
| instantiationOfTearOffRepresentation.getVtableFieldType(fieldIndex); |
| |
| w.BaseFunction function; |
| if (nameCombination.names.isNotEmpty && |
| !tearOffRepresentation.nameCombinations.contains(nameCombination)) { |
| // This name combination only has |
| // - non-generic closure / non-generic tear-off definitions |
| // - non-generic callers |
| // => We make a dummy entry which is unreachable. |
| function = translator |
| .getDummyValuesCollectorForModule(b.moduleBuilder) |
| .getDummyFunction(signature); |
| } else { |
| final int tearOffFieldIndex = tearOffRepresentation |
| .fieldIndexForSignature(posArgCount, nameCombination.names); |
| w.BaseFunction tearOffFunction = tearOffClosure.functions[ |
| tearOffFieldIndex - tearOffRepresentation.vtableBaseIndex]; |
| if (translator |
| .getDummyValuesCollectorForModule(b.moduleBuilder) |
| .isDummyFunction(tearOffFunction)) { |
| // This name combination may not exist for the target, but got |
| // clustered together with other name combinations that do exist. |
| // => We make a dummy entry which is unreachable. |
| function = translator |
| .getDummyValuesCollectorForModule(b.moduleBuilder) |
| .getDummyFunction(signature); |
| } else { |
| function = makeTrampoline(signature, tearOffFunction); |
| } |
| } |
| declareAndAddRefFunc(function); |
| } |
| |
| void makeVtable(w.BaseFunction dynamicCallEntry) { |
| declareAndAddRefFunc(dynamicCallEntry); |
| assert(!instantiationOfTearOffRepresentation.isGeneric); |
| |
| if (translator.dynamicModuleSupportEnabled) { |
| // Dynamic modules only use the dynamic call entry. |
| b.struct_new(instantiationOfTearOffRepresentation.vtableStruct); |
| return; |
| } |
| |
| for (int posArgCount = 0; |
| posArgCount <= positionalCount; |
| posArgCount++) { |
| fillVtableEntry(posArgCount, NameCombination(const [])); |
| } |
| for (NameCombination combination |
| in instantiationOfTearOffRepresentation.nameCombinations) { |
| fillVtableEntry(positionalCount, combination); |
| } |
| b.struct_new(instantiationOfTearOffRepresentation.vtableStruct); |
| } |
| |
| b.pushObjectHeaderFields(translator, closureClassInfo); |
| |
| // Context is not used by the vtable functions, but it's needed for |
| // closure equality checks to work (`_Closure._equals`). |
| constants.instantiateConstant( |
| b, tearOffConstantInfo.constant, translator.topTypeNonNullable); |
| |
| for (final type in typeConstantInfos) { |
| constants.instantiateConstant( |
| b, type.constant, translator.topTypeNonNullable); |
| } |
| b.struct_new(tearOffRepresentation.instantiationContextStruct!); |
| |
| makeVtable(makeDynamicCallEntry()); |
| constants.instantiateConstant( |
| b, functionTypeInfo.constant, types.nonNullableTypeType); |
| b.struct_new(closureStruct); |
| }); |
| } |
| |
| @override |
| ConstantInfo? visitTypeLiteralConstant(TypeLiteralConstant constant) { |
| throw 'Unreachable - should have been lowered'; |
| } |
| |
| @override |
| ConstantInfo? visitSymbolConstant(SymbolConstant constant) { |
| ClassInfo info = translator.classInfo[translator.symbolClass]!; |
| translator.functions.recordClassAllocation(info.classId); |
| w.RefType stringType = translator.stringType; |
| final nameConstant = |
| StringConstant(translator.symbols.getMangledSymbolName(constant)); |
| final nameInfo = ensureConstant(nameConstant); |
| final childConstants = <ConstantInfo>[]; |
| if (nameInfo != null) { |
| childConstants.add(nameInfo); |
| } |
| |
| return createConstant(constant, childConstants, info.nonNullableType, |
| canBeEager: true, (_, b, __) { |
| b.pushObjectHeaderFields(translator, info); |
| constants.instantiateConstant(b, nameConstant, stringType); |
| b.struct_new(info.struct); |
| }); |
| } |
| |
| @override |
| ConstantInfo? visitRecordConstant(RecordConstant constant) { |
| final ClassInfo recordClassInfo = |
| translator.getRecordClassInfo(constant.recordType); |
| translator.functions.recordClassAllocation(recordClassInfo.classId); |
| |
| final List<Constant> arguments = constant.positional.toList(); |
| arguments.addAll(constant.named.values); |
| |
| final childConstants = <ConstantInfo>[]; |
| for (Constant argument in arguments) { |
| final info = ensureConstant(argument); |
| if (info != null) { |
| childConstants.add(info); |
| } |
| } |
| |
| return createConstant( |
| constant, childConstants, recordClassInfo.nonNullableType, |
| canBeEager: true, (_, b, __) { |
| b.pushObjectHeaderFields(translator, recordClassInfo); |
| for (Constant argument in arguments) { |
| constants.instantiateConstant(b, argument, translator.topType); |
| } |
| b.struct_new(recordClassInfo.struct); |
| }); |
| } |
| } |
| |
| class TypeOfConstantVisitor extends ConstantVisitor<w.RefType> |
| with ConstantVisitorDefaultMixin<w.RefType> { |
| final Translator translator; |
| |
| TypeOfConstantVisitor(this.translator); |
| |
| @override |
| w.RefType defaultConstant(Constant constant) { |
| throw UnimplementedError('Unexpected $constant.'); |
| } |
| |
| @override |
| w.RefType visitBoolConstant(BoolConstant constant) { |
| return _typeOfClass(translator.boxedBoolClass); |
| } |
| |
| @override |
| w.RefType visitIntConstant(IntConstant constant) { |
| return _typeOfClass(translator.boxedIntClass); |
| } |
| |
| @override |
| w.RefType visitDoubleConstant(DoubleConstant constant) { |
| return _typeOfClass(translator.boxedDoubleClass); |
| } |
| |
| @override |
| w.RefType visitStringConstant(StringConstant constant) { |
| return _typeOfClass(translator.jsStringClass); |
| } |
| |
| @override |
| w.RefType visitListConstant(ListConstant constant) { |
| return _typeOfClass(translator.immutableListClass); |
| } |
| |
| @override |
| w.RefType visitMapConstant(MapConstant constant) { |
| return _typeOfClass(translator.immutableMapClass); |
| } |
| |
| @override |
| w.RefType visitSetConstant(SetConstant constant) { |
| return _typeOfClass(translator.immutableSetClass); |
| } |
| |
| @override |
| w.RefType visitTypeLiteralConstant(TypeLiteralConstant constant) { |
| throw StateError('Type literal constants should\'ve been lowered already.'); |
| } |
| |
| @override |
| w.RefType visitSymbolConstant(SymbolConstant constant) { |
| return _typeOfClass(translator.symbolClass); |
| } |
| |
| @override |
| w.RefType visitRecordConstant(RecordConstant constant) { |
| return translator.getRecordClassInfo(constant.recordType).nonNullableType; |
| } |
| |
| @override |
| w.RefType visitInstanceConstant(InstanceConstant constant) { |
| w.RefType wasmArrayType(InstanceConstant constant, |
| {required bool mutable}) { |
| final arrayType = translator.arrayTypeForDartType( |
| constant.typeArguments.single, |
| mutable: mutable); |
| return w.RefType.def(arrayType, nullable: false); |
| } |
| |
| final cls = constant.classNode; |
| if (cls == translator.wasmArrayClass) { |
| return wasmArrayType(constant, mutable: true); |
| } |
| if (cls == translator.immutableWasmArrayClass) { |
| return wasmArrayType(constant, mutable: false); |
| } |
| return _typeOfClass(cls); |
| } |
| |
| @override |
| w.RefType visitStaticTearOffConstant(StaticTearOffConstant constant) { |
| final member = constant.targetReference.asProcedure; |
| final function = member.function; |
| final representation = translator.closureLayouter.getClosureRepresentation( |
| function.typeParameters.length, |
| function.positionalParameters.length, |
| function.namedParameters.map((p) => p.name!).toList())!; |
| return w.RefType.def(representation.closureStruct, nullable: false); |
| } |
| |
| @override |
| w.RefType visitInstantiationConstant(InstantiationConstant constant) { |
| final tearOffConstant = constant.tearOffConstant as TearOffConstant; |
| final function = tearOffConstant.function; |
| final representation = translator.closureLayouter.getClosureRepresentation( |
| 0, |
| function.positionalParameters.length, |
| function.namedParameters.map((p) => p.name!).toList())!; |
| return w.RefType.def(representation.closureStruct, nullable: false); |
| } |
| |
| w.RefType _typeOfClass(Class klass) => |
| translator.classInfo[klass]!.nonNullableType; |
| } |
| |
| /// Resolves to true if the visited Constant is accessible from dynamic |
| /// submodules. |
| /// |
| /// Constants that are accessible from dynamic submodules should be: |
| /// (1) Exported from the main module if they exist there and then imported |
| /// into dynamic submodules. |
| /// (2) Runtime canonicalized by dynamic submodules if they are not in the main |
| /// module. |
| class _ConstantDynamicModuleSharedChecker extends ConstantVisitor<bool> |
| with ConstantVisitorDefaultMixin<bool> { |
| final Translator translator; |
| |
| _ConstantDynamicModuleSharedChecker(this.translator); |
| |
| // TODO(natebiggs): Make this more precise by handling more specific |
| // constants. |
| @override |
| bool defaultConstant(Constant constant) => true; |
| |
| @override |
| bool visitInstanceConstant(InstanceConstant constant) { |
| final cls = constant.classNode; |
| if (!cls.enclosingLibrary.isFromMainModule(translator.coreTypes)) { |
| return false; |
| } |
| if (cls == translator.wasmArrayClass || |
| cls == translator.immutableWasmArrayClass) { |
| return true; |
| } |
| return constant.classNode.constructors.any( |
| (c) => c.isConst && c.isDynamicSubmoduleCallable(translator.coreTypes)); |
| } |
| } |
| |
| /// Responsible for reading constants and defining them. |
| class _ConstantAccessor { |
| final Translator translator; |
| |
| /// The modules that use the given constant. |
| /// |
| /// NOTE: If a module uses constant `c` it uses also all constants `c` |
| /// transitively refers to. |
| final Map<ConstantInfo, Set<w.ModuleBuilder>> moduleUses = {}; |
| |
| _ConstantAccessor(this.translator); |
| |
| /// Reads a constant. |
| /// |
| /// If we haven't decided into which module to place the constant it may emit |
| /// a stub in the instruction stream which we'll patch after code generation |
| /// is complete. |
| /// |
| /// We decide the constant placement as follows: |
| /// |
| /// * If the main module uses it: |
| /// => Define the constant in main module. |
| /// => This may happen during code generation. |
| /// |
| /// * If more than two modules use it: |
| /// => Define the constant in main module. |
| /// => This may happen during code generation. |
| /// |
| /// * If a constant is accessed only by a single module: |
| /// => Define the constant in that module. |
| /// => This happens after code generation when we know all constant uses. |
| /// |
| w.ValueType loadConstant(w.InstructionsBuilder b, Constant c, |
| w.ModuleBuilder? deferredModuleGuard) { |
| final info = translator.constants.ensureConstant(c)!; |
| final existingDefinition = info._definition; |
| |
| if (existingDefinition != null) { |
| // We already have a defined constant. Possibly import it and then use it. |
| return _readDefinedConstant(b, info, existingDefinition); |
| } |
| |
| // We have to guarantee that using the constant synchronously works. If the |
| // constant use is preceded with a deferred module load guard, we can |
| // consider the use to be in that deferred module, possibly allowing us to |
| // put the constant in a deferred library (even if the guarded use is e.g. |
| // in main library). |
| final usingModule = deferredModuleGuard ?? b.moduleBuilder; |
| |
| // If the (non-guarded) use is in the main module then the constant has to |
| // be placed in the main module. |
| if (!forceDelayedConstantDefinition && |
| usingModule == translator.mainModule) { |
| final definition = |
| _defineConstantInModuleRecursive(translator.mainModule, info); |
| return _readDefinedConstant(b, info, definition); |
| } |
| |
| // Remember for the transitive DAG of [constant] that we use it in this |
| // module. |
| void rememberConstantUse(ConstantInfo info) { |
| if (moduleUses.putIfAbsent(info, () => {}).add(usingModule)) { |
| for (final child in info.children) { |
| rememberConstantUse(child); |
| } |
| } |
| } |
| |
| rememberConstantUse(info); |
| |
| // If we have uses in multiple modules, we should place it a module that is |
| // guaranteed to be loaded at the use time of those modules. |
| // |
| // => We are conservative and assign it to the main module. |
| // |
| // NOTE: Improve this by finding the closest common ancestor between the |
| // modules in the loading graph. |
| if (!forceDelayedConstantDefinition && moduleUses[info]!.length > 1) { |
| final definition = |
| _defineConstantInModuleRecursive(translator.mainModule, info); |
| return _readDefinedConstant(b, info, definition); |
| } |
| |
| // The current module is the only one using the constant atm, but in the |
| // future other modules may also use it. So we don't know where to place |
| // it just yet. |
| // => Let's emit a patchable constant read and patch the real read later on. |
| |
| // There's no guarantee that the constant is going to be an eager constant. |
| // So the code tring to instantiate the constant shouldn't be in a global |
| // initailizer. |
| assert(!b.constantExpression || constantIsAlwaysEager(info.constant)); |
| |
| final patchInstructions = b.createPatchableRegion([], [info.type]); |
| if (patchInstructions != null) { |
| translator.linkingActions.add(() { |
| // All constant uses have been discovered during codegen phase so we can |
| // know decide into which module to place the constant and patch the |
| // constant access to load it from there. |
| final definition = |
| info._definition ?? _defineConstantInModuleRecursive(null, info); |
| _readDefinedConstant(patchInstructions, info, definition); |
| }); |
| } |
| |
| return info.type; |
| } |
| |
| w.ValueType _readDefinedConstant(w.InstructionsBuilder b, ConstantInfo info, |
| ConstantDefinition definition) { |
| final globalDefinition = definition.global; |
| final globalInitializer = definition._initFunction; |
| |
| // Eagerly initialized constant. |
| if (globalInitializer == null) { |
| translator.globals.readGlobal(b, globalDefinition); |
| return globalDefinition.type.type; |
| } |
| |
| // Lazily initialized constant. |
| w.ValueType type = globalDefinition.type.type.withNullability(false); |
| w.Label done = b.block(const [], [type]); |
| translator.globals.readGlobal(b, globalDefinition); |
| b.br_on_non_null(done); |
| |
| translator.callFunction(globalInitializer, b); |
| b.end(); |
| return type; |
| } |
| |
| /// If [assignedModule] is not null assigns all undefined constants to that |
| /// module. Otherwise takes into account the uses of a constant to determine |
| /// where to place it. |
| ConstantDefinition _defineConstantInModuleRecursive( |
| w.ModuleBuilder? assignedModule, ConstantInfo info) { |
| assert(info._definition == null); |
| |
| for (final child in info.children) { |
| if (child._definition == null) { |
| _defineConstantInModuleRecursive(assignedModule, child); |
| } |
| } |
| // Since constants form a DAG, the [node] shouldn't have a definition yet. |
| assert(info._definition == null); |
| |
| // If we didn't eagerly assign the constant to be in main module, we make a |
| // choice now. |
| // |
| // We want to choose a module that is available by the time the constant is |
| // used. If only one module uses the constant we place it in that module. If |
| // it's used by multiple modules we (conservatively) place it in the main |
| // module. |
| // |
| // NOTE: Improve this by finding the closest common ancestor between the |
| // modules in the loading graph. |
| if (assignedModule == null) { |
| final uses = moduleUses[info]!; |
| assert(uses.isNotEmpty); |
| assignedModule = uses.length == 1 ? uses.single : translator.mainModule; |
| } |
| return _defineConstantInModule(assignedModule, info); |
| } |
| |
| ConstantDefinition _defineConstantInModule( |
| w.ModuleBuilder targetModule, ConstantInfo info) { |
| final constant = info.constant; |
| |
| // The constant itself may be forced to be lazy (e.g. array size too large). |
| bool lazy = !info.canBeEager; |
| |
| // The constant's children may influence laziness. |
| if (!lazy) { |
| for (final child in info.children) { |
| final definition = child._definition!; |
| |
| // If the child is lazy, this constant becomes lazy. |
| if (definition._initFunction != null) { |
| lazy = true; |
| break; |
| } |
| |
| // If we place the constant in a module that may be loaded before the |
| // constants of children, it must get initialized lazily. |
| final childModule = definition.module; |
| final baseModule = translator.isDynamicSubmodule |
| ? translator.dynamicSubmodule |
| : translator.mainModule; |
| if (childModule != targetModule && |
| childModule != translator.mainModule && |
| childModule != baseModule) { |
| lazy = true; |
| break; |
| } |
| } |
| } |
| |
| // The constant itself may be forced to be lazy depending on which module we |
| // place it in. |
| if (!lazy) { |
| // The constant codegen code may decide to make it lazy depending on which |
| // module it's going to be placed in. |
| lazy = info._forceLazy(info, targetModule); |
| } |
| |
| // Define the lazy or non-lazy constant in the module. |
| final ConstantDefinition definition; |
| if (lazy) { |
| final (global, initFunction) = _createLazyConstant(targetModule, info); |
| definition = ConstantDefinition(targetModule, global, initFunction); |
| } else { |
| final global = _createNonLazyConstant(targetModule, info); |
| definition = ConstantDefinition(targetModule, global, null); |
| } |
| info.setDefinition(definition); |
| |
| if (info.exportByMainApp) { |
| assert(translator.dynamicModuleSupportEnabled && |
| !translator.isDynamicSubmodule); |
| translator.exporter.exportDynamicConstant( |
| targetModule, constant, definition.global, |
| initializer: definition._initFunction); |
| } |
| return definition; |
| } |
| |
| void defineMainAppDefinition( |
| ConstantInfo info, String globalName, String? initializeName) { |
| assert(translator.isDynamicSubmodule); |
| final type = info.type; |
| |
| final fakeMainApp = translator.mainModule; |
| |
| // Make fake global in the fake main module. |
| final globalType = w.GlobalType( |
| initializeName != null ? type.withNullability(true) : type, |
| mutable: false); |
| final fakeGlobal = |
| fakeMainApp.globals.define(globalType, _constantName(info.constant)); |
| translator.globals |
| .declareMainAppGlobalExportWithName(globalName, fakeGlobal); |
| |
| // Make fake initializer function in the fake main module. |
| w.BaseFunction? fakeInitializer; |
| if (initializeName != null) { |
| final initFunctionType = |
| translator.typesBuilder.defineFunction(const [], [info.type]); |
| fakeInitializer = fakeMainApp.functions.define(initFunctionType); |
| translator.declareMainAppFunctionExportWithName( |
| globalName, fakeInitializer); |
| } |
| |
| info._definition = |
| ConstantDefinition(fakeMainApp, fakeGlobal, fakeInitializer); |
| } |
| |
| (w.GlobalBuilder, w.FunctionBuilder) _createLazyConstant( |
| w.ModuleBuilder targetModule, ConstantInfo info) { |
| final constant = info.constant; |
| final type = info.type; |
| final generator = info._codeGen; |
| |
| // Create uninitialized global and function to initialize it. |
| |
| final name = _constantName(constant); |
| final globalType = w.GlobalType(type.withNullability(true)); |
| final definedGlobal = targetModule.globals.define(globalType, name); |
| definedGlobal.initializer.ref_null(w.HeapType.none); |
| definedGlobal.initializer.end(); |
| |
| final initFunctionType = |
| translator.typesBuilder.defineFunction(const [], [type]); |
| final initFunction = targetModule.functions |
| .define(initFunctionType, '$name (lazy initializer)}'); |
| final b2 = initFunction.body; |
| generator(info, b2, true); |
| if (info.needsRuntimeCanonicalization) { |
| final valueLocal = b2.addLocal(type); |
| constant.accept(ConstantCanonicalizer(translator, b2, valueLocal)); |
| } |
| w.Local temp = b2.addLocal(type); |
| b2.local_tee(temp); |
| b2.global_set(definedGlobal); |
| b2.local_get(temp); |
| b2.end(); |
| return (definedGlobal, initFunction); |
| } |
| |
| w.GlobalBuilder _createNonLazyConstant( |
| w.ModuleBuilder targetModule, ConstantInfo info) { |
| final constants = translator.constants; |
| |
| // Create global with the constant in its initializer. |
| assert(!constants.currentlyCreating); |
| final globalType = w.GlobalType(info.type, mutable: false); |
| constants.currentlyCreating = true; |
| final definedGlobal = |
| targetModule.globals.define(globalType, _constantName(info.constant)); |
| info._codeGen(info, definedGlobal.initializer, false); |
| definedGlobal.initializer.end(); |
| constants.currentlyCreating = false; |
| |
| return definedGlobal; |
| } |
| |
| bool constantIsAlwaysEager(Constant constant) { |
| if (constant is NullConstant || |
| constant is BoolConstant || |
| constant is IntConstant || |
| constant is DoubleConstant) { |
| // We can always eagerly use those constants because |
| // * null is not a heap object |
| // * true/false should always be defined in main module |
| // * int/double do not have identity and can be just materialized (plus |
| // boxed if needed) |
| return true; |
| } |
| if (constant is InstanceConstant) { |
| final klass = constant.classNode; |
| if (klass == translator.wasmI32Class || |
| klass == translator.wasmI64Class || |
| klass == translator.wasmF32Class || |
| klass == translator.wasmF64Class) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| static int _nextGlobalId = 0; |
| String _constantName(Constant constant) { |
| final id = _nextGlobalId++; |
| if (constant is StringConstant) { |
| var value = constant.value; |
| final newline = value.indexOf('\n'); |
| if (newline != -1) value = value.substring(0, newline); |
| if (value.length > 30) value = '${value.substring(0, 30)}<...>'; |
| return 'C$id "$value"'; |
| } |
| if (constant is BoolConstant) { |
| return 'C$id ${constant.value}'; |
| } |
| if (constant is IntConstant) { |
| return 'C$id ${constant.value}'; |
| } |
| if (constant is DoubleConstant) { |
| return 'C$id ${constant.value}'; |
| } |
| if (constant is InstanceConstant) { |
| final klass = constant.classNode; |
| final name = klass.name; |
| if (constant.typeArguments.isEmpty) { |
| return 'C$id $name'; |
| } |
| final typeArguments = constant.typeArguments.map(_nameType).join(', '); |
| if (klass == translator.wasmArrayClass || |
| klass == translator.immutableWasmArrayClass) { |
| final entries = |
| (constant.fieldValues.values.single as ListConstant).entries; |
| return 'C$id $name<$typeArguments>[${entries.length}]'; |
| } |
| return 'C$id $name<$typeArguments>'; |
| } |
| if (constant is TearOffConstant) { |
| return 'C$id ${constant.target.name} tear-off'; |
| } |
| return 'C$id $constant'; |
| } |
| |
| String _nameType(DartType type) { |
| if (type is InterfaceType) { |
| final name = type.classNode.name; |
| if (type.typeArguments.isEmpty) return name; |
| return '$name<${type.typeArguments.map((t) => _nameType(t)).join(', ')}>'; |
| } |
| return '$type'; |
| } |
| } |