mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 10:28:02 +00:00
[dart2wasm] Categorize and masquerade runtime types by lookup table
This avoids virtual calls for `runtimeType` (unless the user overrides it) and `_runtimeType` (since the internal categorization is now done using the table instead), which saves a huge amount of space in the global dispatch table. It also fixes record runtime types, which now use the masqueraded types for its fields, rather than the (possibly user-overridden) `runtimeType`. A masquerade case was missing for `Type`, which has been added. Fixes https://github.com/dart-lang/sdk/issues/51134 Tested: ci + new test for overridden runtimeType Change-Id: I1909c665ae78eb07b9c0eb22b6e8836e27495d70 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/285684 Reviewed-by: Ömer Ağacan <omersa@google.com> Reviewed-by: Alexander Markov <alexmarkov@google.com> Commit-Queue: Aske Simon Christensen <askesc@google.com>
This commit is contained in:
parent
9ba4957a31
commit
c19720245c
18 changed files with 306 additions and 201 deletions
|
@ -118,6 +118,9 @@ class ClassInfo {
|
||||||
/// follow the Dart class hierarchy.
|
/// follow the Dart class hierarchy.
|
||||||
final ClassInfo? superInfo;
|
final ClassInfo? superInfo;
|
||||||
|
|
||||||
|
/// The class that this class masquerades as via `runtimeType`, if any.
|
||||||
|
ClassInfo? masquerade = null;
|
||||||
|
|
||||||
/// For every type parameter which is directly mapped to a type parameter in
|
/// For every type parameter which is directly mapped to a type parameter in
|
||||||
/// the superclass, this contains the corresponding superclass type
|
/// the superclass, this contains the corresponding superclass type
|
||||||
/// parameter. These will reuse the corresponding type parameter field of
|
/// parameter. These will reuse the corresponding type parameter field of
|
||||||
|
@ -186,6 +189,36 @@ class ClassInfoCollector {
|
||||||
/// shape class with that many fields.
|
/// shape class with that many fields.
|
||||||
final Map<int, w.StructType> _recordStructs = {};
|
final Map<int, w.StructType> _recordStructs = {};
|
||||||
|
|
||||||
|
/// Masquerades for implementation classes. For each entry of the map, all
|
||||||
|
/// subtypes of the key masquerade as the value.
|
||||||
|
late final Map<Class, Class> _masquerades = {
|
||||||
|
translator.coreTypes.boolClass: translator.coreTypes.boolClass,
|
||||||
|
translator.coreTypes.intClass: translator.coreTypes.intClass,
|
||||||
|
translator.coreTypes.doubleClass: translator.coreTypes.doubleClass,
|
||||||
|
translator.coreTypes.stringClass: translator.coreTypes.stringClass,
|
||||||
|
translator.index.getClass("dart:core", "_Type"):
|
||||||
|
translator.coreTypes.typeClass,
|
||||||
|
translator.index.getClass("dart:core", "_ListBase"):
|
||||||
|
translator.coreTypes.listClass,
|
||||||
|
for (Class cls in const <String>[
|
||||||
|
"Int8List",
|
||||||
|
"Uint8List",
|
||||||
|
"Uint8ClampedList",
|
||||||
|
"Int16List",
|
||||||
|
"Uint16List",
|
||||||
|
"Int32List",
|
||||||
|
"Uint32List",
|
||||||
|
"Int64List",
|
||||||
|
"Uint64List",
|
||||||
|
"Float32List",
|
||||||
|
"Float64List",
|
||||||
|
"Int32x4List",
|
||||||
|
"Float32x4List",
|
||||||
|
"Float64x2List",
|
||||||
|
].map((name) => translator.index.getClass("dart:typed_data", name)))
|
||||||
|
cls: cls
|
||||||
|
};
|
||||||
|
|
||||||
/// Wasm field type for fields with type [_Type]. Fields of this type are
|
/// Wasm field type for fields with type [_Type]. Fields of this type are
|
||||||
/// added to classes for type parameters.
|
/// added to classes for type parameters.
|
||||||
///
|
///
|
||||||
|
@ -292,6 +325,26 @@ class ClassInfoCollector {
|
||||||
translator.classes.add(info);
|
translator.classes.add(info);
|
||||||
translator.classInfo[cls] = info;
|
translator.classInfo[cls] = info;
|
||||||
translator.classForHeapType.putIfAbsent(info.struct, () => info!);
|
translator.classForHeapType.putIfAbsent(info.struct, () => info!);
|
||||||
|
|
||||||
|
ClassInfo? computeMasquerade() {
|
||||||
|
if (info!.superInfo?.masquerade != null) {
|
||||||
|
return info.superInfo!.masquerade;
|
||||||
|
}
|
||||||
|
for (Supertype implemented in cls.implementedTypes) {
|
||||||
|
ClassInfo? implementedMasquerade =
|
||||||
|
translator.classInfo[implemented.classNode]!.masquerade;
|
||||||
|
if (implementedMasquerade != null) {
|
||||||
|
return implementedMasquerade;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Class? selfMasquerade = _masquerades[cls];
|
||||||
|
if (selfMasquerade != null) {
|
||||||
|
return translator.classInfo[selfMasquerade]!;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
info.masquerade = computeMasquerade();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _initializeRecordClass(Class cls) {
|
void _initializeRecordClass(Class cls) {
|
||||||
|
@ -399,6 +452,11 @@ class ClassInfoCollector {
|
||||||
// parameters.
|
// parameters.
|
||||||
_initialize(translator.typeClass);
|
_initialize(translator.typeClass);
|
||||||
|
|
||||||
|
// Initialize masquerade classes to make sure they have low class IDs.
|
||||||
|
for (Class cls in _masquerades.values) {
|
||||||
|
_initialize(cls);
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize the record base class if we have record classes.
|
// Initialize the record base class if we have record classes.
|
||||||
if (translator.recordClasses.isNotEmpty) {
|
if (translator.recordClasses.isNotEmpty) {
|
||||||
_initialize(translator.coreTypes.recordClass);
|
_initialize(translator.coreTypes.recordClass);
|
||||||
|
|
|
@ -2156,7 +2156,6 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
|
||||||
b.i64_const(2011);
|
b.i64_const(2011);
|
||||||
break;
|
break;
|
||||||
case "runtimeType":
|
case "runtimeType":
|
||||||
case "_runtimeType":
|
|
||||||
wrap(ConstantExpression(TypeLiteralConstant(NullType())), resultType);
|
wrap(ConstantExpression(TypeLiteralConstant(NullType())), resultType);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -694,25 +694,6 @@ class Intrinsifier {
|
||||||
return translator.types.makeTypeRulesSubstitutions(b);
|
return translator.types.makeTypeRulesSubstitutions(b);
|
||||||
case "_getTypeNames":
|
case "_getTypeNames":
|
||||||
return translator.types.makeTypeNames(b);
|
return translator.types.makeTypeNames(b);
|
||||||
case "_getFunctionTypeRuntimeType":
|
|
||||||
Expression object = node.arguments.positional[0];
|
|
||||||
w.StructType closureBase =
|
|
||||||
translator.closureLayouter.closureBaseStruct;
|
|
||||||
codeGen.wrap(object, w.RefType.def(closureBase, nullable: false));
|
|
||||||
b.struct_get(closureBase, FieldIndex.closureRuntimeType);
|
|
||||||
return translator.types.typeClassInfo.nonNullableType;
|
|
||||||
case "_getInterfaceTypeRuntimeType":
|
|
||||||
Expression object = node.arguments.positional[0];
|
|
||||||
Expression typeArguments = node.arguments.positional[1];
|
|
||||||
ClassInfo info = translator.classInfo[translator.interfaceTypeClass]!;
|
|
||||||
b.i32_const(info.classId);
|
|
||||||
b.i32_const(initialIdentityHash);
|
|
||||||
// Runtime types are never nullable.
|
|
||||||
b.i32_const(0);
|
|
||||||
getID(object);
|
|
||||||
codeGen.wrap(typeArguments, translator.types.typeListExpectedType);
|
|
||||||
b.struct_new(info.struct);
|
|
||||||
return info.nonNullableType;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1148,16 +1129,89 @@ class Intrinsifier {
|
||||||
// Object.runtimeType
|
// Object.runtimeType
|
||||||
if (member.enclosingClass == translator.coreTypes.objectClass &&
|
if (member.enclosingClass == translator.coreTypes.objectClass &&
|
||||||
name == "runtimeType") {
|
name == "runtimeType") {
|
||||||
// Simple redirect to `_runtimeType`. This is done to keep
|
// Simple redirect to `_getMasqueradedRuntimeType`. This is done to keep
|
||||||
// `Object.runtimeType` external, which seems to be necessary for the TFA.
|
// `Object.runtimeType` external. If `Object.runtimeType` is implemented
|
||||||
// If we don't do this, then the TFA assumes things like
|
// in Dart, the TFA will conclude that `null.runtimeType` never returns,
|
||||||
// `null.runtimeType` are impossible and inserts a throw.
|
// since it dispatches to `Object.runtimeType`, which uses the receiver
|
||||||
|
// as non-nullable.
|
||||||
w.Local receiver = paramLocals[0];
|
w.Local receiver = paramLocals[0];
|
||||||
b.local_get(receiver);
|
b.local_get(receiver);
|
||||||
codeGen.call(translator.objectRuntimeType.reference);
|
codeGen.call(translator.getMasqueradedRuntimeType.reference);
|
||||||
return true;
|
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 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,
|
||||||
|
functionType,
|
||||||
|
recordType,
|
||||||
|
if (masqueraded) notMasqueraded
|
||||||
|
], masqueraded ? interfaceType : notMasqueraded);
|
||||||
|
|
||||||
|
b.end(); // abstractClass
|
||||||
|
// We should never see class IDs for abstract types.
|
||||||
|
b.unreachable();
|
||||||
|
|
||||||
|
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 list of type arguments.
|
||||||
|
b.local_get(object);
|
||||||
|
codeGen.call(translator.objectGetTypeArguments.reference);
|
||||||
|
b.struct_new(info.struct);
|
||||||
|
b.return_();
|
||||||
|
}
|
||||||
|
|
||||||
// identical
|
// identical
|
||||||
if (member == translator.coreTypes.identicalProcedure) {
|
if (member == translator.coreTypes.identicalProcedure) {
|
||||||
w.Local first = paramLocals[0];
|
w.Local first = paramLocals[0];
|
||||||
|
|
|
@ -162,12 +162,14 @@ mixin KernelNodes {
|
||||||
// dart:core various procedures
|
// dart:core various procedures
|
||||||
late final Procedure objectNoSuchMethod =
|
late final Procedure objectNoSuchMethod =
|
||||||
index.getProcedure("dart:core", "Object", "noSuchMethod");
|
index.getProcedure("dart:core", "Object", "noSuchMethod");
|
||||||
late final Procedure objectRuntimeType =
|
late final Procedure objectGetTypeArguments =
|
||||||
index.getProcedure("dart:core", "Object", "get:_runtimeType");
|
index.getProcedure("dart:core", "Object", "_getTypeArguments");
|
||||||
late final Procedure nullToString =
|
late final Procedure nullToString =
|
||||||
index.getProcedure("dart:core", "Object", "_nullToString");
|
index.getProcedure("dart:core", "Object", "_nullToString");
|
||||||
late final Procedure nullNoSuchMethod =
|
late final Procedure nullNoSuchMethod =
|
||||||
index.getProcedure("dart:core", "Object", "_nullNoSuchMethod");
|
index.getProcedure("dart:core", "Object", "_nullNoSuchMethod");
|
||||||
|
late final Procedure recordGetRecordRuntimeType =
|
||||||
|
index.getProcedure("dart:core", "Record", "_getRecordRuntimeType");
|
||||||
late final Procedure stringEquals =
|
late final Procedure stringEquals =
|
||||||
index.getProcedure("dart:core", "_StringBase", "==");
|
index.getProcedure("dart:core", "_StringBase", "==");
|
||||||
late final Procedure stringInterpolate =
|
late final Procedure stringInterpolate =
|
||||||
|
@ -212,6 +214,10 @@ mixin KernelNodes {
|
||||||
index.getProcedure("dart:core", "Error", "_throw");
|
index.getProcedure("dart:core", "Error", "_throw");
|
||||||
|
|
||||||
// dart:core type procedures
|
// dart:core type procedures
|
||||||
|
late final Procedure getActualRuntimeType =
|
||||||
|
index.getTopLevelProcedure("dart:core", "_getActualRuntimeType");
|
||||||
|
late final Procedure getMasqueradedRuntimeType =
|
||||||
|
index.getTopLevelProcedure("dart:core", "_getMasqueradedRuntimeType");
|
||||||
late final Procedure isSubtype =
|
late final Procedure isSubtype =
|
||||||
index.getTopLevelProcedure("dart:core", "_isSubtype");
|
index.getTopLevelProcedure("dart:core", "_isSubtype");
|
||||||
late final Procedure isTypeSubtype =
|
late final Procedure isTypeSubtype =
|
||||||
|
|
|
@ -30,14 +30,14 @@ import 'package:dart2wasm/records.dart';
|
||||||
/// Record_2_a(this.$1, this.$2, this.a);
|
/// Record_2_a(this.$1, this.$2, this.a);
|
||||||
///
|
///
|
||||||
/// @pragma('wasm:entry-point')
|
/// @pragma('wasm:entry-point')
|
||||||
/// _Type get _runtimeType =>
|
/// _Type get _recordRuntimeType =>
|
||||||
/// // Uses of `runtimeType` below will be fixed with #51134.
|
|
||||||
/// _RecordType(
|
/// _RecordType(
|
||||||
/// const ["a"],
|
/// const ["a"],
|
||||||
/// [$1.runtimeType, $2.runtimeType, a.runtimeType]);
|
/// [
|
||||||
///
|
/// _getMasqueradedRuntimeTypeNullable($1),
|
||||||
/// @pragma('wasm:entry-point')
|
/// _getMasqueradedRuntimeTypeNullable($2),
|
||||||
/// Type get runtimeType => _runtimeType;
|
/// _getMasqueradedRuntimeTypeNullable(a)
|
||||||
|
/// ]);
|
||||||
///
|
///
|
||||||
/// @pragma('wasm:entry-point')
|
/// @pragma('wasm:entry-point')
|
||||||
/// String toString() =>
|
/// String toString() =>
|
||||||
|
@ -73,9 +73,6 @@ class _RecordClassGenerator {
|
||||||
late final Class recordRuntimeTypeClass =
|
late final Class recordRuntimeTypeClass =
|
||||||
coreTypes.index.getClass('dart:core', '_RecordType');
|
coreTypes.index.getClass('dart:core', '_RecordType');
|
||||||
|
|
||||||
late final Class internalRuntimeTypeClass =
|
|
||||||
coreTypes.index.getClass('dart:core', '_Type');
|
|
||||||
|
|
||||||
late final Constructor recordRuntimeTypeConstructor =
|
late final Constructor recordRuntimeTypeConstructor =
|
||||||
recordRuntimeTypeClass.constructors.single;
|
recordRuntimeTypeClass.constructors.single;
|
||||||
|
|
||||||
|
@ -85,9 +82,6 @@ class _RecordClassGenerator {
|
||||||
late final Procedure objectHashAllProcedure =
|
late final Procedure objectHashAllProcedure =
|
||||||
coreTypes.index.getProcedure('dart:core', 'Object', 'hashAll');
|
coreTypes.index.getProcedure('dart:core', 'Object', 'hashAll');
|
||||||
|
|
||||||
late final Procedure objectRuntimeTypeProcedure =
|
|
||||||
coreTypes.index.getProcedure('dart:core', 'Object', 'get:runtimeType');
|
|
||||||
|
|
||||||
late final Procedure objectToStringProcedure =
|
late final Procedure objectToStringProcedure =
|
||||||
coreTypes.index.getProcedure('dart:core', 'Object', 'toString');
|
coreTypes.index.getProcedure('dart:core', 'Object', 'toString');
|
||||||
|
|
||||||
|
@ -96,10 +90,11 @@ class _RecordClassGenerator {
|
||||||
late final Procedure stringPlusProcedure =
|
late final Procedure stringPlusProcedure =
|
||||||
coreTypes.index.getProcedure('dart:core', 'String', '+');
|
coreTypes.index.getProcedure('dart:core', 'String', '+');
|
||||||
|
|
||||||
DartType get nullableObjectType => coreTypes.objectNullableRawType;
|
late final Procedure getMasqueradedRuntimeTypeNullableProcedure = coreTypes
|
||||||
|
.index
|
||||||
|
.getTopLevelProcedure('dart:core', '_getMasqueradedRuntimeTypeNullable');
|
||||||
|
|
||||||
DartType get internalRuntimeTypeType =>
|
DartType get nullableObjectType => coreTypes.objectNullableRawType;
|
||||||
InterfaceType(internalRuntimeTypeClass, Nullability.nonNullable);
|
|
||||||
|
|
||||||
DartType get nonNullableStringType => coreTypes.stringNonNullableRawType;
|
DartType get nonNullableStringType => coreTypes.stringNonNullableRawType;
|
||||||
|
|
||||||
|
@ -150,13 +145,7 @@ class _RecordClassGenerator {
|
||||||
));
|
));
|
||||||
library.addClass(cls);
|
library.addClass(cls);
|
||||||
cls.addProcedure(_generateEquals(shape, fields, cls));
|
cls.addProcedure(_generateEquals(shape, fields, cls));
|
||||||
final internalRuntimeType = _generateInternalRuntimeType(shape, fields);
|
cls.addProcedure(_generateRecordRuntimeType(shape, fields));
|
||||||
// With `_runtimeType` we also need to override `runtimeType`, as
|
|
||||||
// `Object.runtimeType` is implemented as a direct call to
|
|
||||||
// `Object._runtimeType` instead of a virtual call.
|
|
||||||
final runtimeType = _generateRuntimeType(internalRuntimeType);
|
|
||||||
cls.addProcedure(internalRuntimeType);
|
|
||||||
cls.addProcedure(runtimeType);
|
|
||||||
return cls;
|
return cls;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -352,8 +341,7 @@ class _RecordClassGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate `_Type get _runtimeType` member.
|
/// Generate `_Type get _runtimeType` member.
|
||||||
Procedure _generateInternalRuntimeType(
|
Procedure _generateRecordRuntimeType(RecordShape shape, List<Field> fields) {
|
||||||
RecordShape shape, List<Field> fields) {
|
|
||||||
final List<Statement> statements = [];
|
final List<Statement> statements = [];
|
||||||
|
|
||||||
// const ["name1", "name2", ...]
|
// const ["name1", "name2", ...]
|
||||||
|
@ -361,16 +349,12 @@ class _RecordClassGenerator {
|
||||||
nonNullableStringType,
|
nonNullableStringType,
|
||||||
shape.names.map((name) => StringConstant(name)).toList()));
|
shape.names.map((name) => StringConstant(name)).toList()));
|
||||||
|
|
||||||
// Generate `this.field.runtimeType` for a given field.
|
Expression fieldRuntimeTypeExpr(Field field) => StaticInvocation(
|
||||||
// TODO(51134): We shouldn't use user-provided runtimeType below.
|
getMasqueradedRuntimeTypeNullableProcedure,
|
||||||
Expression fieldRuntimeTypeExpr(Field field) => InstanceGet(
|
Arguments([
|
||||||
InstanceAccessKind.Object,
|
|
||||||
InstanceGet(InstanceAccessKind.Instance, ThisExpression(), field.name,
|
InstanceGet(InstanceAccessKind.Instance, ThisExpression(), field.name,
|
||||||
interfaceTarget: field, resultType: nullableObjectType),
|
interfaceTarget: field, resultType: nullableObjectType)
|
||||||
objectRuntimeTypeProcedure.name,
|
]));
|
||||||
interfaceTarget: objectRuntimeTypeProcedure,
|
|
||||||
resultType: runtimeTypeType,
|
|
||||||
);
|
|
||||||
|
|
||||||
// [this.$1.runtimeType, this.x.runtimeType, ...]
|
// [this.$1.runtimeType, this.x.runtimeType, ...]
|
||||||
final fieldTypesList = ListLiteral(
|
final fieldTypesList = ListLiteral(
|
||||||
|
@ -394,26 +378,7 @@ class _RecordClassGenerator {
|
||||||
);
|
);
|
||||||
|
|
||||||
return _addWasmEntryPointPragma(Procedure(
|
return _addWasmEntryPointPragma(Procedure(
|
||||||
Name('_runtimeType', library),
|
Name('_recordRuntimeType', library),
|
||||||
ProcedureKind.Getter,
|
|
||||||
function,
|
|
||||||
fileUri: library.fileUri,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate `Type get runtimeType member = _runtimeType;`.
|
|
||||||
Procedure _generateRuntimeType(Procedure internalRuntimeType) {
|
|
||||||
final FunctionNode function = FunctionNode(
|
|
||||||
ReturnStatement(InstanceGet(InstanceAccessKind.Instance, ThisExpression(),
|
|
||||||
internalRuntimeType.name,
|
|
||||||
interfaceTarget: internalRuntimeType,
|
|
||||||
resultType: internalRuntimeTypeType)),
|
|
||||||
positionalParameters: [],
|
|
||||||
returnType: runtimeTypeType,
|
|
||||||
);
|
|
||||||
|
|
||||||
return _addWasmEntryPointPragma(Procedure(
|
|
||||||
Name('runtimeType', library),
|
|
||||||
ProcedureKind.Getter,
|
ProcedureKind.Getter,
|
||||||
function,
|
function,
|
||||||
fileUri: library.fileUri,
|
fileUri: library.fileUri,
|
||||||
|
|
|
@ -571,9 +571,12 @@ class Translator with KernelNodes {
|
||||||
translateStorageType(type), type.toText(defaultAstTextStrategy));
|
translateStorageType(type), type.toText(defaultAstTextStrategy));
|
||||||
}
|
}
|
||||||
|
|
||||||
w.ArrayType wasmArrayType(w.StorageType type, String name) {
|
w.ArrayType wasmArrayType(w.StorageType type, String name,
|
||||||
return arrayTypeCache.putIfAbsent(type,
|
{bool mutable = true}) {
|
||||||
() => m.addArrayType("Array<$name>", elementType: w.FieldType(type)));
|
return arrayTypeCache.putIfAbsent(
|
||||||
|
type,
|
||||||
|
() => m.addArrayType("Array<$name>",
|
||||||
|
elementType: w.FieldType(type, mutable: mutable)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Translate a Dart type as it should appear on parameters and returns of
|
/// Translate a Dart type as it should appear on parameters and returns of
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
// BSD-style license that can be found in the LICENSE file.
|
// BSD-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:math' show max;
|
import 'dart:math' show max;
|
||||||
|
import 'dart:typed_data' show Uint8List;
|
||||||
|
|
||||||
import 'package:dart2wasm/class_info.dart';
|
import 'package:dart2wasm/class_info.dart';
|
||||||
import 'package:dart2wasm/code_generator.dart';
|
import 'package:dart2wasm/code_generator.dart';
|
||||||
|
@ -13,6 +14,17 @@ import 'package:kernel/core_types.dart';
|
||||||
|
|
||||||
import 'package:wasm_builder/wasm_builder.dart' as w;
|
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 function = 1;
|
||||||
|
static const record = 2;
|
||||||
|
static const notMasqueraded = 3;
|
||||||
|
static const minMasqueradeClassId = 4;
|
||||||
|
static const maxMasqueradeClassId = 63; // Leaves 2 unused bits for future use
|
||||||
|
}
|
||||||
|
|
||||||
class InterfaceTypeEnvironment {
|
class InterfaceTypeEnvironment {
|
||||||
final Map<TypeParameter, int> _typeOffsets = {};
|
final Map<TypeParameter, int> _typeOffsets = {};
|
||||||
|
|
||||||
|
@ -87,6 +99,9 @@ class Types {
|
||||||
/// parameter index range of their corresponding function type.
|
/// parameter index range of their corresponding function type.
|
||||||
Map<TypeParameter, int> functionTypeParameterIndex = Map.identity();
|
Map<TypeParameter, int> functionTypeParameterIndex = Map.identity();
|
||||||
|
|
||||||
|
/// An `i8` array of type category values, indexed by class ID.
|
||||||
|
late final w.Global typeCategoryTable = _buildTypeCategoryTable();
|
||||||
|
|
||||||
Types(this.translator);
|
Types(this.translator);
|
||||||
|
|
||||||
w.ValueType classAndFieldToType(Class cls, int fieldIndex) =>
|
w.ValueType classAndFieldToType(Class cls, int fieldIndex) =>
|
||||||
|
@ -256,6 +271,53 @@ class Types {
|
||||||
return expectedType;
|
return expectedType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Build a global array of byte values used to categorize runtime types.
|
||||||
|
w.Global _buildTypeCategoryTable() {
|
||||||
|
Set<Class> recordClasses = Set.from(translator.recordClasses.values);
|
||||||
|
Uint8List table = Uint8List(translator.classes.length);
|
||||||
|
for (int i = 0; i < translator.classes.length; i++) {
|
||||||
|
ClassInfo info = translator.classes[i];
|
||||||
|
ClassInfo? masquerade = info.masquerade;
|
||||||
|
Class? cls = info.cls;
|
||||||
|
int category;
|
||||||
|
if (cls == null || cls.isAbstract) {
|
||||||
|
category = TypeCategory.abstractClass;
|
||||||
|
} else if (cls == translator.closureClass) {
|
||||||
|
category = TypeCategory.function;
|
||||||
|
} else if (recordClasses.contains(cls)) {
|
||||||
|
category = TypeCategory.record;
|
||||||
|
} else if (masquerade == null || masquerade.classId == i) {
|
||||||
|
category = TypeCategory.notMasqueraded;
|
||||||
|
} else {
|
||||||
|
// Masqueraded class
|
||||||
|
assert(cls.enclosingLibrary.importUri.scheme == "dart");
|
||||||
|
assert(cls.name.startsWith('_'));
|
||||||
|
assert(masquerade.classId >= TypeCategory.minMasqueradeClassId);
|
||||||
|
assert(masquerade.classId <= TypeCategory.maxMasqueradeClassId);
|
||||||
|
category = masquerade.classId;
|
||||||
|
}
|
||||||
|
table[i] = category;
|
||||||
|
}
|
||||||
|
|
||||||
|
w.DataSegment segment = translator.m.addDataSegment(table);
|
||||||
|
w.ArrayType arrayType =
|
||||||
|
translator.wasmArrayType(w.PackedType.i8, "const i8", mutable: false);
|
||||||
|
w.DefinedGlobal global = translator.m
|
||||||
|
.addGlobal(w.GlobalType(w.RefType.def(arrayType, nullable: false)));
|
||||||
|
// Initialize the global to a dummy array, since `array.new_data` is not
|
||||||
|
// a constant instruction and thus can't be used in the initializer.
|
||||||
|
global.initializer.array_new_fixed(arrayType, 0);
|
||||||
|
global.initializer.end();
|
||||||
|
// Create the actual table in the init function.
|
||||||
|
w.Instructions b = translator.initFunction.body;
|
||||||
|
b.i32_const(0);
|
||||||
|
b.i32_const(table.length);
|
||||||
|
b.array_new_data(arrayType, segment);
|
||||||
|
b.global_set(global);
|
||||||
|
|
||||||
|
return global;
|
||||||
|
}
|
||||||
|
|
||||||
bool isFunctionTypeParameter(TypeParameterType type) =>
|
bool isFunctionTypeParameter(TypeParameterType type) =>
|
||||||
type.parameter.parent == null;
|
type.parameter.parent == null;
|
||||||
|
|
||||||
|
|
|
@ -4,18 +4,6 @@
|
||||||
|
|
||||||
part of "core_patch.dart";
|
part of "core_patch.dart";
|
||||||
|
|
||||||
@patch
|
|
||||||
class bool {
|
|
||||||
// Note: this needs to be in `bool`, cannot be overridden in `_BoxedBool`. I
|
|
||||||
// suspect the problem is there's an assumption in the front-end that `bool`
|
|
||||||
// has one implementation class (unlike `double`, `int`, `String`) which is
|
|
||||||
// the `bool` class itself. So when `runtimeType` is not overridden in
|
|
||||||
// `bool`, in code like `x.runtimeType` where `x` is `bool`, direct call
|
|
||||||
// metadata says that the member is `Object.runtimeType`.
|
|
||||||
@override
|
|
||||||
Type get runtimeType => bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
@pragma("wasm:entry-point")
|
@pragma("wasm:entry-point")
|
||||||
final class _BoxedBool extends bool {
|
final class _BoxedBool extends bool {
|
||||||
// A boxed bool contains an unboxed bool.
|
// A boxed bool contains an unboxed bool.
|
||||||
|
|
|
@ -60,9 +60,6 @@ final class _BoxedDouble extends double {
|
||||||
/// Dummy factory to silence error about missing superclass constructor.
|
/// Dummy factory to silence error about missing superclass constructor.
|
||||||
external factory _BoxedDouble();
|
external factory _BoxedDouble();
|
||||||
|
|
||||||
@override
|
|
||||||
Type get runtimeType => double;
|
|
||||||
|
|
||||||
static const int _mantissaBits = 52;
|
static const int _mantissaBits = 52;
|
||||||
static const int _exponentBits = 11;
|
static const int _exponentBits = 11;
|
||||||
static const int _exponentBias = 1023;
|
static const int _exponentBias = 1023;
|
||||||
|
|
|
@ -7,9 +7,7 @@ part of 'core_patch.dart';
|
||||||
@patch
|
@patch
|
||||||
class Error {
|
class Error {
|
||||||
@patch
|
@patch
|
||||||
static String _objectToString(Object object) {
|
static String _objectToString(Object object) => Object._toString(object);
|
||||||
return "Instance of '${object._runtimeType}'";
|
|
||||||
}
|
|
||||||
|
|
||||||
@patch
|
@patch
|
||||||
static String _stringToSafeString(String string) {
|
static String _stringToSafeString(String string) {
|
||||||
|
|
|
@ -34,9 +34,6 @@ final class _BoxedInt extends int {
|
||||||
/// Dummy factory to silence error about missing superclass constructor.
|
/// Dummy factory to silence error about missing superclass constructor.
|
||||||
external factory _BoxedInt();
|
external factory _BoxedInt();
|
||||||
|
|
||||||
@override
|
|
||||||
Type get runtimeType => int;
|
|
||||||
|
|
||||||
external num operator +(num other);
|
external num operator +(num other);
|
||||||
external num operator -(num other);
|
external num operator -(num other);
|
||||||
external num operator *(num other);
|
external num operator *(num other);
|
||||||
|
|
|
@ -8,11 +8,6 @@ part of "core_patch.dart";
|
||||||
external int _getHash(Object obj);
|
external int _getHash(Object obj);
|
||||||
external void _setHash(Object obj, int hash);
|
external void _setHash(Object obj, int hash);
|
||||||
|
|
||||||
external _Type _getInterfaceTypeRuntimeType(
|
|
||||||
Object object, List<Type> typeArguments);
|
|
||||||
|
|
||||||
external _Type _getFunctionTypeRuntimeType(Object object);
|
|
||||||
|
|
||||||
@patch
|
@patch
|
||||||
class Object {
|
class Object {
|
||||||
@patch
|
@patch
|
||||||
|
@ -43,24 +38,21 @@ class Object {
|
||||||
/// which return their type arguments.
|
/// which return their type arguments.
|
||||||
List<_Type> get _typeArguments => const [];
|
List<_Type> get _typeArguments => const [];
|
||||||
|
|
||||||
/// We use [_runtimeType] for internal type testing, because objects can
|
// An instance member needs a call from Dart code to be properly included in
|
||||||
/// override [runtimeType].
|
// the dispatch table. Hence we use an inlined static wrapper as entry point.
|
||||||
|
@pragma("wasm:entry-point")
|
||||||
|
@pragma("wasm:prefer-inline")
|
||||||
|
static List<_Type> _getTypeArguments(Object object) => object._typeArguments;
|
||||||
|
|
||||||
@patch
|
@patch
|
||||||
external Type get runtimeType;
|
external Type get runtimeType;
|
||||||
|
|
||||||
@pragma("wasm:entry-point")
|
|
||||||
_Type get _runtimeType {
|
|
||||||
if (ClassID.getID(this) == ClassID.cid_Closure) {
|
|
||||||
return _getFunctionTypeRuntimeType(this);
|
|
||||||
} else {
|
|
||||||
return _getInterfaceTypeRuntimeType(this, _typeArguments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@patch
|
@patch
|
||||||
String toString() => _toString(this);
|
String toString() => _toString(this);
|
||||||
// A statically dispatched version of Object.toString.
|
// A statically dispatched version of Object.toString.
|
||||||
static String _toString(obj) => "Instance of '${obj.runtimeType}'";
|
static String _toString(Object obj) {
|
||||||
|
return "Instance of '${_getMasqueradedRuntimeType(obj)}'";
|
||||||
|
}
|
||||||
|
|
||||||
@patch
|
@patch
|
||||||
@pragma("wasm:entry-point")
|
@pragma("wasm:entry-point")
|
||||||
|
|
|
@ -7,4 +7,13 @@ part of "core_patch.dart";
|
||||||
// `entry-point` needed to make sure the class will be in the class hierarchy
|
// `entry-point` needed to make sure the class will be in the class hierarchy
|
||||||
// in programs without records.
|
// in programs without records.
|
||||||
@pragma('wasm:entry-point')
|
@pragma('wasm:entry-point')
|
||||||
abstract class Record {}
|
abstract class Record {
|
||||||
|
_Type 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) =>
|
||||||
|
record._recordRuntimeType;
|
||||||
|
}
|
||||||
|
|
|
@ -177,9 +177,6 @@ final class _NaiveFloat32x4List extends Object
|
||||||
|
|
||||||
_NaiveFloat32x4List(int length) : _storage = Float32List(length * 4);
|
_NaiveFloat32x4List(int length) : _storage = Float32List(length * 4);
|
||||||
|
|
||||||
@override
|
|
||||||
Type get runtimeType => Float32x4List;
|
|
||||||
|
|
||||||
_NaiveFloat32x4List._externalStorage(this._storage);
|
_NaiveFloat32x4List._externalStorage(this._storage);
|
||||||
|
|
||||||
_NaiveFloat32x4List._slowFromList(List<Float32x4> list)
|
_NaiveFloat32x4List._slowFromList(List<Float32x4> list)
|
||||||
|
@ -243,9 +240,6 @@ final class _NaiveFloat64x2List extends Object
|
||||||
|
|
||||||
_NaiveFloat64x2List(int length) : _storage = Float64List(length * 2);
|
_NaiveFloat64x2List(int length) : _storage = Float64List(length * 2);
|
||||||
|
|
||||||
@override
|
|
||||||
Type get runtimeType => Float64x2List;
|
|
||||||
|
|
||||||
_NaiveFloat64x2List._externalStorage(this._storage);
|
_NaiveFloat64x2List._externalStorage(this._storage);
|
||||||
|
|
||||||
_NaiveFloat64x2List._slowFromList(List<Float64x2> list)
|
_NaiveFloat64x2List._slowFromList(List<Float64x2> list)
|
||||||
|
@ -338,9 +332,6 @@ final class _NaiveFloat32x4 implements Float32x4 {
|
||||||
|
|
||||||
_NaiveFloat32x4._truncated(this.x, this.y, this.z, this.w);
|
_NaiveFloat32x4._truncated(this.x, this.y, this.z, this.w);
|
||||||
|
|
||||||
@override
|
|
||||||
Type get runtimeType => Float32x4List;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return '[${x.toStringAsFixed(6)}, '
|
return '[${x.toStringAsFixed(6)}, '
|
||||||
|
|
|
@ -95,9 +95,6 @@ abstract final class _StringBase implements String {
|
||||||
|
|
||||||
_StringBase._();
|
_StringBase._();
|
||||||
|
|
||||||
@override
|
|
||||||
Type get runtimeType => String;
|
|
||||||
|
|
||||||
int get hashCode {
|
int get hashCode {
|
||||||
int hash = _getHash(this);
|
int hash = _getHash(this);
|
||||||
if (hash != 0) return hash;
|
if (hash != 0) return hash;
|
||||||
|
|
|
@ -1026,7 +1026,8 @@ _TypeUniverse _typeUniverse = _TypeUniverse.create();
|
||||||
|
|
||||||
@pragma("wasm:entry-point")
|
@pragma("wasm:entry-point")
|
||||||
bool _isSubtype(Object? s, _Type t) {
|
bool _isSubtype(Object? s, _Type t) {
|
||||||
return _typeUniverse.isSubtype(s._runtimeType, null, t, null);
|
return _typeUniverse.isSubtype(
|
||||||
|
_getActualRuntimeTypeNullable(s), null, t, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@pragma("wasm:entry-point")
|
@pragma("wasm:entry-point")
|
||||||
|
@ -1166,3 +1167,17 @@ void _checkClosureType(_FunctionType functionType, List<_Type> typeArguments,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@pragma("wasm:entry-point")
|
||||||
|
external _Type _getActualRuntimeType(Object object);
|
||||||
|
|
||||||
|
@pragma("wasm:prefer-inline")
|
||||||
|
_Type _getActualRuntimeTypeNullable(Object? object) =>
|
||||||
|
object == null ? const _NullType() : _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);
|
||||||
|
|
|
@ -100,69 +100,3 @@ abstract class _TypedList extends _TypedListBase {
|
||||||
data.setFloat64(offsetInBytes + 1 * 8, value.y, Endian.host);
|
data.setFloat64(offsetInBytes + 1 * 8, value.y, Endian.host);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@patch
|
|
||||||
class _Int8List {
|
|
||||||
@override
|
|
||||||
Type get runtimeType => Int8List;
|
|
||||||
}
|
|
||||||
|
|
||||||
@patch
|
|
||||||
class _Uint8List {
|
|
||||||
@override
|
|
||||||
Type get runtimeType => Uint8List;
|
|
||||||
}
|
|
||||||
|
|
||||||
@patch
|
|
||||||
class _Uint8ClampedList {
|
|
||||||
@override
|
|
||||||
Type get runtimeType => Uint8ClampedList;
|
|
||||||
}
|
|
||||||
|
|
||||||
@patch
|
|
||||||
class _Int16List {
|
|
||||||
@override
|
|
||||||
Type get runtimeType => Int16List;
|
|
||||||
}
|
|
||||||
|
|
||||||
@patch
|
|
||||||
class _Uint16List {
|
|
||||||
@override
|
|
||||||
Type get runtimeType => Uint16List;
|
|
||||||
}
|
|
||||||
|
|
||||||
@patch
|
|
||||||
class _Int32List {
|
|
||||||
@override
|
|
||||||
Type get runtimeType => Int32List;
|
|
||||||
}
|
|
||||||
|
|
||||||
@patch
|
|
||||||
class _Uint32List {
|
|
||||||
@override
|
|
||||||
Type get runtimeType => Uint32List;
|
|
||||||
}
|
|
||||||
|
|
||||||
@patch
|
|
||||||
class _Int64List {
|
|
||||||
@override
|
|
||||||
Type get runtimeType => Int64List;
|
|
||||||
}
|
|
||||||
|
|
||||||
@patch
|
|
||||||
class _Uint64List {
|
|
||||||
@override
|
|
||||||
Type get runtimeType => Uint64List;
|
|
||||||
}
|
|
||||||
|
|
||||||
@patch
|
|
||||||
class _Float32List {
|
|
||||||
@override
|
|
||||||
Type get runtimeType => Float32List;
|
|
||||||
}
|
|
||||||
|
|
||||||
@patch
|
|
||||||
class _Float64List {
|
|
||||||
@override
|
|
||||||
Type get runtimeType => Float64List;
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
|
||||||
|
// for details. All rights reserved. Use of this source code is governed by a
|
||||||
|
// BSD-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// SharedOptions=--enable-experiment=records
|
||||||
|
|
||||||
|
import "package:expect/expect.dart";
|
||||||
|
|
||||||
|
class A {
|
||||||
|
@override
|
||||||
|
Type get runtimeType => B;
|
||||||
|
}
|
||||||
|
|
||||||
|
class B extends A {
|
||||||
|
@override
|
||||||
|
Type get runtimeType => A;
|
||||||
|
}
|
||||||
|
|
||||||
|
Type typeObject<T>() => T;
|
||||||
|
|
||||||
|
main() {
|
||||||
|
final a = A();
|
||||||
|
final b = B();
|
||||||
|
|
||||||
|
Expect.isTrue(a is A);
|
||||||
|
Expect.isFalse(a is B);
|
||||||
|
Expect.isTrue(b is A);
|
||||||
|
Expect.isTrue(b is B);
|
||||||
|
|
||||||
|
Expect.isTrue((a,) is (A,));
|
||||||
|
Expect.isFalse((a,) is (B,));
|
||||||
|
Expect.isTrue((b,) is (A,));
|
||||||
|
Expect.isTrue((b,) is (B,));
|
||||||
|
|
||||||
|
Expect.equals(typeObject<B>(), a.runtimeType);
|
||||||
|
Expect.equals(typeObject<A>(), b.runtimeType);
|
||||||
|
|
||||||
|
Expect.equals(typeObject<(A,)>(), (a,).runtimeType);
|
||||||
|
Expect.equals(typeObject<(B,)>(), (b,).runtimeType);
|
||||||
|
}
|
Loading…
Reference in a new issue