mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 17:18:04 +00:00
[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>
This commit is contained in:
parent
41128c56e2
commit
a2ee738dfc
|
@ -221,6 +221,9 @@ class ClassInfoCollector {
|
|||
/// 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 @@ class ClassInfoCollector {
|
|||
|
||||
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 @@ class ClassInfoCollector {
|
|||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import 'package:dart2wasm/class_info.dart';
|
|||
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 @@ class Intrinsifier {
|
|||
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 @@ class Intrinsifier {
|
|||
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 @@ class Intrinsifier {
|
|||
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 @@ class Intrinsifier {
|
|||
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 @@ class Intrinsifier {
|
|||
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];
|
||||
|
|
|
@ -255,6 +255,8 @@ mixin KernelNodes {
|
|||
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 =
|
||||
|
|
|
@ -14,18 +14,6 @@ import 'package:kernel/core_types.dart';
|
|||
|
||||
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 @@ class Types {
|
|||
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 Types {
|
|||
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
|
||||
|
|
|
@ -22,6 +22,10 @@ final class _Closure implements Function {
|
|||
|
||||
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;
|
||||
|
|
|
@ -9,12 +9,12 @@ part of "core_patch.dart";
|
|||
@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;
|
||||
}
|
||||
|
|
|
@ -413,7 +413,7 @@ class _FunctionType extends _Type {
|
|||
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<WasmI32>> _getTypeRulesSupers();
|
|||
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 @@ void _checkClosureType(
|
|||
}
|
||||
|
||||
@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);
|
||||
|
|
Loading…
Reference in a new issue