blob: 257fbe37398db11bb527ef5ef26edfc0de2f3664 [file] [log] [blame] [edit]
// 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';
}
}