[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:
Aske Simon Christensen 2023-03-09 22:08:19 +00:00 committed by Commit Queue
parent 9ba4957a31
commit c19720245c
18 changed files with 306 additions and 201 deletions

View file

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

View file

@ -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:

View file

@ -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];

View file

@ -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 =

View file

@ -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,

View file

@ -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

View file

@ -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;

View file

@ -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.

View file

@ -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;

View file

@ -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) {

View file

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

View file

@ -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")

View file

@ -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;
}

View file

@ -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)}, '

View file

@ -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;

View file

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

View file

@ -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;
}

View file

@ -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);
}