7d199f2171
Fixes several asserts that were throwing when running with dynamic modules enabled. Bug: https://github.com/dart-lang/sdk/issues/60743 Change-Id: I6bcd3723ecdee784b3d1603e1c87a47e3703a7f3 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/423261 Reviewed-by: Martin Kustermann <kustermann@google.com> Commit-Queue: Nate Biggs <natebiggs@google.com>
1039 lines
37 KiB
Dart
1039 lines
37 KiB
Dart
// 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:math';
|
|
|
|
import 'package:kernel/ast.dart';
|
|
import 'package:wasm_builder/wasm_builder.dart' as w;
|
|
|
|
import 'dynamic_modules.dart';
|
|
import 'serialization.dart';
|
|
import 'translator.dart';
|
|
|
|
/// Wasm struct field indices for fields that are accessed explicitly from Wasm
|
|
/// code, e.g. in intrinsics.
|
|
///
|
|
/// The values are validated by asserts, typically either through
|
|
/// [ClassInfo._addField] (for manually added fields) or by a line in
|
|
/// [FieldIndex.validate] (for fields declared in Dart code).
|
|
class FieldIndex {
|
|
static const asyncSuspendStateResume = 2;
|
|
static const asyncSuspendStateContext = 3;
|
|
static const asyncSuspendStateTargetIndex = 4;
|
|
static const asyncSuspendStateFuture = 5;
|
|
static const asyncSuspendStateCurrentException = 6;
|
|
static const asyncSuspendStateCurrentExceptionStackTrace = 7;
|
|
static const asyncSuspendStateCurrentReturnValue = 8;
|
|
|
|
static const classId = 0;
|
|
static const boxValue = 1;
|
|
static const identityHash = 1;
|
|
static const objectFieldBase = 2;
|
|
static const stringArray = 2;
|
|
static const listLength = 3;
|
|
static const listArray = 4;
|
|
static const hashBaseIndex = 2;
|
|
static const hashBaseData = 4;
|
|
static const closureContext = 2;
|
|
static const closureVtable = 3;
|
|
static const closureRuntimeType = 4;
|
|
static const vtableDynamicCallEntry = 0;
|
|
static const vtableInstantiationTypeComparisonFunction = 1;
|
|
static const vtableInstantiationTypeHashFunction = 2;
|
|
static const vtableInstantiationFunction = 3;
|
|
static const instantiationContextInner = 0;
|
|
static const instantiationContextTypeArgumentsBase = 1;
|
|
static const typeIsDeclaredNullable = 2;
|
|
static const interfaceTypeClassId = 3;
|
|
static const interfaceTypeTypeArguments = 4;
|
|
static const functionTypeNamedParameters = 9;
|
|
static const recordTypeNames = 3;
|
|
static const recordTypeFieldTypes = 4;
|
|
static const suspendStateIterator = 4;
|
|
static const suspendStateContext = 5;
|
|
static const suspendStateTargetIndex = 6;
|
|
static const suspendStateCurrentException = 7;
|
|
static const suspendStateCurrentExceptionStackTrace = 8;
|
|
static const syncStarIteratorCurrent = 3;
|
|
static const syncStarIteratorYieldStarIterable = 4;
|
|
static const recordFieldBase = 2;
|
|
static const jsStringImplRef = 2;
|
|
static const ffiPointerAddress = 3;
|
|
|
|
static void validate(Translator translator) {
|
|
void check(Class cls, String name, int expectedIndex) {
|
|
Field? field;
|
|
|
|
for (Field clsField in cls.fields) {
|
|
if (clsField.name.text == name) {
|
|
field = clsField;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (field == null) {
|
|
throw AssertionError("$cls doesn't have field $name");
|
|
}
|
|
|
|
final actualIndex = translator.fieldIndex[field];
|
|
|
|
if (actualIndex == null) {
|
|
throw AssertionError("$cls field $name doesn't have an index assigned");
|
|
}
|
|
|
|
if (actualIndex != expectedIndex) {
|
|
throw AssertionError(
|
|
"$cls field $name expected index = $expectedIndex, "
|
|
"actual index = $actualIndex");
|
|
}
|
|
}
|
|
|
|
check(translator.asyncSuspendStateClass, "_resume",
|
|
FieldIndex.asyncSuspendStateResume);
|
|
check(translator.asyncSuspendStateClass, "_context",
|
|
FieldIndex.asyncSuspendStateContext);
|
|
check(translator.asyncSuspendStateClass, "_targetIndex",
|
|
FieldIndex.asyncSuspendStateTargetIndex);
|
|
check(translator.asyncSuspendStateClass, "_future",
|
|
FieldIndex.asyncSuspendStateFuture);
|
|
check(translator.asyncSuspendStateClass, "_currentException",
|
|
FieldIndex.asyncSuspendStateCurrentException);
|
|
check(translator.asyncSuspendStateClass, "_currentExceptionStackTrace",
|
|
FieldIndex.asyncSuspendStateCurrentExceptionStackTrace);
|
|
check(translator.asyncSuspendStateClass, "_currentReturnValue",
|
|
FieldIndex.asyncSuspendStateCurrentReturnValue);
|
|
|
|
check(translator.boxedBoolClass, "value", FieldIndex.boxValue);
|
|
check(translator.boxedIntClass, "value", FieldIndex.boxValue);
|
|
check(translator.boxedDoubleClass, "value", FieldIndex.boxValue);
|
|
check(translator.listBaseClass, "_length", FieldIndex.listLength);
|
|
check(translator.listBaseClass, "_data", FieldIndex.listArray);
|
|
check(translator.hashFieldBaseClass, "_index", FieldIndex.hashBaseIndex);
|
|
check(translator.hashFieldBaseClass, "_data", FieldIndex.hashBaseData);
|
|
check(translator.closureClass, "context", FieldIndex.closureContext);
|
|
check(translator.typeClass, "isDeclaredNullable",
|
|
FieldIndex.typeIsDeclaredNullable);
|
|
check(translator.interfaceTypeClass, "typeArguments",
|
|
FieldIndex.interfaceTypeTypeArguments);
|
|
check(translator.functionTypeClass, "namedParameters",
|
|
FieldIndex.functionTypeNamedParameters);
|
|
check(translator.recordTypeClass, "names", FieldIndex.recordTypeNames);
|
|
check(translator.recordTypeClass, "fieldTypes",
|
|
FieldIndex.recordTypeFieldTypes);
|
|
check(translator.suspendStateClass, "_iterator",
|
|
FieldIndex.suspendStateIterator);
|
|
check(translator.suspendStateClass, "_context",
|
|
FieldIndex.suspendStateContext);
|
|
check(translator.suspendStateClass, "_targetIndex",
|
|
FieldIndex.suspendStateTargetIndex);
|
|
check(translator.suspendStateClass, "_currentException",
|
|
FieldIndex.suspendStateCurrentException);
|
|
check(translator.suspendStateClass, "_currentExceptionStackTrace",
|
|
FieldIndex.suspendStateCurrentExceptionStackTrace);
|
|
check(translator.syncStarIteratorClass, "_current",
|
|
FieldIndex.syncStarIteratorCurrent);
|
|
check(translator.syncStarIteratorClass, "_yieldStarIterable",
|
|
FieldIndex.syncStarIteratorYieldStarIterable);
|
|
check(translator.ffiPointerClass, "_address", FieldIndex.ffiPointerAddress);
|
|
}
|
|
}
|
|
|
|
/// Initial value for the hash code field of objects. This value is recognized
|
|
/// by `Object._objectHashCode` wich updates the field first time it's read.
|
|
const int initialIdentityHash = 0;
|
|
|
|
/// We do not assign real class ids to anonymous mixin classes.
|
|
const int anonymousMixinClassId = -1;
|
|
|
|
/// Information about the Wasm representation for a class.
|
|
class ClassInfo {
|
|
/// The Dart class that this info corresponds to. The top type does not have
|
|
/// an associated Dart class.
|
|
final Class? cls;
|
|
|
|
/// The Class ID of this class, stored in every instance of the class.
|
|
ClassId get classId {
|
|
if (_classId._localValue == anonymousMixinClassId) {
|
|
throw 'Tried to access class ID of anonymous mixin $cls';
|
|
}
|
|
return _classId;
|
|
}
|
|
|
|
final ClassId _classId;
|
|
|
|
/// Depth of this class in the Wasm type hierarchy.
|
|
final int depth;
|
|
|
|
/// The Wasm struct used to represent instances of this class. A class will
|
|
/// sometimes use the same struct as its superclass.
|
|
final w.StructType struct;
|
|
|
|
/// The superclass for this class. This will usually be the Dart superclass,
|
|
/// but there are a few exceptions, where the Wasm type hierarchy does not
|
|
/// follow the Dart class hierarchy.
|
|
final ClassInfo? superInfo;
|
|
|
|
/// The class that this class masquerades as via `runtimeType`, if any.
|
|
ClassInfo? masquerade;
|
|
|
|
/// For every type parameter which is directly mapped to a type parameter in
|
|
/// the superclass, this contains the corresponding superclass type
|
|
/// parameter. These will reuse the corresponding type parameter field of
|
|
/// the superclass.
|
|
final Map<TypeParameter, TypeParameter> typeParameterMatch;
|
|
|
|
/// The class whose struct is used as the type for variables of this type.
|
|
/// This is a type which is a superclass of all subtypes of this type.
|
|
ClassInfo get repr => _repr!;
|
|
|
|
ClassInfo? _repr;
|
|
|
|
/// Nullabe Wasm ref type for this class.
|
|
final w.RefType nullableType;
|
|
|
|
/// Non-nullable Wasm ref type for this class.
|
|
final w.RefType nonNullableType;
|
|
|
|
/// Get Wasm ref type for this class with given nullability.
|
|
w.RefType typeWithNullability(bool nullable) =>
|
|
nullable ? nullableType : nonNullableType;
|
|
|
|
ClassInfo(this.cls, this._classId, this.depth, this.struct, this.superInfo,
|
|
{this.typeParameterMatch = const {}})
|
|
: nullableType = w.RefType.def(struct, nullable: true),
|
|
nonNullableType = w.RefType.def(struct, nullable: false);
|
|
|
|
void _addField(w.FieldType fieldType,
|
|
{int? expectedIndex, String? fieldName}) {
|
|
assert(expectedIndex == null || expectedIndex == struct.fields.length);
|
|
struct.fields.add(fieldType);
|
|
if (fieldName != null) {
|
|
final fieldIndex = struct.fields.length - 1;
|
|
struct.fieldNames[fieldIndex] = fieldName;
|
|
}
|
|
}
|
|
|
|
// This returns the types of all the class's fields (including
|
|
// superclass fields), except for the class id and the identity hash
|
|
List<w.ValueType> getClassFieldTypes() => [
|
|
for (var fieldType in struct.fields.skip(FieldIndex.objectFieldBase))
|
|
fieldType.type.unpacked
|
|
];
|
|
|
|
void forEachClassFieldIndex(void Function(int index, w.FieldType type) f) {
|
|
for (int i = FieldIndex.objectFieldBase; i < struct.fields.length; i++) {
|
|
f(i, struct.fields[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
ClassInfo upperBound(ClassInfo a, ClassInfo b) {
|
|
if (a.depth < b.depth) {
|
|
while (b.depth > a.depth) {
|
|
b = b.superInfo!;
|
|
}
|
|
} else {
|
|
while (a.depth > b.depth) {
|
|
a = a.superInfo!;
|
|
}
|
|
}
|
|
assert(a.depth == b.depth);
|
|
while (a != b) {
|
|
a = a.superInfo!;
|
|
b = b.superInfo!;
|
|
}
|
|
return a;
|
|
}
|
|
|
|
/// Constructs the Wasm type hierarchy.
|
|
class ClassInfoCollector {
|
|
final Translator translator;
|
|
late final ClassInfo topInfo;
|
|
|
|
/// Maps number of record fields to the struct type to be used for a record
|
|
/// shape class with that many fields.
|
|
final Map<int, w.StructType> _recordStructs = {};
|
|
|
|
/// Any subtype of these needs to masqueraded (modulo special js-compatibility
|
|
/// mode semantics) or specially treated due to being from a different type
|
|
/// (e.g. record, closure)
|
|
late final Set<Class> masqueraded = _computeMasquerades();
|
|
|
|
Set<Class> _computeMasquerades() {
|
|
final values = {
|
|
translator.coreTypes.boolClass,
|
|
translator.coreTypes.intClass,
|
|
translator.coreTypes.doubleClass,
|
|
translator.coreTypes.stringClass,
|
|
translator.coreTypes.functionClass,
|
|
translator.coreTypes.recordClass,
|
|
translator.index.getClass("dart:core", "_Type"),
|
|
translator.index.getClass("dart:_list", "WasmListBase"),
|
|
translator.index.getClass("dart:_string", "JSStringImpl"),
|
|
};
|
|
for (final name in const <String>[
|
|
"ByteBuffer",
|
|
"ByteData",
|
|
"Int8List",
|
|
"Uint8List",
|
|
"Uint8ClampedList",
|
|
"Int16List",
|
|
"Uint16List",
|
|
"Int32List",
|
|
"Uint32List",
|
|
"Int64List",
|
|
"Uint64List",
|
|
"Float32List",
|
|
"Float64List",
|
|
"Int32x4List",
|
|
"Float32x4List",
|
|
"Float64x2List",
|
|
]) {
|
|
final Class? cls = translator.index.tryGetClass("dart:typed_data", name);
|
|
if (cls != null) values.add(cls);
|
|
}
|
|
return values;
|
|
}
|
|
|
|
/// Wasm field type for fields with type [_Type]. Fields of this type are
|
|
/// added to classes for type parameters.
|
|
///
|
|
/// This field is initialized when a class with a type parameter is first
|
|
/// encountered. Initialization depends on [Translator] visiting the [_Type]
|
|
/// class first and creating a [ClassInfo] for it.
|
|
late final w.FieldType typeType = w.FieldType(
|
|
translator.classInfo[translator.typeClass]!.nonNullableType,
|
|
mutable: false);
|
|
|
|
ClassInfoCollector(this.translator);
|
|
|
|
TranslatorOptions get options => translator.options;
|
|
|
|
void _createStructForClassTop() {
|
|
final w.StructType struct = translator.typesBuilder.defineStruct("#Top");
|
|
topInfo = ClassInfo(null, AbsoluteClassId(0), 0, struct, null);
|
|
translator.classForHeapType[struct] = topInfo;
|
|
}
|
|
|
|
void _createStructForClass(Map<Class, ClassId> classIds, Class cls) {
|
|
ClassInfo? info = translator.classInfo[cls];
|
|
if (info != null) return;
|
|
|
|
final classId = classIds[cls]!;
|
|
Class? superclass = cls.superclass;
|
|
if (superclass == null) {
|
|
ClassInfo superInfo = topInfo;
|
|
final w.StructType struct = translator.typesBuilder
|
|
.defineStruct(cls.name, superType: superInfo.struct);
|
|
info = ClassInfo(cls, classId, superInfo.depth + 1, struct, superInfo);
|
|
// Mark Top type as implementing Object to force the representation
|
|
// type of Object to be Top.
|
|
} else {
|
|
// Recursively initialize all supertypes before initializing this class.
|
|
_createStructForClass(classIds, superclass);
|
|
for (Supertype interface in cls.implementedTypes) {
|
|
_createStructForClass(classIds, interface.classNode);
|
|
}
|
|
|
|
// In the Wasm type hierarchy, Object, bool and num sit directly below
|
|
// the Top type. The implementation classes of _Type sit directly below
|
|
// the public classes they implement. All other classes sit below their
|
|
// superclass.
|
|
ClassInfo superInfo = cls == translator.coreTypes.boolClass ||
|
|
cls == translator.coreTypes.numClass ||
|
|
cls == translator.boxedIntClass ||
|
|
cls == translator.boxedDoubleClass
|
|
? topInfo
|
|
: cls == translator.typeClass
|
|
? translator.classInfo[cls.implementedTypes.single.classNode]!
|
|
: translator.classInfo[superclass]!;
|
|
|
|
// Figure out which type parameters can reuse a type parameter field of
|
|
// the superclass.
|
|
Map<TypeParameter, TypeParameter> typeParameterMatch = {};
|
|
if (cls.typeParameters.isNotEmpty) {
|
|
Supertype supertype = cls.superclass == superInfo.cls
|
|
? cls.supertype!
|
|
: cls.implementedTypes.single;
|
|
for (TypeParameter parameter in cls.typeParameters) {
|
|
for (int i = 0; i < supertype.typeArguments.length; i++) {
|
|
DartType superTypeArg = supertype.typeArguments[i];
|
|
if (superTypeArg is TypeParameterType &&
|
|
superTypeArg.parameter == parameter &&
|
|
superTypeArg.nullability != Nullability.nullable) {
|
|
typeParameterMatch[parameter] = superInfo.cls!.typeParameters[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
w.StructType struct = translator.typesBuilder
|
|
.defineStruct(cls.name, superType: superInfo.struct);
|
|
info = ClassInfo(cls, classId, superInfo.depth + 1, struct, superInfo,
|
|
typeParameterMatch: typeParameterMatch);
|
|
if (translator.dynamicModuleSupportEnabled &&
|
|
cls.isDynamicSubmoduleExtendable(translator.coreTypes)) {
|
|
// If a class is extendable in a submodule then we have to be
|
|
// conservative and mark it as not being final.
|
|
struct.hasAnySubtypes = true;
|
|
}
|
|
|
|
if (translator.isDynamicSubmodule) {
|
|
final brandIndex = translator
|
|
.dynamicModuleInfo!.metadata.classMetadata[cls]?.brandIndex;
|
|
if (brandIndex != null) {
|
|
translator.typesBuilder.addBrandTypeAssignment(struct, brandIndex);
|
|
}
|
|
}
|
|
}
|
|
translator.classesSupersFirst.add(info);
|
|
translator.classInfo[cls] = info;
|
|
translator.classForHeapType.putIfAbsent(info.struct, () => info!);
|
|
if (classId._localValue != anonymousMixinClassId) {
|
|
translator.classes[classId._localValue] = info;
|
|
}
|
|
}
|
|
|
|
void _createStructForRecordClass(Map<Class, ClassId> classIds, Class cls) {
|
|
final numFields = cls.fields.length;
|
|
|
|
final struct = _recordStructs.putIfAbsent(
|
|
numFields,
|
|
() => translator.typesBuilder.defineStruct(
|
|
'Record$numFields',
|
|
superType: translator.recordInfo.struct,
|
|
));
|
|
|
|
final ClassInfo superInfo = translator.recordInfo;
|
|
|
|
final classId = classIds[cls]!;
|
|
final info =
|
|
ClassInfo(cls, classId, superInfo.depth + 1, struct, superInfo);
|
|
|
|
translator.classesSupersFirst.add(info);
|
|
translator.classes[classId._localValue] = info;
|
|
translator.classInfo[cls] = info;
|
|
translator.classForHeapType.putIfAbsent(info.struct, () => info);
|
|
}
|
|
|
|
void _generateFields(ClassInfo info) {
|
|
ClassInfo? superInfo = info.superInfo;
|
|
if (superInfo == null) {
|
|
// Top - add class id field
|
|
info._addField(w.FieldType(w.NumType.i32, mutable: false),
|
|
expectedIndex: FieldIndex.classId);
|
|
} else {
|
|
// Copy fields from superclass
|
|
int superFieldIndex = 0;
|
|
for (w.FieldType fieldType in superInfo.struct.fields) {
|
|
info._addField(fieldType,
|
|
fieldName: superInfo.struct.fieldNames[superFieldIndex]);
|
|
superFieldIndex += 1;
|
|
}
|
|
if (info.cls!.superclass == null) {
|
|
// Object - add identity hash code field
|
|
info._addField(w.FieldType(w.NumType.i32),
|
|
expectedIndex: FieldIndex.identityHash);
|
|
}
|
|
// Add fields for type variables
|
|
for (TypeParameter parameter in info.cls!.typeParameters) {
|
|
TypeParameter? match = info.typeParameterMatch[parameter];
|
|
if (match != null) {
|
|
// Reuse supertype type variable
|
|
translator.typeParameterIndex[parameter] =
|
|
translator.typeParameterIndex[match]!;
|
|
} else {
|
|
translator.typeParameterIndex[parameter] = info.struct.fields.length;
|
|
info._addField(typeType);
|
|
}
|
|
}
|
|
// Add fields for Dart instance fields
|
|
for (Field field in info.cls!.fields) {
|
|
if (field.isInstanceMember) {
|
|
final w.ValueType wasmType = translator.translateTypeOfField(field);
|
|
translator.fieldIndex[field] = info.struct.fields.length;
|
|
info._addField(w.FieldType(wasmType, mutable: !field.isFinal),
|
|
fieldName: field.name.text);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void _generateRecordFields(ClassInfo info) {
|
|
final struct = info.struct;
|
|
final ClassInfo superInfo = info.superInfo!;
|
|
assert(identical(superInfo, translator.recordInfo));
|
|
|
|
// Different record classes can share the same struct, check if the struct
|
|
// is already initialized
|
|
if (struct.fields.isEmpty) {
|
|
// Copy fields from superclass
|
|
int superFieldIndex = 0;
|
|
for (w.FieldType fieldType in superInfo.struct.fields) {
|
|
info._addField(fieldType,
|
|
fieldName: superInfo.struct.fieldNames[superFieldIndex]);
|
|
superFieldIndex += 1;
|
|
}
|
|
|
|
for (Field field in info.cls!.fields) {
|
|
info._addField(w.FieldType(topInfo.nullableType),
|
|
fieldName: field.name.text);
|
|
}
|
|
}
|
|
|
|
int fieldIdx = superInfo.struct.fields.length;
|
|
for (Field field in info.cls!.fields) {
|
|
translator.fieldIndex[field] = fieldIdx++;
|
|
}
|
|
}
|
|
|
|
/// Create class info and Wasm struct for all classes.
|
|
void collect() {
|
|
// `0` is occupied by artificial non-Dart top class.
|
|
const int firstClassId = 1;
|
|
|
|
final classIdNumbering = translator.classIdNumbering =
|
|
ClassIdNumbering._number(translator, masqueraded, firstClassId);
|
|
final classIds = translator.classIdNumbering.classIds;
|
|
final dfsOrder = translator.classIdNumbering.dfsOrder;
|
|
|
|
_createStructForClassTop();
|
|
|
|
// Class infos by class-id, will be populated by the calls to
|
|
// [_createStructForClass] and [_createStructForRecordClass] below.
|
|
translator.classes = List<ClassInfo>.filled(
|
|
(classIdNumbering.maxDynamicSubmoduleClassId ??
|
|
classIdNumbering.maxClassId) +
|
|
1,
|
|
topInfo);
|
|
|
|
// Class infos in different order: Infos of super class and super interfaces
|
|
// before own info.
|
|
translator.classesSupersFirst = [topInfo];
|
|
|
|
// Subclasses of the `_Closure` class are generated on the fly as fields
|
|
// with function types are encountered. Therefore, `_Closure` class must
|
|
// be early in the initialization order.
|
|
_createStructForClass(classIds, translator.closureClass);
|
|
|
|
// Similarly `_Type` is needed for type parameter fields in classes and
|
|
// needs to be initialized before we encounter a class with type
|
|
// parameters.
|
|
_createStructForClass(classIds, translator.typeClass);
|
|
|
|
// Similarly the `Record` class needs to be handled before the loop below as
|
|
// the [_createStructForRecordClass] needs it.
|
|
_createStructForClass(classIds, translator.coreTypes.recordClass);
|
|
|
|
for (final cls in dfsOrder) {
|
|
if (cls.superclass == translator.coreTypes.recordClass) {
|
|
_createStructForRecordClass(classIds, cls);
|
|
} else {
|
|
_createStructForClass(classIds, cls);
|
|
}
|
|
}
|
|
|
|
// Create representations of the classes (i.e. Wasm representation used to
|
|
// represent objects of that Dart type).
|
|
for (final cls in dfsOrder) {
|
|
ClassInfo? representation;
|
|
if (translator.dynamicModuleSupportEnabled &&
|
|
cls.isDynamicSubmoduleExtendable(translator.coreTypes)) {
|
|
assert(!translator.builtinTypes.containsKey(cls));
|
|
|
|
// If a class is extendable in a dynamic submodule then we have to be
|
|
// conservative and assume it might be a subclass of Object. The Object
|
|
// class maps to topInfo because boxed values are a subtype of Object in
|
|
// Dart but not of the object struct.
|
|
representation = cls == translator.coreTypes.objectClass
|
|
? translator.topInfo
|
|
: translator.objectInfo;
|
|
} else {
|
|
void addRanges(List<Range> ranges) {
|
|
for (final range in ranges) {
|
|
for (int classId = range.start; classId <= range.end; ++classId) {
|
|
final current = translator.classes[classId];
|
|
if (representation == null) {
|
|
representation = current;
|
|
continue;
|
|
}
|
|
representation = upperBound(representation!, current);
|
|
}
|
|
}
|
|
}
|
|
|
|
final mainModuleConcreteRange =
|
|
classIdNumbering.getConcreteClassIdRangeForMainModule(cls);
|
|
// Only non-extendable classes can get here so they should only have
|
|
// concrete implementations in either the main module or the submodule,
|
|
// not both.
|
|
if (translator.isDynamicSubmodule && mainModuleConcreteRange.isEmpty) {
|
|
final submoduleConcreteRange =
|
|
classIdNumbering.getConcreteClassIdRangeForDynamicSubmodule(cls);
|
|
addRanges(submoduleConcreteRange);
|
|
} else {
|
|
assert(classIdNumbering
|
|
.getConcreteClassIdRangeForDynamicSubmodule(cls)
|
|
.isEmpty);
|
|
addRanges(mainModuleConcreteRange);
|
|
}
|
|
}
|
|
final info = translator.classInfo[cls]!;
|
|
info._repr = representation ?? info;
|
|
}
|
|
|
|
// Now that the representation types for all classes have been computed,
|
|
// fill in the types of the fields in the generated Wasm structs.
|
|
for (final info in translator.classesSupersFirst) {
|
|
if (info.superInfo == translator.recordInfo) {
|
|
_generateRecordFields(info);
|
|
} else {
|
|
_generateFields(info);
|
|
}
|
|
}
|
|
|
|
// Validate that all internally used fields have the expected indices.
|
|
assert((() {
|
|
FieldIndex.validate(translator);
|
|
return true;
|
|
})());
|
|
}
|
|
}
|
|
|
|
class ClassIdNumbering {
|
|
final Translator translator;
|
|
final Map<Class, List<Class>> _subclasses;
|
|
final Map<Class, List<Class>> _implementors;
|
|
final Map<Class, List<Range>> _concreteSubclassIdRange;
|
|
final Map<Class, List<Range>> _concreteSubclassIdRangeForDynamicSubmodule;
|
|
final Set<Class> _masqueraded;
|
|
|
|
final List<Class> dfsOrder;
|
|
final Map<Class, ClassId> classIds;
|
|
final int maxConcreteClassId;
|
|
final int maxClassId;
|
|
final int? maxDynamicSubmoduleConcreteClassId;
|
|
final int? maxDynamicSubmoduleClassId;
|
|
|
|
int get firstDynamicSubmoduleClassId => maxClassId + 1;
|
|
|
|
ClassIdNumbering._(
|
|
this.translator,
|
|
this._subclasses,
|
|
this._implementors,
|
|
this._concreteSubclassIdRange,
|
|
this._concreteSubclassIdRangeForDynamicSubmodule,
|
|
this._masqueraded,
|
|
this.dfsOrder,
|
|
this.classIds,
|
|
this.maxConcreteClassId,
|
|
this.maxClassId,
|
|
this.maxDynamicSubmoduleConcreteClassId,
|
|
this.maxDynamicSubmoduleClassId);
|
|
|
|
final Map<Class, Set<Class>> _transitiveImplementors = {};
|
|
Set<Class> _getTransitiveImplementors(Class klass) {
|
|
var transitiveImplementors = _transitiveImplementors[klass];
|
|
if (transitiveImplementors != null) return transitiveImplementors;
|
|
|
|
transitiveImplementors = {};
|
|
|
|
List<Class>? classes = _subclasses[klass];
|
|
if (classes != null) {
|
|
for (final cls in classes) {
|
|
transitiveImplementors.addAll(_getTransitiveImplementors(cls));
|
|
}
|
|
}
|
|
classes = _implementors[klass];
|
|
if (classes != null) {
|
|
for (final cls in classes) {
|
|
transitiveImplementors.add(cls);
|
|
transitiveImplementors.addAll(_getTransitiveImplementors(cls));
|
|
}
|
|
}
|
|
|
|
return _transitiveImplementors[klass] = transitiveImplementors;
|
|
}
|
|
|
|
/// Maps a class to a list of class id ranges that implement/extend the given
|
|
/// class directly or transitively.
|
|
///
|
|
/// If this function is invoked from a dynamic module enabled build then it
|
|
/// should be wrapped with [DynamicModuleInfo.callClassIdBranch] so that the
|
|
/// checked range will be updated.
|
|
final Map<Class, List<Range>> _concreteClassIdRanges = {};
|
|
List<Range> getConcreteClassIdRangeForMainModule(Class klass) {
|
|
return _getConcreteClassIdRange(
|
|
klass, _concreteClassIdRanges, _concreteSubclassIdRange);
|
|
}
|
|
|
|
final Map<Class, List<Range>> _concreteClassIdRangesForDynamicSubmodule = {};
|
|
List<Range> getConcreteClassIdRangeForDynamicSubmodule(Class klass) {
|
|
return _getConcreteClassIdRange(
|
|
klass,
|
|
_concreteClassIdRangesForDynamicSubmodule,
|
|
_concreteSubclassIdRangeForDynamicSubmodule);
|
|
}
|
|
|
|
/// In case the [klass] is from a dynamic module the returned class id
|
|
/// ranges may be relative. The caller has to ensure to use them
|
|
/// appropriately.
|
|
List<Range> getConcreteClassIdRangeForClass(Class klass) {
|
|
// We cannot return class id ranges for [klass] if there can be more
|
|
// classes in future dynamic module compilations.
|
|
assert(!klass.isDynamicSubmoduleExtendable(translator.coreTypes));
|
|
|
|
return !translator.isDynamicSubmodule ||
|
|
klass.enclosingLibrary.isFromMainModule(translator.coreTypes)
|
|
? getConcreteClassIdRangeForMainModule(klass)
|
|
: getConcreteClassIdRangeForDynamicSubmodule(klass);
|
|
}
|
|
|
|
List<Range> _getConcreteClassIdRange(Class klass,
|
|
Map<Class, List<Range>> cache, Map<Class, List<Range>> subclasses) {
|
|
var ranges = cache[klass];
|
|
if (ranges != null) return ranges;
|
|
|
|
ranges = [];
|
|
final transitiveImplementors = _getTransitiveImplementors(klass);
|
|
final subclassRanges = subclasses[klass] ?? const [];
|
|
for (final range in subclassRanges) {
|
|
ranges.add(range);
|
|
}
|
|
for (final implementor in transitiveImplementors) {
|
|
final implementorRanges = subclasses[implementor] ?? const [];
|
|
for (final range in implementorRanges) {
|
|
ranges.add(range);
|
|
}
|
|
}
|
|
ranges.normalize();
|
|
|
|
return cache[klass] = ranges;
|
|
}
|
|
|
|
late final int firstNonMasqueradedInterfaceClassCid = (() {
|
|
int lastMasqueradedClassId = 0;
|
|
for (final cls in _masqueraded) {
|
|
final ranges = getConcreteClassIdRangeForMainModule(cls);
|
|
if (ranges.isNotEmpty) {
|
|
lastMasqueradedClassId = max(lastMasqueradedClassId, ranges.last.end);
|
|
}
|
|
}
|
|
return lastMasqueradedClassId + 1;
|
|
})();
|
|
|
|
static ClassIdNumbering _number(
|
|
Translator translator, Set<Class> masqueraded, int firstClassId) {
|
|
// Make graph from class to its subclasses.
|
|
late final Class root;
|
|
int? savedMaxConcreteClassId;
|
|
int? savedMaxClassId;
|
|
final subclasses = <Class, List<Class>>{};
|
|
final implementors = <Class, List<Class>>{};
|
|
final classIds = <Class, ClassId>{};
|
|
|
|
if (translator.isDynamicSubmodule) {
|
|
final savedMapping = translator.dynamicModuleInfo!.metadata.classMetadata;
|
|
savedMapping.forEach((cls, metadata) {
|
|
final classId = metadata.classId;
|
|
classIds[cls] = AbsoluteClassId(classId);
|
|
savedMaxClassId = max(savedMaxClassId ?? -2, classId);
|
|
if (!cls.isAbstract && !cls.isAnonymousMixin) {
|
|
savedMaxConcreteClassId = max(savedMaxConcreteClassId ?? -2, classId);
|
|
}
|
|
});
|
|
}
|
|
|
|
int concreteClassCount = 0;
|
|
int abstractClassCount = 0;
|
|
int anonymousMixinClassCount = 0;
|
|
int alreadyAssignedCount = 0;
|
|
for (final library in translator.component.libraries) {
|
|
for (final cls in library.classes) {
|
|
if (!classIds.containsKey(cls)) {
|
|
if (cls.isAnonymousMixin) {
|
|
assert(cls.isAbstract);
|
|
anonymousMixinClassCount++;
|
|
} else if (cls.isAbstract) {
|
|
abstractClassCount++;
|
|
} else {
|
|
concreteClassCount++;
|
|
}
|
|
} else {
|
|
alreadyAssignedCount++;
|
|
}
|
|
final superClass = cls.superclass;
|
|
if (superClass == null) {
|
|
root = cls;
|
|
} else {
|
|
subclasses.putIfAbsent(superClass, () => []).add(cls);
|
|
}
|
|
for (final interface in cls.implementedTypes) {
|
|
implementors.putIfAbsent(interface.classNode, () => []).add(cls);
|
|
}
|
|
}
|
|
}
|
|
|
|
// We have a preference in which order we explore the direct subclasses of
|
|
// `Object` as that allows us to keep class ids of certain hierarchies
|
|
// low.
|
|
// TODO: If we had statistics (e.g. number of class allocations, number of
|
|
// times class is mentioned in type, ...) we'd have an estimate of how often
|
|
// we have to encode a class-id. Then we could reorder the subclasses
|
|
// depending on usage count of the subclass trees.
|
|
final fixedOrder = <Class, int>{
|
|
translator.coreTypes.boolClass: -9,
|
|
translator.coreTypes.numClass: -8,
|
|
translator.jsStringClass: -7,
|
|
translator.typeClass: -6,
|
|
translator.listBaseClass: -5,
|
|
translator.hashFieldBaseClass: -4,
|
|
};
|
|
int order(Class klass) {
|
|
final order = fixedOrder[klass];
|
|
if (order != null) return order;
|
|
|
|
final importUri = klass.enclosingLibrary.importUri.toString();
|
|
if (importUri.startsWith('dart:')) {
|
|
if (masqueraded.contains(klass)) return -1;
|
|
// Bundle the typed data and collection together, they may not have
|
|
// common base class except for `Object` but most of them have similar
|
|
// selectors.
|
|
if (importUri.startsWith('dart:typed_data')) return 0;
|
|
if (importUri.startsWith('dart:collection')) return 1;
|
|
if (importUri.startsWith('dart:core')) return 2;
|
|
|
|
// The dart:wasm classes are marked as entrypoints, therefore retained by
|
|
// TFA but they can never be instantiated, as they represent raw wasm
|
|
// types that aren't part of the dart object hierarchy.
|
|
// Move them to the very end of the class table.
|
|
if (klass.name.startsWith('_WasmBase')) return 0xffffff;
|
|
return 3;
|
|
}
|
|
return 10;
|
|
}
|
|
|
|
subclasses[root]!.sort((Class a, Class b) => order(a).compareTo(order(b)));
|
|
|
|
// Traverse class inheritence graph in depth-first pre-order.
|
|
void dfs(
|
|
Class root, int Function(Class) pre, void Function(Class, int) post) {
|
|
final classId = pre(root);
|
|
final children = subclasses[root];
|
|
if (children != null) {
|
|
for (final sub in children) {
|
|
dfs(sub, pre, post);
|
|
}
|
|
}
|
|
post(root, classId);
|
|
}
|
|
|
|
// Make a list of the depth-first pre-order traversal.
|
|
final dfsOrder = [
|
|
...?translator.dynamicModuleInfo?.metadata.dfsOrderClassIds
|
|
];
|
|
final inDfsOrder = {...dfsOrder};
|
|
|
|
// Maps any class to a dense range of concrete class ids that are subclasses
|
|
// of that class.
|
|
final concreteSubclassRanges = <Class, List<Range>>{};
|
|
final concreteSubclassRangesForDynamicSubmodule = <Class, List<Range>>{};
|
|
|
|
int nextConcreteClassId = (savedMaxClassId ?? (firstClassId - 1)) + 1;
|
|
int nextAbstractClassId = nextConcreteClassId + concreteClassCount;
|
|
|
|
if (classIds.isNotEmpty) {
|
|
// Assumes that saved IDs form a contiguous region at the top of the
|
|
// subclass tree. So if we encounter a node without a saved ID, then we do
|
|
// not need to explore its children for saved IDs.
|
|
Range? addSavedRanges(Class cls) {
|
|
final savedClassId = classIds[cls];
|
|
if (savedClassId == null) return null;
|
|
final children = subclasses[cls] ?? const [];
|
|
final isConcrete = !cls.isAbstract && !cls.isAnonymousMixin;
|
|
Range? savedRange = isConcrete
|
|
? Range(savedClassId._localValue, savedClassId._localValue)
|
|
: null;
|
|
for (final child in children) {
|
|
final childRange = addSavedRanges(child);
|
|
if (childRange != null) {
|
|
savedRange = savedRange == null
|
|
? Range(childRange.start, childRange.end)
|
|
: Range(savedRange.start, max(savedRange.end, childRange.end));
|
|
}
|
|
}
|
|
if (savedRange != null) {
|
|
(concreteSubclassRanges[cls] ??= []).add(savedRange);
|
|
}
|
|
return savedRange;
|
|
}
|
|
|
|
addSavedRanges(root);
|
|
}
|
|
|
|
final subclassesRangesToBuild = savedMaxClassId != null
|
|
? concreteSubclassRangesForDynamicSubmodule
|
|
: concreteSubclassRanges;
|
|
|
|
dfs(root, (Class cls) {
|
|
if (!inDfsOrder.contains(cls)) {
|
|
dfsOrder.add(cls);
|
|
}
|
|
if (classIds.containsKey(cls)) return nextConcreteClassId;
|
|
if (cls.isAnonymousMixin) {
|
|
classIds[cls] = AbsoluteClassId(anonymousMixinClassId);
|
|
return nextConcreteClassId;
|
|
}
|
|
if (cls.isAbstract) {
|
|
var classId = classIds[cls];
|
|
if (classId == null) {
|
|
classIds[cls] = AbsoluteClassId(nextAbstractClassId++);
|
|
}
|
|
return nextConcreteClassId;
|
|
}
|
|
|
|
assert(classIds[cls] == null);
|
|
final classId = nextConcreteClassId++;
|
|
classIds[cls] = savedMaxClassId != null
|
|
? RelativeClassId(classId)
|
|
: AbsoluteClassId(classId);
|
|
return nextConcreteClassId - 1;
|
|
}, (Class cls, int firstClassId) {
|
|
final range = Range(firstClassId, nextConcreteClassId - 1);
|
|
if (!range.isEmpty) {
|
|
(subclassesRangesToBuild[cls] ??= []).add(range);
|
|
}
|
|
});
|
|
|
|
assert(dfsOrder.length ==
|
|
(concreteClassCount +
|
|
abstractClassCount +
|
|
anonymousMixinClassCount +
|
|
alreadyAssignedCount));
|
|
|
|
return ClassIdNumbering._(
|
|
translator,
|
|
subclasses,
|
|
implementors,
|
|
concreteSubclassRanges,
|
|
concreteSubclassRangesForDynamicSubmodule,
|
|
masqueraded,
|
|
dfsOrder,
|
|
classIds,
|
|
savedMaxConcreteClassId ?? nextConcreteClassId - 1,
|
|
savedMaxClassId ?? nextAbstractClassId - 1,
|
|
savedMaxConcreteClassId == null ? null : nextConcreteClassId - 1,
|
|
savedMaxClassId == null ? null : nextAbstractClassId - 1);
|
|
}
|
|
|
|
List<Range> getConcreteSubclassRanges(Class klass) =>
|
|
_concreteSubclassIdRange[klass] ?? const [];
|
|
}
|
|
|
|
sealed class ClassId {
|
|
int get _localValue;
|
|
}
|
|
|
|
final class AbsoluteClassId extends ClassId {
|
|
final int value;
|
|
|
|
@override
|
|
int get _localValue => value;
|
|
|
|
AbsoluteClassId(this.value);
|
|
|
|
@override
|
|
String toString() => 'Absolute($value)';
|
|
}
|
|
|
|
final class RelativeClassId extends ClassId {
|
|
final int relativeValue;
|
|
@override
|
|
int get _localValue => relativeValue;
|
|
|
|
RelativeClassId(this.relativeValue);
|
|
|
|
@override
|
|
String toString() => 'Relative($relativeValue)';
|
|
}
|
|
|
|
// A range of class ids, both ends inclusive.
|
|
class Range {
|
|
final int start;
|
|
final int end;
|
|
|
|
Range._(this.start, this.end) : assert(start <= end);
|
|
const Range.empty()
|
|
: start = 0,
|
|
end = -1;
|
|
factory Range(int start, int end) {
|
|
if (end < start) return Range.empty();
|
|
return Range._(start, end);
|
|
}
|
|
|
|
void serialize(DataSerializer sink) {
|
|
sink.writeInt(start);
|
|
sink.writeInt(end);
|
|
}
|
|
|
|
factory Range.deserialize(DataDeserializer source) {
|
|
final start = source.readInt();
|
|
final end = source.readInt();
|
|
return Range(start, end);
|
|
}
|
|
|
|
int get length => 1 + (end - start);
|
|
bool get isEmpty => length == 0;
|
|
|
|
bool contains(int id) => start <= id && id <= end;
|
|
bool containsRange(Range other) => start <= other.start && other.end <= end;
|
|
|
|
Range shiftBy(int offset) {
|
|
if (isEmpty) return this;
|
|
return Range(start + offset, end + offset);
|
|
}
|
|
|
|
@override
|
|
int get hashCode => Object.hash(start, end);
|
|
|
|
@override
|
|
bool operator ==(other) =>
|
|
other is Range && other.start == start && other.end == end;
|
|
|
|
@override
|
|
String toString() => isEmpty ? '[]' : '[$start, $end]';
|
|
}
|
|
|
|
extension RangeListExtention on List<Range> {
|
|
void normalize() {
|
|
if (isEmpty) return;
|
|
|
|
// Ensure we sort ranges by start of the range.
|
|
sort((a, b) => a.start.compareTo(b.start));
|
|
|
|
int current = 0;
|
|
Range currentRange = this[0];
|
|
for (int read = 1; read < length; ++read) {
|
|
final nextRange = this[read];
|
|
if (currentRange.isEmpty) {
|
|
currentRange = nextRange;
|
|
continue;
|
|
}
|
|
if (nextRange.isEmpty) continue;
|
|
if (currentRange.containsRange(nextRange)) continue;
|
|
if (currentRange.contains(nextRange.start) ||
|
|
(currentRange.end + 1) == nextRange.start) {
|
|
currentRange = Range(currentRange.start, nextRange.end);
|
|
continue;
|
|
}
|
|
|
|
this[current++] = currentRange;
|
|
currentRange = nextRange;
|
|
}
|
|
this[current++] = currentRange;
|
|
length = current;
|
|
}
|
|
}
|