1
0
mirror of https://github.com/dart-lang/sdk synced 2024-07-08 12:06:26 +00:00

[dart2wasm] Remove usage of type category table

The type category table is a O(number-of-classes) sized byte array that
maps from class-id to either a type category (function, record, ...) or
a masqueraded class-id.

* for flute this table takes up around 1 KB.
* this prevents from making concrete class-ids come before all abstract
  class ids

After recent changes the core RTT implementation no longer involves
masqueraded types (i.e. `<obj> is/as <type>` and `<type> <: <type>`
queries don't trigger masquerading functionality)

This CL removes the type category table, the special casing in the
class-id assignment and the compiler support for building the table.

Instead we move the logic to pure dart code, which can use normal `is`
checks to perform its function.

We add one optimization: The compiler will provide the class-id from
which one only non-masqueraded classes come. This makes the masquerading
function have a fast path.

* We use `Wasm{TypedData,String}Base` marker interfaces i
 `dart:_internal` to check for wasm-backed implementations
* We use `-Ddart.wasm.js_compatibility` to provide JSCM mode

We add a test that actually exercises the 2 modes.

Change-Id: I051c35b17878950402a1336df871a686b649f732
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/349641
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Martin Kustermann 2024-02-02 19:01:49 +00:00 committed by Commit Queue
parent 5b980d6995
commit 0bc92a4333
16 changed files with 324 additions and 344 deletions

View File

@ -7,7 +7,6 @@ import 'dart:math';
import 'package:dart2wasm/translator.dart';
import 'package:kernel/ast.dart';
import 'package:kernel/library_index.dart';
import 'package:wasm_builder/wasm_builder.dart' as w;
@ -217,25 +216,25 @@ 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 = _computeMasquerades();
/// Any subtype of these needs to masqueraded (modulo special js-compatibility
/// mode semantics) or specially treated due to being from a different type
/// (e.g. record, closure)
late final Set<Class> masqueraded = _computeMasquerades();
/// Masqueraded types are mapped to these classes.
late final Set<Class> masqueradeValues = _masquerades.values.toSet();
Map<Class, Class> _computeMasquerades() {
final map = {
translator.coreTypes.boolClass: translator.coreTypes.boolClass,
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
Set<Class> _computeMasquerades() {
final values = {
translator.coreTypes.boolClass,
translator.coreTypes.intClass,
translator.coreTypes.doubleClass,
translator.coreTypes.stringClass,
translator.coreTypes.functionClass,
translator.coreTypes.recordClass,
translator.index.getClass("dart:core", "_Type"),
translator.index.getClass("dart:core", "_ListBase"),
};
for (final name in const <String>[
"ByteBuffer",
"ByteData",
"Int8List",
"Uint8List",
"Uint8ClampedList",
@ -252,49 +251,9 @@ class ClassInfoCollector {
"Float64x2List",
]) {
final Class? cls = translator.index.tryGetClass("dart:typed_data", name);
if (cls != null) {
map[cls] = cls;
}
if (cls != null) values.add(cls);
}
return map;
}
late final Set<Class> _neverMasquerades = _computeNeverMasquerades();
/// These types switch from properly reified non-masquerading types in regular
/// Dart2Wasm mode to masquerading types in js compatibility mode.
final Set<String> jsCompatibilityTypes = {
"JSStringImpl",
"JSArrayBufferImpl",
"JSDataViewImpl",
"JSInt8ArrayImpl",
"JSUint8ArrayImpl",
"JSUint8ClampedArrayImpl",
"JSInt16ArrayImpl",
"JSUint16ArrayImpl",
"JSInt32ArrayImpl",
"JSInt32x4ArrayImpl",
"JSUint32ArrayImpl",
"JSBigUint64ArrayImpl",
"JSBigInt64ArrayImpl",
"JSFloat32ArrayImpl",
"JSFloat32x4ArrayImpl",
"JSFloat64ArrayImpl",
"JSFloat64x2ArrayImpl",
};
Set<Class> _computeNeverMasquerades() {
// The JS types do not masquerade in regular Dart2Wasm, but they aren't
// always used so we have to construct this set programmatically.
final jsTypesLibraryIndex =
LibraryIndex(translator.component, ["dart:_js_types"]);
final neverMasquerades = [
if (!translator.options.jsCompatibility) ...jsCompatibilityTypes,
]
.map((name) => jsTypesLibraryIndex.tryGetClass("dart:_js_types", name))
.toSet();
neverMasquerades.removeWhere((c) => c == null);
return neverMasquerades.cast<Class>();
return values;
}
/// Wasm field type for fields with type [_Type]. Fields of this type are
@ -341,14 +300,14 @@ class ClassInfoCollector {
}
// In the Wasm type hierarchy, Object, bool and num sit directly below
// the Top type. The implementation classes _StringBase and _Type sit
// the Top type. The implementation classes WasmStringBase and _Type sit
// directly below the public classes they implement.
// All other classes sit below their superclass.
ClassInfo superInfo = cls == translator.coreTypes.boolClass ||
cls == translator.coreTypes.numClass
? topInfo
: (!translator.options.jsCompatibility &&
cls == translator.stringBaseClass) ||
cls == translator.wasmStringBaseClass) ||
cls == translator.typeClass
? translator.classInfo[cls.implementedTypes.single.classNode]!
: translator.classInfo[superclass]!;
@ -390,29 +349,6 @@ class ClassInfoCollector {
translator.classes[classId] = info;
translator.classInfo[cls] = info;
translator.classForHeapType.putIfAbsent(info.struct, () => info!);
ClassInfo? computeMasquerade() {
if (_neverMasquerades.contains(cls)) {
return null;
}
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 _createStructForRecordClass(Map<Class, int> classIds, Class cls) {
@ -505,7 +441,7 @@ class ClassInfoCollector {
const int firstClassId = 1;
translator.classIdNumbering =
ClassIdNumbering._number(translator, masqueradeValues, firstClassId);
ClassIdNumbering._number(translator, masqueraded, firstClassId);
final classIds = translator.classIdNumbering.classIds;
final dfsOrder = translator.classIdNumbering.dfsOrder;
@ -560,12 +496,18 @@ class ClassIdNumbering {
final Map<Class, List<Class>> _subclasses;
final Map<Class, List<Class>> _implementors;
final List<Range> _concreteSubclassIdRanges;
final Set<Class> _masqueraded;
final List<Class> dfsOrder;
final Map<Class, int> classIds;
ClassIdNumbering._(this._subclasses, this._implementors,
this._concreteSubclassIdRanges, this.dfsOrder, this.classIds);
ClassIdNumbering._(
this._subclasses,
this._implementors,
this._concreteSubclassIdRanges,
this._masqueraded,
this.dfsOrder,
this.classIds);
final Map<Class, Set<Class>> _transitiveImplementors = {};
Set<Class> _getTransitiveImplementors(Class klass) {
@ -611,8 +553,19 @@ class ClassIdNumbering {
return _concreteClassIdRanges[klass] = ranges;
}
late final int firstNonMasqueradedInterfaceClassCid = (() {
int lastMasqueradedClassId = 0;
for (final cls in _masqueraded) {
final ranges = getConcreteClassIdRanges(cls);
if (ranges.isNotEmpty) {
lastMasqueradedClassId = max(lastMasqueradedClassId, ranges.last.end);
}
}
return lastMasqueradedClassId + 1;
})();
static ClassIdNumbering _number(
Translator translator, Set<Class> masqueradeValues, int firstClassId) {
Translator translator, Set<Class> masqueraded, int firstClassId) {
// Make graph from class to its subclasses.
late final Class root;
final subclasses = <Class, List<Class>>{};
@ -645,7 +598,8 @@ class ClassIdNumbering {
final fixedOrder = <Class, int>{
translator.coreTypes.boolClass: -10,
translator.coreTypes.numClass: -9,
if (!translator.options.jsCompatibility) translator.stringBaseClass: -8,
if (!translator.options.jsCompatibility)
translator.wasmStringBaseClass: -8,
translator.jsStringClass: -7,
translator.typeClass: -6,
translator.listBaseClass: -5,
@ -657,7 +611,7 @@ class ClassIdNumbering {
final importUri = klass.enclosingLibrary.importUri.toString();
if (importUri.startsWith('dart:')) {
if (masqueradeValues.contains(klass)) return -1;
if (masqueraded.contains(klass)) return -1;
// Bundle the typed data and collection together, they may not have
// common base class except for `Object` but most of them have similar
// selectors.
@ -700,26 +654,8 @@ class ClassIdNumbering {
firstClassId + classCount, Range.empty(),
growable: false);
// TODO: We may consider removing the type category table. But until we do
// we have to make sure that all masqueraded types that concrete classes may
// be mapped to have class ids that are <=255.
//
// We still want to maintain that all classes's concrete subclasses form a
// single continious class-id range.
//
// => So we move the abstract masqeraded classes before all the concrete
// ones.
int nextAbstractClassId = firstClassId;
for (Class cls in masqueradeValues) {
if (cls.isAbstract) {
assert(classIds[cls] == null);
classIds[cls] = nextAbstractClassId++;
}
}
int nextConcreteClassId = nextAbstractClassId;
nextAbstractClassId = firstClassId +
(nextAbstractClassId - firstClassId) +
concreteClassCount;
int nextConcreteClassId = firstClassId;
int nextAbstractClassId = firstClassId + concreteClassCount;
dfs(root, (Class cls) {
dfsOrder.add(cls);
if (cls.isAbstract) {
@ -740,11 +676,9 @@ class ClassIdNumbering {
concreteSubclassRanges[classIds[cls]!] = range;
}
});
for (Class cls in masqueradeValues) {
assert(classIds[cls]! <= 255);
}
return ClassIdNumbering._(
subclasses, implementors, concreteSubclassRanges, dfsOrder, classIds);
return ClassIdNumbering._(subclasses, implementors, concreteSubclassRanges,
masqueraded, dfsOrder, classIds);
}
}

View File

@ -3803,3 +3803,46 @@ enum _VirtualCallKind {
bool get isSetter => this == _VirtualCallKind.Set;
}
extension MacroAssembler on w.InstructionsBuilder {
// Expects there to be a i32 on the stack, will consume it and leave
// true/false on the stack.
void emitClassIdRangeCheck(CodeGenerator codeGen, List<Range> ranges) {
if (ranges.isEmpty) {
drop();
i32_const(0);
} else if (ranges.length == 1) {
final range = ranges[0];
i32_const(range.start);
if (range.length == 1) {
i32_eq();
} else {
i32_sub();
i32_const(range.length);
i32_lt_u();
}
} else {
w.Local idLocal = codeGen.addLocal(w.NumType.i32);
local_set(idLocal);
w.Label done = block(const [], const [w.NumType.i32]);
i32_const(1);
for (Range range in ranges) {
local_get(idLocal);
i32_const(range.start);
if (range.length == 1) {
i32_eq();
} else {
i32_sub();
i32_const(range.length);
i32_lt_u();
}
br_if(done);
}
drop();
i32_const(0);
end(); // done
}
}
}

View File

@ -18,7 +18,7 @@ class WasmCompilerOptions {
String outputFile;
String? depFile;
String? outputJSRuntimeFile;
Map<String, String> environment = const {};
Map<String, String> environment = {};
Map<fe.ExperimentalFlag, bool> feExperimentalFlags = const {};
String? multiRootScheme;
List<Uri> multiRoots = const [];

View File

@ -36,9 +36,10 @@ final List<Option> options = [
defaultsTo: _d.translatorOptions.printKernel),
Flag("print-wasm", (o, value) => o.translatorOptions.printWasm = value,
defaultsTo: _d.translatorOptions.printWasm),
Flag("js-compatibility",
(o, value) => o.translatorOptions.jsCompatibility = value,
defaultsTo: _d.translatorOptions.jsCompatibility),
Flag("js-compatibility", (o, value) {
o.translatorOptions.jsCompatibility = value;
o.environment['dart.wasm.js_compatibility'] = 'true';
}, defaultsTo: _d.translatorOptions.jsCompatibility),
Flag(
"enable-asserts", (o, value) => o.translatorOptions.enableAsserts = value,
defaultsTo: _d.translatorOptions.enableAsserts),
@ -73,7 +74,7 @@ final List<Option> options = [
IntMultiOption(
"watch", (o, values) => o.translatorOptions.watchPoints = values),
StringMultiOption(
"define", (o, values) => o.environment = processEnvironment(values),
"define", (o, values) => o.environment.addAll(processEnvironment(values)),
abbr: "D"),
StringMultiOption(
"enable-experiment",

View File

@ -404,24 +404,35 @@ class Intrinsifier {
/// Generate inline code for a [StaticGet] if the member is an inlined
/// intrinsic.
w.ValueType? generateStaticGetterIntrinsic(StaticGet node) {
Member target = node.target;
final Member target = node.target;
final Class? cls = target.enclosingClass;
// ClassID getters
String? libAndClassName = translator.getPragma(target, "wasm:class-id");
if (libAndClassName != null) {
List<String> libAndClassNameParts = libAndClassName.split("#");
final String lib = libAndClassNameParts[0];
final String className = libAndClassNameParts[1];
Class cls = translator.libraries
.firstWhere((l) => l.name == lib && l.importUri.scheme == 'dart',
orElse: () => throw 'Library $lib not found (${target.location})')
.classes
.firstWhere((c) => c.name == className,
orElse: () => throw 'Class $className not found in library $lib '
'(${target.location})');
int classId = translator.classInfo[cls]!.classId;
b.i64_const(classId);
return w.NumType.i64;
if (cls?.name == 'ClassID') {
final libAndClassName = translator.getPragma(target, "wasm:class-id");
if (libAndClassName != null) {
List<String> libAndClassNameParts = libAndClassName.split("#");
final String lib = libAndClassNameParts[0];
final String className = libAndClassNameParts[1];
Class cls = translator.libraries
.firstWhere((l) => l.name == lib && l.importUri.scheme == 'dart',
orElse: () =>
throw 'Library $lib not found (${target.location})')
.classes
.firstWhere((c) => c.name == className,
orElse: () =>
throw 'Class $className not found in library $lib '
'(${target.location})');
int classId = translator.classInfo[cls]!.classId;
b.i64_const(classId);
return w.NumType.i64;
}
if (target.name.text == 'firstNonMasqueradedInterfaceClassCid') {
b.i64_const(
translator.classIdNumbering.firstNonMasqueradedInterfaceClassCid);
return w.NumType.i64;
}
}
// nullptr
@ -440,35 +451,6 @@ class Intrinsifier {
b.i32_const(0);
return w.NumType.i32;
}
if (node.target.enclosingLibrary == translator.coreTypes.coreLibrary) {
switch (node.target.name.text) {
case "_typeCategoryAbstractClass":
translator.types.typeCategoryTable;
b.i32_const(translator.types.typeCategoryAbstractClass);
return w.NumType.i32;
case "_typeCategoryObject":
translator.types.typeCategoryTable;
b.i32_const(translator.types.typeCategoryObject);
return w.NumType.i32;
case "_typeCategoryFunction":
translator.types.typeCategoryTable;
b.i32_const(translator.types.typeCategoryFunction);
return w.NumType.i32;
case "_typeCategoryRecord":
translator.types.typeCategoryTable;
b.i32_const(translator.types.typeCategoryRecord);
return w.NumType.i32;
case "_typeCategoryNotMasqueraded":
translator.types.typeCategoryTable;
b.i32_const(translator.types.typeCategoryNotMasqueraded);
return w.NumType.i32;
case "_typeCategoryTable":
translator.types.typeCategoryTable;
b.global_get(translator.types.typeCategoryTable);
return translator.types.typeCategoryTable.type.type;
}
}
}
return null;
@ -659,16 +641,38 @@ class Intrinsifier {
return translator.types.makeTypeRulesSubstitutions(b);
case "_getTypeNames":
return translator.types.makeTypeNames(b);
case "_isRecordInstance":
Expression o = node.arguments.positional.single;
b.global_get(translator.types.typeCategoryTable);
codeGen.wrap(o, translator.topInfo.nonNullableType);
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
b.array_get_u(
(translator.types.typeCategoryTable.type.type as w.RefType)
.heapType as w.ArrayType);
b.i32_const(translator.types.typeCategoryRecord);
b.i32_eq();
case "_isObjectClassId":
final classId = node.arguments.positional.single;
final objectClassId = translator
.classIdNumbering.classIds[translator.coreTypes.objectClass]!;
codeGen.wrap(classId, w.NumType.i64);
b.i32_wrap_i64();
b.emitClassIdRangeCheck(
codeGen, [Range(objectClassId, objectClassId)]);
return w.NumType.i32;
case "_isClosureClassId":
final classId = node.arguments.positional.single;
final ranges = translator.classIdNumbering
.getConcreteClassIdRanges(translator.coreTypes.functionClass);
assert(ranges.length <= 1);
codeGen.wrap(classId, w.NumType.i64);
b.i32_wrap_i64();
b.emitClassIdRangeCheck(codeGen, ranges);
return w.NumType.i32;
case "_isRecordClassId":
final classId = node.arguments.positional.single;
final ranges = translator.classIdNumbering
.getConcreteClassIdRanges(translator.coreTypes.recordClass);
assert(ranges.length <= 1);
codeGen.wrap(classId, w.NumType.i64);
b.i32_wrap_i64();
b.emitClassIdRangeCheck(codeGen, ranges);
return w.NumType.i32;
}
}

View File

@ -50,6 +50,8 @@ mixin KernelNodes {
index.getClass("dart:core", "_GrowableList");
late final Class immutableListClass =
index.getClass("dart:core", "_ImmutableList");
late final Class wasmStringBaseClass =
index.getClass("dart:_internal", "WasmStringBase");
late final Class stringBaseClass =
index.getClass("dart:_string", "StringBase");
late final Class oneByteStringClass =

View File

@ -3,7 +3,6 @@
// 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';
@ -101,9 +100,6 @@ class Types {
/// parameter index range of their corresponding function type.
Map<StructuralParameter, 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) =>
@ -288,77 +284,6 @@ class Types {
return typeNamesType;
}
// None of these integers belong to masqueraded types like Uint8List.
late final int typeCategoryAbstractClass;
late final int typeCategoryObject;
late final int typeCategoryFunction;
late final int typeCategoryRecord;
late final int typeCategoryNotMasqueraded;
/// Build a global array of byte values used to categorize runtime types.
w.Global _buildTypeCategoryTable() {
// Find 5 class ids whose classes are not masqueraded.
final typeCategories = <int>[];
for (int i = 0; i < translator.classes.length; i++) {
final info = translator.classes[i];
if (!translator.classInfoCollector.masqueradeValues.contains(info.cls)) {
typeCategories.add(i);
if (typeCategories.length == 5) break;
}
}
assert(typeCategories.length == 5 && typeCategories.last <= 255);
typeCategoryAbstractClass = typeCategories[0];
typeCategoryObject = typeCategories[1];
typeCategoryFunction = typeCategories[2];
typeCategoryRecord = typeCategories[3];
typeCategoryNotMasqueraded = typeCategories[4];
Set<Class> recordClasses = Set.from(translator.recordClasses.values);
Uint8List table = Uint8List(translator.classes.length);
for (int i = 0; i < translator.classes.length; i++) {
ClassInfo info = translator.classes[i];
ClassInfo? masquerade = info.masquerade;
Class? cls = info.cls;
int category;
if (cls == null || cls.isAbstract) {
category = typeCategoryAbstractClass;
} else if (cls == coreTypes.objectClass) {
category = typeCategoryObject;
} else if (cls == translator.closureClass) {
category = typeCategoryFunction;
} else if (recordClasses.contains(cls)) {
category = typeCategoryRecord;
} else if (masquerade == null || masquerade.classId == i) {
category = typeCategoryNotMasqueraded;
} else {
// Masqueraded class
assert(cls.enclosingLibrary.importUri.scheme == "dart");
assert(!typeCategories.contains(masquerade.classId));
assert(masquerade.classId <= 255);
category = masquerade.classId;
}
table[i] = category;
}
final segment = translator.m.dataSegments.define(table);
w.ArrayType arrayType = translator.arrayTypeForDartType(
InterfaceType(translator.wasmI8Class, Nullability.nonNullable));
final global = translator.m.globals
.define(w.GlobalType(w.RefType.def(arrayType, nullable: false)));
// Initialize the global to a dummy array, since `array.new_data` is not
// 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.
final 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 _isTypeConstant(DartType type) {
return type is DynamicType ||
type is VoidType ||
@ -727,44 +652,8 @@ class Types {
} else {
final ranges =
translator.classIdNumbering.getConcreteClassIdRanges(interfaceClass);
if (ranges.isEmpty) {
b.drop();
b.i32_const(0);
} else if (ranges.length == 1) {
final range = ranges[0];
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
b.i32_const(range.start);
if (range.length == 1) {
b.i32_eq();
} else {
b.i32_sub();
b.i32_const(range.length);
b.i32_lt_u();
}
} else {
w.Local idLocal = codeGen.addLocal(w.NumType.i32);
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
b.local_set(idLocal);
w.Label done = b.block(const [], const [w.NumType.i32]);
b.i32_const(1);
for (Range range in ranges) {
b.local_get(idLocal);
b.i32_const(range.start);
if (range.length == 1) {
b.i32_eq();
} else {
b.i32_sub();
b.i32_const(range.length);
b.i32_lt_u();
}
b.br_if(done);
}
b.drop();
b.i32_const(0);
b.end(); // done
}
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
b.emitClassIdRangeCheck(codeGen, ranges);
}
if (isPotentiallyNullable) {

View File

@ -26,14 +26,14 @@ class ClassID {
external static int get cid_Int8List;
@pragma("wasm:class-id", "dart.typed_data#_Int8ArrayView")
external static int get cidInt8ArrayView;
@pragma("wasm:class-id", "dart.core#Object")
external static int get cidObject;
@pragma("wasm:class-id", "dart.async#Future")
external static int get cidFuture;
@pragma("wasm:class-id", "dart.core#Function")
external static int get cidFunction;
@pragma("wasm:class-id", "dart.core#_Closure")
external static int get cid_Closure;
@pragma("wasm:class-id", "dart.core#List")
external static int get cidList;
@pragma("wasm:class-id", "dart.core#_List")
external static int get cidFixedLengthList;
@pragma("wasm:class-id", "dart.core#_ListBase")
@ -69,6 +69,10 @@ class ClassID {
@pragma("wasm:class-id", "dart.core#_NamedParameter")
external static int get cidNamedParameter;
// From this class id onwards, all concrete classes are interface classes and
// do not need to be masqueraded.
external static int get firstNonMasqueradedInterfaceClassCid;
// Dummy, only used by VM-specific hash table code.
static final int numPredefinedCids = 1;
}

View File

@ -21,7 +21,9 @@ import "dart:_internal"
makeFixedListUnmodifiable,
makeListFixedLength,
patch,
unsafeCast;
unsafeCast,
WasmStringBase,
WasmTypedDataBase;
import "dart:_internal" as _internal show Symbol;
@ -45,7 +47,7 @@ import 'dart:convert' show Encoding, utf8;
import 'dart:math' show Random;
import "dart:typed_data" show Uint8List, Uint16List;
import "dart:typed_data";
import 'dart:_object_helper';
import 'dart:_string_helper';

View File

@ -36,6 +36,12 @@ class Lists {
}
}
// Base class for any wasm-backed typed data implementation class.
abstract class WasmTypedDataBase {}
// Base class for any wasm-backed string implementation class.
abstract class WasmStringBase implements String {}
// This function can be used to skip implicit or explicit checked down casts in
// the parts of the core library implementation where we know by construction
// the type of a value.

View File

@ -10,8 +10,9 @@ import 'dart:_internal'
import 'dart:collection' show ListMixin;
import 'dart:math' as math;
import 'dart:typed_data';
import 'dart:_internal' show WasmTypedDataBase;
final class NaiveInt32x4List
final class NaiveInt32x4List extends WasmTypedDataBase
with ListMixin<Int32x4>, FixedLengthListMixin<Int32x4>
implements Int32x4List {
final Int32List _storage;
@ -116,7 +117,7 @@ final class NaiveUnmodifiableInt32x4List extends NaiveInt32x4List
ByteBuffer get buffer => UnmodifiableByteBufferView(super.buffer);
}
final class NaiveFloat32x4List
final class NaiveFloat32x4List extends WasmTypedDataBase
with ListMixin<Float32x4>, FixedLengthListMixin<Float32x4>
implements Float32x4List {
final Float32List _storage;
@ -222,7 +223,7 @@ final class NaiveUnmodifiableFloat32x4List extends NaiveFloat32x4List
ByteBuffer get buffer => UnmodifiableByteBufferView(super.buffer);
}
final class NaiveFloat64x2List
final class NaiveFloat64x2List extends WasmTypedDataBase
with ListMixin<Float64x2>, FixedLengthListMixin<Float64x2>
implements Float64x2List {
final Float64List _storage;
@ -322,7 +323,7 @@ final class NaiveUnmodifiableFloat64x2List extends NaiveFloat64x2List
ByteBuffer get buffer => UnmodifiableByteBufferView(super.buffer);
}
final class NaiveFloat32x4 implements Float32x4 {
final class NaiveFloat32x4 extends WasmTypedDataBase implements Float32x4 {
final double x;
final double y;
final double z;
@ -614,7 +615,7 @@ final class NaiveFloat32x4 implements Float32x4 {
}
}
final class NaiveFloat64x2 implements Float64x2 {
final class NaiveFloat64x2 extends WasmTypedDataBase implements Float64x2 {
final double x;
final double y;
@ -688,7 +689,7 @@ final class NaiveFloat64x2 implements Float64x2 {
Float64x2 sqrt() => NaiveFloat64x2._doubles(math.sqrt(x), math.sqrt(y));
}
final class NaiveInt32x4 implements Int32x4 {
final class NaiveInt32x4 extends WasmTypedDataBase implements Int32x4 {
final int x;
final int y;
final int z;

View File

@ -10,7 +10,8 @@ import "dart:_internal"
ClassID,
EfficientLengthIterable,
makeListFixedLength,
unsafeCast;
unsafeCast,
WasmStringBase;
import 'dart:_js_helper' show JS;
import 'dart:_js_types' show JSStringImpl;
@ -70,7 +71,7 @@ String _toLowerCase(String string) => JS<String>(
* [StringBase] contains common methods used by concrete String
* implementations, e.g., OneByteString.
*/
abstract final class StringBase implements String {
abstract final class StringBase extends WasmStringBase {
bool _isWhitespace(int codeUnit);
// Constants used by replaceAll encoding of string slices between matches.

View File

@ -515,7 +515,7 @@ class _AbstractRecordType extends _Type {
@override
bool _checkInstance(Object o) {
return _isRecordInstance(o);
return _isRecordClassId(ClassID.getID(o));
}
@override
@ -537,7 +537,7 @@ class _RecordType extends _Type {
@override
bool _checkInstance(Object o) {
if (!_isRecordInstance(o)) return false;
if (!_isRecordClassId(ClassID.getID(o))) return false;
return unsafeCast<Record>(o)._checkRecordType(fieldTypes, names);
}
@ -605,13 +605,6 @@ external WasmArray<WasmArray<WasmI32>> _getTypeRulesSupers();
external WasmArray<WasmArray<WasmArray<_Type>>> _getTypeRulesSubstitutions();
external WasmArray<String>? _getTypeNames();
external WasmArray<WasmI8> get _typeCategoryTable;
external WasmI32 get _typeCategoryAbstractClass;
external WasmI32 get _typeCategoryObject;
external WasmI32 get _typeCategoryFunction;
external WasmI32 get _typeCategoryRecord;
external WasmI32 get _typeCategoryNotMasqueraded;
/// Type parameter environment used while comparing function types.
///
/// In the case of nested function types, the environment refers to the
@ -1299,20 +1292,14 @@ void _checkClosureType(
_Type _getActualRuntimeType(Object object) {
final classId = ClassID.getID(object);
final category = _typeCategoryTable.readUnsigned(classId);
if (category == _typeCategoryFunction.toIntUnsigned()) {
if (_isObjectClassId(classId)) return _literal<Object>();
if (_isRecordClassId(classId)) {
return Record._getMasqueradedRecordRuntimeType(unsafeCast<Record>(object));
}
if (_isClosureClassId(classId)) {
return _Closure._getClosureRuntimeType(unsafeCast<_Closure>(object));
}
if (category == _typeCategoryRecord.toIntUnsigned()) {
return Record._getRecordRuntimeType(unsafeCast<Record>(object));
}
if (category == _typeCategoryObject.toIntUnsigned()) {
return _literal<Object>();
}
if (category == _typeCategoryAbstractClass.toIntUnsigned()) {
throw 'unreachable';
}
return _InterfaceType(classId, false, Object._getTypeArguments(object));
}
@ -1323,28 +1310,98 @@ _Type _getActualRuntimeTypeNullable(Object? object) =>
@pragma("wasm:entry-point")
_Type _getMasqueradedRuntimeType(Object object) {
final classId = ClassID.getID(object);
final category = _typeCategoryTable.readUnsigned(classId);
if (category == _typeCategoryNotMasqueraded.toIntUnsigned()) {
// Fast path: Most usages of `.runtimeType` may be on user-defined classes
// (e.g. `Widget.runtimeType`, ...)
if (ClassID.firstNonMasqueradedInterfaceClassCid <= classId) {
// Non-masqueraded interface type.
return _InterfaceType(classId, false, Object._getTypeArguments(object));
}
if (category == _typeCategoryFunction.toIntUnsigned()) {
return _Closure._getClosureRuntimeType(unsafeCast<_Closure>(object));
}
if (category == _typeCategoryRecord.toIntUnsigned()) {
if (_isObjectClassId(classId)) return _literal<Object>();
if (_isRecordClassId(classId)) {
return Record._getMasqueradedRecordRuntimeType(unsafeCast<Record>(object));
}
if (category == _typeCategoryObject.toIntUnsigned()) {
return _literal<Object>();
if (_isClosureClassId(classId)) {
return _Closure._getClosureRuntimeType(unsafeCast<_Closure>(object));
}
if (category == _typeCategoryAbstractClass.toIntUnsigned()) {
throw 'unreachable';
// The object is a normal instance of a class (not function or record).
const bool isJsCompatibility =
bool.fromEnvironment('dart.wasm.js_compatibility');
// This method is not used in the RTT implementation, it's purely used for
// producing `Type` objects for `<obj>.runtimeType`.
//
// => We can use normal `is` checks in here that will be desugared to class-id
// range checks.
if (object is bool) return _literal<bool>();
if (object is int) return _literal<int>();
if (object is double) return _literal<double>();
if (object is _Type) return _literal<Type>();
if (object is _ListBase) {
return _InterfaceType(
ClassID.cidList, false, Object._getTypeArguments(object));
}
return _InterfaceType(category, false, Object._getTypeArguments(object));
if (isJsCompatibility) {
if (object is String) return _literal<String>();
if (object is TypedData) {
if (object is ByteData) return _literal<ByteData>();
if (object is Int8List) return _literal<Int8List>();
if (object is Uint8List) return _literal<Uint8List>();
if (object is Uint8ClampedList) return _literal<Uint8ClampedList>();
if (object is Int16List) return _literal<Int16List>();
if (object is Uint16List) return _literal<Uint16List>();
if (object is Int32List) return _literal<Int32List>();
if (object is Uint32List) return _literal<Uint32List>();
if (object is Int64List) return _literal<Int64List>();
if (object is Uint64List) return _literal<Uint64List>();
if (object is Float32List) return _literal<Float32List>();
if (object is Float64List) return _literal<Float64List>();
if (object is Int32x4List) return _literal<Int32x4List>();
if (object is Float32x4List) return _literal<Float32x4List>();
if (object is Float64x2List) return _literal<Float64x2List>();
}
if (object is ByteBuffer) return _literal<ByteBuffer>();
if (object is Float32x4) return _literal<Float32x4>();
if (object is Float64x2) return _literal<Float64x2>();
if (object is Int32x4) return _literal<Int32x4>();
} else {
if (object is WasmStringBase) return _literal<String>();
if (object is WasmTypedDataBase) {
if (object is ByteData) return _literal<ByteData>();
if (object is Int8List) return _literal<Int8List>();
if (object is Uint8List) return _literal<Uint8List>();
if (object is Uint8ClampedList) return _literal<Uint8ClampedList>();
if (object is Int16List) return _literal<Int16List>();
if (object is Uint16List) return _literal<Uint16List>();
if (object is Int32List) return _literal<Int32List>();
if (object is Uint32List) return _literal<Uint32List>();
if (object is Int64List) return _literal<Int64List>();
if (object is Uint64List) return _literal<Uint64List>();
if (object is Float32List) return _literal<Float32List>();
if (object is Float64List) return _literal<Float64List>();
if (object is Int32x4List) return _literal<Int32x4List>();
if (object is Float32x4List) return _literal<Float32x4List>();
if (object is Float64x2List) return _literal<Float64x2List>();
if (object is ByteBuffer) return _literal<ByteBuffer>();
if (object is Float32x4) return _literal<Float32x4>();
if (object is Float64x2) return _literal<Float64x2>();
if (object is Int32x4) return _literal<Int32x4>();
}
}
// Non-masqueraded interface type.
return _InterfaceType(classId, false, Object._getTypeArguments(object));
}
@pragma("wasm:prefer-inline")
_Type _getMasqueradedRuntimeTypeNullable(Object? object) =>
object == null ? _literal<Null>() : _getMasqueradedRuntimeType(object);
external bool _isRecordInstance(Object o);
external bool _isObjectClassId(int classId);
external bool _isClosureClassId(int classId);
external bool _isRecordClassId(int classId);

View File

@ -22,6 +22,7 @@ import 'dart:_internal'
SubListIterable,
TakeWhileIterable,
unsafeCast,
WasmTypedDataBase,
WhereIterable,
WhereTypeIterable;
import 'dart:_simd';
@ -101,7 +102,7 @@ final class _TypedListIterator<E> implements Iterator<E> {
/// and [_setUint8Unchecked] methods. Implementations should implement these
/// methods and override get/set methods for elements matching the buffer
/// element type to provide fast access.
abstract class ByteDataBase implements ByteData {
abstract class ByteDataBase extends WasmTypedDataBase implements ByteData {
final int offsetInBytes;
final int lengthInBytes;
@ -964,7 +965,7 @@ class _UnmodifiableF64ByteData extends _F64ByteData
/// Base class for [ByteBuffer] implementations. Returns slow lists in all
/// methods. Implementations should override relevant methods to return fast
/// lists when possible and implement [asByteData].
abstract class ByteBufferBase extends ByteBuffer {
abstract class ByteBufferBase extends WasmTypedDataBase implements ByteBuffer {
final int lengthInBytes;
final bool _mutable;
@ -1332,7 +1333,8 @@ class _F64ByteBuffer extends ByteBufferBase {
}
}
class UnmodifiableByteBuffer implements UnmodifiableByteBufferView {
class UnmodifiableByteBuffer extends WasmTypedDataBase
implements UnmodifiableByteBufferView {
final ByteBufferBase _buffer;
UnmodifiableByteBuffer(ByteBufferBase buffer) : _buffer = buffer._immutable();
@ -2242,7 +2244,7 @@ mixin _UnmodifiableDoubleListMixin {
// Fast lists
//
abstract class _WasmI8ArrayBase {
abstract class _WasmI8ArrayBase extends WasmTypedDataBase {
final WasmArray<WasmI8> _data;
final int _offsetInElements;
final int length;
@ -2260,7 +2262,7 @@ abstract class _WasmI8ArrayBase {
ByteBuffer get buffer => _I8ByteBuffer(_data);
}
abstract class _WasmI16ArrayBase {
abstract class _WasmI16ArrayBase extends WasmTypedDataBase {
final WasmArray<WasmI16> _data;
final int _offsetInElements;
final int length;
@ -2278,7 +2280,7 @@ abstract class _WasmI16ArrayBase {
ByteBuffer get buffer => _I16ByteBuffer(_data);
}
abstract class _WasmI32ArrayBase {
abstract class _WasmI32ArrayBase extends WasmTypedDataBase {
final WasmArray<WasmI32> _data;
final int _offsetInElements;
final int length;
@ -2296,7 +2298,7 @@ abstract class _WasmI32ArrayBase {
ByteBuffer get buffer => _I32ByteBuffer(_data);
}
abstract class _WasmI64ArrayBase {
abstract class _WasmI64ArrayBase extends WasmTypedDataBase {
final WasmArray<WasmI64> _data;
final int _offsetInElements;
final int length;
@ -2314,7 +2316,7 @@ abstract class _WasmI64ArrayBase {
ByteBuffer get buffer => _I64ByteBuffer(_data);
}
abstract class _WasmF32ArrayBase {
abstract class _WasmF32ArrayBase extends WasmTypedDataBase {
final WasmArray<WasmF32> _data;
final int _offsetInElements;
final int length;
@ -2332,7 +2334,7 @@ abstract class _WasmF32ArrayBase {
ByteBuffer get buffer => _F32ByteBuffer(_data);
}
abstract class _WasmF64ArrayBase {
abstract class _WasmF64ArrayBase extends WasmTypedDataBase {
final WasmArray<WasmF64> _data;
final int _offsetInElements;
final int length;
@ -2931,7 +2933,7 @@ class UnmodifiableF64List extends F64List
// Slow lists
//
class _SlowListBase {
class _SlowListBase extends WasmTypedDataBase {
final ByteBuffer buffer;
final int offsetInBytes;
final int length;

View File

@ -0,0 +1,35 @@
// Copyright (c) 2024, 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.
import 'dart:typed_data';
import 'dart:js_interop';
import 'package:expect/expect.dart';
const bool isJsCompatibility =
bool.fromEnvironment('dart.wasm.js_compatibility');
void main() {
final uint8List = Uint8List(10);
final uint8ListView = uint8List.buffer.asUint8List(1, 2);
final uint16ListView = uint8List.buffer.asUint16List(2, 4);
Expect.equals('Uint8List', uint8List.runtimeType.toString());
Expect.equals('Uint8List', uint8ListView.runtimeType.toString());
Expect.equals('Uint16List', uint16ListView.runtimeType.toString());
final jsString = eval('"foobar"').dartify();
final jsTypedData = eval('new Uint8Array(10)').dartify();
if (isJsCompatibility) {
Expect.equals('String', jsString.runtimeType.toString());
Expect.equals('Uint8List', jsTypedData.runtimeType.toString());
} else {
Expect.equals('JSStringImpl', jsString.runtimeType.toString());
Expect.equals('JSUint8ArrayImpl', jsTypedData.runtimeType.toString());
}
}
@JS()
external JSObject eval(String code);

View File

@ -53,7 +53,6 @@ compile_platform("compile_dart2wasm_platform") {
args = [
"--target=dart2wasm",
"--no-defines",
"dart:core",
"--nnbd-strong",
]
@ -71,7 +70,7 @@ compile_platform("compile_dart2wasm_js_compatibility_platform") {
args = [
"--target=dart2wasm_js_compatibility",
"--no-defines",
"-Ddart.wasm.js_compatibility=true",
"dart:core",
"--nnbd-strong",
]