mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 08:24:39 +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
|
@ -118,6 +118,9 @@ class ClassInfo {
|
|||
/// follow the Dart class hierarchy.
|
||||
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
|
||||
/// the superclass, this contains the corresponding superclass type
|
||||
/// parameter. These will reuse the corresponding type parameter field of
|
||||
|
@ -186,6 +189,36 @@ class ClassInfoCollector {
|
|||
/// shape class with that many fields.
|
||||
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
|
||||
/// added to classes for type parameters.
|
||||
///
|
||||
|
@ -292,6 +325,26 @@ class ClassInfoCollector {
|
|||
translator.classes.add(info);
|
||||
translator.classInfo[cls] = 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) {
|
||||
|
@ -399,6 +452,11 @@ class ClassInfoCollector {
|
|||
// parameters.
|
||||
_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.
|
||||
if (translator.recordClasses.isNotEmpty) {
|
||||
_initialize(translator.coreTypes.recordClass);
|
||||
|
|
|
@ -2156,7 +2156,6 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
|
|||
b.i64_const(2011);
|
||||
break;
|
||||
case "runtimeType":
|
||||
case "_runtimeType":
|
||||
wrap(ConstantExpression(TypeLiteralConstant(NullType())), resultType);
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -694,25 +694,6 @@ class Intrinsifier {
|
|||
return translator.types.makeTypeRulesSubstitutions(b);
|
||||
case "_getTypeNames":
|
||||
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
|
||||
if (member.enclosingClass == translator.coreTypes.objectClass &&
|
||||
name == "runtimeType") {
|
||||
// Simple redirect to `_runtimeType`. This is done to keep
|
||||
// `Object.runtimeType` external, which seems to be necessary for the TFA.
|
||||
// If we don't do this, then the TFA assumes things like
|
||||
// `null.runtimeType` are impossible and inserts a throw.
|
||||
// Simple redirect to `_getMasqueradedRuntimeType`. This is done to keep
|
||||
// `Object.runtimeType` external. If `Object.runtimeType` is implemented
|
||||
// in Dart, the TFA will conclude that `null.runtimeType` never returns,
|
||||
// since it dispatches to `Object.runtimeType`, which uses the receiver
|
||||
// as non-nullable.
|
||||
w.Local receiver = paramLocals[0];
|
||||
b.local_get(receiver);
|
||||
codeGen.call(translator.objectRuntimeType.reference);
|
||||
codeGen.call(translator.getMasqueradedRuntimeType.reference);
|
||||
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
|
||||
if (member == translator.coreTypes.identicalProcedure) {
|
||||
w.Local first = paramLocals[0];
|
||||
|
|
|
@ -162,12 +162,14 @@ mixin KernelNodes {
|
|||
// dart:core various procedures
|
||||
late final Procedure objectNoSuchMethod =
|
||||
index.getProcedure("dart:core", "Object", "noSuchMethod");
|
||||
late final Procedure objectRuntimeType =
|
||||
index.getProcedure("dart:core", "Object", "get:_runtimeType");
|
||||
late final Procedure objectGetTypeArguments =
|
||||
index.getProcedure("dart:core", "Object", "_getTypeArguments");
|
||||
late final Procedure nullToString =
|
||||
index.getProcedure("dart:core", "Object", "_nullToString");
|
||||
late final Procedure nullNoSuchMethod =
|
||||
index.getProcedure("dart:core", "Object", "_nullNoSuchMethod");
|
||||
late final Procedure recordGetRecordRuntimeType =
|
||||
index.getProcedure("dart:core", "Record", "_getRecordRuntimeType");
|
||||
late final Procedure stringEquals =
|
||||
index.getProcedure("dart:core", "_StringBase", "==");
|
||||
late final Procedure stringInterpolate =
|
||||
|
@ -212,6 +214,10 @@ mixin KernelNodes {
|
|||
index.getProcedure("dart:core", "Error", "_throw");
|
||||
|
||||
// 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 =
|
||||
index.getTopLevelProcedure("dart:core", "_isSubtype");
|
||||
late final Procedure isTypeSubtype =
|
||||
|
|
|
@ -30,14 +30,14 @@ import 'package:dart2wasm/records.dart';
|
|||
/// Record_2_a(this.$1, this.$2, this.a);
|
||||
///
|
||||
/// @pragma('wasm:entry-point')
|
||||
/// _Type get _runtimeType =>
|
||||
/// // Uses of `runtimeType` below will be fixed with #51134.
|
||||
/// _Type get _recordRuntimeType =>
|
||||
/// _RecordType(
|
||||
/// const ["a"],
|
||||
/// [$1.runtimeType, $2.runtimeType, a.runtimeType]);
|
||||
///
|
||||
/// @pragma('wasm:entry-point')
|
||||
/// Type get runtimeType => _runtimeType;
|
||||
/// [
|
||||
/// _getMasqueradedRuntimeTypeNullable($1),
|
||||
/// _getMasqueradedRuntimeTypeNullable($2),
|
||||
/// _getMasqueradedRuntimeTypeNullable(a)
|
||||
/// ]);
|
||||
///
|
||||
/// @pragma('wasm:entry-point')
|
||||
/// String toString() =>
|
||||
|
@ -73,9 +73,6 @@ class _RecordClassGenerator {
|
|||
late final Class recordRuntimeTypeClass =
|
||||
coreTypes.index.getClass('dart:core', '_RecordType');
|
||||
|
||||
late final Class internalRuntimeTypeClass =
|
||||
coreTypes.index.getClass('dart:core', '_Type');
|
||||
|
||||
late final Constructor recordRuntimeTypeConstructor =
|
||||
recordRuntimeTypeClass.constructors.single;
|
||||
|
||||
|
@ -85,9 +82,6 @@ class _RecordClassGenerator {
|
|||
late final Procedure objectHashAllProcedure =
|
||||
coreTypes.index.getProcedure('dart:core', 'Object', 'hashAll');
|
||||
|
||||
late final Procedure objectRuntimeTypeProcedure =
|
||||
coreTypes.index.getProcedure('dart:core', 'Object', 'get:runtimeType');
|
||||
|
||||
late final Procedure objectToStringProcedure =
|
||||
coreTypes.index.getProcedure('dart:core', 'Object', 'toString');
|
||||
|
||||
|
@ -96,10 +90,11 @@ class _RecordClassGenerator {
|
|||
late final Procedure stringPlusProcedure =
|
||||
coreTypes.index.getProcedure('dart:core', 'String', '+');
|
||||
|
||||
DartType get nullableObjectType => coreTypes.objectNullableRawType;
|
||||
late final Procedure getMasqueradedRuntimeTypeNullableProcedure = coreTypes
|
||||
.index
|
||||
.getTopLevelProcedure('dart:core', '_getMasqueradedRuntimeTypeNullable');
|
||||
|
||||
DartType get internalRuntimeTypeType =>
|
||||
InterfaceType(internalRuntimeTypeClass, Nullability.nonNullable);
|
||||
DartType get nullableObjectType => coreTypes.objectNullableRawType;
|
||||
|
||||
DartType get nonNullableStringType => coreTypes.stringNonNullableRawType;
|
||||
|
||||
|
@ -150,13 +145,7 @@ class _RecordClassGenerator {
|
|||
));
|
||||
library.addClass(cls);
|
||||
cls.addProcedure(_generateEquals(shape, fields, cls));
|
||||
final internalRuntimeType = _generateInternalRuntimeType(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);
|
||||
cls.addProcedure(_generateRecordRuntimeType(shape, fields));
|
||||
return cls;
|
||||
}
|
||||
|
||||
|
@ -352,8 +341,7 @@ class _RecordClassGenerator {
|
|||
}
|
||||
|
||||
/// Generate `_Type get _runtimeType` member.
|
||||
Procedure _generateInternalRuntimeType(
|
||||
RecordShape shape, List<Field> fields) {
|
||||
Procedure _generateRecordRuntimeType(RecordShape shape, List<Field> fields) {
|
||||
final List<Statement> statements = [];
|
||||
|
||||
// const ["name1", "name2", ...]
|
||||
|
@ -361,16 +349,12 @@ class _RecordClassGenerator {
|
|||
nonNullableStringType,
|
||||
shape.names.map((name) => StringConstant(name)).toList()));
|
||||
|
||||
// Generate `this.field.runtimeType` for a given field.
|
||||
// TODO(51134): We shouldn't use user-provided runtimeType below.
|
||||
Expression fieldRuntimeTypeExpr(Field field) => InstanceGet(
|
||||
InstanceAccessKind.Object,
|
||||
Expression fieldRuntimeTypeExpr(Field field) => StaticInvocation(
|
||||
getMasqueradedRuntimeTypeNullableProcedure,
|
||||
Arguments([
|
||||
InstanceGet(InstanceAccessKind.Instance, ThisExpression(), field.name,
|
||||
interfaceTarget: field, resultType: nullableObjectType),
|
||||
objectRuntimeTypeProcedure.name,
|
||||
interfaceTarget: objectRuntimeTypeProcedure,
|
||||
resultType: runtimeTypeType,
|
||||
);
|
||||
interfaceTarget: field, resultType: nullableObjectType)
|
||||
]));
|
||||
|
||||
// [this.$1.runtimeType, this.x.runtimeType, ...]
|
||||
final fieldTypesList = ListLiteral(
|
||||
|
@ -394,26 +378,7 @@ class _RecordClassGenerator {
|
|||
);
|
||||
|
||||
return _addWasmEntryPointPragma(Procedure(
|
||||
Name('_runtimeType', 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),
|
||||
Name('_recordRuntimeType', library),
|
||||
ProcedureKind.Getter,
|
||||
function,
|
||||
fileUri: library.fileUri,
|
||||
|
|
|
@ -571,9 +571,12 @@ class Translator with KernelNodes {
|
|||
translateStorageType(type), type.toText(defaultAstTextStrategy));
|
||||
}
|
||||
|
||||
w.ArrayType wasmArrayType(w.StorageType type, String name) {
|
||||
return arrayTypeCache.putIfAbsent(type,
|
||||
() => m.addArrayType("Array<$name>", elementType: w.FieldType(type)));
|
||||
w.ArrayType wasmArrayType(w.StorageType type, String name,
|
||||
{bool mutable = true}) {
|
||||
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
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'dart:math' show max;
|
||||
import 'dart:typed_data' show Uint8List;
|
||||
|
||||
import 'package:dart2wasm/class_info.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;
|
||||
|
||||
/// 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 {
|
||||
final Map<TypeParameter, int> _typeOffsets = {};
|
||||
|
||||
|
@ -87,6 +99,9 @@ class Types {
|
|||
/// parameter index range of their corresponding function type.
|
||||
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);
|
||||
|
||||
w.ValueType classAndFieldToType(Class cls, int fieldIndex) =>
|
||||
|
@ -256,6 +271,53 @@ class Types {
|
|||
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) =>
|
||||
type.parameter.parent == null;
|
||||
|
||||
|
|
|
@ -4,18 +4,6 @@
|
|||
|
||||
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")
|
||||
final class _BoxedBool extends 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.
|
||||
external factory _BoxedDouble();
|
||||
|
||||
@override
|
||||
Type get runtimeType => double;
|
||||
|
||||
static const int _mantissaBits = 52;
|
||||
static const int _exponentBits = 11;
|
||||
static const int _exponentBias = 1023;
|
||||
|
|
|
@ -7,9 +7,7 @@ part of 'core_patch.dart';
|
|||
@patch
|
||||
class Error {
|
||||
@patch
|
||||
static String _objectToString(Object object) {
|
||||
return "Instance of '${object._runtimeType}'";
|
||||
}
|
||||
static String _objectToString(Object object) => Object._toString(object);
|
||||
|
||||
@patch
|
||||
static String _stringToSafeString(String string) {
|
||||
|
|
|
@ -34,9 +34,6 @@ final class _BoxedInt extends int {
|
|||
/// Dummy factory to silence error about missing superclass constructor.
|
||||
external factory _BoxedInt();
|
||||
|
||||
@override
|
||||
Type get runtimeType => int;
|
||||
|
||||
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 void _setHash(Object obj, int hash);
|
||||
|
||||
external _Type _getInterfaceTypeRuntimeType(
|
||||
Object object, List<Type> typeArguments);
|
||||
|
||||
external _Type _getFunctionTypeRuntimeType(Object object);
|
||||
|
||||
@patch
|
||||
class Object {
|
||||
@patch
|
||||
|
@ -43,24 +38,21 @@ class Object {
|
|||
/// which return their type arguments.
|
||||
List<_Type> get _typeArguments => const [];
|
||||
|
||||
/// We use [_runtimeType] for internal type testing, because objects can
|
||||
/// override [runtimeType].
|
||||
// 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 List<_Type> _getTypeArguments(Object object) => object._typeArguments;
|
||||
|
||||
@patch
|
||||
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
|
||||
String toString() => _toString(this);
|
||||
// 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
|
||||
@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
|
||||
// in programs without records.
|
||||
@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);
|
||||
|
||||
@override
|
||||
Type get runtimeType => Float32x4List;
|
||||
|
||||
_NaiveFloat32x4List._externalStorage(this._storage);
|
||||
|
||||
_NaiveFloat32x4List._slowFromList(List<Float32x4> list)
|
||||
|
@ -243,9 +240,6 @@ final class _NaiveFloat64x2List extends Object
|
|||
|
||||
_NaiveFloat64x2List(int length) : _storage = Float64List(length * 2);
|
||||
|
||||
@override
|
||||
Type get runtimeType => Float64x2List;
|
||||
|
||||
_NaiveFloat64x2List._externalStorage(this._storage);
|
||||
|
||||
_NaiveFloat64x2List._slowFromList(List<Float64x2> list)
|
||||
|
@ -338,9 +332,6 @@ final class _NaiveFloat32x4 implements Float32x4 {
|
|||
|
||||
_NaiveFloat32x4._truncated(this.x, this.y, this.z, this.w);
|
||||
|
||||
@override
|
||||
Type get runtimeType => Float32x4List;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '[${x.toStringAsFixed(6)}, '
|
||||
|
|
|
@ -95,9 +95,6 @@ abstract final class _StringBase implements String {
|
|||
|
||||
_StringBase._();
|
||||
|
||||
@override
|
||||
Type get runtimeType => String;
|
||||
|
||||
int get hashCode {
|
||||
int hash = _getHash(this);
|
||||
if (hash != 0) return hash;
|
||||
|
|
|
@ -1026,7 +1026,8 @@ _TypeUniverse _typeUniverse = _TypeUniverse.create();
|
|||
|
||||
@pragma("wasm:entry-point")
|
||||
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")
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@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