[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:dart2wasm/translator.dart';
import 'package:kernel/ast.dart'; import 'package:kernel/ast.dart';
import 'package:kernel/library_index.dart';
import 'package:wasm_builder/wasm_builder.dart' as w; import 'package:wasm_builder/wasm_builder.dart' as w;
@ -217,25 +216,25 @@ 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 /// Any subtype of these needs to masqueraded (modulo special js-compatibility
/// subtypes of the key masquerade as the value. /// mode semantics) or specially treated due to being from a different type
late final Map<Class, Class> _masquerades = _computeMasquerades(); /// (e.g. record, closure)
late final Set<Class> masqueraded = _computeMasquerades();
/// Masqueraded types are mapped to these classes. Set<Class> _computeMasquerades() {
late final Set<Class> masqueradeValues = _masquerades.values.toSet(); final values = {
translator.coreTypes.boolClass,
Map<Class, Class> _computeMasquerades() { translator.coreTypes.intClass,
final map = { translator.coreTypes.doubleClass,
translator.coreTypes.boolClass: translator.coreTypes.boolClass, translator.coreTypes.stringClass,
translator.coreTypes.intClass: translator.coreTypes.intClass, translator.coreTypes.functionClass,
translator.coreTypes.doubleClass: translator.coreTypes.doubleClass, translator.coreTypes.recordClass,
translator.coreTypes.stringClass: translator.coreTypes.stringClass, translator.index.getClass("dart:core", "_Type"),
translator.index.getClass("dart:core", "_Type"): translator.index.getClass("dart:core", "_ListBase"),
translator.coreTypes.typeClass,
translator.index.getClass("dart:core", "_ListBase"):
translator.coreTypes.listClass
}; };
for (final name in const <String>[ for (final name in const <String>[
"ByteBuffer",
"ByteData",
"Int8List", "Int8List",
"Uint8List", "Uint8List",
"Uint8ClampedList", "Uint8ClampedList",
@ -252,49 +251,9 @@ class ClassInfoCollector {
"Float64x2List", "Float64x2List",
]) { ]) {
final Class? cls = translator.index.tryGetClass("dart:typed_data", name); final Class? cls = translator.index.tryGetClass("dart:typed_data", name);
if (cls != null) { if (cls != null) values.add(cls);
map[cls] = cls;
}
} }
return map; return values;
}
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>();
} }
/// 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
@ -341,14 +300,14 @@ class ClassInfoCollector {
} }
// In the Wasm type hierarchy, Object, bool and num sit directly below // 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. // directly below the public classes they implement.
// All other classes sit below their superclass. // All other classes sit below their superclass.
ClassInfo superInfo = cls == translator.coreTypes.boolClass || ClassInfo superInfo = cls == translator.coreTypes.boolClass ||
cls == translator.coreTypes.numClass cls == translator.coreTypes.numClass
? topInfo ? topInfo
: (!translator.options.jsCompatibility && : (!translator.options.jsCompatibility &&
cls == translator.stringBaseClass) || cls == translator.wasmStringBaseClass) ||
cls == translator.typeClass cls == translator.typeClass
? translator.classInfo[cls.implementedTypes.single.classNode]! ? translator.classInfo[cls.implementedTypes.single.classNode]!
: translator.classInfo[superclass]!; : translator.classInfo[superclass]!;
@ -390,29 +349,6 @@ class ClassInfoCollector {
translator.classes[classId] = info; translator.classes[classId] = info;
translator.classInfo[cls] = info; translator.classInfo[cls] = info;
translator.classForHeapType.putIfAbsent(info.struct, () => 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) { void _createStructForRecordClass(Map<Class, int> classIds, Class cls) {
@ -505,7 +441,7 @@ class ClassInfoCollector {
const int firstClassId = 1; const int firstClassId = 1;
translator.classIdNumbering = translator.classIdNumbering =
ClassIdNumbering._number(translator, masqueradeValues, firstClassId); ClassIdNumbering._number(translator, masqueraded, firstClassId);
final classIds = translator.classIdNumbering.classIds; final classIds = translator.classIdNumbering.classIds;
final dfsOrder = translator.classIdNumbering.dfsOrder; final dfsOrder = translator.classIdNumbering.dfsOrder;
@ -560,12 +496,18 @@ class ClassIdNumbering {
final Map<Class, List<Class>> _subclasses; final Map<Class, List<Class>> _subclasses;
final Map<Class, List<Class>> _implementors; final Map<Class, List<Class>> _implementors;
final List<Range> _concreteSubclassIdRanges; final List<Range> _concreteSubclassIdRanges;
final Set<Class> _masqueraded;
final List<Class> dfsOrder; final List<Class> dfsOrder;
final Map<Class, int> classIds; final Map<Class, int> classIds;
ClassIdNumbering._(this._subclasses, this._implementors, ClassIdNumbering._(
this._concreteSubclassIdRanges, this.dfsOrder, this.classIds); this._subclasses,
this._implementors,
this._concreteSubclassIdRanges,
this._masqueraded,
this.dfsOrder,
this.classIds);
final Map<Class, Set<Class>> _transitiveImplementors = {}; final Map<Class, Set<Class>> _transitiveImplementors = {};
Set<Class> _getTransitiveImplementors(Class klass) { Set<Class> _getTransitiveImplementors(Class klass) {
@ -611,8 +553,19 @@ class ClassIdNumbering {
return _concreteClassIdRanges[klass] = ranges; 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( static ClassIdNumbering _number(
Translator translator, Set<Class> masqueradeValues, int firstClassId) { Translator translator, Set<Class> masqueraded, int firstClassId) {
// Make graph from class to its subclasses. // Make graph from class to its subclasses.
late final Class root; late final Class root;
final subclasses = <Class, List<Class>>{}; final subclasses = <Class, List<Class>>{};
@ -645,7 +598,8 @@ class ClassIdNumbering {
final fixedOrder = <Class, int>{ final fixedOrder = <Class, int>{
translator.coreTypes.boolClass: -10, translator.coreTypes.boolClass: -10,
translator.coreTypes.numClass: -9, translator.coreTypes.numClass: -9,
if (!translator.options.jsCompatibility) translator.stringBaseClass: -8, if (!translator.options.jsCompatibility)
translator.wasmStringBaseClass: -8,
translator.jsStringClass: -7, translator.jsStringClass: -7,
translator.typeClass: -6, translator.typeClass: -6,
translator.listBaseClass: -5, translator.listBaseClass: -5,
@ -657,7 +611,7 @@ class ClassIdNumbering {
final importUri = klass.enclosingLibrary.importUri.toString(); final importUri = klass.enclosingLibrary.importUri.toString();
if (importUri.startsWith('dart:')) { 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 // Bundle the typed data and collection together, they may not have
// common base class except for `Object` but most of them have similar // common base class except for `Object` but most of them have similar
// selectors. // selectors.
@ -700,26 +654,8 @@ class ClassIdNumbering {
firstClassId + classCount, Range.empty(), firstClassId + classCount, Range.empty(),
growable: false); growable: false);
// TODO: We may consider removing the type category table. But until we do int nextConcreteClassId = firstClassId;
// we have to make sure that all masqueraded types that concrete classes may int nextAbstractClassId = firstClassId + concreteClassCount;
// 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;
dfs(root, (Class cls) { dfs(root, (Class cls) {
dfsOrder.add(cls); dfsOrder.add(cls);
if (cls.isAbstract) { if (cls.isAbstract) {
@ -740,11 +676,9 @@ class ClassIdNumbering {
concreteSubclassRanges[classIds[cls]!] = range; concreteSubclassRanges[classIds[cls]!] = range;
} }
}); });
for (Class cls in masqueradeValues) {
assert(classIds[cls]! <= 255); return ClassIdNumbering._(subclasses, implementors, concreteSubclassRanges,
} masqueraded, dfsOrder, classIds);
return ClassIdNumbering._(
subclasses, implementors, concreteSubclassRanges, dfsOrder, classIds);
} }
} }

View file

@ -3803,3 +3803,46 @@ enum _VirtualCallKind {
bool get isSetter => this == _VirtualCallKind.Set; 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 outputFile;
String? depFile; String? depFile;
String? outputJSRuntimeFile; String? outputJSRuntimeFile;
Map<String, String> environment = const {}; Map<String, String> environment = {};
Map<fe.ExperimentalFlag, bool> feExperimentalFlags = const {}; Map<fe.ExperimentalFlag, bool> feExperimentalFlags = const {};
String? multiRootScheme; String? multiRootScheme;
List<Uri> multiRoots = const []; List<Uri> multiRoots = const [];

View file

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

View file

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

View file

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

View file

@ -3,7 +3,6 @@
// 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';
@ -101,9 +100,6 @@ class Types {
/// parameter index range of their corresponding function type. /// parameter index range of their corresponding function type.
Map<StructuralParameter, int> functionTypeParameterIndex = Map.identity(); 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); Types(this.translator);
w.ValueType classAndFieldToType(Class cls, int fieldIndex) => w.ValueType classAndFieldToType(Class cls, int fieldIndex) =>
@ -288,77 +284,6 @@ class Types {
return typeNamesType; 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) { bool _isTypeConstant(DartType type) {
return type is DynamicType || return type is DynamicType ||
type is VoidType || type is VoidType ||
@ -727,44 +652,8 @@ class Types {
} else { } else {
final ranges = final ranges =
translator.classIdNumbering.getConcreteClassIdRanges(interfaceClass); translator.classIdNumbering.getConcreteClassIdRanges(interfaceClass);
if (ranges.isEmpty) { b.struct_get(translator.topInfo.struct, FieldIndex.classId);
b.drop(); b.emitClassIdRangeCheck(codeGen, ranges);
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
}
} }
if (isPotentiallyNullable) { if (isPotentiallyNullable) {

View file

@ -26,14 +26,14 @@ class ClassID {
external static int get cid_Int8List; external static int get cid_Int8List;
@pragma("wasm:class-id", "dart.typed_data#_Int8ArrayView") @pragma("wasm:class-id", "dart.typed_data#_Int8ArrayView")
external static int get cidInt8ArrayView; external static int get cidInt8ArrayView;
@pragma("wasm:class-id", "dart.core#Object")
external static int get cidObject;
@pragma("wasm:class-id", "dart.async#Future") @pragma("wasm:class-id", "dart.async#Future")
external static int get cidFuture; external static int get cidFuture;
@pragma("wasm:class-id", "dart.core#Function") @pragma("wasm:class-id", "dart.core#Function")
external static int get cidFunction; external static int get cidFunction;
@pragma("wasm:class-id", "dart.core#_Closure") @pragma("wasm:class-id", "dart.core#_Closure")
external static int get cid_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") @pragma("wasm:class-id", "dart.core#_List")
external static int get cidFixedLengthList; external static int get cidFixedLengthList;
@pragma("wasm:class-id", "dart.core#_ListBase") @pragma("wasm:class-id", "dart.core#_ListBase")
@ -69,6 +69,10 @@ class ClassID {
@pragma("wasm:class-id", "dart.core#_NamedParameter") @pragma("wasm:class-id", "dart.core#_NamedParameter")
external static int get cidNamedParameter; 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. // Dummy, only used by VM-specific hash table code.
static final int numPredefinedCids = 1; static final int numPredefinedCids = 1;
} }

View file

@ -21,7 +21,9 @@ import "dart:_internal"
makeFixedListUnmodifiable, makeFixedListUnmodifiable,
makeListFixedLength, makeListFixedLength,
patch, patch,
unsafeCast; unsafeCast,
WasmStringBase,
WasmTypedDataBase;
import "dart:_internal" as _internal show Symbol; import "dart:_internal" as _internal show Symbol;
@ -45,7 +47,7 @@ import 'dart:convert' show Encoding, utf8;
import 'dart:math' show Random; import 'dart:math' show Random;
import "dart:typed_data" show Uint8List, Uint16List; import "dart:typed_data";
import 'dart:_object_helper'; import 'dart:_object_helper';
import 'dart:_string_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 // 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 parts of the core library implementation where we know by construction
// the type of a value. // the type of a value.

View file

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

View file

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

View file

@ -515,7 +515,7 @@ class _AbstractRecordType extends _Type {
@override @override
bool _checkInstance(Object o) { bool _checkInstance(Object o) {
return _isRecordInstance(o); return _isRecordClassId(ClassID.getID(o));
} }
@override @override
@ -537,7 +537,7 @@ class _RecordType extends _Type {
@override @override
bool _checkInstance(Object o) { bool _checkInstance(Object o) {
if (!_isRecordInstance(o)) return false; if (!_isRecordClassId(ClassID.getID(o))) return false;
return unsafeCast<Record>(o)._checkRecordType(fieldTypes, names); return unsafeCast<Record>(o)._checkRecordType(fieldTypes, names);
} }
@ -605,13 +605,6 @@ external WasmArray<WasmArray<WasmI32>> _getTypeRulesSupers();
external WasmArray<WasmArray<WasmArray<_Type>>> _getTypeRulesSubstitutions(); external WasmArray<WasmArray<WasmArray<_Type>>> _getTypeRulesSubstitutions();
external WasmArray<String>? _getTypeNames(); 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. /// Type parameter environment used while comparing function types.
/// ///
/// In the case of nested function types, the environment refers to the /// In the case of nested function types, the environment refers to the
@ -1299,20 +1292,14 @@ void _checkClosureType(
_Type _getActualRuntimeType(Object object) { _Type _getActualRuntimeType(Object object) {
final classId = ClassID.getID(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)); 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)); return _InterfaceType(classId, false, Object._getTypeArguments(object));
} }
@ -1323,28 +1310,98 @@ _Type _getActualRuntimeTypeNullable(Object? object) =>
@pragma("wasm:entry-point") @pragma("wasm:entry-point")
_Type _getMasqueradedRuntimeType(Object object) { _Type _getMasqueradedRuntimeType(Object object) {
final classId = ClassID.getID(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)); return _InterfaceType(classId, false, Object._getTypeArguments(object));
} }
if (category == _typeCategoryFunction.toIntUnsigned()) {
return _Closure._getClosureRuntimeType(unsafeCast<_Closure>(object)); if (_isObjectClassId(classId)) return _literal<Object>();
} if (_isRecordClassId(classId)) {
if (category == _typeCategoryRecord.toIntUnsigned()) {
return Record._getMasqueradedRecordRuntimeType(unsafeCast<Record>(object)); return Record._getMasqueradedRecordRuntimeType(unsafeCast<Record>(object));
} }
if (category == _typeCategoryObject.toIntUnsigned()) { if (_isClosureClassId(classId)) {
return _literal<Object>(); 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") @pragma("wasm:prefer-inline")
_Type _getMasqueradedRuntimeTypeNullable(Object? object) => _Type _getMasqueradedRuntimeTypeNullable(Object? object) =>
object == null ? _literal<Null>() : _getMasqueradedRuntimeType(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, SubListIterable,
TakeWhileIterable, TakeWhileIterable,
unsafeCast, unsafeCast,
WasmTypedDataBase,
WhereIterable, WhereIterable,
WhereTypeIterable; WhereTypeIterable;
import 'dart:_simd'; import 'dart:_simd';
@ -101,7 +102,7 @@ final class _TypedListIterator<E> implements Iterator<E> {
/// and [_setUint8Unchecked] methods. Implementations should implement these /// and [_setUint8Unchecked] methods. Implementations should implement these
/// methods and override get/set methods for elements matching the buffer /// methods and override get/set methods for elements matching the buffer
/// element type to provide fast access. /// element type to provide fast access.
abstract class ByteDataBase implements ByteData { abstract class ByteDataBase extends WasmTypedDataBase implements ByteData {
final int offsetInBytes; final int offsetInBytes;
final int lengthInBytes; final int lengthInBytes;
@ -964,7 +965,7 @@ class _UnmodifiableF64ByteData extends _F64ByteData
/// Base class for [ByteBuffer] implementations. Returns slow lists in all /// Base class for [ByteBuffer] implementations. Returns slow lists in all
/// methods. Implementations should override relevant methods to return fast /// methods. Implementations should override relevant methods to return fast
/// lists when possible and implement [asByteData]. /// lists when possible and implement [asByteData].
abstract class ByteBufferBase extends ByteBuffer { abstract class ByteBufferBase extends WasmTypedDataBase implements ByteBuffer {
final int lengthInBytes; final int lengthInBytes;
final bool _mutable; final bool _mutable;
@ -1332,7 +1333,8 @@ class _F64ByteBuffer extends ByteBufferBase {
} }
} }
class UnmodifiableByteBuffer implements UnmodifiableByteBufferView { class UnmodifiableByteBuffer extends WasmTypedDataBase
implements UnmodifiableByteBufferView {
final ByteBufferBase _buffer; final ByteBufferBase _buffer;
UnmodifiableByteBuffer(ByteBufferBase buffer) : _buffer = buffer._immutable(); UnmodifiableByteBuffer(ByteBufferBase buffer) : _buffer = buffer._immutable();
@ -2242,7 +2244,7 @@ mixin _UnmodifiableDoubleListMixin {
// Fast lists // Fast lists
// //
abstract class _WasmI8ArrayBase { abstract class _WasmI8ArrayBase extends WasmTypedDataBase {
final WasmArray<WasmI8> _data; final WasmArray<WasmI8> _data;
final int _offsetInElements; final int _offsetInElements;
final int length; final int length;
@ -2260,7 +2262,7 @@ abstract class _WasmI8ArrayBase {
ByteBuffer get buffer => _I8ByteBuffer(_data); ByteBuffer get buffer => _I8ByteBuffer(_data);
} }
abstract class _WasmI16ArrayBase { abstract class _WasmI16ArrayBase extends WasmTypedDataBase {
final WasmArray<WasmI16> _data; final WasmArray<WasmI16> _data;
final int _offsetInElements; final int _offsetInElements;
final int length; final int length;
@ -2278,7 +2280,7 @@ abstract class _WasmI16ArrayBase {
ByteBuffer get buffer => _I16ByteBuffer(_data); ByteBuffer get buffer => _I16ByteBuffer(_data);
} }
abstract class _WasmI32ArrayBase { abstract class _WasmI32ArrayBase extends WasmTypedDataBase {
final WasmArray<WasmI32> _data; final WasmArray<WasmI32> _data;
final int _offsetInElements; final int _offsetInElements;
final int length; final int length;
@ -2296,7 +2298,7 @@ abstract class _WasmI32ArrayBase {
ByteBuffer get buffer => _I32ByteBuffer(_data); ByteBuffer get buffer => _I32ByteBuffer(_data);
} }
abstract class _WasmI64ArrayBase { abstract class _WasmI64ArrayBase extends WasmTypedDataBase {
final WasmArray<WasmI64> _data; final WasmArray<WasmI64> _data;
final int _offsetInElements; final int _offsetInElements;
final int length; final int length;
@ -2314,7 +2316,7 @@ abstract class _WasmI64ArrayBase {
ByteBuffer get buffer => _I64ByteBuffer(_data); ByteBuffer get buffer => _I64ByteBuffer(_data);
} }
abstract class _WasmF32ArrayBase { abstract class _WasmF32ArrayBase extends WasmTypedDataBase {
final WasmArray<WasmF32> _data; final WasmArray<WasmF32> _data;
final int _offsetInElements; final int _offsetInElements;
final int length; final int length;
@ -2332,7 +2334,7 @@ abstract class _WasmF32ArrayBase {
ByteBuffer get buffer => _F32ByteBuffer(_data); ByteBuffer get buffer => _F32ByteBuffer(_data);
} }
abstract class _WasmF64ArrayBase { abstract class _WasmF64ArrayBase extends WasmTypedDataBase {
final WasmArray<WasmF64> _data; final WasmArray<WasmF64> _data;
final int _offsetInElements; final int _offsetInElements;
final int length; final int length;
@ -2931,7 +2933,7 @@ class UnmodifiableF64List extends F64List
// Slow lists // Slow lists
// //
class _SlowListBase { class _SlowListBase extends WasmTypedDataBase {
final ByteBuffer buffer; final ByteBuffer buffer;
final int offsetInBytes; final int offsetInBytes;
final int length; 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 = [ args = [
"--target=dart2wasm", "--target=dart2wasm",
"--no-defines",
"dart:core", "dart:core",
"--nnbd-strong", "--nnbd-strong",
] ]
@ -71,7 +70,7 @@ compile_platform("compile_dart2wasm_js_compatibility_platform") {
args = [ args = [
"--target=dart2wasm_js_compatibility", "--target=dart2wasm_js_compatibility",
"--no-defines", "-Ddart.wasm.js_compatibility=true",
"dart:core", "dart:core",
"--nnbd-strong", "--nnbd-strong",
] ]