[dart2wasm] Make cid numbering of concrete classes perfect DFS pre-order
The CL removes a hack that assigned fixed class ids due to the need of
special treatment for masquerading types.
=> Instead of hard coding the type category values, we discover feasible
slots automatically, so we can remove the hack
=> Makes our cid numbering of concrete classes pure DFS pre-order
=> Ensures all concrete subclasses of a class have continious class ids
We also replace an assembly stub that deals with masquerading with one
written in Dart, adding the necessary primitives (e.g.
getting category type table & kinds).
We also align how to get the function type of a closure, namely
add `_Closure._getClosureRuntimeType()` analogous to the method on
`_Record._getRecordRuntimeType()`
Change-Id: I5169fc4953e8e99c4f84a1bbe8e535c08fb47cc5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/348840
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
diff --git a/pkg/dart2wasm/lib/class_info.dart b/pkg/dart2wasm/lib/class_info.dart
index e0bbcd9..e0dd027 100644
--- a/pkg/dart2wasm/lib/class_info.dart
+++ b/pkg/dart2wasm/lib/class_info.dart
@@ -221,6 +221,9 @@
/// subtypes of the key masquerade as the value.
late final Map<Class, Class> _masquerades = _computeMasquerades();
+ /// Masqueraded types are mapped to these classes.
+ late final Set<Class> masqueradeValues = _masquerades.values.toSet();
+
Map<Class, Class> _computeMasquerades() {
final map = {
translator.coreTypes.boolClass: translator.coreTypes.boolClass,
@@ -581,6 +584,7 @@
final importUri = klass.enclosingLibrary.importUri.toString();
if (importUri.startsWith('dart:')) {
+ if (masqueradeValues.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.
@@ -617,24 +621,35 @@
final classIds = <Class, int>{};
- // TODO: Remove this special case that violates strict DFS pre-order
- // numbering by re-doing the masquerades.
+ // TODO: We may consider removing the type category table. But until we do
+ // we have to make sure that all masqueraded types that concrete classes may
+ // be mapped to have class ids that are <=255.
//
- // 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++;
+ // We still want to maintain that all classes's concrete subclasses form a
+ // single continious class-id range.
+ //
+ // => So we move the abstract masqeraded classes before all the concrete
+ // ones.
+ for (Class cls in masqueradeValues) {
+ if (cls.isAbstract) {
+ assert(classIds[cls] == null);
+ classIds[cls] = nextClassId++;
+ }
}
for (final cls in dfsOrder) {
- if (!cls.isAbstract) classIds[cls] = classIds[cls] ?? nextClassId++;
+ if (!cls.isAbstract) {
+ assert(classIds[cls] == null);
+ classIds[cls] = nextClassId++;
+ }
}
for (final cls in dfsOrder) {
if (cls.isAbstract) classIds[cls] = classIds[cls] ?? nextClassId++;
}
+ // Abstract masquerade
+ for (Class cls in masqueradeValues) {
+ assert(classIds[cls]! <= 255);
+ }
return (dfsOrder, classIds);
}
diff --git a/pkg/dart2wasm/lib/intrinsics.dart b/pkg/dart2wasm/lib/intrinsics.dart
index 9def55d..0e7006d 100644
--- a/pkg/dart2wasm/lib/intrinsics.dart
+++ b/pkg/dart2wasm/lib/intrinsics.dart
@@ -6,7 +6,6 @@
import 'package:dart2wasm/code_generator.dart';
import 'package:dart2wasm/dynamic_forwarders.dart';
import 'package:dart2wasm/translator.dart';
-import 'package:dart2wasm/types.dart';
import 'package:kernel/ast.dart';
@@ -433,13 +432,43 @@
return w.NumType.i32;
}
- if (target.enclosingLibrary.name == "dart.core" &&
- target.name.text == "_isIntrinsified") {
- // This is part of the VM's [BigInt] implementation. We just return false.
- // TODO(joshualitt): Can we find another way to reuse this patch file
- // without hardcoding this case?
- b.i32_const(0);
- return w.NumType.i32;
+ if (target.enclosingLibrary.name == "dart.core") {
+ if (target.name.text == "_isIntrinsified") {
+ // This is part of the VM's [BigInt] implementation. We just return false.
+ // TODO(joshualitt): Can we find another way to reuse this patch file
+ // without hardcoding this case?
+ b.i32_const(0);
+ return w.NumType.i32;
+ }
+ if (node.target.enclosingLibrary == translator.coreTypes.coreLibrary) {
+ switch (node.target.name.text) {
+ case "_typeCategoryAbstractClass":
+ translator.types.typeCategoryTable;
+ b.i32_const(translator.types.typeCategoryAbstractClass);
+ return w.NumType.i32;
+ case "_typeCategoryObject":
+ translator.types.typeCategoryTable;
+ b.i32_const(translator.types.typeCategoryObject);
+ return w.NumType.i32;
+ case "_typeCategoryFunction":
+ translator.types.typeCategoryTable;
+ b.i32_const(translator.types.typeCategoryFunction);
+ return w.NumType.i32;
+ case "_typeCategoryRecord":
+ translator.types.typeCategoryTable;
+ b.i32_const(translator.types.typeCategoryRecord);
+ return w.NumType.i32;
+ case "_typeCategoryNotMasqueraded":
+ translator.types.typeCategoryTable;
+ b.i32_const(translator.types.typeCategoryNotMasqueraded);
+ return w.NumType.i32;
+
+ case "_typeCategoryTable":
+ translator.types.typeCategoryTable;
+ b.global_get(translator.types.typeCategoryTable);
+ return translator.types.typeCategoryTable.type.type;
+ }
+ }
}
return null;
@@ -630,16 +659,6 @@
return translator.types.makeTypeRulesSubstitutions(b);
case "_getTypeNames":
return translator.types.makeTypeNames(b);
- case "_getFunctionRuntimeType":
- Expression f = node.arguments.positional.single;
- final w.StructType closureBaseStruct =
- translator.closureLayouter.closureBaseStruct;
- final w.RefType closureBaseStructRef =
- w.RefType.def(closureBaseStruct, nullable: false);
- codeGen.wrap(f, closureBaseStructRef);
- b.struct_get(closureBaseStruct, FieldIndex.closureRuntimeType);
- return closureBaseStruct
- .fields[FieldIndex.closureRuntimeType].type.unpacked;
case "_isRecordInstance":
Expression o = node.arguments.positional.single;
b.global_get(translator.types.typeCategoryTable);
@@ -648,7 +667,7 @@
b.array_get_u(
(translator.types.typeCategoryTable.type.type as w.RefType)
.heapType as w.ArrayType);
- b.i32_const(TypeCategory.record);
+ b.i32_const(translator.types.typeCategoryRecord);
b.i32_eq();
return w.NumType.i32;
}
@@ -1143,89 +1162,6 @@
return true;
}
- // _getActualRuntimeType and _getMasqueradedRuntimeType
- if (member.enclosingLibrary == translator.coreTypes.coreLibrary &&
- (name == "_getActualRuntimeType" ||
- name == "_getMasqueradedRuntimeType")) {
- final bool masqueraded = name == "_getMasqueradedRuntimeType";
-
- final w.Local object = paramLocals[0];
- final w.Local classId = function.addLocal(w.NumType.i32);
- final w.Local resultClassId = function.addLocal(w.NumType.i32);
-
- w.Label interfaceType = b.block();
- 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.
- b.global_get(translator.types.typeCategoryTable);
- b.local_get(object);
- b.struct_get(translator.topInfo.struct, FieldIndex.classId);
- b.local_tee(classId);
- b.array_get_u((translator.types.typeCategoryTable.type.type as w.RefType)
- .heapType as w.ArrayType);
- b.local_tee(resultClassId);
- b.br_table([
- abstractClass,
- objectType,
- functionType,
- recordType,
- if (masqueraded) notMasqueraded
- ], masqueraded ? interfaceType : notMasqueraded);
-
- b.end(); // abstractClass
- // We should never see class IDs for abstract types.
- b.unreachable();
-
- b.end(); // objectType
- translator.constants.instantiateConstant(
- function,
- b,
- TypeLiteralConstant(InterfaceType(translator.coreTypes.objectClass,
- Nullability.nonNullable, const [])),
- function.type.outputs.single);
- b.return_();
-
- b.end(); // functionType
- w.StructType closureBase = translator.closureLayouter.closureBaseStruct;
- b.local_get(object);
- b.ref_cast(w.RefType.def(closureBase, nullable: false));
- b.struct_get(closureBase, FieldIndex.closureRuntimeType);
- b.return_();
-
- b.end(); // recordType
- b.local_get(object);
- translator.convertType(
- function,
- object.type,
- translator.classInfo[translator.coreTypes.recordClass]!.repr
- .nonNullableType);
- codeGen.call(translator.recordGetRecordRuntimeType.reference);
- b.return_();
-
- b.end(); // notMasqueraded
- b.local_get(classId);
- b.local_set(resultClassId);
-
- b.end(); // interfaceType
- ClassInfo info = translator.classInfo[translator.interfaceTypeClass]!;
- b.i32_const(info.classId);
- b.i32_const(initialIdentityHash);
- // Runtime types are never nullable.
- b.i32_const(0);
- // Set class ID of interface type.
- b.local_get(resultClassId);
- b.i64_extend_i32_u();
- // Call _typeArguments to get the array of type arguments.
- b.local_get(object);
- codeGen.call(translator.objectGetTypeArguments.reference);
- b.struct_new(info.struct);
- b.return_();
- }
-
// identical
if (member == translator.coreTypes.identicalProcedure) {
w.Local first = paramLocals[0];
@@ -1311,6 +1247,16 @@
return true;
}
+ // _Closure._getClosureRuntimeType
+ if (member == translator.getClosureRuntimeType) {
+ final w.Local object = paramLocals[0];
+ w.StructType closureBase = translator.closureLayouter.closureBaseStruct;
+ b.local_get(object);
+ b.ref_cast(w.RefType.def(closureBase, nullable: false));
+ b.struct_get(closureBase, FieldIndex.closureRuntimeType);
+ return true;
+ }
+
if (member.enclosingLibrary == translator.coreTypes.coreLibrary &&
name == "identityHashCode") {
final w.Local arg = paramLocals[0];
diff --git a/pkg/dart2wasm/lib/kernel_nodes.dart b/pkg/dart2wasm/lib/kernel_nodes.dart
index 6999b0b..062ea04 100644
--- a/pkg/dart2wasm/lib/kernel_nodes.dart
+++ b/pkg/dart2wasm/lib/kernel_nodes.dart
@@ -255,6 +255,8 @@
index.getProcedure("dart:core", "Error", "_throw");
// dart:core type procedures
+ late final Procedure getClosureRuntimeType =
+ index.getProcedure("dart:core", '_Closure', "_getClosureRuntimeType");
late final Procedure getActualRuntimeType =
index.getTopLevelProcedure("dart:core", "_getActualRuntimeType");
late final Procedure getMasqueradedRuntimeType =
diff --git a/pkg/dart2wasm/lib/types.dart b/pkg/dart2wasm/lib/types.dart
index 48fed60..8849413 100644
--- a/pkg/dart2wasm/lib/types.dart
+++ b/pkg/dart2wasm/lib/types.dart
@@ -14,18 +14,6 @@
import 'package:wasm_builder/wasm_builder.dart' as w;
-/// Values for the type category table. Entries for masqueraded classes contain
-/// the class ID of the masquerade.
-class TypeCategory {
- static const abstractClass = 0;
- 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
-}
-
/// Values for the `_kind` field in `_TopType`. Must match the definitions in
/// `_TopType`.
class TopTypeKind {
@@ -303,8 +291,31 @@
return typeNamesType;
}
+ // None of these integers belong to masqueraded types like Uint8List.
+ late final int typeCategoryAbstractClass;
+ late final int typeCategoryObject;
+ late final int typeCategoryFunction;
+ late final int typeCategoryRecord;
+ late final int typeCategoryNotMasqueraded;
+
/// Build a global array of byte values used to categorize runtime types.
w.Global _buildTypeCategoryTable() {
+ // Find 5 class ids whose classes are not masqueraded.
+ final typeCategories = <int>[];
+ for (int i = 0; i < translator.classes.length; i++) {
+ final info = translator.classes[i];
+ if (!translator.classInfoCollector.masqueradeValues.contains(info.cls)) {
+ typeCategories.add(i);
+ if (typeCategories.length == 5) break;
+ }
+ }
+ assert(typeCategories.length == 5 && typeCategories.last <= 255);
+ typeCategoryAbstractClass = typeCategories[0];
+ typeCategoryObject = typeCategories[1];
+ typeCategoryFunction = typeCategories[2];
+ typeCategoryRecord = typeCategories[3];
+ typeCategoryNotMasqueraded = typeCategories[4];
+
Set<Class> recordClasses = Set.from(translator.recordClasses.values);
Uint8List table = Uint8List(translator.classes.length);
for (int i = 0; i < translator.classes.length; i++) {
@@ -313,28 +324,28 @@
Class? cls = info.cls;
int category;
if (cls == null || cls.isAbstract) {
- category = TypeCategory.abstractClass;
+ category = typeCategoryAbstractClass;
} else if (cls == coreTypes.objectClass) {
- category = TypeCategory.object;
+ category = typeCategoryObject;
} else if (cls == translator.closureClass) {
- category = TypeCategory.function;
+ category = typeCategoryFunction;
} else if (recordClasses.contains(cls)) {
- category = TypeCategory.record;
+ category = typeCategoryRecord;
} else if (masquerade == null || masquerade.classId == i) {
- category = TypeCategory.notMasqueraded;
+ category = typeCategoryNotMasqueraded;
} else {
// Masqueraded class
assert(cls.enclosingLibrary.importUri.scheme == "dart");
- assert(masquerade.classId >= TypeCategory.minMasqueradeClassId);
- assert(masquerade.classId <= TypeCategory.maxMasqueradeClassId);
+ assert(!typeCategories.contains(masquerade.classId));
+ assert(masquerade.classId <= 255);
category = masquerade.classId;
}
table[i] = category;
}
final segment = translator.m.dataSegments.define(table);
- w.ArrayType arrayType =
- translator.wasmArrayType(w.PackedType.i8, "const i8", mutable: false);
+ w.ArrayType arrayType = translator.arrayTypeForDartType(
+ InterfaceType(translator.wasmI8Class, Nullability.nonNullable));
final global = translator.m.globals
.define(w.GlobalType(w.RefType.def(arrayType, nullable: false)));
// Initialize the global to a dummy array, since `array.new_data` is not
diff --git a/sdk/lib/_internal/wasm/lib/closure.dart b/sdk/lib/_internal/wasm/lib/closure.dart
index 3c34c69..26e052f 100644
--- a/sdk/lib/_internal/wasm/lib/closure.dart
+++ b/sdk/lib/_internal/wasm/lib/closure.dart
@@ -22,6 +22,10 @@
external static bool _equals(Function a, Function b);
+ @pragma("wasm:entry-point")
+ @pragma("wasm:prefer-inline")
+ external static _FunctionType _getClosureRuntimeType(_Closure closure);
+
// Simple hash code for now, we can optimize later
@override
int get hashCode => runtimeType.hashCode;
diff --git a/sdk/lib/_internal/wasm/lib/record_patch.dart b/sdk/lib/_internal/wasm/lib/record_patch.dart
index a5e245f..99abaa6 100644
--- a/sdk/lib/_internal/wasm/lib/record_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/record_patch.dart
@@ -9,12 +9,12 @@
@pragma('wasm:entry-point')
@patch
abstract class Record {
- _Type get _recordRuntimeType;
+ _RecordType get _recordRuntimeType;
// An instance member needs a call from Dart code to be properly included in
// the dispatch table. Hence we use an inlined static wrapper as entry point.
@pragma("wasm:entry-point")
@pragma("wasm:prefer-inline")
- static _Type _getRecordRuntimeType(Record record) =>
+ static _RecordType _getRecordRuntimeType(Record record) =>
record._recordRuntimeType;
}
diff --git a/sdk/lib/_internal/wasm/lib/type.dart b/sdk/lib/_internal/wasm/lib/type.dart
index 8ac84df..c1de560 100644
--- a/sdk/lib/_internal/wasm/lib/type.dart
+++ b/sdk/lib/_internal/wasm/lib/type.dart
@@ -413,7 +413,7 @@
bool _checkInstance(Object o) {
if (ClassID.getID(o) != ClassID.cid_Closure) return false;
return _typeUniverse.isFunctionSubtype(
- _getFunctionRuntimeType(unsafeCast(o)), null, this, null);
+ _Closure._getClosureRuntimeType(unsafeCast(o)), null, this, null);
}
bool operator ==(Object o) {
@@ -604,6 +604,13 @@
external WasmArray<WasmArray<WasmArray<_Type>>> _getTypeRulesSubstitutions();
external WasmArray<String>? _getTypeNames();
+external WasmArray<WasmI8> get _typeCategoryTable;
+external WasmI32 get _typeCategoryAbstractClass;
+external WasmI32 get _typeCategoryObject;
+external WasmI32 get _typeCategoryFunction;
+external WasmI32 get _typeCategoryRecord;
+external WasmI32 get _typeCategoryNotMasqueraded;
+
/// Type parameter environment used while comparing function types.
///
/// In the case of nested function types, the environment refers to the
@@ -1280,19 +1287,54 @@
}
@pragma("wasm:entry-point")
-external _Type _getActualRuntimeType(Object object);
+_Type _getActualRuntimeType(Object object) {
+ final classId = ClassID.getID(object);
+ final category = _typeCategoryTable.readUnsigned(classId);
+
+ if (category == _typeCategoryFunction.toIntUnsigned()) {
+ return _Closure._getClosureRuntimeType(unsafeCast<_Closure>(object));
+ }
+ if (category == _typeCategoryRecord.toIntUnsigned()) {
+ return Record._getRecordRuntimeType(unsafeCast<Record>(object));
+ }
+ if (category == _typeCategoryObject.toIntUnsigned()) {
+ return _literal<Object>();
+ }
+ if (category == _typeCategoryAbstractClass.toIntUnsigned()) {
+ throw 'unreachable';
+ }
+ return _InterfaceType(classId, false, Object._getTypeArguments(object));
+}
@pragma("wasm:prefer-inline")
_Type _getActualRuntimeTypeNullable(Object? object) =>
object == null ? _literal<Null>() : _getActualRuntimeType(object);
@pragma("wasm:entry-point")
-external _Type _getMasqueradedRuntimeType(Object object);
+_Type _getMasqueradedRuntimeType(Object object) {
+ final classId = ClassID.getID(object);
+ final category = _typeCategoryTable.readUnsigned(classId);
+
+ if (category == _typeCategoryNotMasqueraded.toIntUnsigned()) {
+ return _InterfaceType(classId, false, Object._getTypeArguments(object));
+ }
+ if (category == _typeCategoryFunction.toIntUnsigned()) {
+ return _Closure._getClosureRuntimeType(unsafeCast<_Closure>(object));
+ }
+ if (category == _typeCategoryRecord.toIntUnsigned()) {
+ return Record._getRecordRuntimeType(unsafeCast<Record>(object));
+ }
+ if (category == _typeCategoryObject.toIntUnsigned()) {
+ return _literal<Object>();
+ }
+ if (category == _typeCategoryAbstractClass.toIntUnsigned()) {
+ throw 'unreachable';
+ }
+ return _InterfaceType(category, false, Object._getTypeArguments(object));
+}
@pragma("wasm:prefer-inline")
_Type _getMasqueradedRuntimeTypeNullable(Object? object) =>
object == null ? _literal<Null>() : _getMasqueradedRuntimeType(object);
-external _FunctionType _getFunctionRuntimeType(Function f);
-
external bool _isRecordInstance(Object o);