| // 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:dart2wasm/translator.dart'; |
| |
| import 'package:kernel/ast.dart'; |
| import 'package:kernel/library_index.dart'; |
| |
| import 'package:wasm_builder/wasm_builder.dart' as w; |
| |
| /// 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 asyncSuspendStateCompleter = 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 vtableInstantiationFunction = 2; |
| static const instantiationContextInner = 0; |
| static const instantiationContextTypeArgumentsBase = 1; |
| static const typeIsDeclaredNullable = 2; |
| 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 syncStarIteratorCurrent = 3; |
| static const syncStarIteratorYieldStarIterable = 4; |
| static const recordFieldBase = 2; |
| static const jsStringImplRef = 2; |
| |
| static void validate(Translator translator) { |
| void check(Class cls, String name, int expectedIndex) { |
| assert( |
| translator.fieldIndex[ |
| cls.fields.firstWhere((f) => f.name.text == name)] == |
| expectedIndex, |
| "Unexpected field index for ${cls.name}.$name"); |
| } |
| |
| check(translator.asyncSuspendStateClass, "_resume", |
| FieldIndex.asyncSuspendStateResume); |
| check(translator.asyncSuspendStateClass, "_context", |
| FieldIndex.asyncSuspendStateContext); |
| check(translator.asyncSuspendStateClass, "_targetIndex", |
| FieldIndex.asyncSuspendStateTargetIndex); |
| check(translator.asyncSuspendStateClass, "_completer", |
| FieldIndex.asyncSuspendStateCompleter); |
| 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); |
| if (!translator.options.jsCompatibility) { |
| check(translator.oneByteStringClass, "_array", FieldIndex.stringArray); |
| check(translator.twoByteStringClass, "_array", FieldIndex.stringArray); |
| } |
| check(translator.listBaseClass, "_length", FieldIndex.listLength); |
| check(translator.listBaseClass, "_data", FieldIndex.listArray); |
| check(translator.hashFieldBaseClass, "_indexNullable", |
| 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.syncStarIteratorClass, "_current", |
| FieldIndex.syncStarIteratorCurrent); |
| check(translator.syncStarIteratorClass, "_yieldStarIterable", |
| FieldIndex.syncStarIteratorYieldStarIterable); |
| } |
| } |
| |
| /// 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; |
| |
| /// 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. |
| final int 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 = null; |
| |
| /// 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. |
| late final ClassInfo repr = upperBound( |
| implementedBy.map((c) => identical(c, this) ? this : c.repr).toSet()); |
| |
| /// All classes which implement this class. This is used to compute `repr`. |
| final List<ClassInfo> implementedBy = []; |
| |
| /// 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) { |
| implementedBy.add(this); |
| } |
| |
| void _addField(w.FieldType fieldType, [int? expectedIndex]) { |
| assert(expectedIndex == null || expectedIndex == struct.fields.length); |
| struct.fields.add(fieldType); |
| } |
| |
| // 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 |
| ]; |
| } |
| |
| ClassInfo upperBound(Set<ClassInfo> classes) { |
| while (classes.length > 1) { |
| Set<ClassInfo> newClasses = {}; |
| int minDepth = 999999999; |
| int maxDepth = 0; |
| for (ClassInfo info in classes) { |
| minDepth = min(minDepth, info.depth); |
| maxDepth = max(maxDepth, info.depth); |
| } |
| int targetDepth = minDepth == maxDepth ? minDepth - 1 : minDepth; |
| for (ClassInfo info in classes) { |
| while (info.depth > targetDepth) { |
| info = info.superInfo!; |
| } |
| newClasses.add(info); |
| } |
| classes = newClasses; |
| } |
| return classes.single; |
| } |
| |
| /// 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 = {}; |
| |
| /// Masquerades for implementation classes. For each entry of the map, all |
| /// subtypes of the key masquerade as the value. |
| late final Map<Class, Class> _masquerades = _computeMasquerades(); |
| |
| Map<Class, Class> _computeMasquerades() { |
| final map = { |
| translator.coreTypes.boolClass: translator.coreTypes.boolClass, |
| translator.coreTypes.intClass: translator.coreTypes.intClass, |
| translator.coreTypes.doubleClass: translator.coreTypes.doubleClass, |
| translator.coreTypes.stringClass: translator.coreTypes.stringClass, |
| translator.index.getClass("dart:core", "_Type"): |
| translator.coreTypes.typeClass, |
| translator.index.getClass("dart:core", "_ListBase"): |
| translator.coreTypes.listClass |
| }; |
| for (final name in const <String>[ |
| "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) { |
| map[cls] = cls; |
| } |
| } |
| return map; |
| } |
| |
| late final Set<Class> _neverMasquerades = _computeNeverMasquerades(); |
| |
| /// These types switch from properly reified non-masquerading types in regular |
| /// Dart2Wasm mode to masquerading types in js compatibility mode. |
| final Set<String> jsCompatibilityTypes = { |
| "JSStringImpl", |
| "JSArrayBufferImpl", |
| "JSDataViewImpl", |
| "JSInt8ArrayImpl", |
| "JSUint8ArrayImpl", |
| "JSUint8ClampedArrayImpl", |
| "JSInt16ArrayImpl", |
| "JSUint16ArrayImpl", |
| "JSInt32ArrayImpl", |
| "JSInt32x4ArrayImpl", |
| "JSUint32ArrayImpl", |
| "JSBigUint64ArrayImpl", |
| "JSBigInt64ArrayImpl", |
| "JSFloat32ArrayImpl", |
| "JSFloat32x4ArrayImpl", |
| "JSFloat64ArrayImpl", |
| "JSFloat64x2ArrayImpl", |
| }; |
| |
| Set<Class> _computeNeverMasquerades() { |
| // The JS types do not masquerade in regular Dart2Wasm, but they aren't |
| // always used so we have to construct this set programmatically. |
| final jsTypesLibraryIndex = |
| LibraryIndex(translator.component, ["dart:_js_types"]); |
| final neverMasquerades = [ |
| if (!translator.options.jsCompatibility) ...jsCompatibilityTypes, |
| ] |
| .map((name) => jsTypesLibraryIndex.tryGetClass("dart:_js_types", name)) |
| .toSet(); |
| neverMasquerades.removeWhere((c) => c == null); |
| return neverMasquerades.cast<Class>(); |
| } |
| |
| /// 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); |
| |
| w.ModuleBuilder get m => translator.m; |
| |
| TranslatorOptions get options => translator.options; |
| |
| void _createStructForClassTop(int classCount) { |
| final w.StructType struct = m.types.defineStruct("#Top"); |
| topInfo = ClassInfo(null, 0, 0, struct, null); |
| translator.classForHeapType[struct] = topInfo; |
| } |
| |
| void _createStructForClass(Map<Class, int> 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 = |
| m.types.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. |
| info.implementedBy.add(topInfo); |
| } 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 _StringBase and _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 |
| ? topInfo |
| : (!translator.options.jsCompatibility && |
| cls == translator.stringBaseClass) || |
| 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 arg = supertype.typeArguments[i]; |
| if (arg is TypeParameterType && arg.parameter == parameter) { |
| typeParameterMatch[parameter] = superInfo.cls!.typeParameters[i]; |
| break; |
| } |
| } |
| } |
| } |
| |
| w.StructType struct = |
| m.types.defineStruct(cls.name, superType: superInfo.struct); |
| info = ClassInfo(cls, classId, superInfo.depth + 1, struct, superInfo, |
| typeParameterMatch: typeParameterMatch); |
| |
| // Mark all interfaces as being implemented by this class. This is |
| // needed to calculate representation types. |
| for (Supertype interface in cls.implementedTypes) { |
| ClassInfo? interfaceInfo = translator.classInfo[interface.classNode]; |
| while (interfaceInfo != null) { |
| interfaceInfo.implementedBy.add(info); |
| interfaceInfo = interfaceInfo.superInfo; |
| } |
| } |
| } |
| translator.classesSupersFirst.add(info); |
| translator.classes[classId] = info; |
| translator.classInfo[cls] = info; |
| translator.classForHeapType.putIfAbsent(info.struct, () => info!); |
| |
| ClassInfo? computeMasquerade() { |
| if (_neverMasquerades.contains(cls)) { |
| return null; |
| } |
| if (info!.superInfo?.masquerade != null) { |
| return info.superInfo!.masquerade; |
| } |
| for (Supertype implemented in cls.implementedTypes) { |
| ClassInfo? implementedMasquerade = |
| translator.classInfo[implemented.classNode]!.masquerade; |
| if (implementedMasquerade != null) { |
| return implementedMasquerade; |
| } |
| } |
| Class? selfMasquerade = _masquerades[cls]; |
| if (selfMasquerade != null) { |
| return translator.classInfo[selfMasquerade]!; |
| } |
| return null; |
| } |
| |
| info.masquerade = computeMasquerade(); |
| } |
| |
| void _createStructForRecordClass(Map<Class, int> classIds, Class cls) { |
| final numFields = cls.fields.length; |
| |
| final struct = _recordStructs.putIfAbsent( |
| numFields, |
| () => m.types.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] = 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), FieldIndex.classId); |
| } else { |
| // Copy fields from superclass |
| for (w.FieldType fieldType in superInfo.struct.fields) { |
| info._addField(fieldType); |
| } |
| if (info.cls!.superclass == null) { |
| // Object - add identity hash code field |
| info._addField(w.FieldType(w.NumType.i32), 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) { |
| w.ValueType wasmType = translator.translateType(field.type); |
| translator.fieldIndex[field] = info.struct.fields.length; |
| info._addField(w.FieldType(wasmType, mutable: !field.isFinal)); |
| } |
| } |
| } |
| } |
| |
| 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 |
| for (w.FieldType fieldType in superInfo.struct.fields) { |
| info._addField(fieldType); |
| } |
| |
| for (Field _ in info.cls!.fields) { |
| info._addField(w.FieldType(topInfo.nullableType)); |
| } |
| } |
| |
| 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 nextClassId = 1; |
| final (dfsOrder, classIds) = _numberClasses(nextClassId); |
| |
| _createStructForClassTop(dfsOrder.length); |
| |
| // Class infos by class-id, will be populated by the calls to |
| // [_createStructForClass] and [_createStructForRecordClass] below. |
| translator.classes = List<ClassInfo>.filled(1 + dfsOrder.length, 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); |
| |
| for (final cls in dfsOrder) { |
| if (cls.superclass == translator.coreTypes.recordClass) { |
| _createStructForRecordClass(classIds, cls); |
| } else { |
| _createStructForClass(classIds, cls); |
| } |
| } |
| |
| // 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. |
| FieldIndex.validate(translator); |
| } |
| |
| (List<Class>, Map<Class, int>) _numberClasses(int nextClassId) { |
| // Make graph from class to its subclasses. |
| late final Class root; |
| final subclasses = <Class, List<Class>>{}; |
| for (final library in translator.component.libraries) { |
| for (final cls in library.classes) { |
| final superClass = cls.superclass; |
| if (superClass == null) { |
| root = cls; |
| } else { |
| subclasses.putIfAbsent(superClass, () => []).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: -10, |
| translator.coreTypes.numClass: -9, |
| if (!translator.options.jsCompatibility) translator.stringBaseClass: -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:')) { |
| // 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, void Function(Class) fun) { |
| fun(root); |
| final children = subclasses[root]; |
| if (children != null) { |
| for (final sub in children) { |
| dfs(sub, fun); |
| } |
| } |
| } |
| |
| // Make a list of the depth-first pre-order traversal. |
| final dfsOrder = <Class>[]; |
| dfs(root, dfsOrder.add); |
| |
| final classIds = <Class, int>{}; |
| |
| // TODO: Remove this special case that violates strict DFS pre-order |
| // numbering by re-doing the masquerades. |
| // |
| // We need to use 4 classes here that don't require name mangling. |
| classIds[translator.coreTypes.objectClass] = nextClassId++; |
| classIds[translator.functionTypeClass] = nextClassId++; |
| classIds[translator.interfaceTypeClass] = nextClassId++; |
| classIds[translator.recordTypeClass] = nextClassId++; |
| for (Class cls in _masquerades.values) { |
| classIds[cls] = classIds[cls] ?? nextClassId++; |
| } |
| |
| for (final cls in dfsOrder) { |
| if (!cls.isAbstract) classIds[cls] = classIds[cls] ?? nextClassId++; |
| } |
| for (final cls in dfsOrder) { |
| if (cls.isAbstract) classIds[cls] = classIds[cls] ?? nextClassId++; |
| } |
| |
| return (dfsOrder, classIds); |
| } |
| } |