From 0c1bb28ad4d43f71eff73ee0ad8b546af983f20a Mon Sep 17 00:00:00 2001 From: Aske Simon Christensen Date: Fri, 11 Aug 2023 09:08:32 +0000 Subject: [PATCH] [dart2wasm] Special runtime type classes for special interface types Adds special representations of runtime types for the interface types `Object`, `Function` and `Record`. With this change, all types with non-interface subtypes have special representations, which avoids some special cases down the line. Change-Id: I61b4da20fa1cc62d42e1770278a3272028c9e2a0 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/318681 Reviewed-by: Joshua Litt --- pkg/dart2wasm/lib/class_info.dart | 10 +- pkg/dart2wasm/lib/constants.dart | 5 +- pkg/dart2wasm/lib/intrinsics.dart | 12 +++ pkg/dart2wasm/lib/kernel_nodes.dart | 5 + pkg/dart2wasm/lib/types.dart | 26 +++++- sdk/lib/_internal/wasm/lib/class_id.dart | 6 ++ sdk/lib/_internal/wasm/lib/type.dart | 113 ++++++++++++++++++----- 7 files changed, 142 insertions(+), 35 deletions(-) diff --git a/pkg/dart2wasm/lib/class_info.dart b/pkg/dart2wasm/lib/class_info.dart index 1134ddb098b..d826eb98101 100644 --- a/pkg/dart2wasm/lib/class_info.dart +++ b/pkg/dart2wasm/lib/class_info.dart @@ -506,6 +506,11 @@ class ClassInfoCollector { void collect() { _initializeTop(); + // Initialize the record base class early to give enough space for special + // values in the type category table before the first masquerade class + // (which is `Type`). + _initialize(translator.coreTypes.recordClass); + // 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. @@ -526,11 +531,6 @@ class ClassInfoCollector { _initialize(cls); } - // Initialize the record base class if we have record classes. - if (translator.recordClasses.isNotEmpty) { - _initialize(translator.coreTypes.recordClass); - } - for (Library library in translator.component.libraries) { for (Class cls in library.classes) { if (cls.superclass == translator.coreTypes.recordClass) { diff --git a/pkg/dart2wasm/lib/constants.dart b/pkg/dart2wasm/lib/constants.dart index 786643cca2c..07cb4e0c349 100644 --- a/pkg/dart2wasm/lib/constants.dart +++ b/pkg/dart2wasm/lib/constants.dart @@ -801,7 +801,7 @@ class ConstantCreator extends ConstantVisitor { ClassInfo info = translator.classInfo[types.classForType(type)]!; translator.functions.allocateClass(info.classId); - if (type is InterfaceType) { + if (type is InterfaceType && !types.isSpecializedClass(type.classNode)) { return _makeInterfaceType(constant, type, info); } else if (type is FutureOrType) { return _makeFutureOrType(constant, type, info); @@ -861,7 +861,8 @@ class ConstantCreator extends ConstantVisitor { assert(type is VoidType || type is NeverType || type is NullType || - type is DynamicType); + type is DynamicType || + type is InterfaceType && types.isSpecializedClass(type.classNode)); return createConstant(constant, info.nonNullableType, (function, b) { b.i32_const(info.classId); b.i32_const(initialIdentityHash); diff --git a/pkg/dart2wasm/lib/intrinsics.dart b/pkg/dart2wasm/lib/intrinsics.dart index 3ce3411bb96..21975a96930 100644 --- a/pkg/dart2wasm/lib/intrinsics.dart +++ b/pkg/dart2wasm/lib/intrinsics.dart @@ -1149,6 +1149,7 @@ class Intrinsifier { w.Label notMasqueraded = b.block(); w.Label recordType = b.block(); w.Label functionType = b.block(); + w.Label objectType = b.block(); w.Label abstractClass = b.block(); // Look up the type category by class ID and switch on it. @@ -1161,6 +1162,7 @@ class Intrinsifier { b.local_tee(resultClassId); b.br_table([ abstractClass, + objectType, functionType, recordType, if (masqueraded) notMasqueraded @@ -1170,6 +1172,16 @@ class Intrinsifier { // We should never see class IDs for abstract types. 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); + b.return_(); + b.end(); // functionType w.StructType closureBase = translator.closureLayouter.closureBaseStruct; b.local_get(object); diff --git a/pkg/dart2wasm/lib/kernel_nodes.dart b/pkg/dart2wasm/lib/kernel_nodes.dart index 9cf45f114cc..4711968e574 100644 --- a/pkg/dart2wasm/lib/kernel_nodes.dart +++ b/pkg/dart2wasm/lib/kernel_nodes.dart @@ -68,6 +68,9 @@ mixin KernelNodes { 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 = index.getClass("dart:core", "_FunctionType"); late final Class functionTypeParameterTypeClass = @@ -86,6 +89,8 @@ mixin KernelNodes { late final Class stackTraceClass = index.getClass("dart:core", "StackTrace"); late final Class typeUniverseClass = index.getClass("dart:core", "_TypeUniverse"); + late final Class abstractRecordTypeClass = + index.getClass("dart:core", "_AbstractRecordType"); late final Class recordTypeClass = index.getClass("dart:core", "_RecordType"); // dart:core sync* support classes diff --git a/pkg/dart2wasm/lib/types.dart b/pkg/dart2wasm/lib/types.dart index 0fe0dff7afd..50d80632244 100644 --- a/pkg/dart2wasm/lib/types.dart +++ b/pkg/dart2wasm/lib/types.dart @@ -18,10 +18,11 @@ import 'package:wasm_builder/wasm_builder.dart' as w; /// the class ID of the masquerade. class TypeCategory { static const abstractClass = 0; - static const function = 1; - static const record = 2; - static const notMasqueraded = 3; - static const minMasqueradeClassId = 4; + static const object = 1; + static const function = 2; + static const record = 3; + static const notMasqueraded = 4; + static const minMasqueradeClassId = 5; static const maxMasqueradeClassId = 63; // Leaves 2 unused bits for future use } @@ -279,6 +280,8 @@ class Types { int category; if (cls == null || cls.isAbstract) { category = TypeCategory.abstractClass; + } else if (cls == coreTypes.objectClass) { + category = TypeCategory.object; } else if (cls == translator.closureClass) { category = TypeCategory.function; } else if (recordClasses.contains(cls)) { @@ -355,6 +358,15 @@ class Types { } else if (type is FutureOrType) { return translator.futureOrTypeClass; } else if (type is InterfaceType) { + if (type.classNode == coreTypes.objectClass) { + return translator.objectTypeClass; + } + if (type.classNode == coreTypes.functionClass) { + return translator.abstractFunctionTypeClass; + } + if (type.classNode == coreTypes.recordClass) { + return translator.abstractRecordTypeClass; + } return translator.interfaceTypeClass; } else if (type is FunctionType) { return translator.functionTypeClass; @@ -372,6 +384,12 @@ class Types { throw "Unexpected DartType: $type"; } + bool isSpecializedClass(Class cls) { + return cls == coreTypes.objectClass || + cls == coreTypes.functionClass || + cls == coreTypes.recordClass; + } + /// Allocates a `List<_Type>` from [types] and pushes it to the stack. void _makeTypeList(CodeGenerator codeGen, Iterable types) { if (types.every(_isTypeConstant)) { diff --git a/sdk/lib/_internal/wasm/lib/class_id.dart b/sdk/lib/_internal/wasm/lib/class_id.dart index 6b247f522f3..4cec5a3b665 100644 --- a/sdk/lib/_internal/wasm/lib/class_id.dart +++ b/sdk/lib/_internal/wasm/lib/class_id.dart @@ -58,12 +58,18 @@ class ClassID { 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") external static int get cidFunctionType; @pragma("wasm:class-id", "dart.core#_FunctionTypeParameterType") external static int get cidFunctionTypeParameterType; @pragma("wasm:class-id", "dart.core#_InterfaceTypeParameterType") external static int get cidInterfaceTypeParameterType; + @pragma("wasm:class-id", "dart.core#_AbstractRecordType") + external static int get cidAbstractRecordType; @pragma("wasm:class-id", "dart.core#_RecordType") external static int get cidRecordType; diff --git a/sdk/lib/_internal/wasm/lib/type.dart b/sdk/lib/_internal/wasm/lib/type.dart index 75aeae564ec..88c6b3e0a1e 100644 --- a/sdk/lib/_internal/wasm/lib/type.dart +++ b/sdk/lib/_internal/wasm/lib/type.dart @@ -13,7 +13,6 @@ part of 'core_patch.dart'; // file: // * [_Type.asNonNullable] // * [_FutureOrType.asFuture]. -// TODO(joshualitt): Make `Function` a canonical type. abstract class _Type implements Type { final bool isDeclaredNullable; @@ -30,7 +29,10 @@ abstract class _Type implements Type { _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); T as() => unsafeCast(this); @@ -308,6 +310,57 @@ class _NamedParameter { } } +class _ObjectType extends _Type { + @pragma("wasm:entry-point") + const _ObjectType(super.isDeclaredNullable); + + @override + _Type get _asNonNullable => _ObjectType(false); + + @override + _Type get _asNullable => _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(); + } +} + +class _AbstractFunctionType extends _Type { + @pragma("wasm:entry-point") + const _AbstractFunctionType(super.isDeclaredNullable); + + @override + _Type get _asNonNullable => _AbstractFunctionType(false); + + @override + _Type get _asNullable => _AbstractFunctionType(true); + + @override + String toString() { + StringBuffer s = StringBuffer(); + s.write("Function"); + if (isDeclaredNullable) s.write("?"); + return s.toString(); + } +} + 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 @@ -438,6 +491,25 @@ class _FunctionType extends _Type { } } +class _AbstractRecordType extends _Type { + @pragma("wasm:entry-point") + const _AbstractRecordType(super.isDeclaredNullable); + + @override + _Type get _asNonNullable => _AbstractRecordType(false); + + @override + _Type get _asNullable => _AbstractRecordType(true); + + @override + String toString() { + StringBuffer s = StringBuffer(); + s.write("Record"); + if (isDeclaredNullable) s.write("?"); + return s.toString(); + } +} + class _RecordType extends _Type { final List names; final List<_Type> fieldTypes; @@ -575,30 +647,20 @@ class _TypeUniverse { return _TypeUniverse._(_getTypeRulesSupers(), _getTypeRulesSubstitutions()); } - static bool isSpecificInterfaceType(_Type t, int classId) { - if (!t.isInterface) return false; - _InterfaceType type = t.as<_InterfaceType>(); - return type.classId == classId; + static bool isTopType(_Type type) { + return type.isObject && type.isDeclaredNullable || + type.isDynamic || + type.isVoid; } - static bool isObjectQuestionType(_Type t) => - isObjectType(t) && t.isDeclaredNullable; - - static bool isObjectType(_Type t) => - isSpecificInterfaceType(t, ClassID.cidObject); - - static bool isTopType(_Type type) { - return isObjectQuestionType(type) || type.isDynamic || type.isVoid; + static bool isObjectOrTopType(_Type type) { + return type.isObject || type.isDynamic || type.isVoid; } static bool isBottomType(_Type type) { return type.isNever; } - static bool isFunctionType(_Type t) => - isSpecificInterfaceType(t, ClassID.cidFunction) || - isSpecificInterfaceType(t, ClassID.cid_Closure); - static _Type substituteInterfaceTypeParameter( _InterfaceTypeParameterType typeParameter, List<_Type> substitutions) { // If the type parameter is non-nullable, or the substitution type is @@ -643,7 +705,11 @@ class _TypeUniverse { /// substituted. static _Type substituteTypeArgument( _Type type, List<_Type> substitutions, _FunctionType? rootFunction) { - if (type.isNever || type.isDynamic || type.isVoid || type.isNull) { + if (type.isNever || + type.isDynamic || + type.isVoid || + type.isNull || + type.isObject) { return type; } else if (type.isFutureOr) { return createNormalizedFutureOrType( @@ -725,7 +791,7 @@ class _TypeUniverse { static _Type createNormalizedFutureOrType( bool isDeclaredNullable, _Type typeArgument) { - if (isTopType(typeArgument) || isObjectType(typeArgument)) { + if (isObjectOrTopType(typeArgument)) { return typeArgument; } else if (typeArgument.isNever) { return _InterfaceType( @@ -940,7 +1006,7 @@ class _TypeUniverse { } // Right Object: - if (isObjectType(t)) { + if (t.isObject) { if (s.isFutureOr) { return isSubtype(s.as<_FutureOrType>().typeArgument, sEnv, t, tEnv); } @@ -984,7 +1050,7 @@ class _TypeUniverse { // Left Promoted Variable does not apply at runtime. // Function Type / Function: - if (s.isFunction && isFunctionType(t)) { + if (s.isFunction && t.isAbstractFunction) { return true; } @@ -1000,9 +1066,8 @@ class _TypeUniverse { } // Records are subtypes of the `Record` type: - if (s.isRecord && t.isInterface) { - final tInterfaceType = t.as<_InterfaceType>(); - return tInterfaceType.classId == ClassID.cidRecord; + if (s.isRecord && t.isAbstractRecord) { + return true; } // Interface Compositionality + Super-Interface: