diff --git a/pkg/dart2wasm/lib/intrinsics.dart b/pkg/dart2wasm/lib/intrinsics.dart index a4ab754bb814..efae53efc491 100644 --- a/pkg/dart2wasm/lib/intrinsics.dart +++ b/pkg/dart2wasm/lib/intrinsics.dart @@ -10,6 +10,7 @@ import 'class_info.dart'; import 'code_generator.dart'; import 'dynamic_forwarders.dart'; import 'translator.dart'; +import 'types.dart'; typedef CodeGenCallback = void Function(CodeGenerator); @@ -112,6 +113,7 @@ class Intrinsifier { }; Translator get translator => codeGen.translator; + Types get types => codeGen.translator.types; w.InstructionsBuilder get b => codeGen.b; DartType dartTypeOf(Expression exp) => codeGen.dartTypeOf(exp); @@ -444,11 +446,29 @@ class Intrinsifier { b.i32_const(0); return w.NumType.i32; case "_typeRulesSupers": - return translator.types.makeTypeRulesSupers(b); - case "_typeRulesSubstitutions": - return translator.types.makeTypeRulesSubstitutions(b); + final type = translator + .translateStorageType(types.rtt.typeRulesSupersType) + .unpacked; + translator.constants + .instantiateConstant(null, b, types.rtt.typeRulesSupers, type); + return type; + case "_canonicalSubstitutionTable": + final type = translator + .translateStorageType(types.rtt.substitutionTableConstantType) + .unpacked; + translator.constants.instantiateConstant( + null, b, types.rtt.substitutionTableConstant, type); + return type; case "_typeNames": - return translator.types.makeTypeNames(b); + final type = + translator.translateStorageType(types.rtt.typeNamesType).unpacked; + if (translator.options.minify) { + b.ref_null((type as w.RefType).heapType); + } else { + translator.constants + .instantiateConstant(null, b, types.rtt.typeNames, type); + } + return type; } } diff --git a/pkg/dart2wasm/lib/types.dart b/pkg/dart2wasm/lib/types.dart index 4308a9ae623a..630377ea48a7 100644 --- a/pkg/dart2wasm/lib/types.dart +++ b/pkg/dart2wasm/lib/types.dart @@ -2,6 +2,7 @@ // 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:collection'; import 'dart:math' show max; import 'package:kernel/ast.dart'; @@ -63,35 +64,13 @@ class Types { late final w.ValueType recordTypeNamesFieldExpectedType = classAndFieldToType( translator.recordTypeClass, FieldIndex.recordTypeNames); - /// A mapping from concrete subclass `classID` to [Map]s of superclass - /// `classID` and the necessary substitutions which must be performed to test - /// for a valid subtyping relationship. - late final Map>> typeRules = _buildTypeRules(); + late final RuntimeTypeInformation rtt = RuntimeTypeInformation(translator); /// We will build the [interfaceTypeEnvironment] when building the /// [typeRules]. final InterfaceTypeEnvironment interfaceTypeEnvironment = InterfaceTypeEnvironment(); - /// Because we can't currently support [Map]s in our `TypeUniverse`, we have - /// to decompose [typeRules] into two [Map]s based on [List]s. - /// - /// [typeRulesSupers] is a [List] where the index in the list is a subclasses' - /// `classID` and the value at that index is a [List] of superclass - /// `classID`s. - late final List> typeRulesSupers = _buildTypeRulesSupers(); - - /// [typeRulesSubstitutions] is a [List] where the index in the list is a - /// subclasses' `classID` and the value at that index is a [List] indexed by - /// the index of the superclasses' `classID` in [typeRulesSuper] and the value - /// at that index is a [List] of [DartType]s which must be substituted for the - /// subtyping relationship to be valid. - late final List>> typeRulesSubstitutions = - _buildTypeRulesSubstitutions(); - - /// A list which maps class ID to the classes [String] name. - late final List typeNames = _buildTypeNames(); - /// Type parameter offset for function types, specifying the lower end of /// their index range for type parameter types. Map functionTypeParameterOffset = Map.identity(); @@ -116,176 +95,6 @@ class Types { CoreTypes get coreTypes => translator.coreTypes; - /// Builds a [Map>>] to store subtype - /// information. The first key is the class id of a subtype. This returns a - /// map where each key is the class id of a transitively implemented super - /// type and each value is a list of the necessary type substitutions required - /// for the subtyping relationship to be valid. - Map>> _buildTypeRules() { - List classes = translator.classes; - Map>> subtypeMap = {}; - for (ClassInfo classInfo in classes) { - ClassInfo superclassInfo = classInfo; - - // We don't need type rules for any class without a superclass, or for - // classes whose supertype is [Object]. The latter case will be handled - // directly in the subtype checking algorithm. - if (superclassInfo.cls == null || - superclassInfo.cls == coreTypes.objectClass) { - continue; - } - Class superclass = superclassInfo.cls!; - - // TODO(joshualitt): This includes abstract types that can't be - // instantiated, but might be needed for subtype checks. The majority of - // abstract classes are probably unnecessary though. We should filter - // these cases to reduce the size of the type rules. - Iterable subclasses = translator.subtypes - .getSubtypesOf(superclass) - .where((cls) => cls != superclass); - Iterable subtypes = subclasses.map( - (Class cls) => cls.getThisType(coreTypes, Nullability.nonNullable)); - for (InterfaceType subtype in subtypes) { - interfaceTypeEnvironment._add(subtype); - List? typeArguments = translator.hierarchy - .getInterfaceTypeArgumentsAsInstanceOfClass(subtype, superclass) - ?.map(normalize) - .toList(); - ClassInfo subclassInfo = translator.classInfo[subtype.classNode]!; - Map> substitutionMap = - subtypeMap[subclassInfo.classId] ??= {}; - substitutionMap[superclassInfo.classId] = typeArguments ?? const []; - } - } - return subtypeMap; - } - - List> _buildTypeRulesSupers() { - List> typeRulesSupers = []; - for (int classId = 0; classId < translator.classes.length; classId++) { - List? superclassIds = typeRules[classId]?.keys.toList(); - if (superclassIds == null) { - typeRulesSupers.add(const []); - } else { - superclassIds.sort(); - typeRulesSupers.add(superclassIds); - } - } - return typeRulesSupers; - } - - List>> _buildTypeRulesSubstitutions() { - List>> typeRulesSubstitutions = []; - for (int classId = 0; classId < translator.classes.length; classId++) { - List supers = typeRulesSupers[classId]; - typeRulesSubstitutions.add(supers.isEmpty ? const [] : []); - for (int j = 0; j < supers.length; j++) { - int superId = supers[j]; - typeRulesSubstitutions.last.add(typeRules[classId]![superId]!); - } - } - return typeRulesSubstitutions; - } - - List _buildTypeNames() { - // This logic assumes `translator.classes` returns the classes indexed by - // class ID. If we ever change that logic, we will need to change this code. - List typeNames = []; - for (ClassInfo classInfo in translator.classes) { - Class? cls = classInfo.cls; - if (cls == null || cls.isAnonymousMixin) { - typeNames.add(""); - } else { - typeNames.add(cls.name); - } - } - return typeNames; - } - - /// Builds a map of subclasses to the transitive set of superclasses they - /// implement. - /// TODO(joshualitt): This implementation is just temporary. Eventually we - /// should move to a data structure more closely resembling [typeRules]. - w.ValueType makeTypeRulesSupers(w.InstructionsBuilder b) { - final wasmI32Type = - InterfaceType(translator.wasmI32Class, Nullability.nonNullable); - - final supersOfClasses = []; - for (List supers in typeRulesSupers) { - supersOfClasses.add(translator.constants.makeArrayOf( - wasmI32Type, [for (final cid in supers) IntConstant(cid)])); - } - - final arrayOfWasmI32Type = InterfaceType( - translator.wasmArrayClass, Nullability.nonNullable, [wasmI32Type]); - final typeRuleSupers = - translator.constants.makeArrayOf(arrayOfWasmI32Type, supersOfClasses); - - final arrayOfArrayOfWasmI32Type = InterfaceType(translator.wasmArrayClass, - Nullability.nonNullable, [arrayOfWasmI32Type]); - - final typeRulesSupersType = - translator.translateStorageType(arrayOfArrayOfWasmI32Type).unpacked; - translator.constants - .instantiateConstant(null, b, typeRuleSupers, typeRulesSupersType); - return typeRulesSupersType; - } - - /// Similar to the above, but provides the substitutions required for each - /// supertype. - /// TODO(joshualitt): Like [makeTypeRulesSupers], this is just temporary. - w.ValueType makeTypeRulesSubstitutions(w.InstructionsBuilder b) { - final typeType = - InterfaceType(translator.typeClass, Nullability.nonNullable); - final arrayOfType = InterfaceType( - translator.wasmArrayClass, Nullability.nonNullable, [typeType]); - final arrayOfArrayOfType = InterfaceType( - translator.wasmArrayClass, Nullability.nonNullable, [arrayOfType]); - final arrayOfArrayOfArrayOfType = InterfaceType(translator.wasmArrayClass, - Nullability.nonNullable, [arrayOfArrayOfType]); - - final substitutionsConstantL0 = []; - for (List> substitutionsL1 in typeRulesSubstitutions) { - final substitutionsConstantL1 = []; - for (List substitutionsL2 in substitutionsL1) { - substitutionsConstantL1.add(translator.constants.makeArrayOf(typeType, - [for (final t in substitutionsL2) TypeLiteralConstant(t)])); - } - substitutionsConstantL0.add(translator.constants - .makeArrayOf(arrayOfType, substitutionsConstantL1)); - } - - final typeRulesSubstitutionsType = - translator.translateStorageType(arrayOfArrayOfArrayOfType).unpacked; - translator.constants.instantiateConstant( - null, - b, - translator.constants - .makeArrayOf(arrayOfArrayOfType, substitutionsConstantL0), - typeRulesSubstitutionsType); - return typeRulesSubstitutionsType; - } - - /// Returns a list of string type names for pretty printing types. - w.ValueType makeTypeNames(w.InstructionsBuilder b) { - final stringType = - translator.coreTypes.stringRawType(Nullability.nonNullable); - final arrayOfStringType = InterfaceType( - translator.wasmArrayClass, Nullability.nonNullable, [stringType]); - - final typeNamesType = - translator.translateStorageType(arrayOfStringType).unpacked; - if (translator.options.minify) { - b.ref_null((typeNamesType as w.RefType).heapType); - } else { - final arrayOfStrings = translator.constants.makeArrayOf( - stringType, [for (final name in typeNames) StringConstant(name)]); - translator.constants - .instantiateConstant(null, b, arrayOfStrings, typeNamesType); - } - return typeNamesType; - } - bool isTypeConstant(DartType type) { return type is DynamicType || type is VoidType || @@ -806,6 +615,198 @@ class Types { type.declaredNullability == Nullability.nullable ? 1 : 0; } +/// Builds up data structures that the Runtime Type System implementation uses. +/// +/// There are 3 data structures: +/// +/// * The name of all classes represented as an wasm array of strings. +/// +/// * A canonical substitution table where each entry represents +/// (potentially uninstantiated) type arguments to a superclass. +/// +/// => This is used for translating type arguments between related classes +/// in a hierarchy. +/// +/// * A table mapping each class id to its transitive super classes (i.e. +/// transitive implements/extends) and an index into the canonical +/// substitution table on how to translate type arguments between the two +/// related clases. +/// +/// See sdk/lib/_internal/wasm/lib/type.dart for more information. +class RuntimeTypeInformation { + final Translator translator; + + /// Canonical substitution table of type `const WasmArray>`. + /// + /// Stores a canonical table of substitution arrays. Each substitution array + /// describes (possibly uninstantiated) type arguments that can be + /// instantiated with actual object type arguments. + /// => This allows translating an objects type arguments to the type arguments + /// of a related super class. + /// + /// See sdk/lib/_internal/wasm/lib/type.dart:_canonicalSubstitutionTable for + /// what this contains and how it's used for substitution. + late final InstanceConstant substitutionTableConstant; + + /// The Dart type of the [substitutionTableConstant] constant. + late final DartType substitutionTableConstantType; + + /// Type rules supers table of type `const WasmArray>`. + /// + /// Has an array for every class id in the system. For a particular class id + /// it has (super-classId, canonical-substitutionIndex) tuples used by the RTT + /// system to determine whether two classes are related and how to translate + /// type arguments from one class to type arguments of a related other class. + /// + /// See sdk/lib/_internal/wasm/lib/type.dart:_typeRulesSupers for + /// what this contains and how it's used for substitution. + late final InstanceConstant typeRulesSupers; + late final DartType typeRulesSupersType; + + /// Table of type names indexed by class id. + late final InstanceConstant typeNames; + late final DartType typeNamesType; + + CoreTypes get coreTypes => translator.coreTypes; + Types get types => translator.types; + + RuntimeTypeInformation(this.translator) { + final ( + Map> typeRules, + LinkedHashMap substitutionTable + ) = _buildTypeRules(); + + // The canonical substitution table of type WasmArray> + _initSubstitutionTableConstant(substitutionTable); + + // The super type substitution rules for each class of type + // WasmArray>. + _initTypeRulesSupers(typeRules); + + // The class name table of type WasmArray + _initTypeNames(); + } + + (Map>, LinkedHashMap) + _buildTypeRules() { + final subtypeMap = >{}; + // ignore: prefer_collection_literals + final substitutionTable = LinkedHashMap(); + + for (ClassInfo classInfo in translator.classes) { + ClassInfo superclassInfo = classInfo; + + // We don't need type rules for any class without a superclass, or for + // classes whose supertype is [Object]. The latter case will be handled + // directly in the subtype checking algorithm. + if (superclassInfo.cls == null || + superclassInfo.cls == coreTypes.objectClass) { + continue; + } + Class superclass = superclassInfo.cls!; + + // TODO(joshualitt): This includes abstract types that can't be + // instantiated, but might be needed for subtype checks. The majority of + // abstract classes are probably unnecessary though. We should filter + // these cases to reduce the size of the type rules. + Iterable subclasses = translator.subtypes + .getSubtypesOf(superclass) + .where((cls) => cls != superclass); + Iterable subtypes = subclasses.map( + (Class cls) => cls.getThisType(coreTypes, Nullability.nonNullable)); + for (InterfaceType subtype in subtypes) { + types.interfaceTypeEnvironment._add(subtype); + + final List? typeArguments = translator.hierarchy + .getInterfaceTypeArgumentsAsInstanceOfClass(subtype, superclass) + ?.map(types.normalize) + .toList(); + + final substitution = + translator.constants.makeTypeArray(typeArguments ?? const []); + final substitutionIndex = substitutionTable.putIfAbsent( + substitution, () => substitutionTable.length); + + final subclassId = translator.classInfo[subtype.classNode]!.classId; + (subtypeMap[subclassId] ??= {})[superclassInfo.classId] = + substitutionIndex; + } + } + return (subtypeMap, substitutionTable); + } + + void _initSubstitutionTableConstant( + LinkedHashMap substitutionTable) { + final typeType = + InterfaceType(translator.typeClass, Nullability.nonNullable); + final arrayOfType = InterfaceType( + translator.wasmArrayClass, Nullability.nonNullable, [typeType]); + + // We rely on the keys being in insertion order. + substitutionTableConstant = translator.constants + .makeArrayOf(arrayOfType, substitutionTable.keys.toList()); + substitutionTableConstantType = InterfaceType( + translator.wasmArrayClass, Nullability.nonNullable, [arrayOfType]); + } + + void _initTypeRulesSupers(Map> typeRules) { + final wasmI32 = + InterfaceType(translator.wasmI32Class, Nullability.nonNullable); + final arrayOfI32 = InterfaceType( + translator.wasmArrayClass, Nullability.nonNullable, [wasmI32]); + + // Maps each class id to a list of + // (implementedClassId, substitutionTableIndex) tuples. + final typeRulesArray = []; + for (int classId = 0; classId < translator.classes.length; classId++) { + final rules = typeRules[classId]; + if (rules == null) { + typeRulesArray.add( + translator.constants.makeArrayOf(wasmI32, const [])); + continue; + } + + final List superclassIds = rules.keys.toList(); + superclassIds.sort(); + final superClassSubstitutionTuples = + List.filled(2 * superclassIds.length, IntConstant(0)); + for (int i = 0; i < superclassIds.length; ++i) { + final superClassId = superclassIds[i]; + final substitutionTableIndex = rules[superClassId]!; + + superClassSubstitutionTuples[2 * i + 0] = IntConstant(superClassId); + superClassSubstitutionTuples[2 * i + 1] = + IntConstant(substitutionTableIndex); + } + typeRulesArray.add(translator.constants + .makeArrayOf(wasmI32, superClassSubstitutionTuples)); + } + typeRulesSupers = + translator.constants.makeArrayOf(arrayOfI32, typeRulesArray); + typeRulesSupersType = InterfaceType( + translator.wasmArrayClass, Nullability.nonNullable, [arrayOfI32]); + } + + void _initTypeNames() { + final stringType = + translator.coreTypes.stringRawType(Nullability.nonNullable); + + final emptyString = StringConstant(''); + List nameConstants = []; + for (ClassInfo classInfo in translator.classes) { + Class? cls = classInfo.cls; + if (cls == null || cls.isAnonymousMixin) { + nameConstants.add(emptyString); + } else { + nameConstants.add(StringConstant(cls.name)); + } + } + typeNames = translator.constants.makeArrayOf(stringType, nameConstants); + typeNamesType = InterfaceType( + translator.wasmArrayClass, Nullability.nonNullable, [stringType]); + } +} + /// For a function type F = `... Function(...)` compute offset(F) /// such that for any function type G = `... Function(...)` /// nested inside F, if G contains a reference to any type parameters of F, then diff --git a/sdk/lib/_internal/wasm/lib/type.dart b/sdk/lib/_internal/wasm/lib/type.dart index eadbcd88c6a7..ed055830355e 100644 --- a/sdk/lib/_internal/wasm/lib/type.dart +++ b/sdk/lib/_internal/wasm/lib/type.dart @@ -600,8 +600,59 @@ class _RecordType extends _Type { identical(names, other.names); } +/// Rules that describe whether two classes are related in a hierarchy and if so +/// how to translate a subtype's class's type arguments to the type arguments of +/// a supertype's class. +/// +/// The table has key for every class in the system. The value is an array of +/// `(superClassId, canonicalSubstitutionIndex)`. +/// +/// For example, let's assume we have these classes: +/// +/// ``` +/// class Sub extends Foo> {} +/// class Foo implements Bar {} +/// class Bar {} +/// ``` +/// +/// The table will have an entry for every transitively extended/implemented +/// class (except `Object`). +/// +/// ``` +/// _typeRulesSupers = [ +/// ... +/// @Sub.classId: [(Foo.classId, IDX-X), (Bar.classId, IDX-Y)] +/// ... +/// ] +/// ``` +/// +/// Where the `IDX-X` and `IDX-Y` are integer indices into the canonical +/// substitution table, which would look like this: +/// ``` +/// _canonicalSubstitutionTable = [ +/// ... +/// @IDX-X: [ +/// _InterfaceType(List.classId, args: [_InterfaceTypeParameterType(index=0)]) +/// ] +/// ... +/// @IDX-Y: [ +/// _InterfaceType(int.classId args: []), +/// _InterfaceType(List.classId, args: [_InterfaceTypeParameterType(index=0)]) +/// ] +/// ... +/// ] +/// ``` +/// external WasmArray> get _typeRulesSupers; -external WasmArray>> get _typeRulesSubstitutions; + +/// Canonical substitution table used to translate type arguments from one class +/// to a related other class. +/// +/// See [_typeRulesSupers] for more information on how they are used. +external WasmArray> get _canonicalSubstitutionTable; + +/// The names of all classes (indexed by class id) or null (if `--minify` was +/// used) external WasmArray? get _typeNames; /// Type parameter environment used while comparing function types. @@ -845,11 +896,15 @@ abstract class _TypeUniverse { @pragma('wasm:prefer-inline') static bool isInterfaceSubtypeWithoutArguments(int sId, int tId) { if (sId == tId) return true; - final WasmArray sSupers = _typeRulesSupers[sId]; - for (int i = 0; i < sSupers.length; i++) { - if (sSupers.readUnsigned(i) == tId) return true; + return _linearSearch(_typeRulesSupers[sId], tId) != -1; + } + + @pragma('wasm:prefer-inline') + static int _linearSearch(WasmArray table, int key) { + for (int i = 0; i < table.length; i += 2) { + if (table.readUnsigned(i) == key) return i; } - return false; + return -1; } static bool isInterfaceSubtypeWithArguments( @@ -869,22 +924,18 @@ abstract class _TypeUniverse { // [s]'s type substitutions with [t]'s type arguments. final WasmArray sSupers = _typeRulesSupers[sId]; if (sSupers.length == 0) return false; - int sSuperIndexOfT = -1; - for (int i = 0; i < sSupers.length; i++) { - if (sSupers.readUnsigned(i) == tId) { - sSuperIndexOfT = i; - break; - } - } - if (sSuperIndexOfT == -1) return false; - assert(sSuperIndexOfT < _typeRulesSubstitutions[sId].length); + + final int idMatchIndex = _linearSearch(sSupers, tId); + if (idMatchIndex == -1) return false; + assert(idMatchIndex < _canonicalSubstitutionTable.length); + final int substitutionIndex = sSupers.readUnsigned(idMatchIndex + 1); // Return early if we don't have to check type arguments as the destination // type is non-generic. if (tTypeArguments.isEmpty) return true; final WasmArray<_Type> substitutions = - _typeRulesSubstitutions[sId][sSuperIndexOfT]; + _canonicalSubstitutionTable[substitutionIndex]; assert(substitutions.isNotEmpty); // Check arguments.