[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);