diff --git a/pkg/dart2wasm/lib/constants.dart b/pkg/dart2wasm/lib/constants.dart index 695f641e7f4..d4ab447e348 100644 --- a/pkg/dart2wasm/lib/constants.dart +++ b/pkg/dart2wasm/lib/constants.dart @@ -265,6 +265,15 @@ class ConstantCreator extends ConstantVisitor { w.ModuleBuilder get m => constants.m; 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); + if (!identical(type, constant.type)) { + constant = TypeLiteralConstant(type); + } + } + ConstantInfo? info = constants.constantInfo[constant]; if (info == null) { info = constant.accept(this); @@ -797,9 +806,8 @@ class ConstantCreator extends ConstantVisitor { @override ConstantInfo? visitTypeLiteralConstant(TypeLiteralConstant constant) { - DartType type = types.normalize(constant.type); - - ClassInfo info = translator.classInfo[types.classForType(type)]!; + final DartType type = constant.type; + final ClassInfo info = translator.classInfo[types.classForType(type)]!; translator.functions.allocateClass(info.classId); if (type is InterfaceType && !types.isSpecializedClass(type.classNode)) { return _makeInterfaceType(constant, type, info); @@ -856,11 +864,20 @@ class ConstantCreator extends ConstantVisitor { function, b, fieldTypes, typeListExpectedType); b.struct_new(info.struct); }); + } else if (type is VoidType || + type is DynamicType || + type is InterfaceType && + type.classNode == translator.coreTypes.objectClass) { + return createConstant(constant, info.nonNullableType, (function, b) { + b.i32_const(info.classId); + b.i32_const(initialIdentityHash); + b.i32_const(types.encodedNullability(type)); + b.i64_const(types.topTypeKind(type)); + b.struct_new(info.struct); + }); } else { - assert(type is VoidType || - type is NeverType || + assert(type is NeverType || type is NullType || - type is DynamicType || type is InterfaceType && types.isSpecializedClass(type.classNode)); return createConstant(constant, info.nonNullableType, (function, b) { b.i32_const(info.classId); diff --git a/pkg/dart2wasm/lib/intrinsics.dart b/pkg/dart2wasm/lib/intrinsics.dart index 0030d261138..cc4e6c1730f 100644 --- a/pkg/dart2wasm/lib/intrinsics.dart +++ b/pkg/dart2wasm/lib/intrinsics.dart @@ -1119,13 +1119,12 @@ class Intrinsifier { b.unreachable(); b.end(); // objectType - ClassInfo objectTypeInfo = - translator.classInfo[translator.objectTypeClass]!; - b.i32_const(objectTypeInfo.classId); - b.i32_const(initialIdentityHash); - // Runtime types are never nullable. - b.i32_const(0); - b.struct_new(objectTypeInfo.struct); + translator.constants.instantiateConstant( + function, + b, + TypeLiteralConstant(InterfaceType(translator.coreTypes.objectClass, + Nullability.nonNullable, const [])), + function.type.outputs.single); b.return_(); b.end(); // functionType diff --git a/pkg/dart2wasm/lib/kernel_nodes.dart b/pkg/dart2wasm/lib/kernel_nodes.dart index 63f487b9a3a..b5117e842fc 100644 --- a/pkg/dart2wasm/lib/kernel_nodes.dart +++ b/pkg/dart2wasm/lib/kernel_nodes.dart @@ -59,9 +59,6 @@ mixin KernelNodes { // dart:core runtime type classes late final Class typeClass = index.getClass("dart:core", "_Type"); - late final Class dynamicTypeClass = - index.getClass("dart:core", "_DynamicType"); - late final Class objectTypeClass = index.getClass("dart:core", "_ObjectType"); late final Class abstractFunctionTypeClass = index.getClass("dart:core", "_AbstractFunctionType"); late final Class functionTypeClass = @@ -76,9 +73,8 @@ mixin KernelNodes { index.getClass("dart:core", "_InterfaceTypeParameterType"); late final Class namedParameterClass = index.getClass("dart:core", "_NamedParameter"); - late final Class neverTypeClass = index.getClass("dart:core", "_NeverType"); - late final Class nullTypeClass = index.getClass("dart:core", "_NullType"); - late final Class voidTypeClass = index.getClass("dart:core", "_VoidType"); + late final Class bottomTypeClass = index.getClass("dart:core", "_BottomType"); + late final Class topTypeClass = index.getClass("dart:core", "_TopType"); late final Class stackTraceClass = index.getClass("dart:core", "StackTrace"); late final Class typeUniverseClass = index.getClass("dart:core", "_TypeUniverse"); diff --git a/pkg/dart2wasm/lib/types.dart b/pkg/dart2wasm/lib/types.dart index 8e2bd975b53..3e4b5726ac3 100644 --- a/pkg/dart2wasm/lib/types.dart +++ b/pkg/dart2wasm/lib/types.dart @@ -26,6 +26,14 @@ class TypeCategory { static const maxMasqueradeClassId = 63; // Leaves 2 unused bits for future use } +/// Values for the `_kind` field in `_TopType`. Must match the definitions in +/// `_TopType`. +class TopTypeKind { + static const int objectKind = 0; + static const int dynamicKind = 1; + static const int voidKind = 2; +} + class InterfaceTypeEnvironment { final Map _typeOffsets = {}; @@ -341,24 +349,18 @@ class Types { Class classForType(DartType type) { if (type is DynamicType) { - return translator.dynamicTypeClass; + return translator.topTypeClass; } else if (type is VoidType) { - return translator.voidTypeClass; + return translator.topTypeClass; } else if (type is NeverType) { - // For runtime types with sound null safety, `Never?` is the same as - // `Null`. - if (type.nullability == Nullability.nullable) { - return translator.nullTypeClass; - } else { - return translator.neverTypeClass; - } + return translator.bottomTypeClass; } else if (type is NullType) { - return translator.nullTypeClass; + return translator.bottomTypeClass; } else if (type is FutureOrType) { return translator.futureOrTypeClass; } else if (type is InterfaceType) { if (type.classNode == coreTypes.objectClass) { - return translator.objectTypeClass; + return translator.topTypeClass; } if (type.classNode == coreTypes.functionClass) { return translator.abstractFunctionTypeClass; @@ -389,6 +391,14 @@ class Types { cls == coreTypes.recordClass; } + int topTypeKind(DartType type) { + return type is VoidType + ? TopTypeKind.voidKind + : type is DynamicType + ? TopTypeKind.dynamicKind + : TopTypeKind.objectKind; + } + /// Allocates a `List<_Type>` from [types] and pushes it to the stack. void _makeTypeList(CodeGenerator codeGen, Iterable types) { if (types.every(_isTypeConstant)) { @@ -425,8 +435,12 @@ class Types { } /// Normalizes a Dart type. Many rules are already applied for us, but we - /// still have to manually normalize [FutureOr]. + /// still have to manually turn `Never?` into `Null` and normalize `FutureOr`. DartType normalize(DartType type) { + if (type is NeverType && type.declaredNullability == Nullability.nullable) { + return const NullType(); + } + if (type is! FutureOrType) return type; final s = normalize(type.typeArgument); diff --git a/sdk/lib/_internal/wasm/lib/class_id.dart b/sdk/lib/_internal/wasm/lib/class_id.dart index 4cec5a3b665..8be07f90057 100644 --- a/sdk/lib/_internal/wasm/lib/class_id.dart +++ b/sdk/lib/_internal/wasm/lib/class_id.dart @@ -46,20 +46,14 @@ class ClassID { external static int get cidRecord; // Class IDs for RTI Types. - @pragma("wasm:class-id", "dart.core#_NeverType") - external static int get cidNeverType; - @pragma("wasm:class-id", "dart.core#_DynamicType") - external static int get cidDynamicType; - @pragma("wasm:class-id", "dart.core#_VoidType") - external static int get cidVoidType; - @pragma("wasm:class-id", "dart.core#_NullType") - external static int get cidNullType; + @pragma("wasm:class-id", "dart.core#_BottomType") + external static int get cidBottomType; + @pragma("wasm:class-id", "dart.core#_TopType") + external static int get cidTopType; @pragma("wasm:class-id", "dart.core#_FutureOrType") external static int get cidFutureOrType; @pragma("wasm:class-id", "dart.core#_InterfaceType") external static int get cidInterfaceType; - @pragma("wasm:class-id", "dart.core#_ObjectType") - external static int get cidObjectType; @pragma("wasm:class-id", "dart.core#_AbstractFunctionType") external static int get cidAbstractFunctionType; @pragma("wasm:class-id", "dart.core#_FunctionType") @@ -72,6 +66,8 @@ class ClassID { external static int get cidAbstractRecordType; @pragma("wasm:class-id", "dart.core#_RecordType") external static int get cidRecordType; + @pragma("wasm:class-id", "dart.core#_NamedParameter") + external static int get cidNamedParameter; // Dummy, only used by VM-specific hash table code. static final int numPredefinedCids = 1; diff --git a/sdk/lib/_internal/wasm/lib/type.dart b/sdk/lib/_internal/wasm/lib/type.dart index ce79484834d..5563a130cbf 100644 --- a/sdk/lib/_internal/wasm/lib/type.dart +++ b/sdk/lib/_internal/wasm/lib/type.dart @@ -7,6 +7,13 @@ part of 'core_patch.dart'; // Representation of runtime types. Code in this file should avoid using `is` or // `as` entirely to avoid a dependency on any inline type checks. +// Helper for type literals used for all singleton types. By using actual type +// literals and letting those through the compiler, rather than calling the +// constructors of the corresponding representation classes, we ensure that they +// are properly canonicalized by the constant instantiator. +@pragma("wasm:prefer-inline") +_Type _literal() => unsafeCast(T); + // TODO(joshualitt): Once we have RTI fully working, we'd like to explore // implementing [isSubtype] using inheritance. // TODO(joshualitt): We can cache the result of [_FutureOrType.asFuture]. @@ -15,83 +22,75 @@ abstract class _Type implements Type { const _Type(this.isDeclaredNullable); + @pragma("wasm:prefer-inline") bool _testID(int value) => ClassID.getID(this) == value; - bool get isNever => _testID(ClassID.cidNeverType); - bool get isDynamic => _testID(ClassID.cidDynamicType); - bool get isVoid => _testID(ClassID.cidVoidType); - bool get isNull => _testID(ClassID.cidNullType); + + bool get isBottom => _testID(ClassID.cidBottomType); + bool get isTop => _testID(ClassID.cidTopType); bool get isFutureOr => _testID(ClassID.cidFutureOrType); bool get isInterface => _testID(ClassID.cidInterfaceType); bool get isInterfaceTypeParameterType => _testID(ClassID.cidInterfaceTypeParameterType); bool get isFunctionTypeParameterType => _testID(ClassID.cidFunctionTypeParameterType); - bool get isObject => _testID(ClassID.cidObjectType); bool get isAbstractFunction => _testID(ClassID.cidAbstractFunctionType); bool get isFunction => _testID(ClassID.cidFunctionType); bool get isAbstractRecord => _testID(ClassID.cidAbstractRecordType); bool get isRecord => _testID(ClassID.cidRecordType); + @pragma("wasm:prefer-inline") T as() => unsafeCast(this); + @pragma("wasm:prefer-inline") _Type get asNullable => isDeclaredNullable ? this : _asNullable; _Type get _asNullable; - - @override - bool operator ==(Object other) => ClassID.getID(this) == ClassID.getID(other); - - @override - int get hashCode => mix64(ClassID.getID(this)); } @pragma("wasm:entry-point") -class _NeverType extends _Type { - @pragma("wasm:entry-point") - const _NeverType() : super(false); - - /// Never? normalizes to Null. - @override - _Type get _asNullable => const _NullType(); +class _BottomType extends _Type { + // To ensure that the `Null` and `Never` types are singleton runtime type + // objects, we only allocate these objects via the constant instantiator. + external const _BottomType(); @override - String toString() => 'Never'; + _Type get _asNullable => _literal(); + + @override + String toString() => isDeclaredNullable ? 'Null' : 'Never'; } @pragma("wasm:entry-point") -class _DynamicType extends _Type { - @pragma("wasm:entry-point") - const _DynamicType() : super(true); +class _TopType extends _Type { + final int _kind; + + // Values for the `_kind` field. Must match the definitions in `TopTypeKind`. + static const int _objectKind = 0; + static const int _dynamicKind = 1; + static const int _voidKind = 2; + + // To ensure that the `Object`, `Object?`, `dynamic` and `void` types are + // singleton runtime type objects, we only allocate these objects via the + // constant instantiator. + external const _TopType(); + + // Only called if Object + @override + _Type get _asNullable => _literal(); @override - _Type get _asNullable => this; - - @override - String toString() => 'dynamic'; -} - -@pragma("wasm:entry-point") -class _VoidType extends _Type { - @pragma("wasm:entry-point") - const _VoidType() : super(true); - - @override - _Type get _asNullable => this; - - @override - String toString() => 'void'; -} - -@pragma("wasm:entry-point") -class _NullType extends _Type { - @pragma("wasm:entry-point") - const _NullType() : super(true); - - @override - _Type get _asNullable => this; - - @override - String toString() => 'Null'; + String toString() { + switch (_kind) { + case _objectKind: + return isDeclaredNullable ? 'Object?' : 'Object'; + case _dynamicKind: + return 'dynamic'; + case _voidKind: + return 'void'; + default: + throw 'Invalid top type kind'; + } + } } /// Reference to a type parameter of an interface type. @@ -129,7 +128,7 @@ class _FunctionTypeParameterType extends _Type { @override bool operator ==(Object o) { - if (!(super == o)) return false; + if (ClassID.getID(o) != ClassID.cidFunctionTypeParameterType) return false; _FunctionTypeParameterType other = unsafeCast<_FunctionTypeParameterType>(o); // References to different type parameters can have the same index and thus @@ -141,7 +140,7 @@ class _FunctionTypeParameterType extends _Type { @override int get hashCode { - int hash = super.hashCode; + int hash = mix64(ClassID.cidFunctionTypeParameterType); hash = mix64(hash ^ (isDeclaredNullable ? 1 : 0)); return mix64(hash ^ index.hashCode); } @@ -166,7 +165,7 @@ class _FutureOrType extends _Type { @override bool operator ==(Object o) { - if (!(super == o)) return false; + if (ClassID.getID(o) != ClassID.cidFutureOrType) return false; _FutureOrType other = unsafeCast<_FutureOrType>(o); if (isDeclaredNullable != other.isDeclaredNullable) return false; return typeArgument == other.typeArgument; @@ -174,7 +173,7 @@ class _FutureOrType extends _Type { @override int get hashCode { - int hash = super.hashCode; + int hash = mix64(ClassID.cidFutureOrType); hash = mix64(hash ^ (isDeclaredNullable ? 1 : 0)); return mix64(hash ^ typeArgument.hashCode); } @@ -191,6 +190,7 @@ class _FutureOrType extends _Type { } } +@pragma("wasm:entry-point") class _InterfaceType extends _Type { final int classId; final List<_Type> typeArguments; @@ -204,7 +204,7 @@ class _InterfaceType extends _Type { @override bool operator ==(Object o) { - if (!(super == o)) return false; + if (ClassID.getID(o) != ClassID.cidInterfaceType) return false; _InterfaceType other = unsafeCast<_InterfaceType>(o); if (classId != other.classId) return false; if (isDeclaredNullable != other.isDeclaredNullable) return false; @@ -217,7 +217,7 @@ class _InterfaceType extends _Type { @override int get hashCode { - int hash = super.hashCode; + int hash = mix64(ClassID.cidInterfaceType); hash = mix64(hash ^ classId); hash = mix64(hash ^ (isDeclaredNullable ? 1 : 0)); for (int i = 0; i < typeArguments.length; i++) { @@ -253,7 +253,7 @@ class _NamedParameter { @override bool operator ==(Object o) { - if (ClassID.getID(this) != ClassID.getID(o)) return false; + if (ClassID.getID(o) != ClassID.cidNamedParameter) return false; _NamedParameter other = unsafeCast<_NamedParameter>(o); return this.name == other.name && this.type == other.type && @@ -262,7 +262,7 @@ class _NamedParameter { @override int get hashCode { - int hash = mix64(ClassID.getID(this)); + int hash = mix64(ClassID.cidNamedParameter); hash = mix64(hash ^ name.hashCode); hash = mix64(hash ^ type.hashCode); return mix64(hash ^ (isRequired ? 1 : 0)); @@ -279,51 +279,22 @@ class _NamedParameter { } } -class _ObjectType extends _Type { - @pragma("wasm:entry-point") - const _ObjectType(super.isDeclaredNullable); - - @override - _Type get _asNullable => const _ObjectType(true); - - @override - bool operator ==(Object o) { - if (!(super == o)) return false; - _ObjectType other = unsafeCast<_ObjectType>(o); - return isDeclaredNullable == other.isDeclaredNullable; - } - - @override - int get hashCode { - int hash = super.hashCode; - return mix64(hash ^ (isDeclaredNullable ? 1 : 0)); - } - - @override - String toString() { - StringBuffer s = StringBuffer(); - s.write("Object"); - if (isDeclaredNullable) s.write("?"); - return s.toString(); - } -} - +@pragma("wasm:entry-point") class _AbstractFunctionType extends _Type { - @pragma("wasm:entry-point") - const _AbstractFunctionType(super.isDeclaredNullable); + // To ensure that the `Function` and `Function?` types are singleton runtime + // type objects, we only allocate these objects via the constant instantiator. + external const _AbstractFunctionType(); @override - _Type get _asNullable => const _AbstractFunctionType(true); + _Type get _asNullable => _literal(); @override String toString() { - StringBuffer s = StringBuffer(); - s.write("Function"); - if (isDeclaredNullable) s.write("?"); - return s.toString(); + return isDeclaredNullable ? 'Function?' : 'Function'; } } +@pragma("wasm:entry-point") class _FunctionType extends _Type { // TODO(askesc): The [typeParameterOffset] is 0 except in the rare case where // the function type contains a nested generic function type that contains a @@ -361,7 +332,7 @@ class _FunctionType extends _Type { true); bool operator ==(Object o) { - if (!(super == o)) return false; + if (ClassID.getID(o) != ClassID.cidFunctionType) return false; _FunctionType other = unsafeCast<_FunctionType>(o); if (isDeclaredNullable != other.isDeclaredNullable) return false; if (typeParameterBounds.length != other.typeParameterBounds.length) { @@ -389,7 +360,7 @@ class _FunctionType extends _Type { @override int get hashCode { - int hash = super.hashCode; + int hash = mix64(ClassID.cidFunctionType); for (int i = 0; i < typeParameterBounds.length; i++) { hash = mix64(hash ^ typeParameterBounds[i].hashCode); } @@ -414,9 +385,7 @@ class _FunctionType extends _Type { if (i > 0) s.write(", "); s.write("X${typeParameterOffset + i}"); final bound = typeParameterBounds[i]; - if (!(bound.isObject && bound.isDeclaredNullable || - bound.isDynamic || - bound.isVoid)) { + if (!(bound.isTop && bound.isDeclaredNullable)) { s.write(" extends "); s.write(bound); } @@ -446,22 +415,22 @@ class _FunctionType extends _Type { } } +@pragma("wasm:entry-point") class _AbstractRecordType extends _Type { - @pragma("wasm:entry-point") - const _AbstractRecordType(super.isDeclaredNullable); + // To ensure that the `Record` and `Record?` types are singleton runtime type + // objects, we only allocate these objects via the constant instantiator. + external const factory _AbstractRecordType(); @override - _Type get _asNullable => const _AbstractRecordType(true); + _Type get _asNullable => _literal(); @override String toString() { - StringBuffer s = StringBuffer(); - s.write("Record"); - if (isDeclaredNullable) s.write("?"); - return s.toString(); + return isDeclaredNullable ? 'Record?' : 'Record'; } } +@pragma("wasm:entry-point") class _RecordType extends _Type { final List names; final List<_Type> fieldTypes; @@ -509,10 +478,9 @@ class _RecordType extends _Type { } @override - bool operator ==(Object other) { - if (other is! _RecordType) { - return false; - } + bool operator ==(Object o) { + if (ClassID.getID(o) != ClassID.cidRecordType) return false; + _RecordType other = unsafeCast<_RecordType>(o); if (!_sameShape(other)) { return false; @@ -640,11 +608,7 @@ class _TypeUniverse { /// substituted. static _Type substituteTypeArgument( _Type type, List<_Type> substitutions, _FunctionType? rootFunction) { - if (type.isNever || - type.isDynamic || - type.isVoid || - type.isNull || - type.isObject) { + if (type.isBottom || type.isTop) { return type; } else if (type.isFutureOr) { return createNormalizedFutureOrType( @@ -726,15 +690,13 @@ class _TypeUniverse { static _Type createNormalizedFutureOrType( bool isDeclaredNullable, _Type typeArgument) { - if (typeArgument.isObject || - typeArgument.isDynamic || - typeArgument.isVoid) { + if (typeArgument.isTop) { return isDeclaredNullable ? typeArgument.asNullable : typeArgument; - } else if (typeArgument.isNever) { + } else if (typeArgument.isBottom) { return _InterfaceType( - ClassID.cidFuture, isDeclaredNullable, const [_NeverType()]); - } else if (typeArgument.isNull) { - return _InterfaceType(ClassID.cidFuture, true, const [_NullType()]); + ClassID.cidFuture, + isDeclaredNullable || typeArgument.isDeclaredNullable, + [typeArgument]); } bool declaredNullability = @@ -783,7 +745,7 @@ class _TypeUniverse { // arguments. if (substitutions.isNotEmpty && sTypeArguments.isEmpty) { sTypeArguments = List<_Type>.generate( - substitutions.length, (int index) => const _DynamicType(), + substitutions.length, (int index) => _literal(), growable: false); } @@ -923,13 +885,13 @@ class _TypeUniverse { } // Left bottom: - if (s.isNull || s.isNever) return true; + if (s.isBottom) return true; // Right top: - if (t.isObject || t.isDynamic || t.isVoid) return true; + if (t.isTop) return true; // Right bottom: - if (t.isNull || t.isNever) return false; + if (t.isBottom) return false; // Left Type Variable Bound 1: if (s.isFunctionTypeParameterType) { @@ -1175,11 +1137,11 @@ external _Type _getActualRuntimeType(Object object); @pragma("wasm:prefer-inline") _Type _getActualRuntimeTypeNullable(Object? object) => - object == null ? const _NullType() : _getActualRuntimeType(object); + object == null ? _literal() : _getActualRuntimeType(object); @pragma("wasm:entry-point") external _Type _getMasqueradedRuntimeType(Object object); @pragma("wasm:prefer-inline") _Type _getMasqueradedRuntimeTypeNullable(Object? object) => - object == null ? const _NullType() : _getMasqueradedRuntimeType(object); + object == null ? _literal() : _getMasqueradedRuntimeType(object);