Version 2.17.0-113.0.dev
Merge commit '6faa5f3bd00ad8cbc640b3fc80cf7466c002a7df' into 'dev'
diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json
index 76aed36..66d9ee6 100644
--- a/.dart_tool/package_config.json
+++ b/.dart_tool/package_config.json
@@ -229,6 +229,12 @@
"languageVersion": "2.12"
},
{
+ "name": "dart2wasm",
+ "rootUri": "../pkg/dart2wasm",
+ "packageUri": "lib/",
+ "languageVersion": "2.12"
+ },
+ {
"name": "dart_internal",
"rootUri": "../pkg/dart_internal",
"packageUri": "lib/",
@@ -781,6 +787,12 @@
"languageVersion": "2.12"
},
{
+ "name": "wasm_builder",
+ "rootUri": "../pkg/wasm_builder",
+ "packageUri": "lib/",
+ "languageVersion": "2.12"
+ },
+ {
"name": "watcher",
"rootUri": "../third_party/pkg/watcher",
"packageUri": "lib/",
diff --git a/.packages b/.packages
index e8855d6..38ab4a9 100644
--- a/.packages
+++ b/.packages
@@ -33,6 +33,7 @@
dart2js_runtime_metrics:pkg/dart2js_runtime_metrics/lib
dart2js_tools:pkg/dart2js_tools/lib
dart2native:pkg/dart2native/lib
+dart2wasm:pkg/dart2wasm/lib
dart_internal:pkg/dart_internal/lib
dart_style:third_party/pkg_tested/dart_style/lib
dartdev:pkg/dartdev/lib
@@ -117,6 +118,7 @@
vm:pkg/vm/lib
vm_service:pkg/vm_service/lib
vm_snapshot_analysis:pkg/vm_snapshot_analysis/lib
+wasm_builder:pkg/wasm_builder/lib
watcher:third_party/pkg/watcher/lib
webdriver:third_party/pkg/webdriver/lib
webkit_inspection_protocol:third_party/pkg/webkit_inspection_protocol/lib
diff --git a/pkg/_fe_analyzer_shared/lib/src/scanner/scanner.dart b/pkg/_fe_analyzer_shared/lib/src/scanner/scanner.dart
index 63fd51c..35a6386 100644
--- a/pkg/_fe_analyzer_shared/lib/src/scanner/scanner.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/scanner/scanner.dart
@@ -72,7 +72,7 @@
bool includeComments: false,
LanguageVersionChanged? languageVersionChanged}) {
if (bytes.last != 0) {
- throw new ArgumentError("[bytes]: the last byte must be null.");
+ throw new ArgumentError("[bytes]: the last byte must be 0.");
}
Scanner scanner = new Utf8BytesScanner(bytes,
configuration: configuration,
diff --git a/pkg/_fe_analyzer_shared/lib/src/testing/id_generation.dart b/pkg/_fe_analyzer_shared/lib/src/testing/id_generation.dart
index 896c670..a1bbadb 100644
--- a/pkg/_fe_analyzer_shared/lib/src/testing/id_generation.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/testing/id_generation.dart
@@ -65,13 +65,13 @@
Map<Id, Map<String, IdValue>> idValuePerId = idValuePerUri[uri] ?? {};
Map<Id, Map<String, ActualData<T>>> actualDataPerId =
actualDataPerUri[uri] ?? {};
- AnnotatedCode code = annotatedCode[uri]!;
- assert(
- // ignore: unnecessary_null_comparison
- code != null, "No annotated code for ${uri} in ${annotatedCode.keys}");
- result[uri] = _computeAnnotations(code, expectedMaps.keys, actualMarkers,
+ AnnotatedCode? code = annotatedCode[uri];
+ if (code != null) {
+ // Annotations are not computed from synthesized code.
+ result[uri] = _computeAnnotations(code, expectedMaps.keys, actualMarkers,
idValuePerId, actualDataPerId, dataInterpreter,
sortMarkers: false, createDiff: createDiff, forceUpdate: forceUpdate);
+ }
}
return result;
}
diff --git a/pkg/_fe_analyzer_shared/lib/src/testing/id_testing.dart b/pkg/_fe_analyzer_shared/lib/src/testing/id_testing.dart
index 68ad3ab..ca35c17 100644
--- a/pkg/_fe_analyzer_shared/lib/src/testing/id_testing.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/testing/id_testing.dart
@@ -771,7 +771,9 @@
List<FileSystemEntity> entities = dataDir
.listSync()
.where((entity) =>
- !entity.path.endsWith('~') && !entity.path.endsWith('marker.options'))
+ !entity.path.endsWith('~') &&
+ !entity.path.endsWith('marker.options') &&
+ !entity.path.endsWith('.expect'))
.toList();
if (shards > 1) {
entities.sort((a, b) => getTestName(a).compareTo(getTestName(b)));
diff --git a/pkg/dart2wasm/bin/dart2wasm.dart b/pkg/dart2wasm/bin/dart2wasm.dart
new file mode 100644
index 0000000..10f1750
--- /dev/null
+++ b/pkg/dart2wasm/bin/dart2wasm.dart
@@ -0,0 +1,99 @@
+// Copyright (c) 2022, 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:io';
+import 'dart:typed_data';
+
+import 'package:front_end/src/api_unstable/vm.dart'
+ show printDiagnosticMessage, resolveInputUri;
+
+import 'package:dart2wasm/compile.dart';
+import 'package:dart2wasm/translator.dart';
+
+final Map<String, void Function(TranslatorOptions, bool)> boolOptionMap = {
+ "export-all": (o, value) => o.exportAll = value,
+ "inlining": (o, value) => o.inlining = value,
+ "lazy-constants": (o, value) => o.lazyConstants = value,
+ "local-nullability": (o, value) => o.localNullability = value,
+ "name-section": (o, value) => o.nameSection = value,
+ "nominal-types": (o, value) => o.nominalTypes = value,
+ "parameter-nullability": (o, value) => o.parameterNullability = value,
+ "polymorphic-specialization": (o, value) =>
+ o.polymorphicSpecialization = value,
+ "print-kernel": (o, value) => o.printKernel = value,
+ "print-wasm": (o, value) => o.printWasm = value,
+ "runtime-types": (o, value) => o.runtimeTypes = value,
+ "string-data-segments": (o, value) => o.stringDataSegments = value,
+};
+final Map<String, void Function(TranslatorOptions, int)> intOptionMap = {
+ "watch": (o, value) => (o.watchPoints ??= []).add(value),
+};
+
+Never usage(String message) {
+ print("Usage: dart2wasm [<options>] <infile.dart> <outfile.wasm>");
+ print("");
+ print("Options:");
+ print(" --dart-sdk=<path>");
+ print("");
+ for (String option in boolOptionMap.keys) {
+ print(" --[no-]$option");
+ }
+ print("");
+ for (String option in intOptionMap.keys) {
+ print(" --$option <value>");
+ }
+ print("");
+
+ throw message;
+}
+
+Future<int> main(List<String> args) async {
+ Uri sdkPath = Platform.script.resolve("../../../sdk");
+ TranslatorOptions options = TranslatorOptions();
+ List<String> nonOptions = [];
+ void Function(TranslatorOptions, int)? intOptionFun = null;
+ for (String arg in args) {
+ if (intOptionFun != null) {
+ intOptionFun(options, int.parse(arg));
+ intOptionFun = null;
+ } else if (arg.startsWith("--dart-sdk=")) {
+ String path = arg.substring("--dart-sdk=".length);
+ sdkPath = Uri.file(Directory(path).absolute.path);
+ } else if (arg.startsWith("--no-")) {
+ var optionFun = boolOptionMap[arg.substring(5)];
+ if (optionFun == null) usage("Unknown option $arg");
+ optionFun(options, false);
+ } else if (arg.startsWith("--")) {
+ var optionFun = boolOptionMap[arg.substring(2)];
+ if (optionFun != null) {
+ optionFun(options, true);
+ } else {
+ intOptionFun = intOptionMap[arg.substring(2)];
+ if (intOptionFun == null) usage("Unknown option $arg");
+ }
+ } else {
+ nonOptions.add(arg);
+ }
+ }
+ if (intOptionFun != null) {
+ usage("Missing argument to ${args.last}");
+ }
+
+ if (nonOptions.length != 2) usage("Requires two file arguments");
+ String input = nonOptions[0];
+ String output = nonOptions[1];
+ Uri mainUri = resolveInputUri(input);
+
+ Uint8List? module = await compileToModule(mainUri, sdkPath, options,
+ (message) => printDiagnosticMessage(message, print));
+
+ if (module == null) {
+ exitCode = 1;
+ return exitCode;
+ }
+
+ await File(output).writeAsBytes(module);
+
+ return 0;
+}
diff --git a/pkg/dart2wasm/bin/run_wasm.js b/pkg/dart2wasm/bin/run_wasm.js
new file mode 100644
index 0000000..08a6d92
--- /dev/null
+++ b/pkg/dart2wasm/bin/run_wasm.js
@@ -0,0 +1,63 @@
+// Copyright (c) 2022, 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.
+//
+// Runner V8 script for testing dart2wasm, takes ".wasm" file as argument.
+// Run as follows:
+//
+// $> d8 --experimental-wasm-gc --wasm-gc-js-interop run_wasm.js -- <file_name>.wasm
+
+function stringFromDartString(string) {
+ var length = inst.exports.$stringLength(string);
+ var array = new Array(length);
+ for (var i = 0; i < length; i++) {
+ array[i] = inst.exports.$stringRead(string, i);
+ }
+ return String.fromCharCode(...array);
+}
+
+function stringToDartString(string) {
+ var length = string.length;
+ var range = 0;
+ for (var i = 0; i < length; i++) {
+ range |= string.codePointAt(i);
+ }
+ if (range < 256) {
+ var dartString = inst.exports.$stringAllocate1(length);
+ for (var i = 0; i < length; i++) {
+ inst.exports.$stringWrite1(dartString, i, string.codePointAt(i));
+ }
+ return dartString;
+ } else {
+ var dartString = inst.exports.$stringAllocate2(length);
+ for (var i = 0; i < length; i++) {
+ inst.exports.$stringWrite2(dartString, i, string.codePointAt(i));
+ }
+ return dartString;
+ }
+}
+
+// Imports for printing and event loop
+var dart2wasm = {
+ printToConsole: function(string) {
+ console.log(stringFromDartString(string))
+ },
+ scheduleCallback: function(milliseconds, closure) {
+ setTimeout(function() {
+ inst.exports.$call0(closure);
+ }, milliseconds);
+ }
+};
+
+// Create a Wasm module from the binary wasm file.
+var bytes = readbuffer(arguments[0]);
+var module = new WebAssembly.Module(bytes);
+
+// Instantiate the Wasm module, importing from the global scope.
+var importObject = (typeof window !== 'undefined')
+ ? window
+ : Realm.global(Realm.current());
+var inst = new WebAssembly.Instance(module, importObject);
+
+var result = inst.exports.main();
+if (result) console.log(result);
diff --git a/pkg/dart2wasm/dart2wasm.md b/pkg/dart2wasm/dart2wasm.md
new file mode 100644
index 0000000..8dc3203
--- /dev/null
+++ b/pkg/dart2wasm/dart2wasm.md
@@ -0,0 +1,65 @@
+## Running dart2wasm
+
+You don't need to build the Dart SDK to run dart2wasm, as long as you have a Dart SDK installed.
+
+To compile a Dart file to Wasm, run:
+
+`dart --enable-asserts pkg/dart2wasm/bin/dart2wasm.dart` *options* *infile*`.dart` *outfile*`.wasm`
+
+where *options* include:
+
+| Option | Default | Description |
+| --------------------------------------- | ------- | ----------- |
+| `--dart-sdk=`*path* | relative to script | The location of the `sdk` directory inside the Dart SDK, containing the core library sources.
+| `--`[`no-`]`export-all` | no | Export all functions; otherwise, just export `main`.
+| `--`[`no-`]`inlining` | no | Inline small functions.
+| `--`[`no-`]`lazy-constants` | no | Instantiate constants lazily.
+| `--`[`no-`]`local-nullability` | no | Use non-nullable types for non-nullable locals and temporaries.
+| `--`[`no-`]`name-section` | yes | Emit Name Section with function names.
+| `--`[`no-`]`nominal-types` | no | Emit experimental nominal types.
+| `--`[`no-`]`parameter-nullability` | yes | Use non-nullable types for non-nullable parameters and return values.
+| `--`[`no-`]`polymorphic-specialization` | no | Do virtual calls by switching on the class ID instead of using `call_indirect`.
+| `--`[`no-`]`print-kernel` | no | Print IR for each function before compiling it.
+| `--`[`no-`]`print-wasm` | no | Print Wasm instructions of each compiled function.
+| `--`[`no-`]`runtime-types` | yes | Use RTTs for allocations and casts.
+| `--`[`no-`]`string-data-segments` | no | Use experimental array init from data segment for string constants.
+| `--watch` *offset* | | Print stack trace leading to the byte at offset *offset* in the `.wasm` output file. Can be specified multiple times.
+
+The resulting `.wasm` file can be run with:
+
+`d8 --experimental-wasm-gc --wasm-gc-js-interop pkg/dart2wasm/bin/run_wasm.js -- `*outfile*`.wasm`
+
+## Imports and exports
+
+To import a function, declare it as a global, external function and mark it with a `wasm:import` pragma indicating the imported name (which must be two identifiers separated by a dot):
+```dart
+@pragma("wasm:import", "foo.bar")
+external void fooBar(Object object);
+```
+which will call `foo.bar` on the host side:
+```javascript
+var foo = {
+ bar: function(object) { /* implementation here */ }
+};
+```
+To export a function, mark it with a `wasm:export` pragma:
+```dart
+@pragma("wasm:export")
+void foo(double x) { /* implementation here */ }
+
+@pragma("wasm:export", "baz")
+void bar(double x) { /* implementation here */ }
+```
+With the Wasm module instance in `inst`, these can be called as:
+```javascript
+inst.exports.foo(1);
+inst.exports.baz(2);
+```
+
+### Types to use for interop
+
+In the signatures of imported and exported functions, use the following types:
+
+- For numbers, use `double`.
+- For Dart objects, use the corresponding Dart type. The fields of the underlying representation can be accessed on the JS side as `.$field0`, `.$field1` etc., but there is currently no defined way of finding the field index of a particular Dart field, so this mechanism is mainly useful for special objects with known layout.
+- For JS objects, use the `WasmAnyRef` type (or `WasmAnyRef?` as applicable) from the `dart:wasm` package. These can be passed around and stored as opaque values on the Dart side.
diff --git a/pkg/dart2wasm/lib/class_info.dart b/pkg/dart2wasm/lib/class_info.dart
new file mode 100644
index 0000000..54037e0
--- /dev/null
+++ b/pkg/dart2wasm/lib/class_info.dart
@@ -0,0 +1,377 @@
+// Copyright (c) 2022, 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:math';
+
+import 'package:dart2wasm/translator.dart';
+
+import 'package:kernel/ast.dart';
+
+import 'package:wasm_builder/wasm_builder.dart' as w;
+
+/// Wasm struct field indices for fields that are accessed explicitly from Wasm
+/// code, e.g. in intrinsics.
+///
+/// The values are validated by asserts, typically either through
+/// [ClassInfo.addField] (for manually added fields) or by a line in
+/// [FieldIndex.validate] (for fields declared in Dart code).
+class FieldIndex {
+ static const classId = 0;
+ static const boxValue = 1;
+ static const identityHash = 1;
+ static const stringArray = 2;
+ static const closureContext = 2;
+ static const closureFunction = 3;
+ static const typedListBaseLength = 2;
+ static const typedListArray = 3;
+ static const typedListViewTypedData = 3;
+ static const typedListViewOffsetInBytes = 4;
+ static const byteDataViewLength = 2;
+ static const byteDataViewTypedData = 3;
+ static const byteDataViewOffsetInBytes = 4;
+
+ static void validate(Translator translator) {
+ void check(Class cls, String name, int expectedIndex) {
+ assert(
+ translator.fieldIndex[
+ cls.fields.firstWhere((f) => f.name.text == name)] ==
+ expectedIndex,
+ "Unexpected field index for ${cls.name}.$name");
+ }
+
+ check(translator.boxedBoolClass, "value", FieldIndex.boxValue);
+ check(translator.boxedIntClass, "value", FieldIndex.boxValue);
+ check(translator.boxedDoubleClass, "value", FieldIndex.boxValue);
+ check(translator.oneByteStringClass, "_array", FieldIndex.stringArray);
+ check(translator.twoByteStringClass, "_array", FieldIndex.stringArray);
+ check(translator.functionClass, "context", FieldIndex.closureContext);
+ }
+}
+
+const int initialIdentityHash = 0;
+
+/// Information about the Wasm representation for a class.
+class ClassInfo {
+ /// The Dart class that this info corresponds to. The top type does not have
+ /// an associated Dart class.
+ final Class? cls;
+
+ /// The Class ID of this class, stored in every instance of the class.
+ final int classId;
+
+ /// Depth of this class in the Wasm type hierarchy.
+ final int depth;
+
+ /// The Wasm struct used to represent instances of this class. A class will
+ /// sometimes use the same struct as its superclass.
+ final w.StructType struct;
+
+ /// Wasm global containing the RTT for this class.
+ late final w.DefinedGlobal rtt;
+
+ /// The superclass for this class. This will usually be the Dart superclass,
+ /// but there are a few exceptions, where the Wasm type hierarchy does not
+ /// follow the Dart class hierarchy.
+ final ClassInfo? superInfo;
+
+ /// 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
+ /// the superclass.
+ final Map<TypeParameter, TypeParameter> typeParameterMatch;
+
+ /// The class whose struct is used as the type for variables of this type.
+ /// This is a type which is a superclass of all subtypes of this type.
+ late ClassInfo repr;
+
+ /// All classes which implement this class. This is used to compute `repr`.
+ final List<ClassInfo> implementedBy = [];
+
+ late final w.RefType nullableType = w.RefType.def(struct, nullable: true);
+ late final w.RefType nonNullableType = w.RefType.def(struct, nullable: false);
+
+ w.RefType typeWithNullability(bool nullable) =>
+ nullable ? nullableType : nonNullableType;
+
+ ClassInfo(this.cls, this.classId, this.depth, this.struct, this.superInfo,
+ ClassInfoCollector collector,
+ {this.typeParameterMatch = const {}}) {
+ if (collector.options.useRttGlobals) {
+ rtt = collector.makeRtt(struct, superInfo);
+ }
+ implementedBy.add(this);
+ }
+
+ void addField(w.FieldType fieldType, [int? expectedIndex]) {
+ assert(expectedIndex == null || expectedIndex == struct.fields.length);
+ struct.fields.add(fieldType);
+ }
+}
+
+ClassInfo upperBound(Iterable<ClassInfo> classes) {
+ while (classes.length > 1) {
+ Set<ClassInfo> newClasses = {};
+ int minDepth = 999999999;
+ int maxDepth = 0;
+ for (ClassInfo info in classes) {
+ minDepth = min(minDepth, info.depth);
+ maxDepth = max(maxDepth, info.depth);
+ }
+ int targetDepth = minDepth == maxDepth ? minDepth - 1 : minDepth;
+ for (ClassInfo info in classes) {
+ while (info.depth > targetDepth) {
+ info = info.superInfo!;
+ }
+ newClasses.add(info);
+ }
+ classes = newClasses;
+ }
+ return classes.single;
+}
+
+/// Constructs the Wasm type hierarchy.
+class ClassInfoCollector {
+ final Translator translator;
+ int nextClassId = 0;
+ late final ClassInfo topInfo;
+
+ late final w.FieldType typeType =
+ w.FieldType(translator.classInfo[translator.typeClass]!.nullableType);
+
+ ClassInfoCollector(this.translator);
+
+ w.Module get m => translator.m;
+
+ TranslatorOptions get options => translator.options;
+
+ w.DefinedGlobal makeRtt(w.StructType struct, ClassInfo? superInfo) {
+ assert(options.useRttGlobals);
+ int depth = superInfo != null ? superInfo.depth + 1 : 0;
+ final w.DefinedGlobal rtt =
+ m.addGlobal(w.GlobalType(w.Rtt(struct, depth), mutable: false));
+ final w.Instructions b = rtt.initializer;
+ if (superInfo != null) {
+ b.global_get(superInfo.rtt);
+ b.rtt_sub(struct);
+ } else {
+ b.rtt_canon(struct);
+ }
+ b.end();
+ return rtt;
+ }
+
+ void initializeTop() {
+ final w.StructType struct = translator.structType("#Top");
+ topInfo = ClassInfo(null, nextClassId++, 0, struct, null, this);
+ translator.classes.add(topInfo);
+ translator.classForHeapType[struct] = topInfo;
+ }
+
+ void initialize(Class cls) {
+ ClassInfo? info = translator.classInfo[cls];
+ if (info == null) {
+ Class? superclass = cls.superclass;
+ if (superclass == null) {
+ ClassInfo superInfo = topInfo;
+ final w.StructType struct =
+ translator.structType(cls.name, superType: superInfo.struct);
+ info = ClassInfo(
+ cls, nextClassId++, superInfo.depth + 1, struct, superInfo, this);
+ // Mark Top type as implementing Object to force the representation
+ // type of Object to be Top.
+ info.implementedBy.add(topInfo);
+ } else {
+ // Recursively initialize all supertypes before initializing this class.
+ initialize(superclass);
+ for (Supertype interface in cls.implementedTypes) {
+ initialize(interface.classNode);
+ }
+
+ // In the Wasm type hierarchy, Object, bool and num sit directly below
+ // the Top type. The implementation classes (_StringBase, _Type and the
+ // box classes) 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
+ : cls == translator.stringBaseClass ||
+ cls == translator.typeClass ||
+ translator.boxedClasses.values.contains(cls)
+ ? translator.classInfo[cls.implementedTypes.single.classNode]!
+ : translator.classInfo[superclass]!;
+
+ // Figure out which type parameters can reuse a type parameter field of
+ // the superclass.
+ Map<TypeParameter, TypeParameter> typeParameterMatch = {};
+ if (cls.typeParameters.isNotEmpty) {
+ Supertype supertype = cls.superclass == superInfo.cls
+ ? cls.supertype!
+ : cls.implementedTypes.single;
+ for (TypeParameter parameter in cls.typeParameters) {
+ for (int i = 0; i < supertype.typeArguments.length; i++) {
+ DartType arg = supertype.typeArguments[i];
+ if (arg is TypeParameterType && arg.parameter == parameter) {
+ typeParameterMatch[parameter] =
+ superInfo.cls!.typeParameters[i];
+ break;
+ }
+ }
+ }
+ }
+
+ // A class can reuse the Wasm struct of the superclass if it doesn't
+ // declare any Wasm fields of its own. This is the case when three
+ // conditions are met:
+ // 1. All type parameters can reuse a type parameter field of the
+ // superclass.
+ // 2. The class declares no Dart fields of its own.
+ // 3. The class is not a special class that contains hidden fields.
+ bool canReuseSuperStruct =
+ typeParameterMatch.length == cls.typeParameters.length &&
+ cls.fields.where((f) => f.isInstanceMember).isEmpty &&
+ cls != translator.typedListBaseClass &&
+ cls != translator.typedListClass &&
+ cls != translator.typedListViewClass &&
+ cls != translator.byteDataViewClass;
+ w.StructType struct = canReuseSuperStruct
+ ? superInfo.struct
+ : translator.structType(cls.name, superType: superInfo.struct);
+ info = ClassInfo(
+ cls, nextClassId++, superInfo.depth + 1, struct, superInfo, this,
+ typeParameterMatch: typeParameterMatch);
+
+ // Mark all interfaces as being implemented by this class. This is
+ // needed to calculate representation types.
+ for (Supertype interface in cls.implementedTypes) {
+ ClassInfo? interfaceInfo = translator.classInfo[interface.classNode];
+ while (interfaceInfo != null) {
+ interfaceInfo.implementedBy.add(info);
+ interfaceInfo = interfaceInfo.superInfo;
+ }
+ }
+ }
+ translator.classes.add(info);
+ translator.classInfo[cls] = info;
+ translator.classForHeapType.putIfAbsent(info.struct, () => info!);
+ }
+ }
+
+ void computeRepresentation(ClassInfo info) {
+ info.repr = upperBound(info.implementedBy);
+ }
+
+ void generateFields(ClassInfo info) {
+ ClassInfo? superInfo = info.superInfo;
+ if (superInfo == null) {
+ // Top - add class id field
+ info.addField(w.FieldType(w.NumType.i32), FieldIndex.classId);
+ } else if (info.struct != superInfo.struct) {
+ // Copy fields from superclass
+ for (w.FieldType fieldType in superInfo.struct.fields) {
+ info.addField(fieldType);
+ }
+ if (info.cls!.superclass == null) {
+ // Object - add identity hash code field
+ info.addField(w.FieldType(w.NumType.i32), FieldIndex.identityHash);
+ }
+ // Add fields for type variables
+ for (TypeParameter parameter in info.cls!.typeParameters) {
+ TypeParameter? match = info.typeParameterMatch[parameter];
+ if (match != null) {
+ // Reuse supertype type variable
+ translator.typeParameterIndex[parameter] =
+ translator.typeParameterIndex[match]!;
+ } else {
+ translator.typeParameterIndex[parameter] = info.struct.fields.length;
+ info.addField(typeType);
+ }
+ }
+ // Add fields for Dart instance fields
+ for (Field field in info.cls!.fields) {
+ if (field.isInstanceMember) {
+ w.ValueType wasmType = translator.translateType(field.type);
+ // TODO(askesc): Generalize this check for finer nullability control
+ if (wasmType != w.RefType.data()) {
+ wasmType = wasmType.withNullability(true);
+ }
+ translator.fieldIndex[field] = info.struct.fields.length;
+ info.addField(w.FieldType(wasmType));
+ }
+ }
+ } else {
+ for (TypeParameter parameter in info.cls!.typeParameters) {
+ // Reuse supertype type variable
+ translator.typeParameterIndex[parameter] =
+ translator.typeParameterIndex[info.typeParameterMatch[parameter]]!;
+ }
+ }
+ }
+
+ void collect() {
+ // Create class info and Wasm structs for all classes.
+ initializeTop();
+ for (Library library in translator.component.libraries) {
+ for (Class cls in library.classes) {
+ initialize(cls);
+ }
+ }
+
+ // For each class, compute which Wasm struct should be used for the type of
+ // variables bearing that class as their Dart type. This is the struct
+ // corresponding to the least common supertype of all Dart classes
+ // implementing this class.
+ for (ClassInfo info in translator.classes) {
+ computeRepresentation(info);
+ }
+
+ // Now that the representation types for all classes have been computed,
+ // fill in the types of the fields in the generated Wasm structs.
+ for (ClassInfo info in translator.classes) {
+ generateFields(info);
+ }
+
+ // Add hidden fields of typed_data classes.
+ addTypedDataFields();
+
+ // Validate that all internally used fields have the expected indices.
+ FieldIndex.validate(translator);
+ }
+
+ void addTypedDataFields() {
+ ClassInfo typedListBaseInfo =
+ translator.classInfo[translator.typedListBaseClass]!;
+ typedListBaseInfo.addField(w.FieldType(w.NumType.i32, mutable: false),
+ FieldIndex.typedListBaseLength);
+
+ ClassInfo typedListInfo = translator.classInfo[translator.typedListClass]!;
+ typedListInfo.addField(w.FieldType(w.NumType.i32, mutable: false),
+ FieldIndex.typedListBaseLength);
+ w.RefType bytesArrayType = w.RefType.def(
+ translator.wasmArrayType(w.PackedType.i8, "i8"),
+ nullable: false);
+ typedListInfo.addField(
+ w.FieldType(bytesArrayType, mutable: false), FieldIndex.typedListArray);
+
+ w.RefType typedListType =
+ w.RefType.def(typedListInfo.struct, nullable: false);
+
+ ClassInfo typedListViewInfo =
+ translator.classInfo[translator.typedListViewClass]!;
+ typedListViewInfo.addField(w.FieldType(w.NumType.i32, mutable: false),
+ FieldIndex.typedListBaseLength);
+ typedListViewInfo.addField(w.FieldType(typedListType, mutable: false),
+ FieldIndex.typedListViewTypedData);
+ typedListViewInfo.addField(w.FieldType(w.NumType.i32, mutable: false),
+ FieldIndex.typedListViewOffsetInBytes);
+
+ ClassInfo byteDataViewInfo =
+ translator.classInfo[translator.byteDataViewClass]!;
+ byteDataViewInfo.addField(w.FieldType(w.NumType.i32, mutable: false),
+ FieldIndex.byteDataViewLength);
+ byteDataViewInfo.addField(w.FieldType(typedListType, mutable: false),
+ FieldIndex.byteDataViewTypedData);
+ byteDataViewInfo.addField(w.FieldType(w.NumType.i32, mutable: false),
+ FieldIndex.byteDataViewOffsetInBytes);
+ }
+}
diff --git a/pkg/dart2wasm/lib/closures.dart b/pkg/dart2wasm/lib/closures.dart
new file mode 100644
index 0000000..cde1b4c
--- /dev/null
+++ b/pkg/dart2wasm/lib/closures.dart
@@ -0,0 +1,318 @@
+// Copyright (c) 2022, 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 'package:dart2wasm/code_generator.dart';
+import 'package:dart2wasm/translator.dart';
+
+import 'package:kernel/ast.dart';
+
+import 'package:wasm_builder/wasm_builder.dart' as w;
+
+/// A local function or function expression.
+class Lambda {
+ final FunctionNode functionNode;
+ final w.DefinedFunction function;
+
+ Lambda(this.functionNode, this.function);
+}
+
+/// The context for one or more closures, containing their captured variables.
+///
+/// Contexts can be nested, corresponding to the scopes covered by the contexts.
+/// Each local function, function expression or loop (`while`, `do`/`while` or
+/// `for`) gives rise to its own context nested inside the context of its
+/// surrounding scope. At runtime, each context has a reference to its parent
+/// context.
+///
+/// Closures corresponding to local functions or function expressions in the
+/// same scope share the same context. Thus, a closure can potentially keep more
+/// values alive than the ones captured by the closure itself.
+///
+/// A context may be empty (containing no captured variables), in which case it
+/// is skipped in the context parent chain and never allocated. A context can
+/// also be skipped if it only contains variables that are not in scope for the
+/// child context (and its descendants).
+class Context {
+ /// The node containing the scope covered by the context. This is either a
+ /// [FunctionNode] (for members, local functions and function expressions),
+ /// a [ForStatement], a [DoStatement] or a [WhileStatement].
+ final TreeNode owner;
+
+ /// The parent of this context, corresponding to the lexically enclosing
+ /// owner. This is null if the context is a member context, or if all contexts
+ /// in the parent chain are skipped.
+ final Context? parent;
+
+ /// The variables captured by this context.
+ final List<VariableDeclaration> variables = [];
+
+ /// Whether this context contains a captured `this`. Only member contexts can.
+ bool containsThis = false;
+
+ /// The Wasm struct representing this context at runtime.
+ late final w.StructType struct;
+
+ /// The local variable currently pointing to this context. Used during code
+ /// generation.
+ late w.Local currentLocal;
+
+ bool get isEmpty => variables.isEmpty && !containsThis;
+
+ int get parentFieldIndex {
+ assert(parent != null);
+ return 0;
+ }
+
+ int get thisFieldIndex {
+ assert(containsThis);
+ return 0;
+ }
+
+ Context(this.owner, this.parent);
+}
+
+/// A captured variable.
+class Capture {
+ final VariableDeclaration variable;
+ late final Context context;
+ late final int fieldIndex;
+ bool written = false;
+
+ Capture(this.variable);
+
+ w.ValueType get type => context.struct.fields[fieldIndex].type.unpacked;
+}
+
+/// Compiler passes to find all captured variables and construct the context
+/// tree for a member.
+class Closures {
+ final CodeGenerator codeGen;
+ final Map<VariableDeclaration, Capture> captures = {};
+ bool isThisCaptured = false;
+ final Map<FunctionNode, Lambda> lambdas = {};
+ final Map<TreeNode, Context> contexts = {};
+ final Set<FunctionDeclaration> closurizedFunctions = {};
+
+ Closures(this.codeGen);
+
+ Translator get translator => codeGen.translator;
+
+ void findCaptures(Member member) {
+ var find = CaptureFinder(this, member);
+ if (member is Constructor) {
+ Class cls = member.enclosingClass;
+ for (Field field in cls.fields) {
+ if (field.isInstanceMember && field.initializer != null) {
+ field.initializer!.accept(find);
+ }
+ }
+ }
+ member.accept(find);
+ }
+
+ void collectContexts(TreeNode node, {TreeNode? container}) {
+ if (captures.isNotEmpty || isThisCaptured) {
+ node.accept(ContextCollector(this, container));
+ }
+ }
+
+ void buildContexts() {
+ // Make struct definitions
+ for (Context context in contexts.values) {
+ if (!context.isEmpty) {
+ context.struct = translator.structType("<context>");
+ }
+ }
+
+ // Build object layouts
+ for (Context context in contexts.values) {
+ if (!context.isEmpty) {
+ w.StructType struct = context.struct;
+ if (context.parent != null) {
+ assert(!context.containsThis);
+ struct.fields.add(w.FieldType(
+ w.RefType.def(context.parent!.struct, nullable: true)));
+ }
+ if (context.containsThis) {
+ struct.fields.add(w.FieldType(
+ codeGen.preciseThisLocal!.type.withNullability(true)));
+ }
+ for (VariableDeclaration variable in context.variables) {
+ int index = struct.fields.length;
+ struct.fields.add(w.FieldType(
+ translator.translateType(variable.type).withNullability(true)));
+ captures[variable]!.fieldIndex = index;
+ }
+ }
+ }
+ }
+}
+
+class CaptureFinder extends RecursiveVisitor {
+ final Closures closures;
+ final Member member;
+ final Map<VariableDeclaration, int> variableDepth = {};
+ int depth = 0;
+
+ CaptureFinder(this.closures, this.member);
+
+ Translator get translator => closures.translator;
+
+ @override
+ void visitAssertStatement(AssertStatement node) {}
+
+ @override
+ void visitVariableDeclaration(VariableDeclaration node) {
+ if (depth > 0) {
+ variableDepth[node] = depth;
+ }
+ super.visitVariableDeclaration(node);
+ }
+
+ void _visitVariableUse(VariableDeclaration variable) {
+ int declDepth = variableDepth[variable] ?? 0;
+ assert(declDepth <= depth);
+ if (declDepth < depth) {
+ closures.captures[variable] = Capture(variable);
+ } else if (variable.parent is FunctionDeclaration) {
+ closures.closurizedFunctions.add(variable.parent as FunctionDeclaration);
+ }
+ }
+
+ @override
+ void visitVariableGet(VariableGet node) {
+ _visitVariableUse(node.variable);
+ super.visitVariableGet(node);
+ }
+
+ @override
+ void visitVariableSet(VariableSet node) {
+ _visitVariableUse(node.variable);
+ super.visitVariableSet(node);
+ }
+
+ void _visitThis() {
+ if (depth > 0) {
+ closures.isThisCaptured = true;
+ }
+ }
+
+ @override
+ void visitThisExpression(ThisExpression node) {
+ _visitThis();
+ }
+
+ @override
+ void visitSuperMethodInvocation(SuperMethodInvocation node) {
+ _visitThis();
+ super.visitSuperMethodInvocation(node);
+ }
+
+ @override
+ void visitTypeParameterType(TypeParameterType node) {
+ if (node.parameter.parent == member.enclosingClass) {
+ _visitThis();
+ }
+ }
+
+ void _visitLambda(FunctionNode node) {
+ if (node.positionalParameters.length != node.requiredParameterCount ||
+ node.namedParameters.isNotEmpty) {
+ throw "Not supported: Optional parameters for "
+ "function expression or local function at ${node.location}";
+ }
+ int parameterCount = node.requiredParameterCount;
+ w.FunctionType type = translator.closureFunctionType(parameterCount);
+ w.DefinedFunction function =
+ translator.m.addFunction(type, "$member (closure)");
+ closures.lambdas[node] = Lambda(node, function);
+
+ depth++;
+ node.visitChildren(this);
+ depth--;
+ }
+
+ @override
+ void visitFunctionExpression(FunctionExpression node) {
+ _visitLambda(node.function);
+ }
+
+ @override
+ void visitFunctionDeclaration(FunctionDeclaration node) {
+ // Variable is in outer scope
+ node.variable.accept(this);
+ _visitLambda(node.function);
+ }
+}
+
+class ContextCollector extends RecursiveVisitor {
+ final Closures closures;
+ Context? currentContext;
+
+ ContextCollector(this.closures, TreeNode? container) {
+ if (container != null) {
+ currentContext = closures.contexts[container]!;
+ }
+ }
+
+ @override
+ void visitAssertStatement(AssertStatement node) {}
+
+ void _newContext(TreeNode node) {
+ bool outerMost = currentContext == null;
+ Context? oldContext = currentContext;
+ Context? parent = currentContext;
+ while (parent != null && parent.isEmpty) parent = parent.parent;
+ currentContext = Context(node, parent);
+ if (closures.isThisCaptured && outerMost) {
+ currentContext!.containsThis = true;
+ }
+ closures.contexts[node] = currentContext!;
+ node.visitChildren(this);
+ currentContext = oldContext;
+ }
+
+ @override
+ void visitConstructor(Constructor node) {
+ node.function.accept(this);
+ currentContext = closures.contexts[node.function]!;
+ visitList(node.initializers, this);
+ }
+
+ @override
+ void visitFunctionNode(FunctionNode node) {
+ _newContext(node);
+ }
+
+ @override
+ void visitWhileStatement(WhileStatement node) {
+ _newContext(node);
+ }
+
+ @override
+ void visitDoStatement(DoStatement node) {
+ _newContext(node);
+ }
+
+ @override
+ void visitForStatement(ForStatement node) {
+ _newContext(node);
+ }
+
+ @override
+ void visitVariableDeclaration(VariableDeclaration node) {
+ Capture? capture = closures.captures[node];
+ if (capture != null) {
+ currentContext!.variables.add(node);
+ capture.context = currentContext!;
+ }
+ super.visitVariableDeclaration(node);
+ }
+
+ @override
+ void visitVariableSet(VariableSet node) {
+ closures.captures[node.variable]?.written = true;
+ super.visitVariableSet(node);
+ }
+}
diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart
new file mode 100644
index 0000000..d4ef423
--- /dev/null
+++ b/pkg/dart2wasm/lib/code_generator.dart
@@ -0,0 +1,1940 @@
+// Copyright (c) 2022, 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 'package:dart2wasm/class_info.dart';
+import 'package:dart2wasm/closures.dart';
+import 'package:dart2wasm/dispatch_table.dart';
+import 'package:dart2wasm/intrinsics.dart';
+import 'package:dart2wasm/param_info.dart';
+import 'package:dart2wasm/reference_extensions.dart';
+import 'package:dart2wasm/translator.dart';
+
+import 'package:kernel/ast.dart';
+import 'package:kernel/type_environment.dart';
+
+import 'package:wasm_builder/wasm_builder.dart' as w;
+
+/// Main code generator for member bodies.
+///
+/// The [generate] method first collects all local functions and function
+/// expressions in the body and then generates code for the body. Code for the
+/// local functions and function expressions must be generated separately by
+/// calling the [generateLambda] method on all lambdas in [closures].
+///
+/// A new [CodeGenerator] object must be created for each new member or lambda.
+///
+/// Every visitor method for an expression takes in the Wasm type that it is
+/// expected to leave on the stack (or the special [voidMarker] to indicate that
+/// it should leave nothing). It returns what it actually left on the stack. The
+/// code generation for every expression or subexpression is done via the [wrap]
+/// method, which emits appropriate conversion code if the produced type is not
+/// a subtype of the expected type.
+class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
+ implements InitializerVisitor<void>, StatementVisitor<void> {
+ final Translator translator;
+ final w.DefinedFunction function;
+ final Reference reference;
+ late final List<w.Local> paramLocals;
+ final w.Label? returnLabel;
+
+ late final Intrinsifier intrinsifier;
+ late final StaticTypeContext typeContext;
+ late final w.Instructions b;
+
+ late final Closures closures;
+
+ final Map<VariableDeclaration, w.Local> locals = {};
+ w.Local? thisLocal;
+ w.Local? preciseThisLocal;
+ final Map<TypeParameter, w.Local> typeLocals = {};
+ final List<Statement> finalizers = [];
+ final Map<LabeledStatement, w.Label> labels = {};
+ final Map<SwitchCase, w.Label> switchLabels = {};
+
+ /// Create a code generator for a member or one of its lambdas.
+ ///
+ /// The [paramLocals] and [returnLabel] parameters can be used to generate
+ /// code for an inlined function by specifying the locals containing the
+ /// parameters (instead of the function inputs) and the label to jump to on
+ /// return (instead of emitting a `return` instruction).
+ CodeGenerator(this.translator, this.function, this.reference,
+ {List<w.Local>? paramLocals, this.returnLabel}) {
+ this.paramLocals = paramLocals ?? function.locals;
+ intrinsifier = Intrinsifier(this);
+ typeContext = StaticTypeContext(member, translator.typeEnvironment);
+ b = function.body;
+ }
+
+ Member get member => reference.asMember;
+
+ w.ValueType get returnType => translator
+ .outputOrVoid(returnLabel?.targetTypes ?? function.type.outputs);
+
+ TranslatorOptions get options => translator.options;
+
+ w.ValueType get voidMarker => translator.voidMarker;
+
+ w.ValueType translateType(DartType type) => translator.translateType(type);
+
+ w.Local addLocal(w.ValueType type) {
+ return function.addLocal(translator.typeForLocal(type));
+ }
+
+ DartType dartTypeOf(Expression exp) {
+ return exp.getStaticType(typeContext);
+ }
+
+ void _unimplemented(
+ TreeNode node, Object message, List<w.ValueType> expectedTypes) {
+ final text = "Not implemented: $message at ${node.location}";
+ print(text);
+ b.comment(text);
+ b.block(const [], expectedTypes);
+ b.unreachable();
+ b.end();
+ }
+
+ @override
+ void defaultInitializer(Initializer node) {
+ _unimplemented(node, node.runtimeType, const []);
+ }
+
+ @override
+ w.ValueType defaultExpression(Expression node, w.ValueType expectedType) {
+ _unimplemented(node, node.runtimeType, [expectedType]);
+ return expectedType;
+ }
+
+ @override
+ void defaultStatement(Statement node) {
+ _unimplemented(node, node.runtimeType, const []);
+ }
+
+ /// Generate code for the body of the member.
+ void generate() {
+ closures = Closures(this);
+
+ Member member = this.member;
+
+ if (reference.isTearOffReference) {
+ // Tear-off getter
+ w.DefinedFunction closureFunction =
+ translator.getTearOffFunction(member as Procedure);
+
+ int parameterCount = member.function.requiredParameterCount;
+ w.DefinedGlobal global = translator.makeFunctionRef(closureFunction);
+
+ ClassInfo info = translator.classInfo[translator.functionClass]!;
+ translator.functions.allocateClass(info.classId);
+
+ b.i32_const(info.classId);
+ b.i32_const(initialIdentityHash);
+ b.local_get(paramLocals[0]);
+ b.global_get(global);
+ translator.struct_new(b, parameterCount);
+ b.end();
+ return;
+ }
+
+ if (intrinsifier.generateMemberIntrinsic(
+ reference, function, paramLocals, returnLabel)) {
+ b.end();
+ return;
+ }
+
+ if (member.isExternal) {
+ final text =
+ "Unimplemented external member $member at ${member.location}";
+ print(text);
+ b.comment(text);
+ b.unreachable();
+ b.end();
+ return;
+ }
+
+ if (member is Field) {
+ if (member.isStatic) {
+ // Static field initializer function
+ assert(reference == member.fieldReference);
+ closures.findCaptures(member);
+ closures.collectContexts(member);
+ closures.buildContexts();
+
+ w.Global global = translator.globals.getGlobal(member);
+ w.Global? flag = translator.globals.getGlobalInitializedFlag(member);
+ wrap(member.initializer!, global.type.type);
+ b.global_set(global);
+ if (flag != null) {
+ b.i32_const(1);
+ b.global_set(flag);
+ }
+ b.global_get(global);
+ translator.convertType(
+ function, global.type.type, function.type.outputs.single);
+ b.end();
+ return;
+ }
+
+ // Implicit getter or setter
+ w.StructType struct =
+ translator.classInfo[member.enclosingClass!]!.struct;
+ int fieldIndex = translator.fieldIndex[member]!;
+ w.ValueType fieldType = struct.fields[fieldIndex].type.unpacked;
+
+ void getThis() {
+ w.Local thisLocal = paramLocals[0];
+ w.RefType structType = w.RefType.def(struct, nullable: true);
+ b.local_get(thisLocal);
+ translator.convertType(function, thisLocal.type, structType);
+ }
+
+ if (reference.isImplicitGetter) {
+ // Implicit getter
+ getThis();
+ b.struct_get(struct, fieldIndex);
+ translator.convertType(function, fieldType, returnType);
+ } else {
+ // Implicit setter
+ w.Local valueLocal = paramLocals[1];
+ getThis();
+ b.local_get(valueLocal);
+ translator.convertType(function, valueLocal.type, fieldType);
+ b.struct_set(struct, fieldIndex);
+ }
+ b.end();
+ return;
+ }
+
+ ParameterInfo paramInfo = translator.paramInfoFor(reference);
+ bool hasThis = member.isInstanceMember || member is Constructor;
+ int typeParameterOffset = hasThis ? 1 : 0;
+ int implicitParams = typeParameterOffset + paramInfo.typeParamCount;
+ List<VariableDeclaration> positional =
+ member.function!.positionalParameters;
+ for (int i = 0; i < positional.length; i++) {
+ locals[positional[i]] = paramLocals[implicitParams + i];
+ }
+ List<VariableDeclaration> named = member.function!.namedParameters;
+ for (var param in named) {
+ locals[param] =
+ paramLocals[implicitParams + paramInfo.nameIndex[param.name]!];
+ }
+ List<TypeParameter> typeParameters = member is Constructor
+ ? member.enclosingClass.typeParameters
+ : member.function!.typeParameters;
+ for (int i = 0; i < typeParameters.length; i++) {
+ typeLocals[typeParameters[i]] = paramLocals[typeParameterOffset + i];
+ }
+
+ closures.findCaptures(member);
+
+ if (hasThis) {
+ Class cls = member.enclosingClass!;
+ ClassInfo info = translator.classInfo[cls]!;
+ thisLocal = paramLocals[0];
+ w.RefType thisType = info.nonNullableType;
+ if (translator.needsConversion(paramLocals[0].type, thisType)) {
+ preciseThisLocal = addLocal(thisType);
+ b.local_get(paramLocals[0]);
+ translator.ref_cast(b, info);
+ b.local_set(preciseThisLocal!);
+ } else {
+ preciseThisLocal = paramLocals[0];
+ }
+ }
+
+ closures.collectContexts(member);
+ if (member is Constructor) {
+ for (Field field in member.enclosingClass.fields) {
+ if (field.isInstanceMember && field.initializer != null) {
+ closures.collectContexts(field.initializer!,
+ container: member.function);
+ }
+ }
+ }
+ closures.buildContexts();
+
+ allocateContext(member.function!);
+ captureParameters();
+
+ if (member is Constructor) {
+ Class cls = member.enclosingClass;
+ ClassInfo info = translator.classInfo[cls]!;
+ for (TypeParameter typeParam in cls.typeParameters) {
+ b.local_get(thisLocal!);
+ b.local_get(typeLocals[typeParam]!);
+ b.struct_set(info.struct, translator.typeParameterIndex[typeParam]!);
+ }
+ for (Field field in cls.fields) {
+ if (field.isInstanceMember && field.initializer != null) {
+ int fieldIndex = translator.fieldIndex[field]!;
+ b.local_get(thisLocal!);
+ wrap(
+ field.initializer!, info.struct.fields[fieldIndex].type.unpacked);
+ b.struct_set(info.struct, fieldIndex);
+ }
+ }
+ for (Initializer initializer in member.initializers) {
+ initializer.accept(this);
+ }
+ }
+
+ member.function!.body?.accept(this);
+ _implicitReturn();
+ b.end();
+ }
+
+ /// Generate code for the body of a lambda.
+ void generateLambda(Lambda lambda, Closures closures) {
+ this.closures = closures;
+
+ final int implicitParams = 1;
+ List<VariableDeclaration> positional =
+ lambda.functionNode.positionalParameters;
+ for (int i = 0; i < positional.length; i++) {
+ locals[positional[i]] = paramLocals[implicitParams + i];
+ }
+
+ Context? context = closures.contexts[lambda.functionNode]?.parent;
+ if (context != null) {
+ b.local_get(paramLocals[0]);
+ translator.ref_cast(b, context.struct);
+ while (true) {
+ w.Local contextLocal =
+ addLocal(w.RefType.def(context!.struct, nullable: false));
+ context.currentLocal = contextLocal;
+ if (context.parent != null || context.containsThis) {
+ b.local_tee(contextLocal);
+ } else {
+ b.local_set(contextLocal);
+ }
+ if (context.parent == null) break;
+
+ b.struct_get(context.struct, context.parentFieldIndex);
+ if (options.localNullability) {
+ b.ref_as_non_null();
+ }
+ context = context.parent!;
+ }
+ if (context.containsThis) {
+ thisLocal = addLocal(
+ context.struct.fields[context.thisFieldIndex].type.unpacked);
+ preciseThisLocal = thisLocal;
+ b.struct_get(context.struct, context.thisFieldIndex);
+ b.local_set(thisLocal!);
+ }
+ }
+ allocateContext(lambda.functionNode);
+ captureParameters();
+
+ lambda.functionNode.body!.accept(this);
+ _implicitReturn();
+ b.end();
+ }
+
+ void _implicitReturn() {
+ if (function.type.outputs.length > 0) {
+ w.ValueType returnType = function.type.outputs[0];
+ if (returnType is w.RefType && returnType.nullable) {
+ // Dart body may have an implicit return null.
+ b.ref_null(returnType.heapType);
+ } else {
+ // This point is unreachable, but the Wasm validator still expects the
+ // stack to contain a value matching the Wasm function return type.
+ b.block(const [], function.type.outputs);
+ b.comment("Unreachable implicit return");
+ b.unreachable();
+ b.end();
+ }
+ }
+ }
+
+ void allocateContext(TreeNode node) {
+ Context? context = closures.contexts[node];
+ if (context != null && !context.isEmpty) {
+ w.Local contextLocal =
+ addLocal(w.RefType.def(context.struct, nullable: false));
+ context.currentLocal = contextLocal;
+ translator.struct_new_default(b, context.struct);
+ b.local_set(contextLocal);
+ if (context.containsThis) {
+ b.local_get(contextLocal);
+ b.local_get(preciseThisLocal!);
+ b.struct_set(context.struct, context.thisFieldIndex);
+ }
+ if (context.parent != null) {
+ w.Local parentLocal = context.parent!.currentLocal;
+ b.local_get(contextLocal);
+ b.local_get(parentLocal);
+ b.struct_set(context.struct, context.parentFieldIndex);
+ }
+ }
+ }
+
+ void captureParameters() {
+ locals.forEach((variable, local) {
+ Capture? capture = closures.captures[variable];
+ if (capture != null) {
+ b.local_get(capture.context.currentLocal);
+ b.local_get(local);
+ translator.convertType(function, local.type, capture.type);
+ b.struct_set(capture.context.struct, capture.fieldIndex);
+ }
+ });
+ }
+
+ /// Generates code for an expression plus conversion code to convert the
+ /// result to the expected type if needed. All expression code generation goes
+ /// through this method.
+ w.ValueType wrap(Expression node, w.ValueType expectedType) {
+ w.ValueType resultType = node.accept1(this, expectedType);
+ translator.convertType(function, resultType, expectedType);
+ return expectedType;
+ }
+
+ w.ValueType _call(Reference target) {
+ w.BaseFunction targetFunction = translator.functions.getFunction(target);
+ if (translator.shouldInline(target)) {
+ List<w.Local> inlinedLocals =
+ targetFunction.type.inputs.map((t) => addLocal(t)).toList();
+ for (w.Local local in inlinedLocals.reversed) {
+ b.local_set(local);
+ }
+ w.Label block = b.block(const [], targetFunction.type.outputs);
+ b.comment("Inlined ${target.asMember}");
+ CodeGenerator(translator, function, target,
+ paramLocals: inlinedLocals, returnLabel: block)
+ .generate();
+ } else {
+ String access =
+ target.isGetter ? "get" : (target.isSetter ? "set" : "call");
+ b.comment("Direct $access of '${target.asMember}'");
+ b.call(targetFunction);
+ }
+ return translator.outputOrVoid(targetFunction.type.outputs);
+ }
+
+ @override
+ void visitInvalidInitializer(InvalidInitializer node) {}
+
+ @override
+ void visitAssertInitializer(AssertInitializer node) {}
+
+ @override
+ void visitLocalInitializer(LocalInitializer node) {
+ node.variable.accept(this);
+ }
+
+ @override
+ void visitFieldInitializer(FieldInitializer node) {
+ Class cls = (node.parent as Constructor).enclosingClass;
+ w.StructType struct = translator.classInfo[cls]!.struct;
+ int fieldIndex = translator.fieldIndex[node.field]!;
+
+ b.local_get(thisLocal!);
+ wrap(node.value, struct.fields[fieldIndex].type.unpacked);
+ b.struct_set(struct, fieldIndex);
+ }
+
+ @override
+ void visitRedirectingInitializer(RedirectingInitializer node) {
+ Class cls = (node.parent as Constructor).enclosingClass;
+ b.local_get(thisLocal!);
+ if (options.parameterNullability && thisLocal!.type.nullable) {
+ b.ref_as_non_null();
+ }
+ for (TypeParameter typeParam in cls.typeParameters) {
+ _makeType(TypeParameterType(typeParam, Nullability.nonNullable), node);
+ }
+ _visitArguments(node.arguments, node.targetReference, 1);
+ _call(node.targetReference);
+ }
+
+ @override
+ void visitSuperInitializer(SuperInitializer node) {
+ Supertype? supertype =
+ (node.parent as Constructor).enclosingClass.supertype;
+ if (supertype?.classNode.superclass == null) {
+ return;
+ }
+ b.local_get(thisLocal!);
+ if (options.parameterNullability && thisLocal!.type.nullable) {
+ b.ref_as_non_null();
+ }
+ for (DartType typeArg in supertype!.typeArguments) {
+ _makeType(typeArg, node);
+ }
+ _visitArguments(node.arguments, node.targetReference,
+ 1 + supertype.typeArguments.length);
+ _call(node.targetReference);
+ }
+
+ @override
+ void visitBlock(Block node) {
+ for (Statement statement in node.statements) {
+ statement.accept(this);
+ }
+ }
+
+ @override
+ void visitLabeledStatement(LabeledStatement node) {
+ w.Label label = b.block();
+ labels[node] = label;
+ node.body.accept(this);
+ labels.remove(node);
+ b.end();
+ }
+
+ @override
+ void visitBreakStatement(BreakStatement node) {
+ b.br(labels[node.target]!);
+ }
+
+ @override
+ void visitVariableDeclaration(VariableDeclaration node) {
+ if (node.type is VoidType) {
+ if (node.initializer != null) {
+ wrap(node.initializer!, voidMarker);
+ }
+ return;
+ }
+ w.ValueType type = translateType(node.type);
+ w.Local? local;
+ Capture? capture = closures.captures[node];
+ if (capture == null || !capture.written) {
+ local = addLocal(type);
+ locals[node] = local;
+ }
+ if (node.initializer != null) {
+ if (capture != null) {
+ w.ValueType expectedType = capture.written ? capture.type : local!.type;
+ b.local_get(capture.context.currentLocal);
+ wrap(node.initializer!, expectedType);
+ if (!capture.written) {
+ b.local_tee(local!);
+ }
+ b.struct_set(capture.context.struct, capture.fieldIndex);
+ } else {
+ wrap(node.initializer!, local!.type);
+ b.local_set(local);
+ }
+ } else if (local != null && !local.type.defaultable) {
+ // Uninitialized variable
+ translator.globals.instantiateDummyValue(b, local.type);
+ b.local_set(local);
+ }
+ }
+
+ @override
+ void visitEmptyStatement(EmptyStatement node) {}
+
+ @override
+ void visitAssertStatement(AssertStatement node) {}
+
+ @override
+ void visitAssertBlock(AssertBlock node) {}
+
+ @override
+ void visitTryCatch(TryCatch node) {
+ // TODO(joshualitt): Include catches
+ node.body.accept(this);
+ }
+
+ @override
+ void visitTryFinally(TryFinally node) {
+ finalizers.add(node.finalizer);
+ node.body.accept(this);
+ finalizers.removeLast().accept(this);
+ }
+
+ @override
+ void visitExpressionStatement(ExpressionStatement node) {
+ wrap(node.expression, voidMarker);
+ }
+
+ bool _hasLogicalOperator(Expression condition) {
+ while (condition is Not) condition = condition.operand;
+ return condition is LogicalExpression;
+ }
+
+ void _branchIf(Expression? condition, w.Label target,
+ {required bool negated}) {
+ if (condition == null) {
+ if (!negated) b.br(target);
+ return;
+ }
+ while (condition is Not) {
+ negated = !negated;
+ condition = condition.operand;
+ }
+ if (condition is LogicalExpression) {
+ bool isConjunctive =
+ (condition.operatorEnum == LogicalExpressionOperator.AND) ^ negated;
+ if (isConjunctive) {
+ w.Label conditionBlock = b.block();
+ _branchIf(condition.left, conditionBlock, negated: !negated);
+ _branchIf(condition.right, target, negated: negated);
+ b.end();
+ } else {
+ _branchIf(condition.left, target, negated: negated);
+ _branchIf(condition.right, target, negated: negated);
+ }
+ } else {
+ wrap(condition!, w.NumType.i32);
+ if (negated) {
+ b.i32_eqz();
+ }
+ b.br_if(target);
+ }
+ }
+
+ void _conditional(Expression condition, void then(), void otherwise()?,
+ List<w.ValueType> result) {
+ if (!_hasLogicalOperator(condition)) {
+ // Simple condition
+ wrap(condition, w.NumType.i32);
+ b.if_(const [], result);
+ then();
+ if (otherwise != null) {
+ b.else_();
+ otherwise();
+ }
+ b.end();
+ } else {
+ // Complex condition
+ w.Label ifBlock = b.block(const [], result);
+ if (otherwise != null) {
+ w.Label elseBlock = b.block();
+ _branchIf(condition, elseBlock, negated: true);
+ then();
+ b.br(ifBlock);
+ b.end();
+ otherwise();
+ } else {
+ _branchIf(condition, ifBlock, negated: true);
+ then();
+ }
+ b.end();
+ }
+ }
+
+ @override
+ void visitIfStatement(IfStatement node) {
+ _conditional(
+ node.condition,
+ () => node.then.accept(this),
+ node.otherwise != null ? () => node.otherwise!.accept(this) : null,
+ const []);
+ }
+
+ @override
+ void visitDoStatement(DoStatement node) {
+ w.Label loop = b.loop();
+ allocateContext(node);
+ node.body.accept(this);
+ _branchIf(node.condition, loop, negated: false);
+ b.end();
+ }
+
+ @override
+ void visitWhileStatement(WhileStatement node) {
+ w.Label block = b.block();
+ w.Label loop = b.loop();
+ _branchIf(node.condition, block, negated: true);
+ allocateContext(node);
+ node.body.accept(this);
+ b.br(loop);
+ b.end();
+ b.end();
+ }
+
+ @override
+ void visitForStatement(ForStatement node) {
+ Context? context = closures.contexts[node];
+ allocateContext(node);
+ for (VariableDeclaration variable in node.variables) {
+ variable.accept(this);
+ }
+ w.Label block = b.block();
+ w.Label loop = b.loop();
+ _branchIf(node.condition, block, negated: true);
+ node.body.accept(this);
+ if (node.variables.any((v) => closures.captures.containsKey(v))) {
+ w.Local oldContext = context!.currentLocal;
+ allocateContext(node);
+ w.Local newContext = context.currentLocal;
+ for (VariableDeclaration variable in node.variables) {
+ Capture? capture = closures.captures[variable];
+ if (capture != null) {
+ b.local_get(oldContext);
+ b.struct_get(context.struct, capture.fieldIndex);
+ b.local_get(newContext);
+ b.struct_set(context.struct, capture.fieldIndex);
+ }
+ }
+ } else {
+ allocateContext(node);
+ }
+ for (Expression update in node.updates) {
+ wrap(update, voidMarker);
+ }
+ b.br(loop);
+ b.end();
+ b.end();
+ }
+
+ @override
+ void visitForInStatement(ForInStatement node) {
+ throw "ForInStatement should have been desugared: $node";
+ }
+
+ @override
+ void visitReturnStatement(ReturnStatement node) {
+ Expression? expression = node.expression;
+ if (expression != null) {
+ wrap(expression, returnType);
+ } else {
+ translator.convertType(function, voidMarker, returnType);
+ }
+ for (Statement finalizer in finalizers.reversed) {
+ finalizer.accept(this);
+ }
+ if (returnLabel != null) {
+ b.br(returnLabel!);
+ } else {
+ b.return_();
+ }
+ }
+
+ @override
+ void visitSwitchStatement(SwitchStatement node) {
+ bool check<L extends Expression, C extends Constant>() =>
+ node.cases.expand((c) => c.expressions).every((e) =>
+ e is L ||
+ e is NullLiteral ||
+ e is ConstantExpression &&
+ (e.constant is C || e.constant is NullConstant));
+
+ // Identify kind of switch
+ w.ValueType valueType;
+ w.ValueType nullableType;
+ void Function() compare;
+ if (check<BoolLiteral, BoolConstant>()) {
+ // bool switch
+ valueType = w.NumType.i32;
+ nullableType =
+ translator.classInfo[translator.boxedBoolClass]!.nullableType;
+ compare = () => b.i32_eq();
+ } else if (check<IntLiteral, IntConstant>()) {
+ // int switch
+ valueType = w.NumType.i64;
+ nullableType =
+ translator.classInfo[translator.boxedIntClass]!.nullableType;
+ compare = () => b.i64_eq();
+ } else if (check<StringLiteral, StringConstant>()) {
+ // String switch
+ valueType =
+ translator.classInfo[translator.stringBaseClass]!.nonNullableType;
+ nullableType = valueType.withNullability(true);
+ compare = () => _call(translator.stringEquals.reference);
+ } else {
+ // Object switch
+ assert(check<InvalidExpression, InstanceConstant>());
+ valueType = w.RefType.eq(nullable: false);
+ nullableType = w.RefType.eq(nullable: true);
+ compare = () => b.ref_eq();
+ }
+ w.Local valueLocal = addLocal(valueType);
+
+ // Special cases
+ SwitchCase? defaultCase = node.cases
+ .cast<SwitchCase?>()
+ .firstWhere((c) => c!.isDefault, orElse: () => null);
+ SwitchCase? nullCase = node.cases.cast<SwitchCase?>().firstWhere(
+ (c) => c!.expressions.any((e) =>
+ e is NullLiteral ||
+ e is ConstantExpression && e.constant is NullConstant),
+ orElse: () => null);
+
+ // Set up blocks, in reverse order of cases so they end in forward order
+ w.Label doneLabel = b.block();
+ for (SwitchCase c in node.cases.reversed) {
+ switchLabels[c] = b.block();
+ }
+
+ // Compute value and handle null
+ bool isNullable = dartTypeOf(node.expression).isPotentiallyNullable;
+ if (isNullable) {
+ w.Label nullLabel = nullCase != null
+ ? switchLabels[nullCase]!
+ : defaultCase != null
+ ? switchLabels[defaultCase]!
+ : doneLabel;
+ wrap(node.expression, nullableType);
+ b.br_on_null(nullLabel);
+ translator.convertType(
+ function, nullableType.withNullability(false), valueType);
+ } else {
+ assert(nullCase == null);
+ wrap(node.expression, valueType);
+ }
+ b.local_set(valueLocal);
+
+ // Compare against all case values
+ for (SwitchCase c in node.cases) {
+ for (Expression exp in c.expressions) {
+ if (exp is NullLiteral ||
+ exp is ConstantExpression && exp.constant is NullConstant) {
+ // Null already checked, skip
+ } else {
+ wrap(exp, valueType);
+ b.local_get(valueLocal);
+ translator.convertType(function, valueLocal.type, valueType);
+ compare();
+ b.br_if(switchLabels[c]!);
+ }
+ }
+ }
+ w.Label defaultLabel =
+ defaultCase != null ? switchLabels[defaultCase]! : doneLabel;
+ b.br(defaultLabel);
+
+ // Emit case bodies
+ for (SwitchCase c in node.cases) {
+ switchLabels.remove(c);
+ b.end();
+ c.body.accept(this);
+ b.br(doneLabel);
+ }
+ b.end();
+ }
+
+ @override
+ void visitContinueSwitchStatement(ContinueSwitchStatement node) {
+ w.Label? label = switchLabels[node.target];
+ if (label != null) {
+ b.br(label);
+ } else {
+ throw "Not supported: Backward jump to switch case at ${node.location}";
+ }
+ }
+
+ @override
+ void visitYieldStatement(YieldStatement node) => defaultStatement(node);
+
+ @override
+ w.ValueType visitBlockExpression(
+ BlockExpression node, w.ValueType expectedType) {
+ node.body.accept(this);
+ return wrap(node.value, expectedType);
+ }
+
+ @override
+ w.ValueType visitLet(Let node, w.ValueType expectedType) {
+ node.variable.accept(this);
+ return wrap(node.body, expectedType);
+ }
+
+ @override
+ w.ValueType visitThisExpression(
+ ThisExpression node, w.ValueType expectedType) {
+ return _visitThis(expectedType);
+ }
+
+ w.ValueType _visitThis(w.ValueType expectedType) {
+ w.ValueType thisType = thisLocal!.type.withNullability(false);
+ w.ValueType preciseThisType = preciseThisLocal!.type.withNullability(false);
+ if (!thisType.isSubtypeOf(expectedType) &&
+ preciseThisType.isSubtypeOf(expectedType)) {
+ b.local_get(preciseThisLocal!);
+ return preciseThisLocal!.type;
+ } else {
+ b.local_get(thisLocal!);
+ return thisLocal!.type;
+ }
+ }
+
+ @override
+ w.ValueType visitConstructorInvocation(
+ ConstructorInvocation node, w.ValueType expectedType) {
+ ClassInfo info = translator.classInfo[node.target.enclosingClass]!;
+ translator.functions.allocateClass(info.classId);
+ w.Local temp = addLocal(info.nonNullableType);
+ translator.struct_new_default(b, info);
+ b.local_tee(temp);
+ b.local_get(temp);
+ b.i32_const(info.classId);
+ b.struct_set(info.struct, FieldIndex.classId);
+ if (options.parameterNullability && temp.type.nullable) {
+ b.ref_as_non_null();
+ }
+ _visitArguments(node.arguments, node.targetReference, 1);
+ _call(node.targetReference);
+ if (expectedType != voidMarker) {
+ b.local_get(temp);
+ return temp.type;
+ } else {
+ return voidMarker;
+ }
+ }
+
+ @override
+ w.ValueType visitStaticInvocation(
+ StaticInvocation node, w.ValueType expectedType) {
+ w.ValueType? intrinsicResult = intrinsifier.generateStaticIntrinsic(node);
+ if (intrinsicResult != null) return intrinsicResult;
+ _visitArguments(node.arguments, node.targetReference, 0);
+ return _call(node.targetReference);
+ }
+
+ Member _lookupSuperTarget(Member interfaceTarget, {required bool setter}) {
+ return translator.hierarchy.getDispatchTarget(
+ member.enclosingClass!.superclass!, interfaceTarget.name,
+ setter: setter)!;
+ }
+
+ @override
+ w.ValueType visitSuperMethodInvocation(
+ SuperMethodInvocation node, w.ValueType expectedType) {
+ Reference target =
+ _lookupSuperTarget(node.interfaceTarget!, setter: false).reference;
+ w.BaseFunction targetFunction = translator.functions.getFunction(target);
+ w.ValueType receiverType = targetFunction.type.inputs.first;
+ w.ValueType thisType = _visitThis(receiverType);
+ translator.convertType(function, thisType, receiverType);
+ _visitArguments(node.arguments, target, 1);
+ return _call(target);
+ }
+
+ @override
+ w.ValueType visitInstanceInvocation(
+ InstanceInvocation node, w.ValueType expectedType) {
+ w.ValueType? intrinsicResult = intrinsifier.generateInstanceIntrinsic(node);
+ if (intrinsicResult != null) return intrinsicResult;
+ Procedure target = node.interfaceTarget;
+ if (node.kind == InstanceAccessKind.Object) {
+ switch (target.name.text) {
+ case "toString":
+ late w.Label done;
+ w.ValueType resultType = _virtualCall(node, target, (signature) {
+ done = b.block(const [], signature.outputs);
+ w.Label nullString = b.block();
+ wrap(node.receiver, translator.topInfo.nullableType);
+ b.br_on_null(nullString);
+ }, (_) {
+ _visitArguments(node.arguments, node.interfaceTargetReference, 1);
+ }, getter: false, setter: false);
+ b.br(done);
+ b.end();
+ wrap(StringLiteral("null"), resultType);
+ b.end();
+ return resultType;
+ default:
+ _unimplemented(node, "Nullable invocation of ${target.name.text}",
+ [if (expectedType != voidMarker) expectedType]);
+ return expectedType;
+ }
+ }
+ Member? singleTarget = translator.singleTarget(node);
+ if (singleTarget != null) {
+ w.BaseFunction targetFunction =
+ translator.functions.getFunction(singleTarget.reference);
+ wrap(node.receiver, targetFunction.type.inputs.first);
+ _visitArguments(node.arguments, node.interfaceTargetReference, 1);
+ return _call(singleTarget.reference);
+ }
+ return _virtualCall(node, target,
+ (signature) => wrap(node.receiver, signature.inputs.first), (_) {
+ _visitArguments(node.arguments, node.interfaceTargetReference, 1);
+ }, getter: false, setter: false);
+ }
+
+ @override
+ w.ValueType visitDynamicInvocation(
+ DynamicInvocation node, w.ValueType expectedType) {
+ if (node.name.text != "call") {
+ _unimplemented(node, "Dynamic invocation of ${node.name.text}",
+ [if (expectedType != voidMarker) expectedType]);
+ return expectedType;
+ }
+ return _functionCall(
+ node.arguments.positional.length, node.receiver, node.arguments);
+ }
+
+ @override
+ w.ValueType visitEqualsCall(EqualsCall node, w.ValueType expectedType) {
+ w.ValueType? intrinsicResult = intrinsifier.generateEqualsIntrinsic(node);
+ if (intrinsicResult != null) return intrinsicResult;
+ Member? singleTarget = translator.singleTarget(node);
+ if (singleTarget == translator.coreTypes.objectEquals) {
+ // Plain reference comparison
+ wrap(node.left, w.RefType.eq(nullable: true));
+ wrap(node.right, w.RefType.eq(nullable: true));
+ b.ref_eq();
+ } else {
+ // Check operands for null, then call implementation
+ bool leftNullable = dartTypeOf(node.left).isPotentiallyNullable;
+ bool rightNullable = dartTypeOf(node.right).isPotentiallyNullable;
+ w.RefType leftType = translator.topInfo.typeWithNullability(leftNullable);
+ w.RefType rightType =
+ translator.topInfo.typeWithNullability(rightNullable);
+ w.Local leftLocal = addLocal(leftType);
+ w.Local rightLocal = addLocal(rightType);
+ w.Label? operandNull;
+ w.Label? done;
+ if (leftNullable || rightNullable) {
+ done = b.block(const [], const [w.NumType.i32]);
+ operandNull = b.block();
+ }
+ wrap(node.left, leftLocal.type);
+ b.local_set(leftLocal);
+ wrap(node.right, rightLocal.type);
+ if (rightNullable) {
+ b.local_tee(rightLocal);
+ b.br_on_null(operandNull!);
+ b.drop();
+ } else {
+ b.local_set(rightLocal);
+ }
+
+ void left([_]) {
+ b.local_get(leftLocal);
+ if (leftNullable) {
+ b.br_on_null(operandNull!);
+ } else if (leftLocal.type.nullable) {
+ b.ref_as_non_null();
+ }
+ }
+
+ void right([_]) {
+ b.local_get(rightLocal);
+ if (rightLocal.type.nullable) {
+ b.ref_as_non_null();
+ }
+ }
+
+ if (singleTarget != null) {
+ left();
+ right();
+ _call(singleTarget.reference);
+ } else {
+ _virtualCall(node, node.interfaceTarget, left, right,
+ getter: false, setter: false);
+ }
+ if (leftNullable || rightNullable) {
+ b.br(done!);
+ b.end(); // operandNull
+ if (leftNullable && rightNullable) {
+ // Both sides nullable - compare references
+ b.local_get(leftLocal);
+ b.local_get(rightLocal);
+ b.ref_eq();
+ } else {
+ // Only one side nullable - not equal if one is null
+ b.i32_const(0);
+ }
+ b.end(); // done
+ }
+ }
+ return w.NumType.i32;
+ }
+
+ @override
+ w.ValueType visitEqualsNull(EqualsNull node, w.ValueType expectedType) {
+ wrap(node.expression, translator.topInfo.nullableType);
+ b.ref_is_null();
+ return w.NumType.i32;
+ }
+
+ w.ValueType _virtualCall(
+ TreeNode node,
+ Member interfaceTarget,
+ void pushReceiver(w.FunctionType signature),
+ void pushArguments(w.FunctionType signature),
+ {required bool getter,
+ required bool setter}) {
+ SelectorInfo selector = translator.dispatchTable.selectorForTarget(
+ interfaceTarget.referenceAs(getter: getter, setter: setter));
+ assert(selector.name == interfaceTarget.name.text);
+
+ pushReceiver(selector.signature);
+
+ int? offset = selector.offset;
+ if (offset == null) {
+ // Singular target or unreachable call
+ assert(selector.targetCount <= 1);
+ if (selector.targetCount == 1) {
+ pushArguments(selector.signature);
+ return _call(selector.singularTarget!);
+ } else {
+ b.comment("Virtual call of ${selector.name} with no targets"
+ " at ${node.location}");
+ b.drop();
+ b.block(const [], selector.signature.outputs);
+ b.unreachable();
+ b.end();
+ return translator.outputOrVoid(selector.signature.outputs);
+ }
+ }
+
+ // Receiver is already on stack.
+ w.Local receiverVar = addLocal(selector.signature.inputs.first);
+ b.local_tee(receiverVar);
+ if (options.parameterNullability && receiverVar.type.nullable) {
+ b.ref_as_non_null();
+ }
+ pushArguments(selector.signature);
+
+ if (options.polymorphicSpecialization) {
+ _polymorphicSpecialization(selector, receiverVar);
+ } else {
+ String access = getter ? "get" : (setter ? "set" : "call");
+ b.comment("Instance $access of '${selector.name}'");
+ b.local_get(receiverVar);
+ b.struct_get(translator.topInfo.struct, FieldIndex.classId);
+ if (offset != 0) {
+ b.i32_const(offset);
+ b.i32_add();
+ }
+ b.call_indirect(selector.signature);
+
+ translator.functions.activateSelector(selector);
+ }
+
+ return translator.outputOrVoid(selector.signature.outputs);
+ }
+
+ void _polymorphicSpecialization(SelectorInfo selector, w.Local receiver) {
+ Map<int, Reference> implementations = Map.from(selector.targets);
+ implementations.removeWhere((id, target) => target.asMember.isAbstract);
+
+ w.Local idVar = addLocal(w.NumType.i32);
+ b.local_get(receiver);
+ b.struct_get(translator.topInfo.struct, FieldIndex.classId);
+ b.local_set(idVar);
+
+ w.Label block =
+ b.block(selector.signature.inputs, selector.signature.outputs);
+ calls:
+ while (Set.from(implementations.values).length > 1) {
+ for (int id in implementations.keys) {
+ Reference target = implementations[id]!;
+ if (implementations.values.where((t) => t == target).length == 1) {
+ // Single class id implements method.
+ b.local_get(idVar);
+ b.i32_const(id);
+ b.i32_eq();
+ b.if_(selector.signature.inputs, selector.signature.inputs);
+ _call(target);
+ b.br(block);
+ b.end();
+ implementations.remove(id);
+ continue calls;
+ }
+ }
+ // Find class id that separates remaining classes in two.
+ List<int> sorted = implementations.keys.toList()..sort();
+ int pivotId = sorted.firstWhere(
+ (id) => implementations[id] != implementations[sorted.first]);
+ // Fail compilation if no such id exists.
+ assert(sorted.lastWhere(
+ (id) => implementations[id] != implementations[pivotId]) ==
+ pivotId - 1);
+ Reference target = implementations[sorted.first]!;
+ b.local_get(idVar);
+ b.i32_const(pivotId);
+ b.i32_lt_u();
+ b.if_(selector.signature.inputs, selector.signature.inputs);
+ _call(target);
+ b.br(block);
+ b.end();
+ for (int id in sorted) {
+ if (id == pivotId) break;
+ implementations.remove(id);
+ }
+ continue calls;
+ }
+ // Call remaining implementation.
+ Reference target = implementations.values.first;
+ _call(target);
+ b.end();
+ }
+
+ @override
+ w.ValueType visitVariableGet(VariableGet node, w.ValueType expectedType) {
+ w.Local? local = locals[node.variable];
+ Capture? capture = closures.captures[node.variable];
+ if (capture != null) {
+ if (!capture.written && local != null) {
+ b.local_get(local);
+ return local.type;
+ } else {
+ b.local_get(capture.context.currentLocal);
+ b.struct_get(capture.context.struct, capture.fieldIndex);
+ return capture.type;
+ }
+ } else {
+ if (local == null) {
+ throw "Read of undefined variable ${node.variable}";
+ }
+ b.local_get(local);
+ return local.type;
+ }
+ }
+
+ @override
+ w.ValueType visitVariableSet(VariableSet node, w.ValueType expectedType) {
+ w.Local? local = locals[node.variable];
+ Capture? capture = closures.captures[node.variable];
+ bool preserved = expectedType != voidMarker;
+ if (capture != null) {
+ assert(capture.written);
+ b.local_get(capture.context.currentLocal);
+ wrap(node.value, capture.type);
+ if (preserved) {
+ w.Local temp = addLocal(translateType(node.variable.type));
+ b.local_tee(temp);
+ b.struct_set(capture.context.struct, capture.fieldIndex);
+ b.local_get(temp);
+ return temp.type;
+ } else {
+ b.struct_set(capture.context.struct, capture.fieldIndex);
+ return voidMarker;
+ }
+ } else {
+ if (local == null) {
+ throw "Write of undefined variable ${node.variable}";
+ }
+ wrap(node.value, local.type);
+ if (preserved) {
+ b.local_tee(local);
+ return local.type;
+ } else {
+ b.local_set(local);
+ return voidMarker;
+ }
+ }
+ }
+
+ @override
+ w.ValueType visitStaticGet(StaticGet node, w.ValueType expectedType) {
+ w.ValueType? intrinsicResult =
+ intrinsifier.generateStaticGetterIntrinsic(node);
+ if (intrinsicResult != null) return intrinsicResult;
+ Member target = node.target;
+ if (target is Field) {
+ return translator.globals.readGlobal(b, target);
+ } else {
+ return _call(target.reference);
+ }
+ }
+
+ @override
+ w.ValueType visitStaticTearOff(StaticTearOff node, w.ValueType expectedType) {
+ translator.constants.instantiateConstant(
+ function, b, StaticTearOffConstant(node.target), expectedType);
+ return expectedType;
+ }
+
+ @override
+ w.ValueType visitStaticSet(StaticSet node, w.ValueType expectedType) {
+ bool preserved = expectedType != voidMarker;
+ Member target = node.target;
+ if (target is Field) {
+ w.Global global = translator.globals.getGlobal(target);
+ wrap(node.value, global.type.type);
+ b.global_set(global);
+ if (preserved) {
+ b.global_get(global);
+ return global.type.type;
+ } else {
+ return voidMarker;
+ }
+ } else {
+ w.BaseFunction targetFunction =
+ translator.functions.getFunction(target.reference);
+ wrap(node.value, targetFunction.type.inputs.single);
+ w.Local? temp;
+ if (preserved) {
+ temp = addLocal(translateType(dartTypeOf(node.value)));
+ b.local_tee(temp);
+ }
+ _call(target.reference);
+ if (preserved) {
+ b.local_get(temp!);
+ return temp.type;
+ } else {
+ return voidMarker;
+ }
+ }
+ }
+
+ @override
+ w.ValueType visitSuperPropertyGet(
+ SuperPropertyGet node, w.ValueType expectedType) {
+ Member target = _lookupSuperTarget(node.interfaceTarget!, setter: false);
+ if (target is Procedure && !target.isGetter) {
+ throw "Not supported: Super tear-off at ${node.location}";
+ }
+ return _directGet(target, ThisExpression(), () => null);
+ }
+
+ @override
+ w.ValueType visitSuperPropertySet(
+ SuperPropertySet node, w.ValueType expectedType) {
+ Member target = _lookupSuperTarget(node.interfaceTarget!, setter: true);
+ return _directSet(target, ThisExpression(), node.value,
+ preserved: expectedType != voidMarker);
+ }
+
+ @override
+ w.ValueType visitInstanceGet(InstanceGet node, w.ValueType expectedType) {
+ Member target = node.interfaceTarget;
+ if (node.kind == InstanceAccessKind.Object) {
+ late w.Label doneLabel;
+ w.ValueType resultType = _virtualCall(node, target, (signature) {
+ doneLabel = b.block(const [], signature.outputs);
+ w.Label nullLabel = b.block();
+ wrap(node.receiver, translator.topInfo.nullableType);
+ b.br_on_null(nullLabel);
+ }, (_) {}, getter: true, setter: false);
+ b.br(doneLabel);
+ b.end(); // nullLabel
+ switch (target.name.text) {
+ case "hashCode":
+ b.i64_const(2011);
+ break;
+ case "runtimeType":
+ wrap(ConstantExpression(TypeLiteralConstant(NullType())), resultType);
+ break;
+ default:
+ _unimplemented(
+ node, "Nullable get of ${target.name.text}", [resultType]);
+ break;
+ }
+ b.end(); // doneLabel
+ return resultType;
+ }
+ Member? singleTarget = translator.singleTarget(node);
+ if (singleTarget != null) {
+ return _directGet(singleTarget, node.receiver,
+ () => intrinsifier.generateInstanceGetterIntrinsic(node));
+ } else {
+ return _virtualCall(node, target,
+ (signature) => wrap(node.receiver, signature.inputs.first), (_) {},
+ getter: true, setter: false);
+ }
+ }
+
+ @override
+ w.ValueType visitDynamicGet(DynamicGet node, w.ValueType expectedType) {
+ // Provisional implementation of dynamic get which assumes the getter
+ // is present (otherwise it traps or calls something random) and
+ // does not support tearoffs. This is sufficient to handle the
+ // dynamic .length calls in the core libraries.
+
+ SelectorInfo selector =
+ translator.dispatchTable.selectorForDynamicName(node.name.text);
+
+ // Evaluate receiver
+ wrap(node.receiver, selector.signature.inputs.first);
+ w.Local receiverVar = addLocal(selector.signature.inputs.first);
+ b.local_tee(receiverVar);
+ if (options.parameterNullability && receiverVar.type.nullable) {
+ b.ref_as_non_null();
+ }
+
+ // Dispatch table call
+ b.comment("Dynamic get of '${selector.name}'");
+ int offset = selector.offset!;
+ b.local_get(receiverVar);
+ b.struct_get(translator.topInfo.struct, FieldIndex.classId);
+ if (offset != 0) {
+ b.i32_const(offset);
+ b.i32_add();
+ }
+ b.call_indirect(selector.signature);
+
+ translator.functions.activateSelector(selector);
+
+ return translator.outputOrVoid(selector.signature.outputs);
+ }
+
+ w.ValueType _directGet(
+ Member target, Expression receiver, w.ValueType? Function() intrinsify) {
+ if (target is Field) {
+ ClassInfo info = translator.classInfo[target.enclosingClass]!;
+ int fieldIndex = translator.fieldIndex[target]!;
+ w.ValueType receiverType = info.nullableType;
+ w.ValueType fieldType = info.struct.fields[fieldIndex].type.unpacked;
+ wrap(receiver, receiverType);
+ b.struct_get(info.struct, fieldIndex);
+ return fieldType;
+ } else {
+ // Instance call of getter
+ assert(target is Procedure && target.isGetter);
+ w.ValueType? intrinsicResult = intrinsify();
+ if (intrinsicResult != null) return intrinsicResult;
+ w.BaseFunction targetFunction =
+ translator.functions.getFunction(target.reference);
+ wrap(receiver, targetFunction.type.inputs.single);
+ return _call(target.reference);
+ }
+ }
+
+ @override
+ w.ValueType visitInstanceTearOff(
+ InstanceTearOff node, w.ValueType expectedType) {
+ return _virtualCall(node, node.interfaceTarget,
+ (signature) => wrap(node.receiver, signature.inputs.first), (_) {},
+ getter: true, setter: false);
+ }
+
+ @override
+ w.ValueType visitInstanceSet(InstanceSet node, w.ValueType expectedType) {
+ bool preserved = expectedType != voidMarker;
+ w.Local? temp;
+ Member? singleTarget = translator.singleTarget(node);
+ if (singleTarget != null) {
+ return _directSet(singleTarget, node.receiver, node.value,
+ preserved: preserved);
+ } else {
+ _virtualCall(node, node.interfaceTarget,
+ (signature) => wrap(node.receiver, signature.inputs.first),
+ (signature) {
+ w.ValueType paramType = signature.inputs.last;
+ wrap(node.value, paramType);
+ if (preserved) {
+ temp = addLocal(paramType);
+ b.local_tee(temp!);
+ }
+ }, getter: false, setter: true);
+ if (preserved) {
+ b.local_get(temp!);
+ return temp!.type;
+ } else {
+ return voidMarker;
+ }
+ }
+ }
+
+ w.ValueType _directSet(Member target, Expression receiver, Expression value,
+ {required bool preserved}) {
+ w.Local? temp;
+ if (target is Field) {
+ ClassInfo info = translator.classInfo[target.enclosingClass]!;
+ int fieldIndex = translator.fieldIndex[target]!;
+ w.ValueType receiverType = info.nullableType;
+ w.ValueType fieldType = info.struct.fields[fieldIndex].type.unpacked;
+ wrap(receiver, receiverType);
+ wrap(value, fieldType);
+ if (preserved) {
+ temp = addLocal(fieldType);
+ b.local_tee(temp);
+ }
+ b.struct_set(info.struct, fieldIndex);
+ } else {
+ w.BaseFunction targetFunction =
+ translator.functions.getFunction(target.reference);
+ w.ValueType paramType = targetFunction.type.inputs.last;
+ wrap(receiver, targetFunction.type.inputs.first);
+ wrap(value, paramType);
+ if (preserved) {
+ temp = addLocal(paramType);
+ b.local_tee(temp);
+ translator.convertType(function, temp.type, paramType);
+ }
+ _call(target.reference);
+ }
+ if (preserved) {
+ b.local_get(temp!);
+ return temp.type;
+ } else {
+ return voidMarker;
+ }
+ }
+
+ @override
+ void visitFunctionDeclaration(FunctionDeclaration node) {
+ Capture? capture = closures.captures[node.variable];
+ bool locallyClosurized = closures.closurizedFunctions.contains(node);
+ if (capture != null || locallyClosurized) {
+ if (capture != null) {
+ b.local_get(capture.context.currentLocal);
+ }
+ w.StructType struct = _instantiateClosure(node.function);
+ if (locallyClosurized) {
+ w.Local local = addLocal(w.RefType.def(struct, nullable: false));
+ locals[node.variable] = local;
+ if (capture != null) {
+ b.local_tee(local);
+ } else {
+ b.local_set(local);
+ }
+ }
+ if (capture != null) {
+ b.struct_set(capture.context.struct, capture.fieldIndex);
+ }
+ }
+ }
+
+ @override
+ w.ValueType visitFunctionExpression(
+ FunctionExpression node, w.ValueType expectedType) {
+ w.StructType struct = _instantiateClosure(node.function);
+ return w.RefType.def(struct, nullable: false);
+ }
+
+ w.StructType _instantiateClosure(FunctionNode functionNode) {
+ int parameterCount = functionNode.requiredParameterCount;
+ Lambda lambda = closures.lambdas[functionNode]!;
+ w.DefinedGlobal global = translator.makeFunctionRef(lambda.function);
+
+ ClassInfo info = translator.classInfo[translator.functionClass]!;
+ translator.functions.allocateClass(info.classId);
+ w.StructType struct = translator.closureStructType(parameterCount);
+
+ b.i32_const(info.classId);
+ b.i32_const(initialIdentityHash);
+ _pushContext(functionNode);
+ b.global_get(global);
+ translator.struct_new(b, parameterCount);
+
+ return struct;
+ }
+
+ void _pushContext(FunctionNode functionNode) {
+ Context? context = closures.contexts[functionNode]?.parent;
+ if (context != null) {
+ b.local_get(context.currentLocal);
+ if (context.currentLocal.type.nullable) {
+ b.ref_as_non_null();
+ }
+ } else {
+ b.global_get(translator.globals.dummyGlobal); // Dummy context
+ }
+ }
+
+ @override
+ w.ValueType visitFunctionInvocation(
+ FunctionInvocation node, w.ValueType expectedType) {
+ FunctionType functionType = node.functionType!;
+ int parameterCount = functionType.requiredParameterCount;
+ return _functionCall(parameterCount, node.receiver, node.arguments);
+ }
+
+ w.ValueType _functionCall(
+ int parameterCount, Expression receiver, Arguments arguments) {
+ w.StructType struct = translator.closureStructType(parameterCount);
+ w.Local temp = addLocal(w.RefType.def(struct, nullable: false));
+ wrap(receiver, temp.type);
+ b.local_tee(temp);
+ b.struct_get(struct, FieldIndex.closureContext);
+ for (Expression arg in arguments.positional) {
+ wrap(arg, translator.topInfo.nullableType);
+ }
+ b.local_get(temp);
+ b.struct_get(struct, FieldIndex.closureFunction);
+ b.call_ref();
+ return translator.topInfo.nullableType;
+ }
+
+ @override
+ w.ValueType visitLocalFunctionInvocation(
+ LocalFunctionInvocation node, w.ValueType expectedType) {
+ var decl = node.variable.parent as FunctionDeclaration;
+ _pushContext(decl.function);
+ for (Expression arg in node.arguments.positional) {
+ wrap(arg, translator.topInfo.nullableType);
+ }
+ Lambda lambda = closures.lambdas[decl.function]!;
+ b.comment("Local call of ${decl.variable.name}");
+ b.call(lambda.function);
+ return translator.topInfo.nullableType;
+ }
+
+ @override
+ w.ValueType visitLogicalExpression(
+ LogicalExpression node, w.ValueType expectedType) {
+ _conditional(node, () => b.i32_const(1), () => b.i32_const(0),
+ const [w.NumType.i32]);
+ return w.NumType.i32;
+ }
+
+ @override
+ w.ValueType visitNot(Not node, w.ValueType expectedType) {
+ wrap(node.operand, w.NumType.i32);
+ b.i32_eqz();
+ return w.NumType.i32;
+ }
+
+ @override
+ w.ValueType visitConditionalExpression(
+ ConditionalExpression node, w.ValueType expectedType) {
+ _conditional(
+ node.condition,
+ () => wrap(node.then, expectedType),
+ () => wrap(node.otherwise, expectedType),
+ [if (expectedType != voidMarker) expectedType]);
+ return expectedType;
+ }
+
+ @override
+ w.ValueType visitNullCheck(NullCheck node, w.ValueType expectedType) {
+ // TODO(joshualitt): Check and throw exception
+ return wrap(node.operand, expectedType);
+ }
+
+ void _visitArguments(Arguments node, Reference target, int signatureOffset) {
+ final w.FunctionType signature = translator.signatureFor(target);
+ final ParameterInfo paramInfo = translator.paramInfoFor(target);
+ for (int i = 0; i < node.types.length; i++) {
+ _makeType(node.types[i], node);
+ }
+ signatureOffset += node.types.length;
+ for (int i = 0; i < node.positional.length; i++) {
+ wrap(node.positional[i], signature.inputs[signatureOffset + i]);
+ }
+ // Default values for positional parameters
+ for (int i = node.positional.length; i < paramInfo.positional.length; i++) {
+ final w.ValueType type = signature.inputs[signatureOffset + i];
+ translator.constants
+ .instantiateConstant(function, b, paramInfo.positional[i]!, type);
+ }
+ // Named arguments
+ final Map<String, w.Local> namedLocals = {};
+ for (var namedArg in node.named) {
+ final w.ValueType type = signature
+ .inputs[signatureOffset + paramInfo.nameIndex[namedArg.name]!];
+ final w.Local namedLocal = addLocal(type);
+ namedLocals[namedArg.name] = namedLocal;
+ wrap(namedArg.value, namedLocal.type);
+ b.local_set(namedLocal);
+ }
+ for (String name in paramInfo.names) {
+ w.Local? namedLocal = namedLocals[name];
+ final w.ValueType type =
+ signature.inputs[signatureOffset + paramInfo.nameIndex[name]!];
+ if (namedLocal != null) {
+ b.local_get(namedLocal);
+ translator.convertType(function, namedLocal.type, type);
+ } else {
+ translator.constants
+ .instantiateConstant(function, b, paramInfo.named[name]!, type);
+ }
+ }
+ }
+
+ @override
+ w.ValueType visitStringConcatenation(
+ StringConcatenation node, w.ValueType expectedType) {
+ _makeList(
+ node.expressions,
+ translator.fixedLengthListClass,
+ InterfaceType(translator.stringBaseClass, Nullability.nonNullable),
+ node);
+ return _call(translator.stringInterpolate.reference);
+ }
+
+ @override
+ w.ValueType visitThrow(Throw node, w.ValueType expectedType) {
+ wrap(node.expression, translator.topInfo.nullableType);
+ // TODO(joshualitt): Throw exception
+ b.comment(node.toStringInternal());
+ b.drop();
+ b.block(const [], [if (expectedType != voidMarker) expectedType]);
+ b.unreachable();
+ b.end();
+ return expectedType;
+ }
+
+ @override
+ w.ValueType visitInstantiation(Instantiation node, w.ValueType expectedType) {
+ throw "Not supported: Generic function instantiation at ${node.location}";
+ }
+
+ @override
+ w.ValueType visitConstantExpression(
+ ConstantExpression node, w.ValueType expectedType) {
+ translator.constants
+ .instantiateConstant(function, b, node.constant, expectedType);
+ return expectedType;
+ }
+
+ @override
+ w.ValueType visitNullLiteral(NullLiteral node, w.ValueType expectedType) {
+ translator.constants
+ .instantiateConstant(function, b, NullConstant(), expectedType);
+ return expectedType;
+ }
+
+ @override
+ w.ValueType visitStringLiteral(StringLiteral node, w.ValueType expectedType) {
+ translator.constants.instantiateConstant(
+ function, b, StringConstant(node.value), expectedType);
+ return expectedType;
+ }
+
+ @override
+ w.ValueType visitBoolLiteral(BoolLiteral node, w.ValueType expectedType) {
+ b.i32_const(node.value ? 1 : 0);
+ return w.NumType.i32;
+ }
+
+ @override
+ w.ValueType visitIntLiteral(IntLiteral node, w.ValueType expectedType) {
+ b.i64_const(node.value);
+ return w.NumType.i64;
+ }
+
+ @override
+ w.ValueType visitDoubleLiteral(DoubleLiteral node, w.ValueType expectedType) {
+ b.f64_const(node.value);
+ return w.NumType.f64;
+ }
+
+ @override
+ w.ValueType visitListLiteral(ListLiteral node, w.ValueType expectedType) {
+ return _makeList(node.expressions, translator.growableListClass,
+ node.typeArgument, node);
+ }
+
+ w.ValueType _makeList(List<Expression> expressions, Class cls,
+ DartType typeArg, TreeNode node) {
+ ClassInfo info = translator.classInfo[cls]!;
+ translator.functions.allocateClass(info.classId);
+ w.RefType refType = info.struct.fields.last.type.unpacked as w.RefType;
+ w.ArrayType arrayType = refType.heapType as w.ArrayType;
+ w.ValueType elementType = arrayType.elementType.type.unpacked;
+ int length = expressions.length;
+
+ b.i32_const(info.classId);
+ b.i32_const(initialIdentityHash);
+ _makeType(typeArg, node);
+ b.i64_const(length);
+ if (options.lazyConstants) {
+ // Avoid array.init instruction in lazy constants mode
+ b.i32_const(length);
+ translator.array_new_default(b, arrayType);
+ if (length > 0) {
+ w.Local arrayLocal = addLocal(refType.withNullability(false));
+ b.local_set(arrayLocal);
+ for (int i = 0; i < length; i++) {
+ b.local_get(arrayLocal);
+ b.i32_const(i);
+ wrap(expressions[i], elementType);
+ b.array_set(arrayType);
+ }
+ b.local_get(arrayLocal);
+ if (arrayLocal.type.nullable) {
+ b.ref_as_non_null();
+ }
+ }
+ } else {
+ for (Expression expression in expressions) {
+ wrap(expression, elementType);
+ }
+ translator.array_init(b, arrayType, length);
+ }
+ translator.struct_new(b, info);
+
+ return info.nonNullableType;
+ }
+
+ @override
+ w.ValueType visitMapLiteral(MapLiteral node, w.ValueType expectedType) {
+ w.BaseFunction mapFactory =
+ translator.functions.getFunction(translator.mapFactory.reference);
+ w.ValueType factoryReturnType = mapFactory.type.outputs.single;
+ _makeType(node.keyType, node);
+ _makeType(node.valueType, node);
+ b.call(mapFactory);
+ if (node.entries.isEmpty) {
+ return factoryReturnType;
+ }
+ w.BaseFunction mapPut =
+ translator.functions.getFunction(translator.mapPut.reference);
+ w.ValueType putReceiverType = mapPut.type.inputs[0];
+ w.ValueType putKeyType = mapPut.type.inputs[1];
+ w.ValueType putValueType = mapPut.type.inputs[2];
+ w.Local mapLocal = addLocal(putReceiverType);
+ translator.convertType(function, factoryReturnType, mapLocal.type);
+ b.local_set(mapLocal);
+ for (MapLiteralEntry entry in node.entries) {
+ b.local_get(mapLocal);
+ translator.convertType(function, mapLocal.type, putReceiverType);
+ wrap(entry.key, putKeyType);
+ wrap(entry.value, putValueType);
+ b.call(mapPut);
+ }
+ b.local_get(mapLocal);
+ return mapLocal.type;
+ }
+
+ @override
+ w.ValueType visitTypeLiteral(TypeLiteral node, w.ValueType expectedType) {
+ return _makeType(node.type, node);
+ }
+
+ w.ValueType _makeType(DartType type, TreeNode node) {
+ w.ValueType typeType =
+ translator.classInfo[translator.typeClass]!.nullableType;
+ if (_isTypeConstant(type)) {
+ return wrap(ConstantExpression(TypeLiteralConstant(type)), typeType);
+ }
+ if (type is TypeParameterType) {
+ if (type.parameter.parent is FunctionNode) {
+ // Type argument to function
+ w.Local? local = typeLocals[type.parameter];
+ if (local != null) {
+ b.local_get(local);
+ return local.type;
+ } else {
+ _unimplemented(
+ node, "Type parameter access inside lambda", [typeType]);
+ return typeType;
+ }
+ }
+ // Type argument of class
+ Class cls = type.parameter.parent as Class;
+ ClassInfo info = translator.classInfo[cls]!;
+ int fieldIndex = translator.typeParameterIndex[type.parameter]!;
+ w.ValueType thisType = _visitThis(info.nullableType);
+ translator.convertType(function, thisType, info.nullableType);
+ b.struct_get(info.struct, fieldIndex);
+ return typeType;
+ }
+ ClassInfo info = translator.classInfo[translator.typeClass]!;
+ translator.functions.allocateClass(info.classId);
+ if (type is FutureOrType) {
+ // TODO(askesc): Have an actual representation of FutureOr types
+ b.ref_null(info.nullableType.heapType);
+ return info.nullableType;
+ }
+ if (type is! InterfaceType) {
+ _unimplemented(node, type, [info.nullableType]);
+ return info.nullableType;
+ }
+ ClassInfo typeInfo = translator.classInfo[type.classNode]!;
+ w.ValueType typeListExpectedType = info.struct.fields[3].type.unpacked;
+ b.i32_const(info.classId);
+ b.i32_const(initialIdentityHash);
+ b.i64_const(typeInfo.classId);
+ if (type.typeArguments.isEmpty) {
+ b.global_get(translator.constants.emptyTypeList);
+ translator.convertType(function,
+ translator.constants.emptyTypeList.type.type, typeListExpectedType);
+ } else if (type.typeArguments.every(_isTypeConstant)) {
+ ListConstant typeArgs = ListConstant(
+ InterfaceType(translator.typeClass, Nullability.nonNullable),
+ type.typeArguments.map((t) => TypeLiteralConstant(t)).toList());
+ translator.constants
+ .instantiateConstant(function, b, typeArgs, typeListExpectedType);
+ } else {
+ w.ValueType listType = _makeList(
+ type.typeArguments.map((t) => TypeLiteral(t)).toList(),
+ translator.fixedLengthListClass,
+ InterfaceType(translator.typeClass, Nullability.nonNullable),
+ node);
+ translator.convertType(function, listType, typeListExpectedType);
+ }
+ translator.struct_new(b, info);
+ return info.nullableType;
+ }
+
+ bool _isTypeConstant(DartType type) {
+ return type is DynamicType ||
+ type is VoidType ||
+ type is NeverType ||
+ type is NullType ||
+ type is FunctionType ||
+ type is InterfaceType && type.typeArguments.every(_isTypeConstant);
+ }
+
+ @override
+ w.ValueType visitIsExpression(IsExpression node, w.ValueType expectedType) {
+ wrap(node.operand, translator.topInfo.nullableType);
+ emitTypeTest(node.type, dartTypeOf(node.operand), node);
+ return w.NumType.i32;
+ }
+
+ /// Test value against a Dart type. Expects the value on the stack as a
+ /// (ref null #Top) and leaves the result on the stack as an i32.
+ void emitTypeTest(DartType type, DartType operandType, TreeNode node) {
+ if (type is! InterfaceType) {
+ // TODO(askesc): Implement type test for remaining types
+ print("Not implemented: Type test with non-interface type $type"
+ " at ${node.location}");
+ b.drop();
+ b.i32_const(1);
+ return;
+ }
+ bool isNullable = operandType.isPotentiallyNullable;
+ w.Label? resultLabel;
+ if (isNullable) {
+ // Store operand in a temporary variable, since Binaryen does not support
+ // block inputs.
+ w.Local operand = addLocal(translator.topInfo.nullableType);
+ b.local_set(operand);
+ resultLabel = b.block(const [], const [w.NumType.i32]);
+ w.Label nullLabel = b.block(const [], const []);
+ b.local_get(operand);
+ b.br_on_null(nullLabel);
+ }
+ if (type.typeArguments.any((t) => t is! DynamicType)) {
+ // If the tested-against type as an instance of the static operand type
+ // has the same type arguments as the static operand type, it is not
+ // necessary to test the type arguments.
+ Class cls = translator.classForType(operandType);
+ InterfaceType? base = translator.hierarchy
+ .getTypeAsInstanceOf(type, cls, member.enclosingLibrary)
+ ?.withDeclaredNullability(operandType.declaredNullability);
+ if (base != operandType) {
+ print("Not implemented: Type test with type arguments"
+ " at ${node.location}");
+ }
+ }
+ List<Class> concrete = translator.subtypes
+ .getSubtypesOf(type.classNode)
+ .where((c) => !c.isAbstract)
+ .toList();
+ if (concrete.isEmpty) {
+ b.drop();
+ b.i32_const(0);
+ } else if (concrete.length == 1) {
+ ClassInfo info = translator.classInfo[concrete.single]!;
+ b.struct_get(translator.topInfo.struct, FieldIndex.classId);
+ b.i32_const(info.classId);
+ b.i32_eq();
+ } else {
+ w.Local idLocal = 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 (Class cls in concrete) {
+ ClassInfo info = translator.classInfo[cls]!;
+ b.i32_const(info.classId);
+ b.local_get(idLocal);
+ b.i32_eq();
+ b.br_if(done);
+ }
+ b.drop();
+ b.i32_const(0);
+ b.end(); // done
+ }
+ if (isNullable) {
+ b.br(resultLabel!);
+ b.end(); // nullLabel
+ b.i32_const(type.declaredNullability == Nullability.nullable ? 1 : 0);
+ b.end(); // resultLabel
+ }
+ }
+
+ @override
+ w.ValueType visitAsExpression(AsExpression node, w.ValueType expectedType) {
+ // TODO(joshualitt): Emit type test and throw exception on failure
+ return wrap(node.operand, expectedType);
+ }
+}
diff --git a/pkg/dart2wasm/lib/compile.dart b/pkg/dart2wasm/lib/compile.dart
new file mode 100644
index 0000000..c69fdab
--- /dev/null
+++ b/pkg/dart2wasm/lib/compile.dart
@@ -0,0 +1,69 @@
+// Copyright (c) 2022, 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 'package:front_end/src/api_unstable/vm.dart'
+ show
+ CompilerOptions,
+ CompilerResult,
+ DiagnosticMessage,
+ kernelForProgram,
+ Severity;
+
+import 'package:kernel/ast.dart';
+import 'package:kernel/core_types.dart';
+import 'package:kernel/target/targets.dart';
+import 'package:kernel/type_environment.dart';
+
+import 'package:vm/transformations/type_flow/transformer.dart' as globalTypeFlow
+ show transformComponent;
+
+import 'package:dart2wasm/target.dart';
+import 'package:dart2wasm/translator.dart';
+
+/// Compile a Dart file into a Wasm module.
+///
+/// Returns `null` if an error occurred during compilation. The
+/// [handleDiagnosticMessage] callback will have received an error message
+/// describing the error.
+Future<Uint8List?> compileToModule(
+ Uri mainUri,
+ Uri sdkRoot,
+ TranslatorOptions options,
+ void Function(DiagnosticMessage) handleDiagnosticMessage) async {
+ var succeeded = true;
+ void diagnosticMessageHandler(DiagnosticMessage message) {
+ if (message.severity == Severity.error) {
+ succeeded = false;
+ }
+ handleDiagnosticMessage(message);
+ }
+
+ Target target = WasmTarget();
+ CompilerOptions compilerOptions = CompilerOptions()
+ ..target = target
+ ..compileSdk = true
+ ..sdkRoot = sdkRoot
+ ..environmentDefines = {}
+ ..verbose = false
+ ..onDiagnostic = diagnosticMessageHandler;
+
+ CompilerResult? compilerResult =
+ await kernelForProgram(mainUri, compilerOptions);
+ if (compilerResult == null || !succeeded) {
+ return null;
+ }
+ Component component = compilerResult.component!;
+ CoreTypes coreTypes = compilerResult.coreTypes!;
+
+ globalTypeFlow.transformComponent(target, coreTypes, component,
+ treeShakeSignatures: true,
+ treeShakeWriteOnlyFields: true,
+ useRapidTypeAnalysis: false);
+
+ var translator = Translator(component, coreTypes,
+ TypeEnvironment(coreTypes, compilerResult.classHierarchy!), options);
+ return translator.translate();
+}
diff --git a/pkg/dart2wasm/lib/constants.dart b/pkg/dart2wasm/lib/constants.dart
new file mode 100644
index 0000000..789f070
--- /dev/null
+++ b/pkg/dart2wasm/lib/constants.dart
@@ -0,0 +1,637 @@
+// Copyright (c) 2022, 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 'package:dart2wasm/class_info.dart';
+import 'package:dart2wasm/translator.dart';
+
+import 'package:kernel/ast.dart';
+import 'package:kernel/type_algebra.dart' show substitute;
+
+import 'package:wasm_builder/wasm_builder.dart' as w;
+
+class ConstantInfo {
+ final Constant constant;
+ final w.DefinedGlobal global;
+ final w.DefinedFunction? function;
+
+ ConstantInfo(this.constant, this.global, this.function);
+}
+
+typedef ConstantCodeGenerator = void Function(
+ w.DefinedFunction?, w.Instructions);
+
+/// Handles the creation of Dart constants. Can operate in two modes - eager and
+/// lazy - controlled by [TranslatorOptions.lazyConstants].
+///
+/// Each (non-trivial) constant is assigned to a Wasm global. Multiple
+/// occurrences of the same constant use the same global.
+///
+/// In eager mode, the constant is contained within the global initializer,
+/// meaning all constants are initialized eagerly during module initialization.
+/// In lazy mode, the global starts out uninitialized, and every use of the
+/// constant checks the global to see if it has been initialized and calls an
+/// initialization function otherwise.
+class Constants {
+ final Translator translator;
+ final Map<Constant, ConstantInfo> constantInfo = {};
+ final StringBuffer oneByteStrings = StringBuffer();
+ final StringBuffer twoByteStrings = StringBuffer();
+ late final w.DefinedFunction oneByteStringFunction;
+ late final w.DefinedFunction twoByteStringFunction;
+ late final w.DataSegment oneByteStringSegment;
+ late final w.DataSegment twoByteStringSegment;
+ late final w.DefinedGlobal emptyString;
+ late final w.DefinedGlobal emptyTypeList;
+ late final ClassInfo typeInfo = translator.classInfo[translator.typeClass]!;
+
+ bool currentlyCreating = false;
+
+ Constants(this.translator) {
+ if (lazyConstants) {
+ oneByteStringFunction = makeStringFunction(translator.oneByteStringClass);
+ twoByteStringFunction = makeStringFunction(translator.twoByteStringClass);
+ } else if (stringDataSegments) {
+ oneByteStringSegment = m.addDataSegment();
+ twoByteStringSegment = m.addDataSegment();
+ }
+ initEmptyString();
+ initEmptyTypeList();
+ }
+
+ w.Module get m => translator.m;
+ bool get lazyConstants => translator.options.lazyConstants;
+ bool get stringDataSegments => translator.options.stringDataSegments;
+
+ void initEmptyString() {
+ ClassInfo info = translator.classInfo[translator.oneByteStringClass]!;
+ translator.functions.allocateClass(info.classId);
+ w.ArrayType arrayType =
+ (info.struct.fields.last.type as w.RefType).heapType as w.ArrayType;
+
+ if (lazyConstants) {
+ w.RefType emptyStringType = info.nullableType;
+ emptyString = m.addGlobal(w.GlobalType(emptyStringType));
+ emptyString.initializer.ref_null(emptyStringType.heapType);
+ emptyString.initializer.end();
+
+ w.Instructions b = translator.initFunction.body;
+ b.i32_const(info.classId);
+ b.i32_const(initialIdentityHash);
+ b.i32_const(0);
+ translator.array_new_default(b, arrayType);
+ translator.struct_new(b, info);
+ b.global_set(emptyString);
+ } else {
+ w.RefType emptyStringType = info.nonNullableType;
+ emptyString = m.addGlobal(w.GlobalType(emptyStringType, mutable: false));
+ w.Instructions ib = emptyString.initializer;
+ ib.i32_const(info.classId);
+ ib.i32_const(initialIdentityHash);
+ translator.array_init(ib, arrayType, 0);
+ translator.struct_new(ib, info);
+ ib.end();
+ }
+
+ Constant emptyStringConstant = StringConstant("");
+ constantInfo[emptyStringConstant] =
+ ConstantInfo(emptyStringConstant, emptyString, null);
+ }
+
+ void initEmptyTypeList() {
+ ClassInfo info = translator.classInfo[translator.immutableListClass]!;
+ translator.functions.allocateClass(info.classId);
+ w.RefType refType = info.struct.fields.last.type.unpacked as w.RefType;
+ w.ArrayType arrayType = refType.heapType as w.ArrayType;
+
+ // Create the empty type list with its type parameter uninitialized for now.
+ if (lazyConstants) {
+ w.RefType emptyListType = info.nullableType;
+ emptyTypeList = m.addGlobal(w.GlobalType(emptyListType));
+ emptyTypeList.initializer.ref_null(emptyListType.heapType);
+ emptyTypeList.initializer.end();
+
+ w.Instructions b = translator.initFunction.body;
+ b.i32_const(info.classId);
+ b.i32_const(initialIdentityHash);
+ b.ref_null(typeInfo.struct); // Initialized later
+ b.i64_const(0);
+ b.i32_const(0);
+ translator.array_new_default(b, arrayType);
+ translator.struct_new(b, info);
+ b.global_set(emptyTypeList);
+ } else {
+ w.RefType emptyListType = info.nonNullableType;
+ emptyTypeList = m.addGlobal(w.GlobalType(emptyListType, mutable: false));
+ w.Instructions ib = emptyTypeList.initializer;
+ ib.i32_const(info.classId);
+ ib.i32_const(initialIdentityHash);
+ ib.ref_null(typeInfo.struct); // Initialized later
+ ib.i64_const(0);
+ translator.array_init(ib, arrayType, 0);
+ translator.struct_new(ib, info);
+ ib.end();
+ }
+
+ Constant emptyTypeListConstant = ListConstant(
+ InterfaceType(translator.typeClass, Nullability.nonNullable), const []);
+ constantInfo[emptyTypeListConstant] =
+ ConstantInfo(emptyTypeListConstant, emptyTypeList, null);
+
+ // Initialize the type parameter of the empty type list to the type object
+ // for _Type, which itself refers to the empty type list.
+ w.Instructions b = translator.initFunction.body;
+ b.global_get(emptyTypeList);
+ instantiateConstant(
+ translator.initFunction,
+ b,
+ TypeLiteralConstant(
+ InterfaceType(translator.typeClass, Nullability.nonNullable)),
+ typeInfo.nullableType);
+ b.struct_set(info.struct,
+ translator.typeParameterIndex[info.cls!.typeParameters.single]!);
+ }
+
+ void finalize() {
+ if (lazyConstants) {
+ finalizeStrings();
+ }
+ }
+
+ void finalizeStrings() {
+ Uint8List oneByteStringsAsBytes =
+ Uint8List.fromList(oneByteStrings.toString().codeUnits);
+ assert(Endian.host == Endian.little);
+ Uint8List twoByteStringsAsBytes =
+ Uint16List.fromList(twoByteStrings.toString().codeUnits)
+ .buffer
+ .asUint8List();
+ Uint8List stringsAsBytes = (BytesBuilder()
+ ..add(twoByteStringsAsBytes)
+ ..add(oneByteStringsAsBytes))
+ .toBytes();
+
+ w.Memory stringMemory =
+ m.addMemory(stringsAsBytes.length, stringsAsBytes.length);
+ m.addDataSegment(stringsAsBytes, stringMemory, 0);
+ makeStringFunctionBody(translator.oneByteStringClass, oneByteStringFunction,
+ (b) {
+ b.i32_load8_u(stringMemory, twoByteStringsAsBytes.length);
+ });
+ makeStringFunctionBody(translator.twoByteStringClass, twoByteStringFunction,
+ (b) {
+ b.i32_const(1);
+ b.i32_shl();
+ b.i32_load16_u(stringMemory, 0);
+ });
+ }
+
+ /// Create one of the two Wasm functions (one for each string type) called
+ /// from every lazily initialized string constant (of that type) to create and
+ /// initialize the string.
+ ///
+ /// The function signature is (i32 offset, i32 length) -> (ref stringClass)
+ /// where offset and length are measured in characters and indicate the place
+ /// in the corresponding string data segment from which to copy this string.
+ w.DefinedFunction makeStringFunction(Class cls) {
+ ClassInfo info = translator.classInfo[cls]!;
+ w.FunctionType ftype = translator.functionType(
+ const [w.NumType.i32, w.NumType.i32], [info.nonNullableType]);
+ return m.addFunction(ftype, "makeString (${cls.name})");
+ }
+
+ void makeStringFunctionBody(Class cls, w.DefinedFunction function,
+ void Function(w.Instructions) emitLoad) {
+ ClassInfo info = translator.classInfo[cls]!;
+ w.ArrayType arrayType =
+ (info.struct.fields.last.type as w.RefType).heapType as w.ArrayType;
+
+ w.Local offset = function.locals[0];
+ w.Local length = function.locals[1];
+ w.Local array = function.addLocal(
+ translator.typeForLocal(w.RefType.def(arrayType, nullable: false)));
+ w.Local index = function.addLocal(w.NumType.i32);
+
+ w.Instructions b = function.body;
+ b.local_get(length);
+ translator.array_new_default(b, arrayType);
+ b.local_set(array);
+
+ b.i32_const(0);
+ b.local_set(index);
+ w.Label loop = b.loop();
+ b.local_get(array);
+ b.local_get(index);
+ b.local_get(offset);
+ b.local_get(index);
+ b.i32_add();
+ emitLoad(b);
+ b.array_set(arrayType);
+ b.local_get(index);
+ b.i32_const(1);
+ b.i32_add();
+ b.local_tee(index);
+ b.local_get(length);
+ b.i32_lt_u();
+ b.br_if(loop);
+ b.end();
+
+ b.i32_const(info.classId);
+ b.i32_const(initialIdentityHash);
+ b.local_get(array);
+ translator.struct_new(b, info);
+ b.end();
+ }
+
+ /// Ensure that the constant has a Wasm global assigned.
+ ///
+ /// In eager mode, sub-constants must have Wasm globals assigned before the
+ /// global for the composite constant is assigned, since global initializers
+ /// can only refer to earlier globals.
+ void ensureConstant(Constant constant) {
+ ConstantCreator(this).ensureConstant(constant);
+ }
+
+ /// Emit code to push a constant onto the stack.
+ void instantiateConstant(w.DefinedFunction? function, w.Instructions b,
+ Constant constant, w.ValueType expectedType) {
+ if (expectedType == translator.voidMarker) return;
+ ConstantInstantiator(this, function, b, expectedType).instantiate(constant);
+ }
+}
+
+class ConstantInstantiator extends ConstantVisitor<w.ValueType> {
+ final Constants constants;
+ final w.DefinedFunction? function;
+ final w.Instructions b;
+ final w.ValueType expectedType;
+
+ ConstantInstantiator(
+ this.constants, this.function, this.b, this.expectedType);
+
+ Translator get translator => constants.translator;
+ w.Module get m => translator.m;
+
+ void instantiate(Constant constant) {
+ w.ValueType resultType = constant.accept(this);
+ assert(!translator.needsConversion(resultType, expectedType),
+ "For $constant: expected $expectedType, got $resultType");
+ }
+
+ @override
+ w.ValueType defaultConstant(Constant constant) {
+ ConstantInfo info = ConstantCreator(constants).ensureConstant(constant)!;
+ w.ValueType globalType = info.global.type.type;
+ if (globalType.nullable) {
+ if (info.function != null) {
+ // Lazily initialized constant.
+ w.Label done = b.block(const [], [globalType.withNullability(false)]);
+ b.global_get(info.global);
+ b.br_on_non_null(done);
+ b.call(info.function!);
+ b.end();
+ } else {
+ // Constant initialized in the module init function.
+ b.global_get(info.global);
+ b.ref_as_non_null();
+ }
+ return globalType.withNullability(false);
+ } else {
+ // Constant initialized eagerly in a global initializer.
+ b.global_get(info.global);
+ return globalType;
+ }
+ }
+
+ @override
+ w.ValueType visitNullConstant(NullConstant node) {
+ w.ValueType? expectedType = this.expectedType;
+ if (expectedType != translator.voidMarker) {
+ if (expectedType.nullable) {
+ w.HeapType heapType =
+ expectedType is w.RefType ? expectedType.heapType : w.HeapType.data;
+ b.ref_null(heapType);
+ } else {
+ // This only happens in invalid but unreachable code produced by the
+ // TFA dead-code elimination.
+ b.comment("Non-nullable null constant");
+ b.block(const [], [expectedType]);
+ b.unreachable();
+ b.end();
+ }
+ }
+ return expectedType;
+ }
+
+ w.ValueType _maybeBox(w.ValueType wasmType, void Function() pushValue) {
+ if (expectedType is w.RefType) {
+ ClassInfo info = translator.classInfo[translator.boxedClasses[wasmType]]!;
+ b.i32_const(info.classId);
+ pushValue();
+ translator.struct_new(b, info);
+ return info.nonNullableType;
+ } else {
+ pushValue();
+ return wasmType;
+ }
+ }
+
+ @override
+ w.ValueType visitBoolConstant(BoolConstant constant) {
+ return _maybeBox(w.NumType.i32, () {
+ b.i32_const(constant.value ? 1 : 0);
+ });
+ }
+
+ @override
+ w.ValueType visitIntConstant(IntConstant constant) {
+ return _maybeBox(w.NumType.i64, () {
+ b.i64_const(constant.value);
+ });
+ }
+
+ @override
+ w.ValueType visitDoubleConstant(DoubleConstant constant) {
+ return _maybeBox(w.NumType.f64, () {
+ b.f64_const(constant.value);
+ });
+ }
+}
+
+class ConstantCreator extends ConstantVisitor<ConstantInfo?> {
+ final Constants constants;
+
+ ConstantCreator(this.constants);
+
+ Translator get translator => constants.translator;
+ w.Module get m => constants.m;
+ bool get lazyConstants => constants.lazyConstants;
+
+ ConstantInfo? ensureConstant(Constant constant) {
+ ConstantInfo? info = constants.constantInfo[constant];
+ if (info == null) {
+ info = constant.accept(this);
+ if (info != null) {
+ constants.constantInfo[constant] = info;
+ }
+ }
+ return info;
+ }
+
+ ConstantInfo createConstant(
+ Constant constant, w.RefType type, ConstantCodeGenerator generator) {
+ assert(!type.nullable);
+ if (lazyConstants) {
+ // Create uninitialized global and function to initialize it.
+ w.DefinedGlobal global =
+ m.addGlobal(w.GlobalType(type.withNullability(true)));
+ global.initializer.ref_null(type.heapType);
+ global.initializer.end();
+ w.FunctionType ftype = translator.functionType(const [], [type]);
+ w.DefinedFunction function = m.addFunction(ftype, "$constant");
+ generator(function, function.body);
+ w.Local temp = function.addLocal(translator.typeForLocal(type));
+ w.Instructions b2 = function.body;
+ b2.local_tee(temp);
+ b2.global_set(global);
+ b2.local_get(temp);
+ translator.convertType(function, temp.type, type);
+ b2.end();
+
+ return ConstantInfo(constant, global, function);
+ } else {
+ // Create global with the constant in its initializer.
+ assert(!constants.currentlyCreating);
+ constants.currentlyCreating = true;
+ w.DefinedGlobal global = m.addGlobal(w.GlobalType(type, mutable: false));
+ generator(null, global.initializer);
+ global.initializer.end();
+ constants.currentlyCreating = false;
+
+ return ConstantInfo(constant, global, null);
+ }
+ }
+
+ @override
+ ConstantInfo? defaultConstant(Constant constant) => null;
+
+ @override
+ ConstantInfo? visitStringConstant(StringConstant constant) {
+ bool isOneByte = constant.value.codeUnits.every((c) => c <= 255);
+ ClassInfo info = translator.classInfo[isOneByte
+ ? translator.oneByteStringClass
+ : translator.twoByteStringClass]!;
+ translator.functions.allocateClass(info.classId);
+ w.RefType type = info.nonNullableType;
+ return createConstant(constant, type, (function, b) {
+ if (lazyConstants) {
+ // Copy string contents from linear memory on initialization. The memory
+ // is initialized by an active data segment for each string type.
+ StringBuffer buffer =
+ isOneByte ? constants.oneByteStrings : constants.twoByteStrings;
+ int offset = buffer.length;
+ int length = constant.value.length;
+ buffer.write(constant.value);
+
+ b.i32_const(offset);
+ b.i32_const(length);
+ b.call(isOneByte
+ ? constants.oneByteStringFunction
+ : constants.twoByteStringFunction);
+ } else {
+ w.ArrayType arrayType =
+ (info.struct.fields.last.type as w.RefType).heapType as w.ArrayType;
+
+ b.i32_const(info.classId);
+ b.i32_const(initialIdentityHash);
+ if (constants.stringDataSegments) {
+ // Initialize string contents from passive data segment.
+ w.DataSegment segment;
+ Uint8List bytes;
+ if (isOneByte) {
+ segment = constants.oneByteStringSegment;
+ bytes = Uint8List.fromList(constant.value.codeUnits);
+ } else {
+ assert(Endian.host == Endian.little);
+ segment = constants.twoByteStringSegment;
+ bytes = Uint16List.fromList(constant.value.codeUnits)
+ .buffer
+ .asUint8List();
+ }
+ int offset = segment.length;
+ segment.append(bytes);
+ b.i32_const(offset);
+ b.i32_const(constant.value.length);
+ translator.array_init_from_data(b, arrayType, segment);
+ } else {
+ // Initialize string contents from i32 constants on the stack.
+ for (int charCode in constant.value.codeUnits) {
+ b.i32_const(charCode);
+ }
+ translator.array_init(b, arrayType, constant.value.length);
+ }
+ translator.struct_new(b, info);
+ }
+ });
+ }
+
+ @override
+ ConstantInfo? visitInstanceConstant(InstanceConstant constant) {
+ Class cls = constant.classNode;
+ ClassInfo info = translator.classInfo[cls]!;
+ translator.functions.allocateClass(info.classId);
+ w.RefType type = info.nonNullableType;
+
+ // Collect sub-constants for field values.
+ const int baseFieldCount = 2;
+ int fieldCount = info.struct.fields.length;
+ List<Constant?> subConstants = List.filled(fieldCount, null);
+ constant.fieldValues.forEach((reference, subConstant) {
+ int index = translator.fieldIndex[reference.asField]!;
+ assert(subConstants[index] == null);
+ subConstants[index] = subConstant;
+ ensureConstant(subConstant);
+ });
+
+ // Collect sub-constants for type arguments.
+ Map<TypeParameter, DartType> substitution = {};
+ List<DartType> args = constant.typeArguments;
+ while (true) {
+ for (int i = 0; i < cls.typeParameters.length; i++) {
+ TypeParameter parameter = cls.typeParameters[i];
+ DartType arg = substitute(args[i], substitution);
+ substitution[parameter] = arg;
+ int index = translator.typeParameterIndex[parameter]!;
+ Constant typeArgConstant = TypeLiteralConstant(arg);
+ subConstants[index] = typeArgConstant;
+ ensureConstant(typeArgConstant);
+ }
+ Supertype? supertype = cls.supertype;
+ if (supertype == null) break;
+ cls = supertype.classNode;
+ args = supertype.typeArguments;
+ }
+
+ return createConstant(constant, type, (function, b) {
+ b.i32_const(info.classId);
+ b.i32_const(initialIdentityHash);
+ for (int i = baseFieldCount; i < fieldCount; i++) {
+ Constant subConstant = subConstants[i]!;
+ constants.instantiateConstant(
+ function, b, subConstant, info.struct.fields[i].type.unpacked);
+ }
+ translator.struct_new(b, info);
+ });
+ }
+
+ @override
+ ConstantInfo? visitListConstant(ListConstant constant) {
+ Constant typeArgConstant = TypeLiteralConstant(constant.typeArgument);
+ ensureConstant(typeArgConstant);
+ for (Constant subConstant in constant.entries) {
+ ensureConstant(subConstant);
+ }
+
+ ClassInfo info = translator.classInfo[translator.immutableListClass]!;
+ translator.functions.allocateClass(info.classId);
+ w.RefType type = info.nonNullableType;
+ return createConstant(constant, type, (function, b) {
+ w.RefType refType = info.struct.fields.last.type.unpacked as w.RefType;
+ w.ArrayType arrayType = refType.heapType as w.ArrayType;
+ w.ValueType elementType = arrayType.elementType.type.unpacked;
+ int length = constant.entries.length;
+ b.i32_const(info.classId);
+ b.i32_const(initialIdentityHash);
+ constants.instantiateConstant(
+ function, b, typeArgConstant, constants.typeInfo.nullableType);
+ b.i64_const(length);
+ if (lazyConstants) {
+ // Allocate array and set each entry to the corresponding sub-constant.
+ w.Local arrayLocal = function!.addLocal(
+ refType.withNullability(!translator.options.localNullability));
+ b.i32_const(length);
+ translator.array_new_default(b, arrayType);
+ b.local_set(arrayLocal);
+ for (int i = 0; i < length; i++) {
+ b.local_get(arrayLocal);
+ b.i32_const(i);
+ constants.instantiateConstant(
+ function, b, constant.entries[i], elementType);
+ b.array_set(arrayType);
+ }
+ b.local_get(arrayLocal);
+ if (arrayLocal.type.nullable) {
+ b.ref_as_non_null();
+ }
+ } else {
+ // Push all sub-constants on the stack and initialize array from them.
+ for (int i = 0; i < length; i++) {
+ constants.instantiateConstant(
+ function, b, constant.entries[i], elementType);
+ }
+ translator.array_init(b, arrayType, length);
+ }
+ translator.struct_new(b, info);
+ });
+ }
+
+ @override
+ ConstantInfo? visitStaticTearOffConstant(StaticTearOffConstant constant) {
+ w.DefinedFunction closureFunction =
+ translator.getTearOffFunction(constant.targetReference.asProcedure);
+ int parameterCount = closureFunction.type.inputs.length - 1;
+ w.StructType struct = translator.closureStructType(parameterCount);
+ w.RefType type = w.RefType.def(struct, nullable: false);
+ return createConstant(constant, type, (function, b) {
+ ClassInfo info = translator.classInfo[translator.functionClass]!;
+ translator.functions.allocateClass(info.classId);
+
+ b.i32_const(info.classId);
+ b.i32_const(initialIdentityHash);
+ b.global_get(translator.globals.dummyGlobal); // Dummy context
+ if (lazyConstants) {
+ w.DefinedGlobal global = translator.makeFunctionRef(closureFunction);
+ b.global_get(global);
+ } else {
+ b.ref_func(closureFunction);
+ }
+ translator.struct_new(b, parameterCount);
+ });
+ }
+
+ @override
+ ConstantInfo? visitTypeLiteralConstant(TypeLiteralConstant constant) {
+ DartType cType = constant.type;
+ assert(cType is! TypeParameterType);
+ DartType type = cType is DynamicType ||
+ cType is VoidType ||
+ cType is NeverType ||
+ cType is NullType
+ ? translator.coreTypes.objectRawType(Nullability.nullable)
+ : cType is FunctionType
+ ? InterfaceType(translator.functionClass, cType.declaredNullability)
+ : cType;
+ if (type is! InterfaceType) throw "Not implemented: $constant";
+
+ ListConstant typeArgs = ListConstant(
+ InterfaceType(translator.typeClass, Nullability.nonNullable),
+ type.typeArguments.map((t) => TypeLiteralConstant(t)).toList());
+ ensureConstant(typeArgs);
+
+ ClassInfo info = constants.typeInfo;
+ translator.functions.allocateClass(info.classId);
+ return createConstant(constant, info.nonNullableType, (function, b) {
+ ClassInfo typeInfo = translator.classInfo[type.classNode]!;
+ w.ValueType typeListExpectedType = info.struct.fields[3].type.unpacked;
+
+ b.i32_const(info.classId);
+ b.i32_const(initialIdentityHash);
+ b.i64_const(typeInfo.classId);
+ constants.instantiateConstant(
+ function, b, typeArgs, typeListExpectedType);
+ translator.struct_new(b, info);
+ });
+ }
+}
diff --git a/pkg/dart2wasm/lib/constants_backend.dart b/pkg/dart2wasm/lib/constants_backend.dart
new file mode 100644
index 0000000..6871599
--- /dev/null
+++ b/pkg/dart2wasm/lib/constants_backend.dart
@@ -0,0 +1,110 @@
+// Copyright (c) 2022, 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 'package:kernel/ast.dart';
+import 'package:kernel/target/targets.dart';
+import 'package:kernel/core_types.dart';
+
+class WasmConstantsBackend extends ConstantsBackend {
+ final Class immutableMapClass;
+ final Class unmodifiableSetClass;
+ final Field unmodifiableSetMap;
+
+ WasmConstantsBackend._(this.immutableMapClass, this.unmodifiableSetMap,
+ this.unmodifiableSetClass);
+
+ factory WasmConstantsBackend(CoreTypes coreTypes) {
+ final Library coreLibrary = coreTypes.coreLibrary;
+ final Class immutableMapClass = coreLibrary.classes
+ .firstWhere((Class klass) => klass.name == '_ImmutableMap');
+ Field unmodifiableSetMap =
+ coreTypes.index.getField('dart:collection', '_UnmodifiableSet', '_map');
+
+ return new WasmConstantsBackend._(immutableMapClass, unmodifiableSetMap,
+ unmodifiableSetMap.enclosingClass!);
+ }
+
+ @override
+ Constant lowerMapConstant(MapConstant constant) {
+ // The _ImmutableMap class is implemented via one field pointing to a list
+ // of key/value pairs -- see runtime/lib/immutable_map.dart!
+ final List<Constant> kvListPairs =
+ new List<Constant>.generate(2 * constant.entries.length, (int i) {
+ final int index = i ~/ 2;
+ final ConstantMapEntry entry = constant.entries[index];
+ return i % 2 == 0 ? entry.key : entry.value;
+ });
+ // This is a bit fishy, since we merge the key and the value type by
+ // putting both into the same list.
+ final ListConstant kvListConstant =
+ new ListConstant(const DynamicType(), kvListPairs);
+ assert(immutableMapClass.fields.length == 1);
+ final Field kvPairListField = immutableMapClass.fields[0];
+ return new InstanceConstant(immutableMapClass.reference, <DartType>[
+ constant.keyType,
+ constant.valueType,
+ ], <Reference, Constant>{
+ // We use getterReference as we refer to the field itself.
+ kvPairListField.getterReference: kvListConstant,
+ });
+ }
+
+ @override
+ bool isLoweredMapConstant(Constant constant) {
+ return constant is InstanceConstant &&
+ constant.classNode == immutableMapClass;
+ }
+
+ @override
+ void forEachLoweredMapConstantEntry(
+ Constant constant, void Function(Constant key, Constant value) f) {
+ assert(isLoweredMapConstant(constant));
+ final InstanceConstant instance = constant as InstanceConstant;
+ assert(immutableMapClass.fields.length == 1);
+ final Field kvPairListField = immutableMapClass.fields[0];
+ final ListConstant kvListConstant =
+ instance.fieldValues[kvPairListField.getterReference] as ListConstant;
+ assert(kvListConstant.entries.length % 2 == 0);
+ for (int index = 0; index < kvListConstant.entries.length; index += 2) {
+ f(kvListConstant.entries[index], kvListConstant.entries[index + 1]);
+ }
+ }
+
+ @override
+ Constant lowerSetConstant(SetConstant constant) {
+ final DartType elementType = constant.typeArgument;
+ final List<Constant> entries = constant.entries;
+ final List<ConstantMapEntry> mapEntries =
+ new List<ConstantMapEntry>.generate(entries.length, (int index) {
+ return new ConstantMapEntry(entries[index], new NullConstant());
+ });
+ Constant map = lowerMapConstant(
+ new MapConstant(elementType, const NullType(), mapEntries));
+ return new InstanceConstant(unmodifiableSetClass.reference, [elementType],
+ <Reference, Constant>{unmodifiableSetMap.getterReference: map});
+ }
+
+ @override
+ bool isLoweredSetConstant(Constant constant) {
+ if (constant is InstanceConstant &&
+ constant.classNode == unmodifiableSetClass) {
+ InstanceConstant instance = constant;
+ return isLoweredMapConstant(
+ instance.fieldValues[unmodifiableSetMap.getterReference]!);
+ }
+ return false;
+ }
+
+ @override
+ void forEachLoweredSetConstantElement(
+ Constant constant, void Function(Constant element) f) {
+ assert(isLoweredSetConstant(constant));
+ final InstanceConstant instance = constant as InstanceConstant;
+ final Constant mapConstant =
+ instance.fieldValues[unmodifiableSetMap.getterReference]!;
+ forEachLoweredMapConstantEntry(mapConstant, (Constant key, Constant value) {
+ f(key);
+ });
+ }
+}
diff --git a/pkg/dart2wasm/lib/dispatch_table.dart b/pkg/dart2wasm/lib/dispatch_table.dart
new file mode 100644
index 0000000..3c6b024
--- /dev/null
+++ b/pkg/dart2wasm/lib/dispatch_table.dart
@@ -0,0 +1,308 @@
+// Copyright (c) 2022, 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:math';
+
+import 'package:dart2wasm/class_info.dart';
+import 'package:dart2wasm/param_info.dart';
+import 'package:dart2wasm/reference_extensions.dart';
+import 'package:dart2wasm/translator.dart';
+
+import 'package:kernel/ast.dart';
+
+import 'package:vm/metadata/procedure_attributes.dart';
+import 'package:vm/metadata/table_selector.dart';
+
+import 'package:wasm_builder/wasm_builder.dart' as w;
+
+/// Information for a dispatch table selector.
+class SelectorInfo {
+ final Translator translator;
+
+ final int id;
+ final int callCount;
+ final bool tornOff;
+ final ParameterInfo paramInfo;
+ int returnCount;
+
+ final Map<int, Reference> targets = {};
+ late final w.FunctionType signature = computeSignature();
+
+ late final List<int> classIds;
+ late final int targetCount;
+ bool forced = false;
+ Reference? singularTarget;
+ int? offset;
+
+ String get name => paramInfo.member.name.text;
+
+ bool get alive => callCount > 0 && targetCount > 1 || forced;
+
+ int get sortWeight => classIds.length * 10 + callCount;
+
+ SelectorInfo(this.translator, this.id, this.callCount, this.tornOff,
+ this.paramInfo, this.returnCount);
+
+ /// Compute the signature for the functions implementing members targeted by
+ /// this selector.
+ ///
+ /// When the selector has multiple targets, the type of each parameter/return
+ /// is the upper bound across all targets, such that all targets have the
+ /// same signature, and the actual representation types of the parameters and
+ /// returns are subtypes (resp. supertypes) of the types in the signature.
+ w.FunctionType computeSignature() {
+ var nameIndex = paramInfo.nameIndex;
+ List<Set<ClassInfo>> inputSets =
+ List.generate(1 + paramInfo.paramCount, (_) => {});
+ List<Set<ClassInfo>> outputSets = List.generate(returnCount, (_) => {});
+ List<bool> inputNullable = List.filled(1 + paramInfo.paramCount, false);
+ List<bool> outputNullable = List.filled(returnCount, false);
+ targets.forEach((id, target) {
+ ClassInfo receiver = translator.classes[id];
+ List<DartType> positional;
+ Map<String, DartType> named;
+ List<DartType> returns;
+ Member member = target.asMember;
+ if (member is Field) {
+ if (target.isImplicitGetter) {
+ positional = const [];
+ named = const {};
+ returns = [member.getterType];
+ } else {
+ positional = [member.setterType];
+ named = const {};
+ returns = const [];
+ }
+ } else {
+ FunctionNode function = member.function!;
+ if (target.isTearOffReference) {
+ positional = const [];
+ named = const {};
+ returns = [function.computeFunctionType(Nullability.nonNullable)];
+ } else {
+ positional = [
+ for (VariableDeclaration param in function.positionalParameters)
+ param.type
+ ];
+ named = {
+ for (VariableDeclaration param in function.namedParameters)
+ param.name!: param.type
+ };
+ returns = function.returnType is VoidType
+ ? const []
+ : [function.returnType];
+ }
+ }
+ assert(returns.length <= outputSets.length);
+ inputSets[0].add(receiver);
+ for (int i = 0; i < positional.length; i++) {
+ DartType type = positional[i];
+ inputSets[1 + i]
+ .add(translator.classInfo[translator.classForType(type)]!);
+ inputNullable[1 + i] |= type.isPotentiallyNullable;
+ }
+ for (String name in named.keys) {
+ int i = nameIndex[name]!;
+ DartType type = named[name]!;
+ inputSets[1 + i]
+ .add(translator.classInfo[translator.classForType(type)]!);
+ inputNullable[1 + i] |= type.isPotentiallyNullable;
+ }
+ for (int i = 0; i < returnCount; i++) {
+ if (i < returns.length) {
+ outputSets[i]
+ .add(translator.classInfo[translator.classForType(returns[i])]!);
+ outputNullable[i] |= returns[i].isPotentiallyNullable;
+ } else {
+ outputNullable[i] = true;
+ }
+ }
+ });
+
+ List<w.ValueType> typeParameters = List.filled(paramInfo.typeParamCount,
+ translator.classInfo[translator.typeClass]!.nullableType);
+ List<w.ValueType> inputs = List.generate(
+ inputSets.length,
+ (i) => translator.typeForInfo(
+ upperBound(inputSets[i]), inputNullable[i]) as w.ValueType);
+ inputs[0] = translator.ensureBoxed(inputs[0]);
+ if (name == '==') {
+ // == can't be called with null
+ inputs[1] = inputs[1].withNullability(false);
+ }
+ List<w.ValueType> outputs = List.generate(
+ outputSets.length,
+ (i) => translator.typeForInfo(
+ upperBound(outputSets[i]), outputNullable[i]) as w.ValueType);
+ return translator.functionType(
+ [inputs[0], ...typeParameters, ...inputs.sublist(1)], outputs);
+ }
+}
+
+// Build dispatch table for member calls.
+class DispatchTable {
+ final Translator translator;
+ final List<TableSelectorInfo> selectorMetadata;
+ final Map<TreeNode, ProcedureAttributesMetadata> procedureAttributeMetadata;
+
+ final Map<int, SelectorInfo> selectorInfo = {};
+ final Map<String, int> dynamicGets = {};
+ late final List<Reference?> table;
+
+ DispatchTable(this.translator)
+ : selectorMetadata =
+ (translator.component.metadata["vm.table-selector.metadata"]
+ as TableSelectorMetadataRepository)
+ .mapping[translator.component]!
+ .selectors,
+ procedureAttributeMetadata =
+ (translator.component.metadata["vm.procedure-attributes.metadata"]
+ as ProcedureAttributesMetadataRepository)
+ .mapping;
+
+ SelectorInfo selectorForTarget(Reference target) {
+ Member member = target.asMember;
+ bool isGetter = target.isGetter || target.isTearOffReference;
+ ProcedureAttributesMetadata metadata = procedureAttributeMetadata[member]!;
+ int selectorId = isGetter
+ ? metadata.getterSelectorId
+ : metadata.methodOrSetterSelectorId;
+ ParameterInfo paramInfo = ParameterInfo.fromMember(target);
+ int returnCount = isGetter ||
+ member is Procedure && member.function.returnType is! VoidType
+ ? 1
+ : 0;
+ bool calledDynamically = isGetter && metadata.getterCalledDynamically;
+ if (calledDynamically) {
+ // Merge all same-named getter selectors that are called dynamically.
+ selectorId = dynamicGets.putIfAbsent(member.name.text, () => selectorId);
+ }
+ var selector = selectorInfo.putIfAbsent(
+ selectorId,
+ () => SelectorInfo(
+ translator,
+ selectorId,
+ selectorMetadata[selectorId].callCount,
+ selectorMetadata[selectorId].tornOff,
+ paramInfo,
+ returnCount)
+ ..forced = calledDynamically);
+ selector.paramInfo.merge(paramInfo);
+ selector.returnCount = max(selector.returnCount, returnCount);
+ return selector;
+ }
+
+ SelectorInfo selectorForDynamicName(String name) {
+ return selectorInfo[dynamicGets[name]!]!;
+ }
+
+ void build() {
+ // Collect class/selector combinations
+ List<List<int>> selectorsInClass = [];
+ for (ClassInfo info in translator.classes) {
+ List<int> selectorIds = [];
+ ClassInfo? superInfo = info.superInfo;
+ if (superInfo != null) {
+ int superId = superInfo.classId;
+ selectorIds = List.of(selectorsInClass[superId]);
+ for (int selectorId in selectorIds) {
+ SelectorInfo selector = selectorInfo[selectorId]!;
+ selector.targets[info.classId] = selector.targets[superId]!;
+ }
+ }
+
+ SelectorInfo addMember(Reference reference) {
+ SelectorInfo selector = selectorForTarget(reference);
+ if (reference.asMember.isAbstract) {
+ selector.targets[info.classId] ??= reference;
+ } else {
+ selector.targets[info.classId] = reference;
+ }
+ selectorIds.add(selector.id);
+ return selector;
+ }
+
+ for (Member member
+ in info.cls?.members ?? translator.coreTypes.objectClass.members) {
+ if (member.isInstanceMember) {
+ if (member is Field) {
+ addMember(member.getterReference);
+ if (member.hasSetter) addMember(member.setterReference!);
+ } else if (member is Procedure) {
+ SelectorInfo method = addMember(member.reference);
+ if (method.tornOff) {
+ addMember(member.tearOffReference);
+ }
+ }
+ }
+ }
+ selectorsInClass.add(selectorIds);
+ }
+
+ // Build lists of class IDs and count targets
+ for (SelectorInfo selector in selectorInfo.values) {
+ selector.classIds = selector.targets.keys
+ .where((id) => !(translator.classes[id].cls?.isAbstract ?? true))
+ .toList()
+ ..sort();
+ Set<Reference> targets =
+ selector.targets.values.where((t) => !t.asMember.isAbstract).toSet();
+ selector.targetCount = targets.length;
+ if (targets.length == 1) selector.singularTarget = targets.single;
+ }
+
+ // Assign selector offsets
+ List<SelectorInfo> selectors = selectorInfo.values
+ .where((s) => s.alive)
+ .toList()
+ ..sort((a, b) => b.sortWeight - a.sortWeight);
+ int firstAvailable = 0;
+ table = [];
+ bool first = true;
+ for (SelectorInfo selector in selectors) {
+ int offset = first ? 0 : firstAvailable - selector.classIds.first;
+ first = false;
+ bool fits;
+ do {
+ fits = true;
+ for (int classId in selector.classIds) {
+ int entry = offset + classId;
+ if (entry >= table.length) {
+ // Fits
+ break;
+ }
+ if (table[entry] != null) {
+ fits = false;
+ break;
+ }
+ }
+ if (!fits) offset++;
+ } while (!fits);
+ selector.offset = offset;
+ for (int classId in selector.classIds) {
+ int entry = offset + classId;
+ while (table.length <= entry) table.add(null);
+ assert(table[entry] == null);
+ table[entry] = selector.targets[classId];
+ }
+ while (firstAvailable < table.length && table[firstAvailable] != null) {
+ firstAvailable++;
+ }
+ }
+ }
+
+ void output() {
+ w.Module m = translator.m;
+ w.Table wasmTable = m.addTable(table.length);
+ for (int i = 0; i < table.length; i++) {
+ Reference? target = table[i];
+ if (target != null) {
+ w.BaseFunction? fun = translator.functions.getExistingFunction(target);
+ if (fun != null) {
+ wasmTable.setElement(i, fun);
+ }
+ }
+ }
+ }
+}
diff --git a/pkg/dart2wasm/lib/functions.dart b/pkg/dart2wasm/lib/functions.dart
new file mode 100644
index 0000000..eaee6d1
--- /dev/null
+++ b/pkg/dart2wasm/lib/functions.dart
@@ -0,0 +1,216 @@
+// Copyright (c) 2022, 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 'package:dart2wasm/dispatch_table.dart';
+import 'package:dart2wasm/reference_extensions.dart';
+import 'package:dart2wasm/translator.dart';
+
+import 'package:kernel/ast.dart';
+
+import 'package:wasm_builder/wasm_builder.dart' as w;
+
+/// This class is responsible for collecting import and export annotations.
+/// It also creates Wasm functions for Dart members and manages the worklist
+/// used to achieve tree shaking.
+class FunctionCollector extends MemberVisitor1<w.FunctionType, Reference> {
+ final Translator translator;
+
+ // Wasm function for each Dart function
+ final Map<Reference, w.BaseFunction> _functions = {};
+ // Names of exported functions
+ final Map<Reference, String> exports = {};
+ // Functions for which code has not yet been generated
+ final List<Reference> worklist = [];
+ // Class IDs for classes that are allocated somewhere in the program
+ final Set<int> _allocatedClasses = {};
+ // For each class ID, which functions should be added to the worklist if an
+ // allocation of that class is encountered
+ final Map<int, List<Reference>> _pendingAllocation = {};
+
+ FunctionCollector(this.translator);
+
+ w.Module get m => translator.m;
+
+ void collectImportsAndExports() {
+ for (Library library in translator.libraries) {
+ for (Procedure procedure in library.procedures) {
+ _importOrExport(procedure);
+ }
+ for (Class cls in library.classes) {
+ for (Procedure procedure in cls.procedures) {
+ _importOrExport(procedure);
+ }
+ }
+ }
+ }
+
+ void _importOrExport(Procedure procedure) {
+ String? importName = translator.getPragma(procedure, "wasm:import");
+ if (importName != null) {
+ int dot = importName.indexOf('.');
+ if (dot != -1) {
+ assert(!procedure.isInstanceMember);
+ String module = importName.substring(0, dot);
+ String name = importName.substring(dot + 1);
+ w.FunctionType ftype = _makeFunctionType(
+ procedure.reference, procedure.function.returnType, null,
+ isImportOrExport: true);
+ _functions[procedure.reference] =
+ m.importFunction(module, name, ftype, "$importName (import)");
+ }
+ }
+ String? exportName =
+ translator.getPragma(procedure, "wasm:export", procedure.name.text);
+ if (exportName != null) {
+ addExport(procedure.reference, exportName);
+ }
+ }
+
+ void addExport(Reference target, String exportName) {
+ exports[target] = exportName;
+ }
+
+ void initialize() {
+ // Add all exports to the worklist
+ for (Reference target in exports.keys) {
+ worklist.add(target);
+ Procedure node = target.asProcedure;
+ assert(!node.isInstanceMember);
+ assert(!node.isGetter);
+ w.FunctionType ftype = _makeFunctionType(
+ target, node.function.returnType, null,
+ isImportOrExport: true);
+ _functions[target] = m.addFunction(ftype, "$node");
+ }
+
+ // Value classes are always implicitly allocated.
+ allocateClass(translator.classInfo[translator.boxedBoolClass]!.classId);
+ allocateClass(translator.classInfo[translator.boxedIntClass]!.classId);
+ allocateClass(translator.classInfo[translator.boxedDoubleClass]!.classId);
+ }
+
+ w.BaseFunction? getExistingFunction(Reference target) {
+ return _functions[target];
+ }
+
+ w.BaseFunction getFunction(Reference target) {
+ return _functions.putIfAbsent(target, () {
+ worklist.add(target);
+ w.FunctionType ftype = target.isTearOffReference
+ ? translator.dispatchTable.selectorForTarget(target).signature
+ : target.asMember.accept1(this, target);
+ return m.addFunction(ftype, "${target.asMember}");
+ });
+ }
+
+ void activateSelector(SelectorInfo selector) {
+ selector.targets.forEach((classId, target) {
+ if (!target.asMember.isAbstract) {
+ if (_allocatedClasses.contains(classId)) {
+ // Class declaring or inheriting member is allocated somewhere.
+ getFunction(target);
+ } else {
+ // Remember the member in case an allocation is encountered later.
+ _pendingAllocation.putIfAbsent(classId, () => []).add(target);
+ }
+ }
+ });
+ }
+
+ void allocateClass(int classId) {
+ if (_allocatedClasses.add(classId)) {
+ // Schedule all members that were pending allocation of this class.
+ for (Reference target in _pendingAllocation[classId] ?? const []) {
+ getFunction(target);
+ }
+ }
+ }
+
+ @override
+ w.FunctionType defaultMember(Member node, Reference target) {
+ throw "No Wasm function for member: $node";
+ }
+
+ @override
+ w.FunctionType visitField(Field node, Reference target) {
+ if (!node.isInstanceMember) {
+ if (target == node.fieldReference) {
+ // Static field initializer function
+ return _makeFunctionType(target, node.type, null);
+ }
+ String kind = target == node.setterReference ? "setter" : "getter";
+ throw "No implicit $kind function for static field: $node";
+ }
+ return translator.dispatchTable.selectorForTarget(target).signature;
+ }
+
+ @override
+ w.FunctionType visitProcedure(Procedure node, Reference target) {
+ assert(!node.isAbstract);
+ return node.isInstanceMember
+ ? translator.dispatchTable.selectorForTarget(node.reference).signature
+ : _makeFunctionType(target, node.function.returnType, null);
+ }
+
+ @override
+ w.FunctionType visitConstructor(Constructor node, Reference target) {
+ return _makeFunctionType(target, VoidType(),
+ translator.classInfo[node.enclosingClass]!.nonNullableType);
+ }
+
+ w.FunctionType _makeFunctionType(
+ Reference target, DartType returnType, w.ValueType? receiverType,
+ {bool isImportOrExport = false}) {
+ Member member = target.asMember;
+ int typeParamCount = 0;
+ Iterable<DartType> params;
+ if (member is Field) {
+ params = [if (target.isImplicitSetter) member.setterType];
+ } else {
+ FunctionNode function = member.function!;
+ typeParamCount = (member is Constructor
+ ? member.enclosingClass.typeParameters
+ : function.typeParameters)
+ .length;
+ List<String> names = [for (var p in function.namedParameters) p.name!]
+ ..sort();
+ Map<String, DartType> nameTypes = {
+ for (var p in function.namedParameters) p.name!: p.type
+ };
+ params = [
+ for (var p in function.positionalParameters) p.type,
+ for (String name in names) nameTypes[name]!
+ ];
+ function.positionalParameters.map((p) => p.type);
+ }
+
+ List<w.ValueType> typeParameters = List.filled(typeParamCount,
+ translator.classInfo[translator.typeClass]!.nullableType);
+
+ // The JS embedder will not accept Wasm struct types as parameter or return
+ // types for functions called from JS. We need to use eqref instead.
+ w.ValueType adjustExternalType(w.ValueType type) {
+ if (isImportOrExport && type.isSubtypeOf(w.RefType.eq())) {
+ return w.RefType.eq();
+ }
+ return type;
+ }
+
+ List<w.ValueType> inputs = [];
+ if (receiverType != null) {
+ inputs.add(adjustExternalType(receiverType));
+ }
+ inputs.addAll(typeParameters.map(adjustExternalType));
+ inputs.addAll(
+ params.map((t) => adjustExternalType(translator.translateType(t))));
+
+ List<w.ValueType> outputs = returnType is VoidType ||
+ returnType is NeverType ||
+ returnType is NullType
+ ? const []
+ : [adjustExternalType(translator.translateType(returnType))];
+
+ return translator.functionType(inputs, outputs);
+ }
+}
diff --git a/pkg/dart2wasm/lib/globals.dart b/pkg/dart2wasm/lib/globals.dart
new file mode 100644
index 0000000..c72aeb1
--- /dev/null
+++ b/pkg/dart2wasm/lib/globals.dart
@@ -0,0 +1,198 @@
+// Copyright (c) 2022, 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 'package:kernel/ast.dart';
+
+import 'package:wasm_builder/wasm_builder.dart' as w;
+
+import 'package:dart2wasm/translator.dart';
+
+/// Handles lazy initialization of static fields.
+class Globals {
+ final Translator translator;
+
+ final Map<Field, w.Global> globals = {};
+ final Map<Field, w.BaseFunction> globalInitializers = {};
+ final Map<Field, w.Global> globalInitializedFlag = {};
+ final Map<w.HeapType, w.DefinedGlobal> dummyValues = {};
+ late final w.DefinedGlobal dummyGlobal;
+
+ Globals(this.translator) {
+ _initDummyValues();
+ }
+
+ void _initDummyValues() {
+ // Create dummy struct for anyref/eqref/dataref/context dummy values
+ w.StructType structType = translator.structType("#Dummy");
+ w.RefType type = w.RefType.def(structType, nullable: false);
+ dummyGlobal = translator.m.addGlobal(w.GlobalType(type, mutable: false));
+ w.Instructions ib = dummyGlobal.initializer;
+ translator.struct_new(ib, structType);
+ ib.end();
+ dummyValues[w.HeapType.any] = dummyGlobal;
+ dummyValues[w.HeapType.eq] = dummyGlobal;
+ dummyValues[w.HeapType.data] = dummyGlobal;
+ }
+
+ w.Global? prepareDummyValue(w.ValueType type) {
+ if (type is w.RefType && !type.nullable) {
+ w.HeapType heapType = type.heapType;
+ w.DefinedGlobal? global = dummyValues[heapType];
+ if (global != null) return global;
+ if (heapType is w.DefType) {
+ if (heapType is w.StructType) {
+ for (w.FieldType field in heapType.fields) {
+ prepareDummyValue(field.type.unpacked);
+ }
+ global = translator.m.addGlobal(w.GlobalType(type, mutable: false));
+ w.Instructions ib = global.initializer;
+ for (w.FieldType field in heapType.fields) {
+ instantiateDummyValue(ib, field.type.unpacked);
+ }
+ translator.struct_new(ib, heapType);
+ ib.end();
+ } else if (heapType is w.ArrayType) {
+ global = translator.m.addGlobal(w.GlobalType(type, mutable: false));
+ w.Instructions ib = global.initializer;
+ translator.array_init(ib, heapType, 0);
+ ib.end();
+ } else if (heapType is w.FunctionType) {
+ w.DefinedFunction function =
+ translator.m.addFunction(heapType, "#dummy function $heapType");
+ w.Instructions b = function.body;
+ b.unreachable();
+ b.end();
+ global = translator.m.addGlobal(w.GlobalType(type, mutable: false));
+ w.Instructions ib = global.initializer;
+ ib.ref_func(function);
+ ib.end();
+ }
+ dummyValues[heapType] = global!;
+ }
+ return global;
+ }
+
+ return null;
+ }
+
+ void instantiateDummyValue(w.Instructions b, w.ValueType type) {
+ w.Global? global = prepareDummyValue(type);
+ switch (type) {
+ case w.NumType.i32:
+ b.i32_const(0);
+ break;
+ case w.NumType.i64:
+ b.i64_const(0);
+ break;
+ case w.NumType.f32:
+ b.f32_const(0);
+ break;
+ case w.NumType.f64:
+ b.f64_const(0);
+ break;
+ default:
+ if (type is w.RefType) {
+ w.HeapType heapType = type.heapType;
+ if (type.nullable) {
+ b.ref_null(heapType);
+ } else {
+ b.global_get(global!);
+ }
+ } else {
+ throw "Unsupported global type ${type} ($type)";
+ }
+ break;
+ }
+ }
+
+ Constant? _getConstantInitializer(Field variable) {
+ Expression? init = variable.initializer;
+ if (init == null || init is NullLiteral) return NullConstant();
+ if (init is IntLiteral) return IntConstant(init.value);
+ if (init is DoubleLiteral) return DoubleConstant(init.value);
+ if (init is BoolLiteral) return BoolConstant(init.value);
+ if (translator.options.lazyConstants) return null;
+ if (init is StringLiteral) return StringConstant(init.value);
+ if (init is ConstantExpression) return init.constant;
+ return null;
+ }
+
+ /// Return (and if needed create) the Wasm global corresponding to a static
+ /// field.
+ w.Global getGlobal(Field variable) {
+ assert(!variable.isLate);
+ return globals.putIfAbsent(variable, () {
+ w.ValueType type = translator.translateType(variable.type);
+ Constant? init = _getConstantInitializer(variable);
+ if (init != null) {
+ // Initialized to a constant
+ translator.constants.ensureConstant(init);
+ w.DefinedGlobal global = translator.m
+ .addGlobal(w.GlobalType(type, mutable: !variable.isFinal));
+ translator.constants
+ .instantiateConstant(null, global.initializer, init, type);
+ global.initializer.end();
+ return global;
+ } else {
+ if (type is w.RefType && !type.nullable) {
+ // Null signals uninitialized
+ type = type.withNullability(true);
+ } else {
+ // Explicit initialization flag
+ w.DefinedGlobal flag =
+ translator.m.addGlobal(w.GlobalType(w.NumType.i32));
+ flag.initializer.i32_const(0);
+ flag.initializer.end();
+ globalInitializedFlag[variable] = flag;
+ }
+
+ w.DefinedGlobal global = translator.m.addGlobal(w.GlobalType(type));
+ instantiateDummyValue(global.initializer, type);
+ global.initializer.end();
+
+ globalInitializers[variable] =
+ translator.functions.getFunction(variable.fieldReference);
+ return global;
+ }
+ });
+ }
+
+ /// Return the Wasm global containing the flag indicating whether this static
+ /// field has been initialized, if such a flag global is needed.
+ ///
+ /// Note that [getGlobal] must have been called for the field beforehand.
+ w.Global? getGlobalInitializedFlag(Field variable) {
+ return globalInitializedFlag[variable];
+ }
+
+ /// Emit code to read a static field.
+ w.ValueType readGlobal(w.Instructions b, Field variable) {
+ w.Global global = getGlobal(variable);
+ w.BaseFunction? initFunction = globalInitializers[variable];
+ if (initFunction == null) {
+ // Statically initialized
+ b.global_get(global);
+ return global.type.type;
+ }
+ w.Global? flag = globalInitializedFlag[variable];
+ if (flag != null) {
+ // Explicit initialization flag
+ assert(global.type.type == initFunction.type.outputs.single);
+ b.global_get(flag);
+ b.if_(const [], [global.type.type]);
+ b.global_get(global);
+ b.else_();
+ b.call(initFunction);
+ b.end();
+ } else {
+ // Null signals uninitialized
+ w.Label block = b.block(const [], [initFunction.type.outputs.single]);
+ b.global_get(global);
+ b.br_on_non_null(block);
+ b.call(initFunction);
+ b.end();
+ }
+ return initFunction.type.outputs.single;
+ }
+}
diff --git a/pkg/dart2wasm/lib/intrinsics.dart b/pkg/dart2wasm/lib/intrinsics.dart
new file mode 100644
index 0000000..d94cd8c
--- /dev/null
+++ b/pkg/dart2wasm/lib/intrinsics.dart
@@ -0,0 +1,931 @@
+// Copyright (c) 2022, 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 'package:dart2wasm/class_info.dart';
+import 'package:dart2wasm/code_generator.dart';
+import 'package:dart2wasm/translator.dart';
+
+import 'package:kernel/ast.dart';
+
+import 'package:wasm_builder/wasm_builder.dart' as w;
+
+/// Specialized code generation for external members.
+///
+/// The code is generated either inlined at the call site, or as the body of the
+/// member in [generateMemberIntrinsic].
+class Intrinsifier {
+ final CodeGenerator codeGen;
+ static const w.ValueType boolType = w.NumType.i32;
+ static const w.ValueType intType = w.NumType.i64;
+ static const w.ValueType doubleType = w.NumType.f64;
+
+ static final Map<w.ValueType, Map<w.ValueType, Map<String, CodeGenCallback>>>
+ binaryOperatorMap = {
+ intType: {
+ intType: {
+ '+': (b) => b.i64_add(),
+ '-': (b) => b.i64_sub(),
+ '*': (b) => b.i64_mul(),
+ '~/': (b) => b.i64_div_s(),
+ '&': (b) => b.i64_and(),
+ '|': (b) => b.i64_or(),
+ '^': (b) => b.i64_xor(),
+ '<<': (b) => b.i64_shl(),
+ '>>': (b) => b.i64_shr_s(),
+ '>>>': (b) => b.i64_shr_u(),
+ '<': (b) => b.i64_lt_s(),
+ '<=': (b) => b.i64_le_s(),
+ '>': (b) => b.i64_gt_s(),
+ '>=': (b) => b.i64_ge_s(),
+ }
+ },
+ doubleType: {
+ doubleType: {
+ '+': (b) => b.f64_add(),
+ '-': (b) => b.f64_sub(),
+ '*': (b) => b.f64_mul(),
+ '/': (b) => b.f64_div(),
+ '<': (b) => b.f64_lt(),
+ '<=': (b) => b.f64_le(),
+ '>': (b) => b.f64_gt(),
+ '>=': (b) => b.f64_ge(),
+ }
+ },
+ };
+ static final Map<w.ValueType, Map<String, CodeGenCallback>> unaryOperatorMap =
+ {
+ intType: {
+ 'unary-': (b) {
+ b.i64_const(-1);
+ b.i64_mul();
+ },
+ '~': (b) {
+ b.i64_const(-1);
+ b.i64_xor();
+ },
+ 'toDouble': (b) {
+ b.f64_convert_i64_s();
+ },
+ },
+ doubleType: {
+ 'unary-': (b) {
+ b.f64_neg();
+ },
+ 'toInt': (b) {
+ b.i64_trunc_sat_f64_s();
+ },
+ 'roundToDouble': (b) {
+ b.f64_nearest();
+ },
+ 'floorToDouble': (b) {
+ b.f64_floor();
+ },
+ 'ceilToDouble': (b) {
+ b.f64_ceil();
+ },
+ 'truncateToDouble': (b) {
+ b.f64_trunc();
+ },
+ },
+ };
+ static final Map<String, w.ValueType> unaryResultMap = {
+ 'toDouble': w.NumType.f64,
+ 'toInt': w.NumType.i64,
+ 'roundToDouble': w.NumType.f64,
+ 'floorToDouble': w.NumType.f64,
+ 'ceilToDouble': w.NumType.f64,
+ 'truncateToDouble': w.NumType.f64,
+ };
+
+ Translator get translator => codeGen.translator;
+ w.Instructions get b => codeGen.b;
+
+ DartType dartTypeOf(Expression exp) => codeGen.dartTypeOf(exp);
+
+ w.ValueType typeOfExp(Expression exp) {
+ return translator.translateType(dartTypeOf(exp));
+ }
+
+ static bool isComparison(String op) =>
+ op == '<' || op == '<=' || op == '>' || op == '>=';
+
+ Intrinsifier(this.codeGen);
+
+ w.ValueType? generateInstanceGetterIntrinsic(InstanceGet node) {
+ DartType receiverType = dartTypeOf(node.receiver);
+ String name = node.name.text;
+
+ // _WasmArray.length
+ if (node.interfaceTarget.enclosingClass == translator.wasmArrayBaseClass) {
+ assert(name == 'length');
+ DartType elementType =
+ (receiverType as InterfaceType).typeArguments.single;
+ w.ArrayType arrayType = translator.arrayTypeForDartType(elementType);
+ Expression array = node.receiver;
+ codeGen.wrap(array, w.RefType.def(arrayType, nullable: true));
+ b.array_len(arrayType);
+ b.i64_extend_i32_u();
+ return w.NumType.i64;
+ }
+
+ // int.bitlength
+ if (node.interfaceTarget.enclosingClass == translator.coreTypes.intClass &&
+ name == 'bitLength') {
+ w.Local temp = codeGen.function.addLocal(w.NumType.i64);
+ b.i64_const(64);
+ codeGen.wrap(node.receiver, w.NumType.i64);
+ b.local_tee(temp);
+ b.local_get(temp);
+ b.i64_const(63);
+ b.i64_shr_s();
+ b.i64_xor();
+ b.i64_clz();
+ b.i64_sub();
+ return w.NumType.i64;
+ }
+
+ return null;
+ }
+
+ w.ValueType? generateInstanceIntrinsic(InstanceInvocation node) {
+ Expression receiver = node.receiver;
+ DartType receiverType = dartTypeOf(receiver);
+ String name = node.name.text;
+ Procedure target = node.interfaceTarget;
+
+ // _TypedListBase._setRange
+ if (target.enclosingClass == translator.typedListBaseClass &&
+ name == "_setRange") {
+ // Always fall back to alternative implementation.
+ b.i32_const(0);
+ return w.NumType.i32;
+ }
+
+ // _TypedList._(get|set)(Int|Uint|Float)(8|16|32|64)
+ if (node.interfaceTarget.enclosingClass == translator.typedListClass) {
+ Match? match = RegExp("^_(get|set)(Int|Uint|Float)(8|16|32|64)\$")
+ .matchAsPrefix(name);
+ if (match != null) {
+ bool setter = match.group(1) == "set";
+ bool signed = match.group(2) == "Int";
+ bool float = match.group(2) == "Float";
+ int bytes = int.parse(match.group(3)!) ~/ 8;
+ bool wide = bytes == 8;
+
+ ClassInfo typedListInfo =
+ translator.classInfo[translator.typedListClass]!;
+ w.RefType arrayType = typedListInfo.struct
+ .fields[FieldIndex.typedListArray].type.unpacked as w.RefType;
+ w.ArrayType arrayHeapType = arrayType.heapType as w.ArrayType;
+ w.ValueType valueType = float ? w.NumType.f64 : w.NumType.i64;
+ w.ValueType intType = wide ? w.NumType.i64 : w.NumType.i32;
+
+ // Prepare array and offset
+ w.Local array = codeGen.addLocal(arrayType);
+ w.Local offset = codeGen.addLocal(w.NumType.i32);
+ codeGen.wrap(receiver, typedListInfo.nullableType);
+ b.struct_get(typedListInfo.struct, FieldIndex.typedListArray);
+ b.local_set(array);
+ codeGen.wrap(node.arguments.positional[0], w.NumType.i64);
+ b.i32_wrap_i64();
+ b.local_set(offset);
+
+ if (setter) {
+ // Setter
+ w.Local value = codeGen.addLocal(intType);
+ codeGen.wrap(node.arguments.positional[1], valueType);
+ if (wide) {
+ if (float) {
+ b.i64_reinterpret_f64();
+ }
+ } else {
+ if (float) {
+ b.f32_demote_f64();
+ b.i32_reinterpret_f32();
+ } else {
+ b.i32_wrap_i64();
+ }
+ }
+ b.local_set(value);
+
+ for (int i = 0; i < bytes; i++) {
+ b.local_get(array);
+ b.local_get(offset);
+ if (i > 0) {
+ b.i32_const(i);
+ b.i32_add();
+ }
+ b.local_get(value);
+ if (i > 0) {
+ if (wide) {
+ b.i64_const(i * 8);
+ b.i64_shr_u();
+ } else {
+ b.i32_const(i * 8);
+ b.i32_shr_u();
+ }
+ }
+ if (wide) {
+ b.i32_wrap_i64();
+ }
+ b.array_set(arrayHeapType);
+ }
+ return translator.voidMarker;
+ } else {
+ // Getter
+ for (int i = 0; i < bytes; i++) {
+ b.local_get(array);
+ b.local_get(offset);
+ if (i > 0) {
+ b.i32_const(i);
+ b.i32_add();
+ }
+ if (signed && i == bytes - 1) {
+ b.array_get_s(arrayHeapType);
+ } else {
+ b.array_get_u(arrayHeapType);
+ }
+ if (wide) {
+ if (signed) {
+ b.i64_extend_i32_s();
+ } else {
+ b.i64_extend_i32_u();
+ }
+ }
+ if (i > 0) {
+ if (wide) {
+ b.i64_const(i * 8);
+ b.i64_shl();
+ b.i64_or();
+ } else {
+ b.i32_const(i * 8);
+ b.i32_shl();
+ b.i32_or();
+ }
+ }
+ }
+
+ if (wide) {
+ if (float) {
+ b.f64_reinterpret_i64();
+ }
+ } else {
+ if (float) {
+ b.f32_reinterpret_i32();
+ b.f64_promote_f32();
+ } else {
+ if (signed) {
+ b.i64_extend_i32_s();
+ } else {
+ b.i64_extend_i32_u();
+ }
+ }
+ }
+ return valueType;
+ }
+ }
+ }
+
+ // WasmIntArray.(readSigned|readUnsigned|write)
+ // WasmFloatArray.(read|write)
+ // WasmObjectArray.(read|write)
+ if (node.interfaceTarget.enclosingClass?.superclass ==
+ translator.wasmArrayBaseClass) {
+ DartType elementType =
+ (receiverType as InterfaceType).typeArguments.single;
+ w.ArrayType arrayType = translator.arrayTypeForDartType(elementType);
+ w.StorageType wasmType = arrayType.elementType.type;
+ bool innerExtend =
+ wasmType == w.PackedType.i8 || wasmType == w.PackedType.i16;
+ bool outerExtend =
+ wasmType.unpacked == w.NumType.i32 || wasmType == w.NumType.f32;
+ switch (name) {
+ case 'read':
+ case 'readSigned':
+ case 'readUnsigned':
+ bool unsigned = name == 'readUnsigned';
+ Expression array = receiver;
+ Expression index = node.arguments.positional.single;
+ codeGen.wrap(array, w.RefType.def(arrayType, nullable: true));
+ codeGen.wrap(index, w.NumType.i64);
+ b.i32_wrap_i64();
+ if (innerExtend) {
+ if (unsigned) {
+ b.array_get_u(arrayType);
+ } else {
+ b.array_get_s(arrayType);
+ }
+ } else {
+ b.array_get(arrayType);
+ }
+ if (outerExtend) {
+ if (wasmType == w.NumType.f32) {
+ b.f64_promote_f32();
+ return w.NumType.f64;
+ } else {
+ if (unsigned) {
+ b.i64_extend_i32_u();
+ } else {
+ b.i64_extend_i32_s();
+ }
+ return w.NumType.i64;
+ }
+ }
+ return wasmType.unpacked;
+ case 'write':
+ Expression array = receiver;
+ Expression index = node.arguments.positional[0];
+ Expression value = node.arguments.positional[1];
+ codeGen.wrap(array, w.RefType.def(arrayType, nullable: true));
+ codeGen.wrap(index, w.NumType.i64);
+ b.i32_wrap_i64();
+ codeGen.wrap(value, typeOfExp(value));
+ if (outerExtend) {
+ if (wasmType == w.NumType.f32) {
+ b.f32_demote_f64();
+ } else {
+ b.i32_wrap_i64();
+ }
+ }
+ b.array_set(arrayType);
+ return codeGen.voidMarker;
+ default:
+ throw "Unsupported array method: $name";
+ }
+ }
+
+ // List.[] on list constants
+ if (receiver is ConstantExpression &&
+ receiver.constant is ListConstant &&
+ name == '[]') {
+ ClassInfo info = translator.classInfo[translator.listBaseClass]!;
+ w.RefType listType = info.nullableType;
+ Field arrayField = translator.listBaseClass.fields
+ .firstWhere((f) => f.name.text == '_data');
+ int arrayFieldIndex = translator.fieldIndex[arrayField]!;
+ w.ArrayType arrayType =
+ (info.struct.fields[arrayFieldIndex].type as w.RefType).heapType
+ as w.ArrayType;
+ codeGen.wrap(receiver, listType);
+ b.struct_get(info.struct, arrayFieldIndex);
+ codeGen.wrap(node.arguments.positional.single, w.NumType.i64);
+ b.i32_wrap_i64();
+ b.array_get(arrayType);
+ return translator.topInfo.nullableType;
+ }
+
+ if (node.arguments.positional.length == 1) {
+ // Binary operator
+ Expression left = node.receiver;
+ Expression right = node.arguments.positional.single;
+ DartType argType = dartTypeOf(right);
+ if (argType is VoidType) return null;
+ w.ValueType leftType = translator.translateType(receiverType);
+ w.ValueType rightType = translator.translateType(argType);
+ var code = binaryOperatorMap[leftType]?[rightType]?[name];
+ if (code != null) {
+ w.ValueType outType = isComparison(name) ? w.NumType.i32 : leftType;
+ codeGen.wrap(left, leftType);
+ codeGen.wrap(right, rightType);
+ code(b);
+ return outType;
+ }
+ } else if (node.arguments.positional.length == 0) {
+ // Unary operator
+ Expression operand = node.receiver;
+ w.ValueType opType = translator.translateType(receiverType);
+ var code = unaryOperatorMap[opType]?[name];
+ if (code != null) {
+ codeGen.wrap(operand, opType);
+ code(b);
+ return unaryResultMap[name] ?? opType;
+ }
+ }
+
+ return null;
+ }
+
+ w.ValueType? generateEqualsIntrinsic(EqualsCall node) {
+ w.ValueType leftType = typeOfExp(node.left);
+ w.ValueType rightType = typeOfExp(node.right);
+
+ if (leftType == boolType && rightType == boolType) {
+ codeGen.wrap(node.left, w.NumType.i32);
+ codeGen.wrap(node.right, w.NumType.i32);
+ b.i32_eq();
+ return w.NumType.i32;
+ }
+
+ if (leftType == intType && rightType == intType) {
+ codeGen.wrap(node.left, w.NumType.i64);
+ codeGen.wrap(node.right, w.NumType.i64);
+ b.i64_eq();
+ return w.NumType.i32;
+ }
+
+ if (leftType == doubleType && rightType == doubleType) {
+ codeGen.wrap(node.left, w.NumType.f64);
+ codeGen.wrap(node.right, w.NumType.f64);
+ b.f64_eq();
+ return w.NumType.i32;
+ }
+
+ return null;
+ }
+
+ w.ValueType? generateStaticGetterIntrinsic(StaticGet node) {
+ Member target = node.target;
+
+ // ClassID getters
+ String? className = translator.getPragma(target, "wasm:class-id");
+ if (className != null) {
+ List<String> libAndClass = className.split("#");
+ Class cls = translator.libraries
+ .firstWhere((l) => l.name == libAndClass[0])
+ .classes
+ .firstWhere((c) => c.name == libAndClass[1]);
+ int classId = translator.classInfo[cls]!.classId;
+ b.i64_const(classId);
+ return w.NumType.i64;
+ }
+
+ return null;
+ }
+
+ w.ValueType? generateStaticIntrinsic(StaticInvocation node) {
+ String name = node.name.text;
+
+ // dart:core static functions
+ if (node.target.enclosingLibrary == translator.coreTypes.coreLibrary) {
+ switch (name) {
+ case "identical":
+ Expression first = node.arguments.positional[0];
+ Expression second = node.arguments.positional[1];
+ DartType boolType = translator.coreTypes.boolNonNullableRawType;
+ InterfaceType intType = translator.coreTypes.intNonNullableRawType;
+ DartType doubleType = translator.coreTypes.doubleNonNullableRawType;
+ List<DartType> types = [dartTypeOf(first), dartTypeOf(second)];
+ if (types.every((t) => t == intType)) {
+ codeGen.wrap(first, w.NumType.i64);
+ codeGen.wrap(second, w.NumType.i64);
+ b.i64_eq();
+ return w.NumType.i32;
+ }
+ if (types.any((t) =>
+ t is InterfaceType &&
+ t != boolType &&
+ t != doubleType &&
+ !translator.hierarchy
+ .isSubtypeOf(intType.classNode, t.classNode))) {
+ codeGen.wrap(first, w.RefType.eq(nullable: true));
+ codeGen.wrap(second, w.RefType.eq(nullable: true));
+ b.ref_eq();
+ return w.NumType.i32;
+ }
+ break;
+ case "_getHash":
+ Expression arg = node.arguments.positional[0];
+ w.ValueType objectType = translator.objectInfo.nullableType;
+ codeGen.wrap(arg, objectType);
+ b.struct_get(translator.objectInfo.struct, FieldIndex.identityHash);
+ b.i64_extend_i32_u();
+ return w.NumType.i64;
+ case "_setHash":
+ Expression arg = node.arguments.positional[0];
+ Expression hash = node.arguments.positional[1];
+ w.ValueType objectType = translator.objectInfo.nullableType;
+ codeGen.wrap(arg, objectType);
+ codeGen.wrap(hash, w.NumType.i64);
+ b.i32_wrap_i64();
+ b.struct_set(translator.objectInfo.struct, FieldIndex.identityHash);
+ return codeGen.voidMarker;
+ }
+ }
+
+ // dart:_internal static functions
+ if (node.target.enclosingLibrary.name == "dart._internal") {
+ switch (name) {
+ case "unsafeCast":
+ w.ValueType targetType =
+ translator.translateType(node.arguments.types.single);
+ Expression operand = node.arguments.positional.single;
+ return codeGen.wrap(operand, targetType);
+ case "allocateOneByteString":
+ ClassInfo info = translator.classInfo[translator.oneByteStringClass]!;
+ translator.functions.allocateClass(info.classId);
+ w.ArrayType arrayType =
+ translator.wasmArrayType(w.PackedType.i8, "WasmI8");
+ Expression length = node.arguments.positional[0];
+ b.i32_const(info.classId);
+ b.i32_const(initialIdentityHash);
+ codeGen.wrap(length, w.NumType.i64);
+ b.i32_wrap_i64();
+ translator.array_new_default(b, arrayType);
+ translator.struct_new(b, info);
+ return info.nonNullableType;
+ case "writeIntoOneByteString":
+ ClassInfo info = translator.classInfo[translator.oneByteStringClass]!;
+ w.ArrayType arrayType =
+ translator.wasmArrayType(w.PackedType.i8, "WasmI8");
+ Field arrayField = translator.oneByteStringClass.fields
+ .firstWhere((f) => f.name.text == '_array');
+ int arrayFieldIndex = translator.fieldIndex[arrayField]!;
+ Expression string = node.arguments.positional[0];
+ Expression index = node.arguments.positional[1];
+ Expression codePoint = node.arguments.positional[2];
+ codeGen.wrap(string, info.nonNullableType);
+ b.struct_get(info.struct, arrayFieldIndex);
+ codeGen.wrap(index, w.NumType.i64);
+ b.i32_wrap_i64();
+ codeGen.wrap(codePoint, w.NumType.i64);
+ b.i32_wrap_i64();
+ b.array_set(arrayType);
+ return codeGen.voidMarker;
+ case "allocateTwoByteString":
+ ClassInfo info = translator.classInfo[translator.twoByteStringClass]!;
+ translator.functions.allocateClass(info.classId);
+ w.ArrayType arrayType =
+ translator.wasmArrayType(w.PackedType.i16, "WasmI16");
+ Expression length = node.arguments.positional[0];
+ b.i32_const(info.classId);
+ b.i32_const(initialIdentityHash);
+ codeGen.wrap(length, w.NumType.i64);
+ b.i32_wrap_i64();
+ translator.array_new_default(b, arrayType);
+ translator.struct_new(b, info);
+ return info.nonNullableType;
+ case "writeIntoTwoByteString":
+ ClassInfo info = translator.classInfo[translator.twoByteStringClass]!;
+ w.ArrayType arrayType =
+ translator.wasmArrayType(w.PackedType.i16, "WasmI16");
+ Field arrayField = translator.oneByteStringClass.fields
+ .firstWhere((f) => f.name.text == '_array');
+ int arrayFieldIndex = translator.fieldIndex[arrayField]!;
+ Expression string = node.arguments.positional[0];
+ Expression index = node.arguments.positional[1];
+ Expression codePoint = node.arguments.positional[2];
+ codeGen.wrap(string, info.nonNullableType);
+ b.struct_get(info.struct, arrayFieldIndex);
+ codeGen.wrap(index, w.NumType.i64);
+ b.i32_wrap_i64();
+ codeGen.wrap(codePoint, w.NumType.i64);
+ b.i32_wrap_i64();
+ b.array_set(arrayType);
+ return codeGen.voidMarker;
+ case "floatToIntBits":
+ codeGen.wrap(node.arguments.positional.single, w.NumType.f64);
+ b.f32_demote_f64();
+ b.i32_reinterpret_f32();
+ b.i64_extend_i32_u();
+ return w.NumType.i64;
+ case "intBitsToFloat":
+ codeGen.wrap(node.arguments.positional.single, w.NumType.i64);
+ b.i32_wrap_i64();
+ b.f32_reinterpret_i32();
+ b.f64_promote_f32();
+ return w.NumType.f64;
+ case "doubleToIntBits":
+ codeGen.wrap(node.arguments.positional.single, w.NumType.f64);
+ b.i64_reinterpret_f64();
+ return w.NumType.i64;
+ case "intBitsToDouble":
+ codeGen.wrap(node.arguments.positional.single, w.NumType.i64);
+ b.f64_reinterpret_i64();
+ return w.NumType.f64;
+ case "getID":
+ assert(node.target.enclosingClass?.name == "ClassID");
+ ClassInfo info = translator.topInfo;
+ codeGen.wrap(node.arguments.positional.single, info.nullableType);
+ b.struct_get(info.struct, FieldIndex.classId);
+ b.i64_extend_i32_u();
+ return w.NumType.i64;
+ }
+ }
+
+ // Wasm(Int|Float|Object)Array constructors
+ if (node.target.enclosingClass?.superclass ==
+ translator.wasmArrayBaseClass) {
+ Expression length = node.arguments.positional[0];
+ w.ArrayType arrayType =
+ translator.arrayTypeForDartType(node.arguments.types.single);
+ codeGen.wrap(length, w.NumType.i64);
+ b.i32_wrap_i64();
+ translator.array_new_default(b, arrayType);
+ return w.RefType.def(arrayType, nullable: false);
+ }
+
+ return null;
+ }
+
+ bool generateMemberIntrinsic(Reference target, w.DefinedFunction function,
+ List<w.Local> paramLocals, w.Label? returnLabel) {
+ Member member = target.asMember;
+ if (member is! Procedure) return false;
+ String name = member.name.text;
+ FunctionNode functionNode = member.function;
+
+ // Object.==
+ if (member == translator.coreTypes.objectEquals) {
+ b.local_get(paramLocals[0]);
+ b.local_get(paramLocals[1]);
+ b.ref_eq();
+ return true;
+ }
+
+ // Object.runtimeType
+ if (member.enclosingClass == translator.coreTypes.objectClass &&
+ name == "runtimeType") {
+ w.Local receiver = paramLocals[0];
+ ClassInfo info = translator.classInfo[translator.typeClass]!;
+ translator.functions.allocateClass(info.classId);
+ w.ValueType typeListExpectedType = info.struct.fields[3].type.unpacked;
+
+ b.i32_const(info.classId);
+ b.i32_const(initialIdentityHash);
+ b.local_get(receiver);
+ b.struct_get(translator.topInfo.struct, FieldIndex.classId);
+ b.i64_extend_i32_u();
+ // TODO(askesc): Type arguments
+ b.global_get(translator.constants.emptyTypeList);
+ translator.convertType(function,
+ translator.constants.emptyTypeList.type.type, typeListExpectedType);
+ translator.struct_new(b, info);
+
+ return true;
+ }
+
+ // identical
+ if (member == translator.coreTypes.identicalProcedure) {
+ w.Local first = paramLocals[0];
+ w.Local second = paramLocals[1];
+ ClassInfo boolInfo = translator.classInfo[translator.boxedBoolClass]!;
+ ClassInfo intInfo = translator.classInfo[translator.boxedIntClass]!;
+ ClassInfo doubleInfo = translator.classInfo[translator.boxedDoubleClass]!;
+ w.Local cid = function.addLocal(w.NumType.i32);
+ w.Label ref_eq = b.block();
+ b.local_get(first);
+ b.br_on_null(ref_eq);
+ b.struct_get(translator.topInfo.struct, FieldIndex.classId);
+ b.local_tee(cid);
+
+ // Both bool?
+ b.i32_const(boolInfo.classId);
+ b.i32_eq();
+ b.if_();
+ b.local_get(first);
+ translator.ref_cast(b, boolInfo);
+ b.struct_get(boolInfo.struct, FieldIndex.boxValue);
+ w.Label bothBool = b.block(const [], [boolInfo.nullableType]);
+ b.local_get(second);
+ translator.br_on_cast(b, bothBool, boolInfo);
+ b.i32_const(0);
+ b.return_();
+ b.end();
+ b.struct_get(boolInfo.struct, FieldIndex.boxValue);
+ b.i32_eq();
+ b.return_();
+ b.end();
+
+ // Both int?
+ b.local_get(cid);
+ b.i32_const(intInfo.classId);
+ b.i32_eq();
+ b.if_();
+ b.local_get(first);
+ translator.ref_cast(b, intInfo);
+ b.struct_get(intInfo.struct, FieldIndex.boxValue);
+ w.Label bothInt = b.block(const [], [intInfo.nullableType]);
+ b.local_get(second);
+ translator.br_on_cast(b, bothInt, intInfo);
+ b.i32_const(0);
+ b.return_();
+ b.end();
+ b.struct_get(intInfo.struct, FieldIndex.boxValue);
+ b.i64_eq();
+ b.return_();
+ b.end();
+
+ // Both double?
+ b.local_get(cid);
+ b.i32_const(doubleInfo.classId);
+ b.i32_eq();
+ b.if_();
+ b.local_get(first);
+ translator.ref_cast(b, doubleInfo);
+ b.struct_get(doubleInfo.struct, FieldIndex.boxValue);
+ b.i64_reinterpret_f64();
+ w.Label bothDouble = b.block(const [], [doubleInfo.nullableType]);
+ b.local_get(second);
+ translator.br_on_cast(b, bothDouble, doubleInfo);
+ b.i32_const(0);
+ b.return_();
+ b.end();
+ b.struct_get(doubleInfo.struct, FieldIndex.boxValue);
+ b.i64_reinterpret_f64();
+ b.i64_eq();
+ b.return_();
+ b.end();
+
+ // Compare as references
+ b.end();
+ b.local_get(first);
+ b.local_get(second);
+ b.ref_eq();
+
+ return true;
+ }
+
+ // (Int|Uint|Float)(8|16|32|64)(Clamped)?(List|ArrayView) constructors
+ if (member.isExternal &&
+ member.enclosingLibrary.name == "dart.typed_data") {
+ if (member.isFactory) {
+ String className = member.enclosingClass!.name;
+
+ Match? match = RegExp("^(Int|Uint|Float)(8|16|32|64)(Clamped)?List\$")
+ .matchAsPrefix(className);
+ if (match != null) {
+ int shift = int.parse(match.group(2)!).bitLength - 4;
+ Class cls = member.enclosingLibrary.classes
+ .firstWhere((c) => c.name == "_$className");
+ ClassInfo info = translator.classInfo[cls]!;
+ translator.functions.allocateClass(info.classId);
+ w.ArrayType arrayType =
+ translator.wasmArrayType(w.PackedType.i8, "i8");
+
+ w.Local length = paramLocals[0];
+ b.i32_const(info.classId);
+ b.i32_const(initialIdentityHash);
+ b.local_get(length);
+ b.i32_wrap_i64();
+ b.local_get(length);
+ if (shift > 0) {
+ b.i64_const(shift);
+ b.i64_shl();
+ }
+ b.i32_wrap_i64();
+ translator.array_new_default(b, arrayType);
+ translator.struct_new(b, info);
+ return true;
+ }
+
+ match = RegExp("^_(Int|Uint|Float)(8|16|32|64)(Clamped)?ArrayView\$")
+ .matchAsPrefix(className);
+ if (match != null ||
+ member.enclosingClass == translator.byteDataViewClass) {
+ ClassInfo info = translator.classInfo[member.enclosingClass]!;
+ translator.functions.allocateClass(info.classId);
+
+ w.Local buffer = paramLocals[0];
+ w.Local offsetInBytes = paramLocals[1];
+ w.Local length = paramLocals[2];
+ b.i32_const(info.classId);
+ b.i32_const(initialIdentityHash);
+ b.local_get(length);
+ b.i32_wrap_i64();
+ b.local_get(buffer);
+ b.local_get(offsetInBytes);
+ b.i32_wrap_i64();
+ translator.struct_new(b, info);
+ return true;
+ }
+ }
+
+ // _TypedListBase.length
+ // _TypedListView.offsetInBytes
+ // _TypedListView._typedData
+ // _ByteDataView.length
+ // _ByteDataView.offsetInBytes
+ // _ByteDataView._typedData
+ if (member.isGetter) {
+ Class cls = member.enclosingClass!;
+ ClassInfo info = translator.classInfo[cls]!;
+ b.local_get(paramLocals[0]);
+ translator.ref_cast(b, info);
+ switch (name) {
+ case "length":
+ assert(cls == translator.typedListBaseClass ||
+ cls == translator.byteDataViewClass);
+ if (cls == translator.typedListBaseClass) {
+ b.struct_get(info.struct, FieldIndex.typedListBaseLength);
+ } else {
+ b.struct_get(info.struct, FieldIndex.byteDataViewLength);
+ }
+ b.i64_extend_i32_u();
+ return true;
+ case "offsetInBytes":
+ assert(cls == translator.typedListViewClass ||
+ cls == translator.byteDataViewClass);
+ if (cls == translator.typedListViewClass) {
+ b.struct_get(info.struct, FieldIndex.typedListViewOffsetInBytes);
+ } else {
+ b.struct_get(info.struct, FieldIndex.byteDataViewOffsetInBytes);
+ }
+ b.i64_extend_i32_u();
+ return true;
+ case "_typedData":
+ assert(cls == translator.typedListViewClass ||
+ cls == translator.byteDataViewClass);
+ if (cls == translator.typedListViewClass) {
+ b.struct_get(info.struct, FieldIndex.typedListViewTypedData);
+ } else {
+ b.struct_get(info.struct, FieldIndex.byteDataViewTypedData);
+ }
+ return true;
+ }
+ throw "Unrecognized typed data getter: ${cls.name}.$name";
+ }
+ }
+
+ // int members
+ if (member.enclosingClass == translator.boxedIntClass &&
+ member.function.body == null) {
+ String op = member.name.text;
+ if (functionNode.requiredParameterCount == 0) {
+ CodeGenCallback? code = unaryOperatorMap[intType]![op];
+ if (code != null) {
+ w.ValueType resultType = unaryResultMap[op] ?? intType;
+ w.ValueType inputType = function.type.inputs.single;
+ w.ValueType outputType = function.type.outputs.single;
+ b.local_get(function.locals[0]);
+ translator.convertType(function, inputType, intType);
+ code(b);
+ translator.convertType(function, resultType, outputType);
+ return true;
+ }
+ } else if (functionNode.requiredParameterCount == 1) {
+ CodeGenCallback? code = binaryOperatorMap[intType]![intType]![op];
+ if (code != null) {
+ w.ValueType leftType = function.type.inputs[0];
+ w.ValueType rightType = function.type.inputs[1];
+ w.ValueType outputType = function.type.outputs.single;
+ if (rightType == intType) {
+ // int parameter
+ b.local_get(function.locals[0]);
+ translator.convertType(function, leftType, intType);
+ b.local_get(function.locals[1]);
+ code(b);
+ if (!isComparison(op)) {
+ translator.convertType(function, intType, outputType);
+ }
+ return true;
+ }
+ // num parameter
+ ClassInfo intInfo = translator.classInfo[translator.boxedIntClass]!;
+ w.Label intArg = b.block(const [], [intInfo.nonNullableType]);
+ b.local_get(function.locals[1]);
+ translator.br_on_cast(b, intArg, intInfo);
+ // double argument
+ b.drop();
+ b.local_get(function.locals[0]);
+ translator.convertType(function, leftType, intType);
+ b.f64_convert_i64_s();
+ b.local_get(function.locals[1]);
+ translator.convertType(function, rightType, doubleType);
+ // Inline double op
+ CodeGenCallback doubleCode =
+ binaryOperatorMap[doubleType]![doubleType]![op]!;
+ doubleCode(b);
+ if (!isComparison(op)) {
+ translator.convertType(function, doubleType, outputType);
+ }
+ b.return_();
+ b.end();
+ // int argument
+ translator.convertType(function, intInfo.nonNullableType, intType);
+ w.Local rightTemp = function.addLocal(intType);
+ b.local_set(rightTemp);
+ b.local_get(function.locals[0]);
+ translator.convertType(function, leftType, intType);
+ b.local_get(rightTemp);
+ code(b);
+ if (!isComparison(op)) {
+ translator.convertType(function, intType, outputType);
+ }
+ return true;
+ }
+ }
+ }
+
+ // double unary members
+ if (member.enclosingClass == translator.boxedDoubleClass &&
+ member.function.body == null) {
+ String op = member.name.text;
+ if (functionNode.requiredParameterCount == 0) {
+ CodeGenCallback? code = unaryOperatorMap[doubleType]![op];
+ if (code != null) {
+ w.ValueType resultType = unaryResultMap[op] ?? doubleType;
+ w.ValueType inputType = function.type.inputs.single;
+ w.ValueType outputType = function.type.outputs.single;
+ b.local_get(function.locals[0]);
+ translator.convertType(function, inputType, doubleType);
+ code(b);
+ translator.convertType(function, resultType, outputType);
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/pkg/dart2wasm/lib/param_info.dart b/pkg/dart2wasm/lib/param_info.dart
new file mode 100644
index 0000000..182af47
--- /dev/null
+++ b/pkg/dart2wasm/lib/param_info.dart
@@ -0,0 +1,93 @@
+// Copyright (c) 2022, 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 'package:dart2wasm/reference_extensions.dart';
+
+import 'package:kernel/ast.dart';
+
+/// Information about optional parameters and their default values for a
+/// member or a set of members belonging to the same override group.
+class ParameterInfo {
+ final Member member;
+ int typeParamCount = 0;
+ late final List<Constant?> positional;
+ late final Map<String, Constant?> named;
+
+ // Do not access these until the info is complete.
+ late final List<String> names = named.keys.toList()..sort();
+ late final Map<String, int> nameIndex = {
+ for (int i = 0; i < names.length; i++) names[i]: positional.length + i
+ };
+
+ int get paramCount => positional.length + named.length;
+
+ static Constant? defaultValue(VariableDeclaration param) {
+ Expression? initializer = param.initializer;
+ if (initializer is ConstantExpression) {
+ return initializer.constant;
+ } else if (initializer == null) {
+ return null;
+ } else {
+ throw "Non-constant default value";
+ }
+ }
+
+ ParameterInfo.fromMember(Reference target) : member = target.asMember {
+ FunctionNode? function = member.function;
+ if (target.isTearOffReference) {
+ positional = [];
+ named = {};
+ } else if (function != null) {
+ typeParamCount = (member is Constructor
+ ? member.enclosingClass!.typeParameters
+ : function.typeParameters)
+ .length;
+ positional = List.generate(function.positionalParameters.length, (i) {
+ // A required parameter has no default value.
+ if (i < function.requiredParameterCount) return null;
+ return defaultValue(function.positionalParameters[i]);
+ });
+ named = {
+ for (VariableDeclaration param in function.namedParameters)
+ param.name!: defaultValue(param)
+ };
+ } else {
+ // A setter parameter has no default value.
+ positional = [if (target.isSetter) null];
+ named = {};
+ }
+ }
+
+ void merge(ParameterInfo other) {
+ assert(typeParamCount == other.typeParamCount);
+ for (int i = 0; i < other.positional.length; i++) {
+ if (i >= positional.length) {
+ positional.add(other.positional[i]);
+ } else {
+ if (positional[i] == null) {
+ positional[i] = other.positional[i];
+ } else if (other.positional[i] != null) {
+ if (positional[i] != other.positional[i]) {
+ print("Mismatching default value for parameter $i: "
+ "${member}: ${positional[i]} vs "
+ "${other.member}: ${other.positional[i]}");
+ }
+ }
+ }
+ }
+ for (String name in other.named.keys) {
+ Constant? value = named[name];
+ Constant? otherValue = other.named[name];
+ if (value == null) {
+ named[name] = otherValue;
+ } else if (otherValue != null) {
+ if (value != otherValue) {
+ print("Mismatching default value for parameter '$name': "
+ "${member}: ${value} vs "
+ "${other.member}: ${otherValue}");
+ }
+ }
+ }
+ }
+}
diff --git a/pkg/dart2wasm/lib/reference_extensions.dart b/pkg/dart2wasm/lib/reference_extensions.dart
new file mode 100644
index 0000000..ba9a68c
--- /dev/null
+++ b/pkg/dart2wasm/lib/reference_extensions.dart
@@ -0,0 +1,62 @@
+// Copyright (c) 2022, 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 'package:kernel/ast.dart';
+
+// Extend references with flags to more easily identify getters and setters.
+
+extension GetterSetterReference on Reference {
+ bool get isImplicitGetter {
+ Member member = asMember;
+ return member is Field && member.getterReference == this;
+ }
+
+ bool get isImplicitSetter {
+ Member member = asMember;
+ return member is Field && member.setterReference == this;
+ }
+
+ bool get isGetter {
+ Member member = asMember;
+ return member is Procedure && member.isGetter || isImplicitGetter;
+ }
+
+ bool get isSetter {
+ Member member = asMember;
+ return member is Procedure && member.isSetter || isImplicitSetter;
+ }
+}
+
+// Extend procedures with a tearOffReference that refers to the tear-off
+// implementation for that procedure. This enables a Reference to refer to any
+// implementation relating to a member, including its tear-off, which it can't
+// do in plain kernel.
+
+extension TearOffReference on Procedure {
+ // Use an Expando to avoid keeping the procedure alive.
+ static final Expando<Reference> _tearOffReference = Expando();
+
+ Reference get tearOffReference =>
+ _tearOffReference[this] ??= Reference()..node = this;
+}
+
+extension IsTearOffReference on Reference {
+ bool get isTearOffReference {
+ Member member = asMember;
+ return member is Procedure && member.tearOffReference == this;
+ }
+}
+
+extension ReferenceAs on Member {
+ Reference referenceAs({required bool getter, required bool setter}) {
+ Member member = this;
+ return member is Field
+ ? setter
+ ? member.setterReference!
+ : member.getterReference
+ : getter && member is Procedure && member.kind == ProcedureKind.Method
+ ? member.tearOffReference
+ : member.reference;
+ }
+}
diff --git a/pkg/dart2wasm/lib/target.dart b/pkg/dart2wasm/lib/target.dart
new file mode 100644
index 0000000..a7827e1
--- /dev/null
+++ b/pkg/dart2wasm/lib/target.dart
@@ -0,0 +1,191 @@
+// Copyright (c) 2022, 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 'package:kernel/ast.dart';
+import 'package:kernel/class_hierarchy.dart';
+import 'package:kernel/clone.dart';
+import 'package:kernel/core_types.dart';
+import 'package:kernel/reference_from_index.dart';
+import 'package:kernel/target/changed_structure_notifier.dart';
+import 'package:kernel/target/targets.dart';
+import 'package:kernel/transformations/mixin_full_resolution.dart'
+ as transformMixins show transformLibraries;
+
+import 'package:dart2wasm/constants_backend.dart';
+import 'package:dart2wasm/transformers.dart' as wasmTrans;
+
+class WasmTarget extends Target {
+ Class? _growableList;
+ Class? _immutableList;
+ Class? _immutableMap;
+ Class? _unmodifiableSet;
+ Class? _compactLinkedCustomHashMap;
+ Class? _compactLinkedHashSet;
+ Class? _oneByteString;
+ Class? _twoByteString;
+
+ @override
+ late final ConstantsBackend constantsBackend;
+
+ @override
+ String get name => 'wasm';
+
+ @override
+ TargetFlags get flags => TargetFlags(enableNullSafety: true);
+
+ @override
+ List<String> get extraIndexedLibraries => const <String>[
+ "dart:collection",
+ "dart:typed_data",
+ ];
+
+ void _patchHostEndian(CoreTypes coreTypes) {
+ // Fix Endian.host to be a const field equal to Endian.little instead of
+ // a final field. Wasm is a little-endian platform.
+ // Can't use normal patching process for this because CFE does not
+ // support patching fields.
+ // See http://dartbug.com/32836 for the background.
+ final Field host =
+ coreTypes.index.getField('dart:typed_data', 'Endian', 'host');
+ final Field little =
+ coreTypes.index.getField('dart:typed_data', 'Endian', 'little');
+ host.isConst = true;
+ host.initializer = new CloneVisitorNotMembers().clone(little.initializer!)
+ ..parent = host;
+ }
+
+ @override
+ void performPreConstantEvaluationTransformations(
+ Component component,
+ CoreTypes coreTypes,
+ List<Library> libraries,
+ DiagnosticReporter diagnosticReporter,
+ {void Function(String msg)? logger,
+ ChangedStructureNotifier? changedStructureNotifier}) {
+ constantsBackend = WasmConstantsBackend(coreTypes);
+ _patchHostEndian(coreTypes);
+ }
+
+ @override
+ void performModularTransformationsOnLibraries(
+ Component component,
+ CoreTypes coreTypes,
+ ClassHierarchy hierarchy,
+ List<Library> libraries,
+ Map<String, String>? environmentDefines,
+ DiagnosticReporter diagnosticReporter,
+ ReferenceFromIndex? referenceFromIndex,
+ {void logger(String msg)?,
+ ChangedStructureNotifier? changedStructureNotifier}) {
+ transformMixins.transformLibraries(
+ this, coreTypes, hierarchy, libraries, referenceFromIndex);
+ logger?.call("Transformed mixin applications");
+
+ wasmTrans.transformLibraries(libraries, coreTypes, hierarchy);
+ }
+
+ @override
+ void performTransformationsOnProcedure(
+ CoreTypes coreTypes,
+ ClassHierarchy hierarchy,
+ Procedure procedure,
+ Map<String, String>? environmentDefines,
+ {void logger(String msg)?}) {
+ wasmTrans.transformProcedure(procedure, coreTypes, hierarchy);
+ }
+
+ @override
+ Expression instantiateInvocation(CoreTypes coreTypes, Expression receiver,
+ String name, Arguments arguments, int offset, bool isSuper) {
+ throw "Unsupported: instantiateInvocation";
+ }
+
+ Expression instantiateNoSuchMethodError(CoreTypes coreTypes,
+ Expression receiver, String name, Arguments arguments, int offset,
+ {bool isMethod: false,
+ bool isGetter: false,
+ bool isSetter: false,
+ bool isField: false,
+ bool isLocalVariable: false,
+ bool isDynamic: false,
+ bool isSuper: false,
+ bool isStatic: false,
+ bool isConstructor: false,
+ bool isTopLevel: false}) {
+ throw "Unsupported: instantiateNoSuchMethodError";
+ }
+
+ @override
+ bool get supportsSetLiterals => false;
+
+ @override
+ int get enabledLateLowerings => LateLowering.all;
+
+ @override
+ int get enabledConstructorTearOffLowerings => ConstructorTearOffLowering.all;
+
+ @override
+ bool get supportsExplicitGetterCalls => true;
+
+ @override
+ bool get supportsLateLoweringSentinel => false;
+
+ @override
+ bool get useStaticFieldLowering => false;
+
+ @override
+ bool enableNative(Uri uri) => true;
+
+ @override
+ Class concreteListLiteralClass(CoreTypes coreTypes) {
+ return _growableList ??=
+ coreTypes.index.getClass('dart:core', '_GrowableList');
+ }
+
+ @override
+ Class concreteConstListLiteralClass(CoreTypes coreTypes) {
+ return _immutableList ??=
+ coreTypes.index.getClass('dart:core', '_ImmutableList');
+ }
+
+ @override
+ Class concreteMapLiteralClass(CoreTypes coreTypes) {
+ return _compactLinkedCustomHashMap ??= coreTypes.index
+ .getClass('dart:collection', '_CompactLinkedCustomHashMap');
+ }
+
+ @override
+ Class concreteConstMapLiteralClass(CoreTypes coreTypes) {
+ return _immutableMap ??=
+ coreTypes.index.getClass('dart:collection', '_ImmutableMap');
+ }
+
+ @override
+ Class concreteSetLiteralClass(CoreTypes coreTypes) {
+ return _compactLinkedHashSet ??=
+ coreTypes.index.getClass('dart:collection', '_CompactLinkedHashSet');
+ }
+
+ @override
+ Class concreteConstSetLiteralClass(CoreTypes coreTypes) {
+ return _unmodifiableSet ??=
+ coreTypes.index.getClass('dart:collection', '_UnmodifiableSet');
+ }
+
+ @override
+ Class concreteStringLiteralClass(CoreTypes coreTypes, String value) {
+ const int maxLatin1 = 0xff;
+ for (int i = 0; i < value.length; ++i) {
+ if (value.codeUnitAt(i) > maxLatin1) {
+ return _twoByteString ??=
+ coreTypes.index.getClass('dart:core', '_TwoByteString');
+ }
+ }
+ return _oneByteString ??=
+ coreTypes.index.getClass('dart:core', '_OneByteString');
+ }
+
+ @override
+ bool isSupportedPragma(String pragmaName) => pragmaName.startsWith("wasm:");
+}
diff --git a/pkg/dart2wasm/lib/transformers.dart b/pkg/dart2wasm/lib/transformers.dart
new file mode 100644
index 0000000..83793e3
--- /dev/null
+++ b/pkg/dart2wasm/lib/transformers.dart
@@ -0,0 +1,108 @@
+// Copyright (c) 2022, 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 'package:kernel/ast.dart';
+import 'package:kernel/class_hierarchy.dart';
+import 'package:kernel/core_types.dart';
+import 'package:kernel/type_environment.dart';
+
+void transformLibraries(
+ List<Library> libraries, CoreTypes coreTypes, ClassHierarchy hierarchy) {
+ final transformer = _WasmTransformer(coreTypes, hierarchy);
+ libraries.forEach(transformer.visitLibrary);
+}
+
+void transformProcedure(
+ Procedure procedure, CoreTypes coreTypes, ClassHierarchy hierarchy) {
+ final transformer = _WasmTransformer(coreTypes, hierarchy);
+ procedure.accept(transformer);
+}
+
+class _WasmTransformer extends Transformer {
+ final TypeEnvironment env;
+
+ Member? _currentMember;
+ StaticTypeContext? _cachedTypeContext;
+
+ StaticTypeContext get typeContext =>
+ _cachedTypeContext ??= StaticTypeContext(_currentMember!, env);
+
+ _WasmTransformer(CoreTypes coreTypes, ClassHierarchy hierarchy)
+ : env = TypeEnvironment(coreTypes, hierarchy);
+
+ @override
+ defaultMember(Member node) {
+ _currentMember = node;
+ _cachedTypeContext = null;
+
+ final result = super.defaultMember(node);
+
+ _currentMember = null;
+ _cachedTypeContext = null;
+ return result;
+ }
+
+ @override
+ TreeNode visitForInStatement(ForInStatement stmt) {
+ // Transform
+ //
+ // for ({var/final} T <variable> in <iterable>) { ... }
+ //
+ // Into
+ //
+ // {
+ // final Iterator<T> #forIterator = <iterable>.iterator;
+ // for (; #forIterator.moveNext() ;) {
+ // {var/final} T variable = #forIterator.current;
+ // ...
+ // }
+ // }
+ // }
+ final CoreTypes coreTypes = typeContext.typeEnvironment.coreTypes;
+
+ // The CFE might invoke this transformation despite the program having
+ // compile-time errors. So we will not transform this [stmt] if the
+ // `stmt.iterable` is an invalid expression or has an invalid type and
+ // instead eliminate the entire for-in and replace it with a invalid
+ // expression statement.
+ final iterable = stmt.iterable;
+ final iterableType = iterable.getStaticType(typeContext);
+ if (iterableType is InvalidType) {
+ return ExpressionStatement(
+ InvalidExpression('Invalid iterable type in for-in'));
+ }
+
+ final DartType elementType = stmt.getElementType(typeContext);
+ final iteratorType = InterfaceType(
+ coreTypes.iteratorClass, Nullability.nonNullable, [elementType]);
+
+ final iterator = VariableDeclaration("#forIterator",
+ initializer: InstanceGet(
+ InstanceAccessKind.Instance, iterable, Name('iterator'),
+ interfaceTarget: coreTypes.iterableGetIterator,
+ resultType: coreTypes.iterableGetIterator.function.returnType)
+ ..fileOffset = iterable.fileOffset,
+ type: iteratorType)
+ ..fileOffset = iterable.fileOffset;
+
+ final condition = InstanceInvocation(InstanceAccessKind.Instance,
+ VariableGet(iterator), Name('moveNext'), Arguments(const []),
+ interfaceTarget: coreTypes.iteratorMoveNext,
+ functionType: coreTypes.iteratorMoveNext.function
+ .computeFunctionType(Nullability.nonNullable))
+ ..fileOffset = iterable.fileOffset;
+
+ final variable = stmt.variable
+ ..initializer = (InstanceGet(
+ InstanceAccessKind.Instance, VariableGet(iterator), Name('current'),
+ interfaceTarget: coreTypes.iteratorGetCurrent,
+ resultType: coreTypes.iteratorGetCurrent.function.returnType)
+ ..fileOffset = stmt.bodyOffset);
+
+ final Block body = Block([variable, stmt.body]);
+
+ return Block([iterator, ForStatement(const [], condition, const [], body)])
+ .accept<TreeNode>(this);
+ }
+}
diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart
new file mode 100644
index 0000000..7a33ce4
--- /dev/null
+++ b/pkg/dart2wasm/lib/translator.dart
@@ -0,0 +1,819 @@
+// Copyright (c) 2022, 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 'package:dart2wasm/class_info.dart';
+import 'package:dart2wasm/closures.dart';
+import 'package:dart2wasm/code_generator.dart';
+import 'package:dart2wasm/constants.dart';
+import 'package:dart2wasm/dispatch_table.dart';
+import 'package:dart2wasm/functions.dart';
+import 'package:dart2wasm/globals.dart';
+import 'package:dart2wasm/param_info.dart';
+import 'package:dart2wasm/reference_extensions.dart';
+
+import 'package:kernel/ast.dart';
+import 'package:kernel/class_hierarchy.dart'
+ show ClassHierarchy, ClassHierarchySubtypes, ClosedWorldClassHierarchy;
+import 'package:kernel/core_types.dart';
+import 'package:kernel/src/printer.dart';
+import 'package:kernel/type_environment.dart';
+import 'package:vm/metadata/direct_call.dart';
+
+import 'package:wasm_builder/wasm_builder.dart' as w;
+
+/// Options controlling the translation.
+class TranslatorOptions {
+ bool exportAll = false;
+ bool inlining = false;
+ int inliningLimit = 3;
+ bool lazyConstants = false;
+ bool localNullability = false;
+ bool nameSection = true;
+ bool nominalTypes = false;
+ bool parameterNullability = true;
+ bool polymorphicSpecialization = false;
+ bool printKernel = false;
+ bool printWasm = false;
+ bool runtimeTypes = true;
+ bool stringDataSegments = false;
+ List<int>? watchPoints = null;
+
+ bool get useRttGlobals => runtimeTypes && !nominalTypes;
+}
+
+typedef CodeGenCallback = void Function(w.Instructions);
+
+/// The main entry point for the translation from kernel to Wasm and the hub for
+/// all global state in the compiler.
+///
+/// This class also contains utility methods for types and code generation used
+/// throughout the compiler.
+class Translator {
+ // Options for the translation.
+ final TranslatorOptions options;
+
+ // Kernel input and context.
+ final Component component;
+ final List<Library> libraries;
+ final CoreTypes coreTypes;
+ final TypeEnvironment typeEnvironment;
+ final ClosedWorldClassHierarchy hierarchy;
+ late final ClassHierarchySubtypes subtypes;
+
+ // Classes and members referenced specifically by the compiler.
+ late final Class wasmTypesBaseClass;
+ late final Class wasmArrayBaseClass;
+ late final Class wasmAnyRefClass;
+ late final Class wasmEqRefClass;
+ late final Class wasmDataRefClass;
+ late final Class boxedBoolClass;
+ late final Class boxedIntClass;
+ late final Class boxedDoubleClass;
+ late final Class functionClass;
+ late final Class listBaseClass;
+ late final Class fixedLengthListClass;
+ late final Class growableListClass;
+ late final Class immutableListClass;
+ late final Class stringBaseClass;
+ late final Class oneByteStringClass;
+ late final Class twoByteStringClass;
+ late final Class typeClass;
+ late final Class typedListBaseClass;
+ late final Class typedListClass;
+ late final Class typedListViewClass;
+ late final Class byteDataViewClass;
+ late final Procedure stringEquals;
+ late final Procedure stringInterpolate;
+ late final Procedure mapFactory;
+ late final Procedure mapPut;
+ late final Map<Class, w.StorageType> builtinTypes;
+ late final Map<w.ValueType, Class> boxedClasses;
+
+ // Other parts of the global compiler state.
+ late final ClassInfoCollector classInfoCollector;
+ late final DispatchTable dispatchTable;
+ late final Globals globals;
+ late final Constants constants;
+ late final FunctionCollector functions;
+
+ // Information about the program used and updated by the various phases.
+ final List<ClassInfo> classes = [];
+ final Map<Class, ClassInfo> classInfo = {};
+ final Map<w.HeapType, ClassInfo> classForHeapType = {};
+ final Map<Field, int> fieldIndex = {};
+ final Map<TypeParameter, int> typeParameterIndex = {};
+ final Map<Reference, ParameterInfo> staticParamInfo = {};
+ late Procedure mainFunction;
+ late final w.Module m;
+ late final w.DefinedFunction initFunction;
+ late final w.ValueType voidMarker;
+
+ // Caches for when identical source constructs need a common representation.
+ final Map<w.StorageType, w.ArrayType> arrayTypeCache = {};
+ final Map<int, w.StructType> functionTypeCache = {};
+ final Map<w.StructType, int> functionTypeParameterCount = {};
+ final Map<int, w.DefinedGlobal> functionTypeRtt = {};
+ final Map<w.DefinedFunction, w.DefinedGlobal> functionRefCache = {};
+ final Map<Procedure, w.DefinedFunction> tearOffFunctionCache = {};
+
+ ClassInfo get topInfo => classes[0];
+ ClassInfo get objectInfo => classInfo[coreTypes.objectClass]!;
+
+ Translator(this.component, this.coreTypes, this.typeEnvironment, this.options)
+ : libraries = component.libraries,
+ hierarchy =
+ ClassHierarchy(component, coreTypes) as ClosedWorldClassHierarchy {
+ subtypes = hierarchy.computeSubtypesInformation();
+ classInfoCollector = ClassInfoCollector(this);
+ dispatchTable = DispatchTable(this);
+ functions = FunctionCollector(this);
+
+ Library coreLibrary =
+ component.libraries.firstWhere((l) => l.name == "dart.core");
+ Class lookupCore(String name) {
+ return coreLibrary.classes.firstWhere((c) => c.name == name);
+ }
+
+ Library collectionLibrary =
+ component.libraries.firstWhere((l) => l.name == "dart.collection");
+ Class lookupCollection(String name) {
+ return collectionLibrary.classes.firstWhere((c) => c.name == name);
+ }
+
+ Library typedDataLibrary =
+ component.libraries.firstWhere((l) => l.name == "dart.typed_data");
+ Class lookupTypedData(String name) {
+ return typedDataLibrary.classes.firstWhere((c) => c.name == name);
+ }
+
+ Library wasmLibrary =
+ component.libraries.firstWhere((l) => l.name == "dart.wasm");
+ Class lookupWasm(String name) {
+ return wasmLibrary.classes.firstWhere((c) => c.name == name);
+ }
+
+ wasmTypesBaseClass = lookupWasm("_WasmBase");
+ wasmArrayBaseClass = lookupWasm("_WasmArray");
+ wasmAnyRefClass = lookupWasm("WasmAnyRef");
+ wasmEqRefClass = lookupWasm("WasmEqRef");
+ wasmDataRefClass = lookupWasm("WasmDataRef");
+ boxedBoolClass = lookupCore("_BoxedBool");
+ boxedIntClass = lookupCore("_BoxedInt");
+ boxedDoubleClass = lookupCore("_BoxedDouble");
+ functionClass = lookupCore("_Function");
+ fixedLengthListClass = lookupCore("_List");
+ listBaseClass = lookupCore("_ListBase");
+ growableListClass = lookupCore("_GrowableList");
+ immutableListClass = lookupCore("_ImmutableList");
+ stringBaseClass = lookupCore("_StringBase");
+ oneByteStringClass = lookupCore("_OneByteString");
+ twoByteStringClass = lookupCore("_TwoByteString");
+ typeClass = lookupCore("_Type");
+ typedListBaseClass = lookupTypedData("_TypedListBase");
+ typedListClass = lookupTypedData("_TypedList");
+ typedListViewClass = lookupTypedData("_TypedListView");
+ byteDataViewClass = lookupTypedData("_ByteDataView");
+ stringEquals =
+ stringBaseClass.procedures.firstWhere((p) => p.name.text == "==");
+ stringInterpolate = stringBaseClass.procedures
+ .firstWhere((p) => p.name.text == "_interpolate");
+ mapFactory = lookupCollection("LinkedHashMap").procedures.firstWhere(
+ (p) => p.kind == ProcedureKind.Factory && p.name.text == "_default");
+ mapPut = lookupCollection("_CompactLinkedCustomHashMap")
+ .superclass! // _HashBase
+ .superclass! // _LinkedHashMapMixin<K, V>
+ .procedures
+ .firstWhere((p) => p.name.text == "[]=");
+ builtinTypes = {
+ coreTypes.boolClass: w.NumType.i32,
+ coreTypes.intClass: w.NumType.i64,
+ coreTypes.doubleClass: w.NumType.f64,
+ wasmAnyRefClass: w.RefType.any(nullable: false),
+ wasmEqRefClass: w.RefType.eq(nullable: false),
+ wasmDataRefClass: w.RefType.data(nullable: false),
+ boxedBoolClass: w.NumType.i32,
+ boxedIntClass: w.NumType.i64,
+ boxedDoubleClass: w.NumType.f64,
+ lookupWasm("WasmI8"): w.PackedType.i8,
+ lookupWasm("WasmI16"): w.PackedType.i16,
+ lookupWasm("WasmI32"): w.NumType.i32,
+ lookupWasm("WasmI64"): w.NumType.i64,
+ lookupWasm("WasmF32"): w.NumType.f32,
+ lookupWasm("WasmF64"): w.NumType.f64,
+ };
+ boxedClasses = {
+ w.NumType.i32: boxedBoolClass,
+ w.NumType.i64: boxedIntClass,
+ w.NumType.f64: boxedDoubleClass,
+ };
+ }
+
+ Uint8List translate() {
+ m = w.Module(watchPoints: options.watchPoints);
+ voidMarker = w.RefType.def(w.StructType("void"), nullable: true);
+
+ classInfoCollector.collect();
+
+ functions.collectImportsAndExports();
+ mainFunction =
+ libraries.first.procedures.firstWhere((p) => p.name.text == "main");
+ functions.addExport(mainFunction.reference, "main");
+
+ initFunction = m.addFunction(functionType(const [], const []), "#init");
+ m.startFunction = initFunction;
+
+ globals = Globals(this);
+ constants = Constants(this);
+
+ dispatchTable.build();
+
+ functions.initialize();
+ while (functions.worklist.isNotEmpty) {
+ Reference reference = functions.worklist.removeLast();
+ Member member = reference.asMember;
+ var function =
+ functions.getExistingFunction(reference) as w.DefinedFunction;
+
+ String canonicalName = "$member";
+ if (reference.isSetter) {
+ canonicalName = "$canonicalName=";
+ } else if (reference.isGetter || reference.isTearOffReference) {
+ int dot = canonicalName.indexOf('.');
+ canonicalName = canonicalName.substring(0, dot + 1) +
+ '=' +
+ canonicalName.substring(dot + 1);
+ }
+ canonicalName = member.enclosingLibrary == libraries.first
+ ? canonicalName
+ : "${member.enclosingLibrary.importUri} $canonicalName";
+
+ String? exportName = functions.exports[reference];
+
+ if (options.printKernel || options.printWasm) {
+ if (exportName != null) {
+ print("#${function.index}: $canonicalName (exported as $exportName)");
+ } else {
+ print("#${function.index}: $canonicalName");
+ }
+ print(member.function
+ ?.computeFunctionType(Nullability.nonNullable)
+ .toStringInternal());
+ }
+ if (options.printKernel) {
+ if (member is Constructor) {
+ Class cls = member.enclosingClass;
+ for (Field field in cls.fields) {
+ if (field.isInstanceMember && field.initializer != null) {
+ print("${field.name}: ${field.initializer}");
+ }
+ }
+ for (Initializer initializer in member.initializers) {
+ print(initializer);
+ }
+ }
+ Statement? body = member.function?.body;
+ if (body != null) {
+ print(body);
+ }
+ if (!options.printWasm) print("");
+ }
+
+ if (exportName != null) {
+ m.exportFunction(exportName, function);
+ } else if (options.exportAll) {
+ m.exportFunction(canonicalName, function);
+ }
+ var codeGen = CodeGenerator(this, function, reference);
+ codeGen.generate();
+
+ if (options.printWasm) {
+ print(function.type);
+ print(function.body.trace);
+ }
+
+ for (Lambda lambda in codeGen.closures.lambdas.values) {
+ CodeGenerator(this, lambda.function, reference)
+ .generateLambda(lambda, codeGen.closures);
+ _printFunction(lambda.function, "$canonicalName (closure)");
+ }
+ }
+
+ dispatchTable.output();
+ constants.finalize();
+ initFunction.body.end();
+
+ for (ConstantInfo info in constants.constantInfo.values) {
+ w.DefinedFunction? function = info.function;
+ if (function != null) {
+ _printFunction(function, info.constant);
+ } else {
+ if (options.printWasm) {
+ print("Global #${info.global.index}: ${info.constant}");
+ print(info.global.initializer.trace);
+ }
+ }
+ }
+ if (options.lazyConstants) {
+ _printFunction(constants.oneByteStringFunction, "makeOneByteString");
+ _printFunction(constants.twoByteStringFunction, "makeTwoByteString");
+ }
+ _printFunction(initFunction, "init");
+
+ return m.encode(emitNameSection: options.nameSection);
+ }
+
+ void _printFunction(w.DefinedFunction function, Object name) {
+ if (options.printWasm) {
+ print("#${function.index}: $name");
+ print(function.body.trace);
+ }
+ }
+
+ Class classForType(DartType type) {
+ return type is InterfaceType
+ ? type.classNode
+ : type is TypeParameterType
+ ? classForType(type.bound)
+ : coreTypes.objectClass;
+ }
+
+ w.ValueType translateType(DartType type) {
+ w.StorageType wasmType = translateStorageType(type);
+ if (wasmType is w.ValueType) return wasmType;
+ throw "Packed types are only allowed in arrays and fields";
+ }
+
+ bool isWasmType(Class cls) {
+ while (cls.superclass != null) {
+ cls = cls.superclass!;
+ if (cls == wasmTypesBaseClass) return true;
+ }
+ return false;
+ }
+
+ w.StorageType typeForInfo(ClassInfo info, bool nullable) {
+ Class? cls = info.cls;
+ if (cls != null) {
+ w.StorageType? builtin = builtinTypes[cls];
+ if (builtin != null) {
+ if (!nullable) return builtin;
+ if (isWasmType(cls)) {
+ if (builtin.isPrimitive) throw "Wasm numeric types can't be nullable";
+ return (builtin as w.RefType).withNullability(true);
+ }
+ Class? boxedClass = boxedClasses[builtin];
+ if (boxedClass != null) {
+ info = classInfo[boxedClass]!;
+ }
+ }
+ }
+ return w.RefType.def(info.repr.struct,
+ nullable: !options.parameterNullability || nullable);
+ }
+
+ w.StorageType translateStorageType(DartType type) {
+ if (type is InterfaceType) {
+ if (type.classNode.superclass == wasmArrayBaseClass) {
+ DartType elementType = type.typeArguments.single;
+ return w.RefType.def(arrayTypeForDartType(elementType),
+ nullable: false);
+ }
+ return typeForInfo(
+ classInfo[type.classNode]!, type.isPotentiallyNullable);
+ }
+ if (type is DynamicType) {
+ return topInfo.nullableType;
+ }
+ if (type is NullType) {
+ return topInfo.nullableType;
+ }
+ if (type is NeverType) {
+ return topInfo.nullableType;
+ }
+ if (type is VoidType) {
+ return voidMarker;
+ }
+ if (type is TypeParameterType) {
+ return translateStorageType(type.isPotentiallyNullable
+ ? type.bound.withDeclaredNullability(type.nullability)
+ : type.bound);
+ }
+ if (type is FutureOrType) {
+ return topInfo.nullableType;
+ }
+ if (type is FunctionType) {
+ if (type.requiredParameterCount != type.positionalParameters.length ||
+ type.namedParameters.isNotEmpty) {
+ throw "Function types with optional parameters not supported: $type";
+ }
+ return w.RefType.def(closureStructType(type.requiredParameterCount),
+ nullable:
+ !options.parameterNullability || type.isPotentiallyNullable);
+ }
+ throw "Unsupported type ${type.runtimeType}";
+ }
+
+ w.ArrayType arrayTypeForDartType(DartType type) {
+ while (type is TypeParameterType) type = type.bound;
+ return wasmArrayType(
+ translateStorageType(type), type.toText(defaultAstTextStrategy));
+ }
+
+ w.ArrayType wasmArrayType(w.StorageType type, String name) {
+ return arrayTypeCache.putIfAbsent(
+ type, () => arrayType("Array<$name>", elementType: w.FieldType(type)));
+ }
+
+ w.StructType closureStructType(int parameterCount) {
+ return functionTypeCache.putIfAbsent(parameterCount, () {
+ ClassInfo info = classInfo[functionClass]!;
+ w.StructType struct = structType("Function$parameterCount",
+ fields: info.struct.fields, superType: info.struct);
+ assert(struct.fields.length == FieldIndex.closureFunction);
+ struct.fields.add(w.FieldType(
+ w.RefType.def(closureFunctionType(parameterCount), nullable: false),
+ mutable: false));
+ if (options.useRttGlobals) {
+ functionTypeRtt[parameterCount] =
+ classInfoCollector.makeRtt(struct, info);
+ }
+ functionTypeParameterCount[struct] = parameterCount;
+ return struct;
+ });
+ }
+
+ w.FunctionType closureFunctionType(int parameterCount) {
+ return functionType([
+ w.RefType.data(),
+ ...List<w.ValueType>.filled(parameterCount, topInfo.nullableType)
+ ], [
+ topInfo.nullableType
+ ]);
+ }
+
+ int parameterCountForFunctionStruct(w.HeapType heapType) {
+ return functionTypeParameterCount[heapType]!;
+ }
+
+ w.DefinedGlobal makeFunctionRef(w.DefinedFunction f) {
+ return functionRefCache.putIfAbsent(f, () {
+ w.DefinedGlobal global = m.addGlobal(
+ w.GlobalType(w.RefType.def(f.type, nullable: false), mutable: false));
+ global.initializer.ref_func(f);
+ global.initializer.end();
+ return global;
+ });
+ }
+
+ w.DefinedFunction getTearOffFunction(Procedure member) {
+ return tearOffFunctionCache.putIfAbsent(member, () {
+ assert(member.kind == ProcedureKind.Method);
+ FunctionNode functionNode = member.function;
+ int parameterCount = functionNode.requiredParameterCount;
+ if (functionNode.positionalParameters.length != parameterCount ||
+ functionNode.namedParameters.isNotEmpty) {
+ throw "Not supported: Tear-off with optional parameters"
+ " at ${member.location}";
+ }
+ if (functionNode.typeParameters.isNotEmpty) {
+ throw "Not supported: Tear-off with type parameters"
+ " at ${member.location}";
+ }
+ w.FunctionType memberSignature = signatureFor(member.reference);
+ w.FunctionType closureSignature = closureFunctionType(parameterCount);
+ int signatureOffset = member.isInstanceMember ? 1 : 0;
+ assert(memberSignature.inputs.length == signatureOffset + parameterCount);
+ assert(closureSignature.inputs.length == 1 + parameterCount);
+ w.DefinedFunction function =
+ m.addFunction(closureSignature, "$member (tear-off)");
+ w.BaseFunction target = functions.getFunction(member.reference);
+ w.Instructions b = function.body;
+ for (int i = 0; i < memberSignature.inputs.length; i++) {
+ w.Local paramLocal = function.locals[(1 - signatureOffset) + i];
+ b.local_get(paramLocal);
+ convertType(function, paramLocal.type, memberSignature.inputs[i]);
+ }
+ b.call(target);
+ convertType(function, outputOrVoid(target.type.outputs),
+ outputOrVoid(closureSignature.outputs));
+ b.end();
+ return function;
+ });
+ }
+
+ w.ValueType ensureBoxed(w.ValueType type) {
+ // Box receiver if it's primitive
+ if (type is w.RefType) return type;
+ return w.RefType.def(classInfo[boxedClasses[type]!]!.struct,
+ nullable: false);
+ }
+
+ w.ValueType typeForLocal(w.ValueType type) {
+ return options.localNullability ? type : type.withNullability(true);
+ }
+
+ w.ValueType outputOrVoid(List<w.ValueType> outputs) {
+ return outputs.isEmpty ? voidMarker : outputs.single;
+ }
+
+ bool needsConversion(w.ValueType from, w.ValueType to) {
+ return (from == voidMarker) ^ (to == voidMarker) || !from.isSubtypeOf(to);
+ }
+
+ void convertType(
+ w.DefinedFunction function, w.ValueType from, w.ValueType to) {
+ w.Instructions b = function.body;
+ if (from == voidMarker || to == voidMarker) {
+ if (from != voidMarker) {
+ b.drop();
+ return;
+ }
+ if (to != voidMarker) {
+ if (to is w.RefType && to.nullable) {
+ // This can happen when a void method has its return type overridden to
+ // return a value, in which case the selector signature will have a
+ // non-void return type to encompass all possible return values.
+ b.ref_null(to.heapType);
+ } else {
+ // This only happens in invalid but unreachable code produced by the
+ // TFA dead-code elimination.
+ b.comment("Non-nullable void conversion");
+ b.unreachable();
+ }
+ return;
+ }
+ }
+ if (!from.isSubtypeOf(to)) {
+ if (from is! w.RefType && to is w.RefType) {
+ // Boxing
+ ClassInfo info = classInfo[boxedClasses[from]!]!;
+ assert(info.struct.isSubtypeOf(to.heapType));
+ w.Local temp = function.addLocal(from);
+ b.local_set(temp);
+ b.i32_const(info.classId);
+ b.local_get(temp);
+ struct_new(b, info);
+ } else if (from is w.RefType && to is! w.RefType) {
+ // Unboxing
+ ClassInfo info = classInfo[boxedClasses[to]!]!;
+ if (!from.heapType.isSubtypeOf(info.struct)) {
+ // Cast to box type
+ if (!from.heapType.isSubtypeOf(w.HeapType.data)) {
+ b.ref_as_data();
+ }
+ ref_cast(b, info);
+ }
+ b.struct_get(info.struct, FieldIndex.boxValue);
+ } else if (from.withNullability(false).isSubtypeOf(to)) {
+ // Null check
+ b.ref_as_non_null();
+ } else {
+ // Downcast
+ var heapType = (to as w.RefType).heapType;
+ ClassInfo? info = classForHeapType[heapType];
+ if (from.nullable && !to.nullable) {
+ b.ref_as_non_null();
+ }
+ if (!(from as w.RefType).heapType.isSubtypeOf(w.HeapType.data)) {
+ b.ref_as_data();
+ }
+ ref_cast(
+ b,
+ info ??
+ (heapType.isSubtypeOf(classInfo[functionClass]!.struct)
+ ? parameterCountForFunctionStruct(heapType)
+ : heapType));
+ }
+ }
+ }
+
+ w.FunctionType signatureFor(Reference target) {
+ Member member = target.asMember;
+ if (member.isInstanceMember) {
+ return dispatchTable.selectorForTarget(target).signature;
+ } else {
+ return functions.getFunction(target).type;
+ }
+ }
+
+ ParameterInfo paramInfoFor(Reference target) {
+ Member member = target.asMember;
+ if (member.isInstanceMember) {
+ return dispatchTable.selectorForTarget(target).paramInfo;
+ } else {
+ return staticParamInfo.putIfAbsent(
+ target, () => ParameterInfo.fromMember(target));
+ }
+ }
+
+ Member? singleTarget(TreeNode node) {
+ DirectCallMetadataRepository metadata =
+ component.metadata[DirectCallMetadataRepository.repositoryTag]
+ as DirectCallMetadataRepository;
+ return metadata.mapping[node]?.target;
+ }
+
+ bool shouldInline(Reference target) {
+ if (!options.inlining) return false;
+ Member member = target.asMember;
+ if (member is Field) return true;
+ Statement? body = member.function!.body;
+ return body != null &&
+ NodeCounter().countNodes(body) <= options.inliningLimit;
+ }
+
+ T? getPragma<T>(Annotatable node, String name, [T? defaultvalue]) {
+ for (Expression annotation in node.annotations) {
+ if (annotation is ConstantExpression) {
+ Constant constant = annotation.constant;
+ if (constant is InstanceConstant) {
+ if (constant.classNode == coreTypes.pragmaClass) {
+ Constant? nameConstant =
+ constant.fieldValues[coreTypes.pragmaName.fieldReference];
+ if (nameConstant is StringConstant && nameConstant.value == name) {
+ Object? value =
+ constant.fieldValues[coreTypes.pragmaOptions.fieldReference];
+ if (value is PrimitiveConstant<T>) {
+ return value.value;
+ }
+ return value as T? ?? defaultvalue;
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ // Wrappers for type creation to abstract over equi-recursive versus nominal
+ // typing. The given supertype is ignored when nominal types are disabled,
+ // and a suitable default is inserted when nominal types are enabled.
+
+ w.FunctionType functionType(
+ Iterable<w.ValueType> inputs, Iterable<w.ValueType> outputs,
+ {w.HeapType? superType}) {
+ return m.addFunctionType(inputs, outputs,
+ superType: options.nominalTypes ? superType ?? w.HeapType.func : null);
+ }
+
+ w.StructType structType(String name,
+ {Iterable<w.FieldType>? fields, w.HeapType? superType}) {
+ return m.addStructType(name,
+ fields: fields,
+ superType: options.nominalTypes ? superType ?? w.HeapType.data : null);
+ }
+
+ w.ArrayType arrayType(String name,
+ {w.FieldType? elementType, w.HeapType? superType}) {
+ return m.addArrayType(name,
+ elementType: elementType,
+ superType: options.nominalTypes ? superType ?? w.HeapType.data : null);
+ }
+
+ // Wrappers for object allocation and cast instructions to abstract over
+ // RTT-based and static versions of the instructions.
+ // The [type] parameter taken by the methods is either a [ClassInfo] (to use
+ // the RTT for the class), an [int] (to use the RTT for the closure struct
+ // corresponding to functions with that number of parameters) or a
+ // [w.DataType] (to use the canonical RTT for the type).
+
+ void struct_new(w.Instructions b, Object type) {
+ if (options.runtimeTypes) {
+ final struct = _emitRtt(b, type) as w.StructType;
+ b.struct_new_with_rtt(struct);
+ } else {
+ b.struct_new(_targetType(type) as w.StructType);
+ }
+ }
+
+ void struct_new_default(w.Instructions b, Object type) {
+ if (options.runtimeTypes) {
+ final struct = _emitRtt(b, type) as w.StructType;
+ b.struct_new_default_with_rtt(struct);
+ } else {
+ b.struct_new_default(_targetType(type) as w.StructType);
+ }
+ }
+
+ void array_new(w.Instructions b, w.ArrayType type) {
+ if (options.runtimeTypes) {
+ b.rtt_canon(type);
+ b.array_new_with_rtt(type);
+ } else {
+ b.array_new(type);
+ }
+ }
+
+ void array_new_default(w.Instructions b, w.ArrayType type) {
+ if (options.runtimeTypes) {
+ b.rtt_canon(type);
+ b.array_new_default_with_rtt(type);
+ } else {
+ b.array_new_default(type);
+ }
+ }
+
+ void array_init(w.Instructions b, w.ArrayType type, int length) {
+ if (options.runtimeTypes) {
+ b.rtt_canon(type);
+ b.array_init(type, length);
+ } else {
+ b.array_init_static(type, length);
+ }
+ }
+
+ void array_init_from_data(
+ w.Instructions b, w.ArrayType type, w.DataSegment data) {
+ if (options.runtimeTypes) {
+ b.rtt_canon(type);
+ b.array_init_from_data(type, data);
+ } else {
+ b.array_init_from_data_static(type, data);
+ }
+ }
+
+ void ref_test(w.Instructions b, Object type) {
+ if (options.runtimeTypes) {
+ _emitRtt(b, type);
+ b.ref_test();
+ } else {
+ b.ref_test_static(_targetType(type));
+ }
+ }
+
+ void ref_cast(w.Instructions b, Object type) {
+ if (options.runtimeTypes) {
+ _emitRtt(b, type);
+ b.ref_cast();
+ } else {
+ b.ref_cast_static(_targetType(type));
+ }
+ }
+
+ void br_on_cast(w.Instructions b, w.Label label, Object type) {
+ if (options.runtimeTypes) {
+ _emitRtt(b, type);
+ b.br_on_cast(label);
+ } else {
+ b.br_on_cast_static(label, _targetType(type));
+ }
+ }
+
+ void br_on_cast_fail(w.Instructions b, w.Label label, Object type) {
+ if (options.runtimeTypes) {
+ _emitRtt(b, type);
+ b.br_on_cast_fail(label);
+ } else {
+ b.br_on_cast_static_fail(label, _targetType(type));
+ }
+ }
+
+ w.DefType _emitRtt(w.Instructions b, Object type) {
+ if (type is ClassInfo) {
+ if (options.nominalTypes) {
+ b.rtt_canon(type.struct);
+ } else {
+ b.global_get(type.rtt);
+ }
+ return type.struct;
+ } else if (type is int) {
+ int parameterCount = type;
+ w.StructType struct = closureStructType(parameterCount);
+ if (options.nominalTypes) {
+ b.rtt_canon(struct);
+ } else {
+ w.DefinedGlobal rtt = functionTypeRtt[parameterCount]!;
+ b.global_get(rtt);
+ }
+ return struct;
+ } else {
+ b.rtt_canon(type as w.DataType);
+ return type;
+ }
+ }
+
+ w.DefType _targetType(Object type) => type is ClassInfo
+ ? type.struct
+ : type is int
+ ? closureStructType(type)
+ : type as w.DefType;
+}
+
+class NodeCounter extends Visitor<void> with VisitorVoidMixin {
+ int count = 0;
+
+ int countNodes(Node node) {
+ count = 0;
+ node.accept(this);
+ return count;
+ }
+
+ @override
+ void defaultNode(Node node) {
+ count++;
+ node.visitChildren(this);
+ }
+}
diff --git a/pkg/dart2wasm/pubspec.yaml b/pkg/dart2wasm/pubspec.yaml
new file mode 100644
index 0000000..dd0b8de
--- /dev/null
+++ b/pkg/dart2wasm/pubspec.yaml
@@ -0,0 +1,26 @@
+name: dart2wasm
+# This package is not intended for consumption on pub.dev. DO NOT publish.
+publish_to: none
+environment:
+ sdk: '>=2.12.0'
+
+dependencies:
+ front_end:
+ path: ../front_end
+ kernel:
+ path: ../kernel
+ vm:
+ path: ../vm
+ wasm_builder:
+ path: ../wasm_builder
+
+dependency_overrides:
+ # Packages with source in the SDK
+ front_end:
+ path: ../front_end
+ kernel:
+ path: ../kernel
+ vm:
+ path: ../vm
+ wasm_builder:
+ path: ../wasm_builder
diff --git a/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart b/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart
index 7952b4b..755208c 100644
--- a/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart
@@ -422,6 +422,17 @@
}, () => loader.currentUriForCrashReporting);
}
+ /// Builds [augmentationLibraries] to the state expected after applying phase
+ /// 1 macros.
+ Future<void> _buildForPhase1(
+ Iterable<SourceLibraryBuilder> augmentationLibraries) async {
+ await loader.buildOutlines();
+ loader.computeLibraryScopes(augmentationLibraries);
+ // TODO(johnniwinther): Support computation of macro applications in
+ // augmentation libraries?
+ loader.resolveTypes(augmentationLibraries);
+ }
+
Future<BuildResult> buildOutlines({CanonicalName? nameRoot}) async {
if (loader.first == null) return new BuildResult();
return withCrashReporting<BuildResult>(() async {
@@ -441,7 +452,7 @@
}
benchmarker?.enterPhase(BenchmarkPhases.outline_computeLibraryScopes);
- loader.computeLibraryScopes();
+ loader.computeLibraryScopes(loader.libraryBuilders);
benchmarker?.enterPhase(BenchmarkPhases.outline_computeMacroApplications);
MacroApplications? macroApplications =
@@ -451,10 +462,10 @@
setupTopAndBottomTypes();
benchmarker?.enterPhase(BenchmarkPhases.outline_resolveTypes);
- loader.resolveTypes();
+ loader.resolveTypes(loader.sourceLibraryBuilders);
benchmarker?.enterPhase(BenchmarkPhases.outline_computeVariances);
- loader.computeVariances();
+ loader.computeVariances(loader.sourceLibraryBuilders);
benchmarker?.enterPhase(BenchmarkPhases.outline_computeDefaultTypes);
loader.computeDefaultTypes(
@@ -462,7 +473,9 @@
if (macroApplications != null) {
benchmarker?.enterPhase(BenchmarkPhases.outline_applyTypeMacros);
- await macroApplications.applyTypeMacros();
+ List<SourceLibraryBuilder> augmentationLibraries =
+ await macroApplications.applyTypeMacros();
+ await _buildForPhase1(augmentationLibraries);
}
benchmarker?.enterPhase(BenchmarkPhases.outline_checkSemantics);
diff --git a/pkg/front_end/lib/src/fasta/kernel/macro.dart b/pkg/front_end/lib/src/fasta/kernel/macro.dart
index c14a5cd..6f6abea 100644
--- a/pkg/front_end/lib/src/fasta/kernel/macro.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/macro.dart
@@ -78,12 +78,14 @@
class MacroApplicationDataForTesting {
Map<SourceLibraryBuilder, LibraryMacroApplicationData> libraryData = {};
- Map<SourceClassBuilder, String> classTypesResults = {};
+ Map<SourceLibraryBuilder, String> libraryTypesResult = {};
+ Map<SourceClassBuilder, List<macro.MacroExecutionResult>> classTypesResults =
+ {};
Map<SourceClassBuilder, List<macro.MacroExecutionResult>>
classDeclarationsResults = {};
Map<SourceClassBuilder, List<macro.MacroExecutionResult>>
classDefinitionsResults = {};
- Map<MemberBuilder, String> memberTypesResults = {};
+ Map<MemberBuilder, List<macro.MacroExecutionResult>> memberTypesResults = {};
Map<MemberBuilder, List<macro.MacroExecutionResult>>
memberDeclarationsResults = {};
Map<MemberBuilder, List<macro.MacroExecutionResult>>
@@ -235,6 +237,8 @@
uri = typeDeclarationBuilder.library.importUri;
} else if (typeDeclarationBuilder is TypeAliasBuilder) {
uri = typeDeclarationBuilder.library.importUri;
+ } else if (identifier.name == 'dynamic') {
+ uri = Uri.parse('dart:core');
}
return new macro.ResolvedIdentifier(
kind: macro.IdentifierKind.topLevelMember,
@@ -252,19 +256,33 @@
}
} else {
// TODO(johnniwinther): Use [_IdentifierImpl] for all identifiers.
- return new macro.ResolvedIdentifier(
- kind: macro.IdentifierKind.topLevelMember,
- name: identifier.name,
- staticScope: null,
- uri: null);
+ if (identical(identifier, dynamicIdentifier)) {
+ return new macro.ResolvedIdentifier(
+ kind: macro.IdentifierKind.topLevelMember,
+ name: identifier.name,
+ staticScope: null,
+ uri: Uri.parse('dart:core'));
+ } else {
+ return new macro.ResolvedIdentifier(
+ kind: macro.IdentifierKind.topLevelMember,
+ name: identifier.name,
+ staticScope: null,
+ uri: null);
+ }
}
}
- Future<void> _applyMacros(
- Future<void> Function(Builder, macro.Declaration, List<MacroApplication>)
- applyMacros) async {
+ Future<Map<SourceLibraryBuilder, List<macro.MacroExecutionResult>>>
+ _applyMacros(
+ Future<List<macro.MacroExecutionResult>> Function(
+ Builder, macro.Declaration, List<MacroApplication>)
+ applyMacros) async {
+ Map<SourceLibraryBuilder, List<macro.MacroExecutionResult>> libraryResults =
+ {};
for (MapEntry<SourceLibraryBuilder,
LibraryMacroApplicationData> libraryEntry in libraryData.entries) {
+ SourceLibraryBuilder libraryBuilder = libraryEntry.key;
+ List<macro.MacroExecutionResult> results = [];
LibraryMacroApplicationData libraryMacroApplicationData =
libraryEntry.value;
for (MapEntry<MemberBuilder, List<MacroApplication>> memberEntry
@@ -272,7 +290,8 @@
MemberBuilder memberBuilder = memberEntry.key;
macro.Declaration? declaration = _getMemberDeclaration(memberBuilder);
if (declaration != null) {
- await applyMacros(memberBuilder, declaration, memberEntry.value);
+ results.addAll(
+ await applyMacros(memberBuilder, declaration, memberEntry.value));
}
}
for (MapEntry<SourceClassBuilder, ClassMacroApplicationData> classEntry
@@ -283,18 +302,22 @@
if (classApplications != null) {
macro.ClassDeclaration classDeclaration =
_getClassDeclaration(classBuilder);
- await applyMacros(classBuilder, classDeclaration, classApplications);
+ results.addAll(await applyMacros(
+ classBuilder, classDeclaration, classApplications));
}
for (MapEntry<MemberBuilder, List<MacroApplication>> memberEntry
in classData.memberApplications.entries) {
MemberBuilder memberBuilder = memberEntry.key;
macro.Declaration? declaration = _getMemberDeclaration(memberBuilder);
if (declaration != null) {
- await applyMacros(memberBuilder, declaration, memberEntry.value);
+ results.addAll(await applyMacros(
+ memberBuilder, declaration, memberEntry.value));
}
}
}
+ libraryResults[libraryBuilder] = results;
}
+ return libraryResults;
}
Future<List<macro.MacroExecutionResult>> _applyTypeMacros(
@@ -311,20 +334,33 @@
results.add(result);
}
}
- String result =
- _macroExecutor.buildAugmentationLibrary(results, _resolveIdentifier);
+
if (retainDataForTesting) {
if (builder is SourceClassBuilder) {
- dataForTesting?.classTypesResults[builder] = result;
+ dataForTesting?.classTypesResults[builder] = results;
} else {
- dataForTesting?.memberTypesResults[builder as MemberBuilder] = result;
+ dataForTesting?.memberTypesResults[builder as MemberBuilder] = results;
}
}
return results;
}
- Future<void> applyTypeMacros() async {
- await _applyMacros(_applyTypeMacros);
+ Future<List<SourceLibraryBuilder>> applyTypeMacros() async {
+ List<SourceLibraryBuilder> augmentationLibraries = [];
+ Map<SourceLibraryBuilder, List<macro.MacroExecutionResult>> results =
+ await _applyMacros(_applyTypeMacros);
+ for (MapEntry<SourceLibraryBuilder, List<macro.MacroExecutionResult>> entry
+ in results.entries) {
+ SourceLibraryBuilder sourceLibraryBuilder = entry.key;
+ String result = _macroExecutor.buildAugmentationLibrary(
+ entry.value, _resolveIdentifier);
+ if (retainDataForTesting) {
+ dataForTesting?.libraryTypesResult[sourceLibraryBuilder] = result;
+ }
+ augmentationLibraries
+ .add(await sourceLibraryBuilder.createAugmentationLibrary(result));
+ }
+ return augmentationLibraries;
}
Future<List<macro.MacroExecutionResult>> _applyDeclarationsMacros(
diff --git a/pkg/front_end/lib/src/fasta/source/source_library_builder.dart b/pkg/front_end/lib/src/fasta/source/source_library_builder.dart
index a5a4d57..70e9378 100644
--- a/pkg/front_end/lib/src/fasta/source/source_library_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_library_builder.dart
@@ -504,6 +504,28 @@
(_patchLibraries ??= []).add(patchLibrary);
}
+ /// Creates a synthesized augmentation library for the [source] code and
+ /// attach it as a patch library of this library.
+ ///
+ /// To support the parser of the [source], the library is registered as an
+ /// unparsed library on the [loader].
+ SourceLibraryBuilder createAugmentationLibrary(String source) {
+ int index = _patchLibraries?.length ?? 0;
+ Uri uri = new Uri(
+ scheme: 'org-dartlang-augmentation', path: '${fileUri.path}-$index');
+ SourceLibraryBuilder augmentationLibrary = new SourceLibraryBuilder(
+ fileUri: uri,
+ importUri: uri,
+ packageLanguageVersion: packageLanguageVersion,
+ loader: loader,
+ isUnsupported: false,
+ target: library,
+ origin: this);
+ addPatchLibrary(augmentationLibrary);
+ loader.registerUnparsedLibrarySource(augmentationLibrary, source);
+ return augmentationLibrary;
+ }
+
List<NamedTypeBuilder> get unresolvedNamedTypes =>
_libraryTypeParameterScopeBuilder.unresolvedNamedTypes;
diff --git a/pkg/front_end/lib/src/fasta/source/source_loader.dart b/pkg/front_end/lib/src/fasta/source/source_loader.dart
index 3130c75..a429ef0 100644
--- a/pkg/front_end/lib/src/fasta/source/source_loader.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_loader.dart
@@ -160,7 +160,8 @@
List<SourceLibraryBuilder>? _sourceLibraryBuilders;
- final Queue<LibraryBuilder> _unparsedLibraries = new Queue<LibraryBuilder>();
+ final Queue<SourceLibraryBuilder> _unparsedLibraries =
+ new Queue<SourceLibraryBuilder>();
final List<Library> libraries = <Library>[];
@@ -964,12 +965,25 @@
_typeInferenceEngine!.typeDependencies[member] = typeDependency;
}
+ /// Registers the [library] as unparsed with the given [source] code.
+ ///
+ /// This is used for creating synthesized augmentation libraries.
+ void registerUnparsedLibrarySource(
+ SourceLibraryBuilder library, String source) {
+ List<int> codeUnits = source.codeUnits;
+ Uint8List bytes = new Uint8List(codeUnits.length + 1);
+ bytes.setRange(0, codeUnits.length, codeUnits);
+ sourceBytes[library.fileUri] = bytes;
+ _unparsedLibraries.addLast(library);
+ }
+
+ /// Runs the [OutlineBuilder] on the source of all [_unparsedLibraries].
Future<void> buildOutlines() async {
_ensureCoreLibrary();
while (_unparsedLibraries.isNotEmpty) {
- LibraryBuilder library = _unparsedLibraries.removeFirst();
+ SourceLibraryBuilder library = _unparsedLibraries.removeFirst();
currentUriForCrashReporting = library.importUri;
- await buildOutline(library as SourceLibraryBuilder);
+ await buildOutline(library);
}
currentUriForCrashReporting = null;
logSummary(outlineSummaryTemplate);
@@ -1256,7 +1270,8 @@
ticker.logMs("Applied patches");
}
- void computeLibraryScopes() {
+ /// Compute library scopes for [libraryBuilders].
+ void computeLibraryScopes(Iterable<LibraryBuilder> libraryBuilders) {
Set<LibraryBuilder> exporters = new Set<LibraryBuilder>();
Set<LibraryBuilder> exportees = new Set<LibraryBuilder>();
for (LibraryBuilder library in libraryBuilders) {
@@ -1347,9 +1362,10 @@
});
}
- void resolveTypes() {
+ /// Resolve [NamedTypeBuilder]s in [libraryBuilders].
+ void resolveTypes(Iterable<SourceLibraryBuilder> libraryBuilders) {
int typeCount = 0;
- for (SourceLibraryBuilder library in sourceLibraryBuilders) {
+ for (SourceLibraryBuilder library in libraryBuilders) {
typeCount += library.resolveTypes();
}
ticker.logMs("Resolved $typeCount types");
@@ -1697,9 +1713,10 @@
ticker.logMs("Resolved $count type-variable bounds");
}
- void computeVariances() {
+ /// Computes variances of type parameters on typedefs in [libraryBuilders].
+ void computeVariances(Iterable<SourceLibraryBuilder> libraryBuilders) {
int count = 0;
- for (SourceLibraryBuilder library in sourceLibraryBuilders) {
+ for (SourceLibraryBuilder library in libraryBuilders) {
count += library.computeVariances();
}
ticker.logMs("Computed variances of $count type variables");
diff --git a/pkg/front_end/lib/src/testing/compiler_common.dart b/pkg/front_end/lib/src/testing/compiler_common.dart
index 6c2620b..407f814 100644
--- a/pkg/front_end/lib/src/testing/compiler_common.dart
+++ b/pkg/front_end/lib/src/testing/compiler_common.dart
@@ -114,9 +114,13 @@
}
}
+const String _testUriScheme = 'org-dartlang-test';
+
+bool isTestUri(Uri uri) => uri.isScheme(_testUriScheme);
+
/// A fake absolute directory used as the root of a memory-file system in the
/// helpers above.
-Uri _defaultDir = Uri.parse('org-dartlang-test:///a/b/c/');
+Uri _defaultDir = Uri.parse('${_testUriScheme}:///a/b/c/');
/// Convert relative file paths into an absolute Uri as expected by the test
/// helpers above.
diff --git a/pkg/front_end/lib/src/testing/id_testing_helper.dart b/pkg/front_end/lib/src/testing/id_testing_helper.dart
index 48fe099..be478b3 100644
--- a/pkg/front_end/lib/src/testing/id_testing_helper.dart
+++ b/pkg/front_end/lib/src/testing/id_testing_helper.dart
@@ -85,6 +85,10 @@
dynamic customizeCompilerOptions(
CompilerOptions options, TestData testData) =>
null;
+
+ /// Called after running test on [testData] with the resulting
+ /// [testResultData].
+ void onCompilationResult(TestData testData, TestResultData testResultData) {}
}
abstract class DataComputer<T> {
@@ -369,6 +373,7 @@
TestResultData testResultData =
new TestResultData(config, customData, compilerResult);
+ config.onCompilationResult(testData, testResultData);
Component component = compilerResult.component!;
Map<Uri, Map<Id, ActualData<T>>> actualMaps = <Uri, Map<Id, ActualData<T>>>{};
diff --git a/pkg/front_end/test/macro_application/data/pkgs/macro/lib/macro.dart b/pkg/front_end/test/macro_application/data/pkgs/macro/lib/macro.dart
index a394202..5f4db94 100644
--- a/pkg/front_end/test/macro_application/data/pkgs/macro/lib/macro.dart
+++ b/pkg/front_end/test/macro_application/data/pkgs/macro/lib/macro.dart
@@ -48,8 +48,10 @@
TypeBuilder builder) {
var name = '${function.identifier.name}GeneratedClass';
builder.declareType(
- name, new DeclarationCode.fromParts(['class $name<T extends ',
- function.returnType.code, '> {}']));
+ name, new DeclarationCode.fromParts(['''
+class $name {
+ external ''', function.returnType.code, ''' method();
+}''']));
}
}
diff --git a/pkg/front_end/test/macro_application/data/tests/declarations.dart b/pkg/front_end/test/macro_application/data/tests/declarations.dart
index 0f95ed9..dfed26d 100644
--- a/pkg/front_end/test/macro_application/data/tests/declarations.dart
+++ b/pkg/front_end/test/macro_application/data/tests/declarations.dart
@@ -2,66 +2,62 @@
// 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.
+/*library:
+
+
+*/
+
import 'package:macro/macro.dart';
/*member: topLevelFunction1:
-
void topLevelFunction1GeneratedMethod_() {}
*/
@FunctionDeclarationsMacro1()
void topLevelFunction1() {}
/*member: topLevelFunction2:
-
void topLevelFunction2GeneratedMethod_e() {}
*/
@FunctionDeclarationsMacro1()
external void topLevelFunction2();
/*member: topLevelField1:
-
void topLevelField1GeneratedMethod_() {}
*/
@VariableDeclarationsMacro1()
int? topLevelField1;
/*member: topLevelField2:
-
void topLevelField2GeneratedMethod_e() {}
*/
@VariableDeclarationsMacro1()
external int? topLevelField2;
/*member: topLevelField3:
-
void topLevelField3GeneratedMethod_f() {}
*/
@VariableDeclarationsMacro1()
final int? topLevelField3 = null;
/*member: topLevelField4:
-
void topLevelField4GeneratedMethod_l() {}
*/
@VariableDeclarationsMacro1()
late int? topLevelField4;
/*member: topLevelGetter1:
-
void topLevelGetter1GeneratedMethod_g() {}
*/
@FunctionDeclarationsMacro1()
int? get topLevelGetter1 => null;
/*member: topLevelSetter1=:
-
void topLevelSetter1GeneratedMethod_s() {}
*/
@FunctionDeclarationsMacro1()
void set topLevelSetter1(int? value) {}
/*class: Class1:
-
void Class1GeneratedMethod_() {}
void Class1Introspection() {
@@ -74,7 +70,6 @@
@ClassDeclarationsMacro2()
class Class1 {
/*member: Class1.:
-
augment class Class1 {
void Class1_GeneratedMethod_() {}
@@ -83,7 +78,6 @@
Class1();
/*member: Class1.redirect:
-
augment class Class1 {
void Class1_redirectGeneratedMethod_f() {}
@@ -92,7 +86,6 @@
factory Class1.redirect() = Class1;
/*member: Class1.fact:
-
augment class Class1 {
void Class1_factGeneratedMethod_f() {}
@@ -101,49 +94,42 @@
factory Class1.fact() => new Class1();
/*member: Class1.instanceMethod1:
-
void Class1_instanceMethod1GeneratedMethod_() {}
*/
@MethodDeclarationsMacro1()
void instanceMethod1() {}
/*member: Class1.instanceGetter1:
-
void Class1_instanceGetter1GeneratedMethod_g() {}
*/
@MethodDeclarationsMacro1()
int? get instanceGetter1 => null;
/*member: Class1.instanceSetter1=:
-
void Class1_instanceSetter1GeneratedMethod_s() {}
*/
@MethodDeclarationsMacro1()
void set instanceSetter1(int? value) {}
/*member: Class1.[]:
-
void Class1_[]GeneratedMethod_o() {}
*/
@MethodDeclarationsMacro1()
int operator [](int i) => i;
/*member: Class1.instanceField1:
-
void Class1_instanceField1GeneratedMethod_() {}
*/
@FieldDeclarationsMacro1()
int? instanceField1;
/*member: Class1.instanceField2:
-
void Class1_instanceField2GeneratedMethod_f() {}
*/
@FieldDeclarationsMacro1()
final int? instanceField2 = null;
/*member: Class1.instanceField3:
-
void Class1_instanceField3GeneratedMethod_fl() {}
*/
@FieldDeclarationsMacro1()
@@ -151,7 +137,6 @@
}
/*class: Class2:
-
void Class2GeneratedMethod_a() {}
void Class2Introspection() {
@@ -164,14 +149,12 @@
@ClassDeclarationsMacro2()
abstract class Class2 {
/*member: Class2.instanceMethod1:
-
void Class2_instanceMethod1GeneratedMethod_a() {}
*/
@MethodDeclarationsMacro1()
void instanceMethod1();
/*member: Class2.instanceField1:
-
void Class2_instanceField1GeneratedMethod_() {}
*/
@FieldDeclarationsMacro1()
diff --git a/pkg/front_end/test/macro_application/data/tests/declarations.dart.expect b/pkg/front_end/test/macro_application/data/tests/declarations.dart.expect
new file mode 100644
index 0000000..786028f
--- /dev/null
+++ b/pkg/front_end/test/macro_application/data/tests/declarations.dart.expect
@@ -0,0 +1,81 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "package:macro/macro.dart" as mac;
+import "dart:core" as core;
+
+import "package:macro/macro.dart";
+
+@#C1
+@#C2
+class Class1 extends core::Object {
+ @#C3
+ field core::int? instanceField1 = null;
+ @#C3
+ final field core::int? instanceField2 = null;
+ @#C3
+ late final field core::int? instanceField3 = null;
+ static final field dynamic _redirecting# = <dynamic>[#C4]/*isLegacy*/;
+ @#C5
+ constructor •() → self::Class1
+ : super core::Object::•()
+ ;
+ @#C5
+ static factory redirect() → self::Class1
+ return new self::Class1::•();
+ @#C5
+ static factory fact() → self::Class1
+ return new self::Class1::•();
+ @#C6
+ method instanceMethod1() → void {}
+ @#C6
+ get instanceGetter1() → core::int?
+ return null;
+ @#C6
+ set instanceSetter1(core::int? value) → void {}
+ @#C6
+ operator [](core::int i) → core::int
+ return i;
+}
+@#C1
+@#C2
+abstract class Class2 extends core::Object {
+ synthetic constructor •() → self::Class2
+ : super core::Object::•()
+ ;
+ @#C6
+ abstract method instanceMethod1() → void;
+ @#C3
+ abstract get instanceField1() → core::int?;
+ @#C3
+ abstract set instanceField1(core::int? #externalFieldValue) → void;
+}
+@#C7
+static field core::int? topLevelField1;
+@#C7
+static final field core::int? topLevelField3 = null;
+@#C7
+late static field core::int? topLevelField4;
+@#C8
+static method topLevelFunction1() → void {}
+@#C8
+external static method topLevelFunction2() → void;
+@#C7
+external static get topLevelField2() → core::int?;
+@#C7
+external static set topLevelField2(core::int? #externalFieldValue) → void;
+@#C8
+static get topLevelGetter1() → core::int?
+ return null;
+@#C8
+static set topLevelSetter1(core::int? value) → void {}
+
+constants {
+ #C1 = mac::ClassDeclarationsMacro1 {}
+ #C2 = mac::ClassDeclarationsMacro2 {}
+ #C3 = mac::FieldDeclarationsMacro1 {}
+ #C4 = constructor-tearoff self::Class1::redirect
+ #C5 = mac::ConstructorDeclarationsMacro1 {}
+ #C6 = mac::MethodDeclarationsMacro1 {}
+ #C7 = mac::VariableDeclarationsMacro1 {}
+ #C8 = mac::FunctionDeclarationsMacro1 {}
+}
diff --git a/pkg/front_end/test/macro_application/data/tests/parameters.dart b/pkg/front_end/test/macro_application/data/tests/parameters.dart
index 132979a..89fa87e 100644
--- a/pkg/front_end/test/macro_application/data/tests/parameters.dart
+++ b/pkg/front_end/test/macro_application/data/tests/parameters.dart
@@ -2,10 +2,14 @@
// 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.
+/*library:
+
+
+*/
+
import 'package:macro/macro.dart';
/*member: topLevelFunction1:
-
augment void topLevelFunction1(int a, ) {
return 42;
}*/
@@ -13,7 +17,6 @@
external void topLevelFunction1(int a);
/*member: topLevelFunction2:
-
augment void topLevelFunction2(int a, int b, ) {
return 42;
}*/
@@ -21,7 +24,6 @@
external void topLevelFunction2(int a, int b);
/*member: topLevelFunction3:
-
augment void topLevelFunction3(int a, [int? b, ]) {
return 42;
}*/
@@ -29,7 +31,6 @@
external void topLevelFunction3(int a, [int? b]);
/*member: topLevelFunction4:
-
augment void topLevelFunction4(int a, {int? b, int? c, }) {
return 42;
}*/
diff --git a/pkg/front_end/test/macro_application/data/tests/parameters.dart.expect b/pkg/front_end/test/macro_application/data/tests/parameters.dart.expect
new file mode 100644
index 0000000..8f916e5
--- /dev/null
+++ b/pkg/front_end/test/macro_application/data/tests/parameters.dart.expect
@@ -0,0 +1,20 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "package:macro/macro.dart" as mac;
+import "dart:core" as core;
+
+import "package:macro/macro.dart";
+
+@#C1
+external static method topLevelFunction1(core::int a) → void;
+@#C1
+external static method topLevelFunction2(core::int a, core::int b) → void;
+@#C1
+external static method topLevelFunction3(core::int a, [core::int? b = #C2]) → void;
+@#C1
+external static method topLevelFunction4(core::int a, {core::int? b = #C2, core::int? c = #C2}) → void;
+
+constants {
+ #C1 = mac::FunctionDefinitionMacro1 {}
+ #C2 = null
+}
diff --git a/pkg/front_end/test/macro_application/data/tests/subtypes.dart b/pkg/front_end/test/macro_application/data/tests/subtypes.dart
index aae13edd..ffdd9f7 100644
--- a/pkg/front_end/test/macro_application/data/tests/subtypes.dart
+++ b/pkg/front_end/test/macro_application/data/tests/subtypes.dart
@@ -2,6 +2,11 @@
// 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.
+/*library:
+
+
+*/
+
import 'package:macro/macro.dart';
class A {}
@@ -19,7 +24,6 @@
class D2 {}
/*member: topLevelFunction1:
-
void topLevelFunction1GeneratedMethod_es() {}
augment A topLevelFunction1(A a, ) {
@@ -31,7 +35,6 @@
external A topLevelFunction1(A a);
/*member: topLevelFunction2:
-
void topLevelFunction2GeneratedMethod_s() {}
augment B2 topLevelFunction2(B1 a, ) {
@@ -43,7 +46,6 @@
external B2 topLevelFunction2(B1 a);
/*member: topLevelFunction3:
-
void topLevelFunction3GeneratedMethod_() {}
augment C2 topLevelFunction3(C1 a, ) {
@@ -55,7 +57,6 @@
external C2 topLevelFunction3(C1 a);
/*member: topLevelFunction4:
-
void topLevelFunction4GeneratedMethod_() {}
augment D2 topLevelFunction4(D1 a, ) {
diff --git a/pkg/front_end/test/macro_application/data/tests/subtypes.dart.expect b/pkg/front_end/test/macro_application/data/tests/subtypes.dart.expect
new file mode 100644
index 0000000..d428183
--- /dev/null
+++ b/pkg/front_end/test/macro_application/data/tests/subtypes.dart.expect
@@ -0,0 +1,59 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "package:macro/macro.dart" as mac;
+
+import "package:macro/macro.dart";
+
+class A extends core::Object {
+ synthetic constructor •() → self::A
+ : super core::Object::•()
+ ;
+}
+class B1 extends core::Object {
+ synthetic constructor •() → self::B1
+ : super core::Object::•()
+ ;
+}
+class B2 extends self::B1 {
+ synthetic constructor •() → self::B2
+ : super self::B1::•()
+ ;
+}
+class C1 extends self::C2 {
+ synthetic constructor •() → self::C1
+ : super self::C2::•()
+ ;
+}
+class C2 extends core::Object {
+ synthetic constructor •() → self::C2
+ : super core::Object::•()
+ ;
+}
+class D1 extends core::Object {
+ synthetic constructor •() → self::D1
+ : super core::Object::•()
+ ;
+}
+class D2 extends core::Object {
+ synthetic constructor •() → self::D2
+ : super core::Object::•()
+ ;
+}
+@#C1
+@#C2
+external static method topLevelFunction1(self::A a) → self::A;
+@#C1
+@#C2
+external static method topLevelFunction2(self::B1 a) → self::B2;
+@#C1
+@#C2
+external static method topLevelFunction3(self::C1 a) → self::C2;
+@#C1
+@#C2
+external static method topLevelFunction4(self::D1 a) → self::D2;
+
+constants {
+ #C1 = mac::FunctionDeclarationsMacro2 {}
+ #C2 = mac::FunctionDefinitionMacro2 {}
+}
diff --git a/pkg/front_end/test/macro_application/data/tests/type_annotations.dart b/pkg/front_end/test/macro_application/data/tests/type_annotations.dart
index 35867d9..dfee364 100644
--- a/pkg/front_end/test/macro_application/data/tests/type_annotations.dart
+++ b/pkg/front_end/test/macro_application/data/tests/type_annotations.dart
@@ -2,12 +2,16 @@
// 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.
+/*library:
+
+
+*/
+
import 'dart:math' as math;
import 'package:macro/macro.dart';
/*member: topLevelFunction1:
-
augment void topLevelFunction1() {
return 42;
}*/
@@ -15,7 +19,6 @@
external void topLevelFunction1();
/*member: topLevelFunction2:
-
augment dynamic topLevelFunction2() {
return 42;
}*/
@@ -23,7 +26,6 @@
external dynamic topLevelFunction2();
/*member: topLevelFunction3:
-
augment int topLevelFunction3() {
return 42;
}*/
@@ -31,7 +33,6 @@
external int topLevelFunction3();
/*member: topLevelFunction4:
-
augment dynamic topLevelFunction4() {
return 42;
}*/
@@ -39,7 +40,6 @@
external topLevelFunction4();
/*member: topLevelFunction5:
-
augment math.Random topLevelFunction5() {
return 42;
}*/
@@ -47,7 +47,6 @@
external math.Random topLevelFunction5();
/*member: topLevelFunction6:
-
augment List<int> topLevelFunction6() {
return 42;
}*/
@@ -55,7 +54,6 @@
external List<int> topLevelFunction6();
/*member: topLevelFunction7:
-
augment Map<math.Random, List<int>> topLevelFunction7() {
return 42;
}*/
@@ -63,7 +61,6 @@
external Map<math.Random, List<int>> topLevelFunction7();
/*member: topLevelFunction8:
-
augment Map<int?, String>? topLevelFunction8() {
return 42;
}*/
diff --git a/pkg/front_end/test/macro_application/data/tests/type_annotations.dart.expect b/pkg/front_end/test/macro_application/data/tests/type_annotations.dart.expect
new file mode 100644
index 0000000..219150c
--- /dev/null
+++ b/pkg/front_end/test/macro_application/data/tests/type_annotations.dart.expect
@@ -0,0 +1,29 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "package:macro/macro.dart" as mac;
+import "dart:core" as core;
+import "dart:math" as math;
+
+import "dart:math" as math;
+import "package:macro/macro.dart";
+
+@#C1
+external static method topLevelFunction1() → void;
+@#C1
+external static method topLevelFunction2() → dynamic;
+@#C1
+external static method topLevelFunction3() → core::int;
+@#C1
+external static method topLevelFunction4() → dynamic;
+@#C1
+external static method topLevelFunction5() → math::Random;
+@#C1
+external static method topLevelFunction6() → core::List<core::int>;
+@#C1
+external static method topLevelFunction7() → core::Map<math::Random, core::List<core::int>>;
+@#C1
+external static method topLevelFunction8() → core::Map<core::int?, core::String>?;
+
+constants {
+ #C1 = mac::FunctionDefinitionMacro1 {}
+}
diff --git a/pkg/front_end/test/macro_application/data/tests/types.dart b/pkg/front_end/test/macro_application/data/tests/types.dart
index 2dbb05d..883e037 100644
--- a/pkg/front_end/test/macro_application/data/tests/types.dart
+++ b/pkg/front_end/test/macro_application/data/tests/types.dart
@@ -2,35 +2,61 @@
// 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.
+/*library:
+import 'dart:core' as i0;
+import 'package:macro/macro.dart' as i1;
+
+
+class topLevelFunction1GeneratedClass {
+ external void method();
+}
+class topLevelFunction2GeneratedClass {
+ external i0.dynamic method();
+}
+class topLevelFunction3GeneratedClass {
+ external i0.int method();
+}
+class topLevelFunction4GeneratedClass {
+ external i1.FunctionTypesMacro1? method();
+}
+class topLevelFunction5GeneratedClass {
+ external i0.dynamic method();
+}
+*/
+
import 'package:macro/macro.dart';
/*member: topLevelFunction1:
-class topLevelFunction1GeneratedClass<T extends void> {}*/
+class topLevelFunction1GeneratedClass {
+ external void method();
+}*/
@FunctionTypesMacro1()
void topLevelFunction1() {}
/*member: topLevelFunction2:
-class topLevelFunction2GeneratedClass<T extends dynamic> {}*/
+class topLevelFunction2GeneratedClass {
+ external dynamic method();
+}*/
@FunctionTypesMacro1()
dynamic topLevelFunction2() {}
/*member: topLevelFunction3:
-import 'dart:core' as i0;
-
-
-class topLevelFunction3GeneratedClass<T extends i0.int> {}*/
+class topLevelFunction3GeneratedClass {
+ external int method();
+}*/
@FunctionTypesMacro1()
int topLevelFunction3() => 0;
/*member: topLevelFunction4:
-import 'package:macro/macro.dart' as i0;
-
-
-class topLevelFunction4GeneratedClass<T extends i0.FunctionTypesMacro1?> {}*/
+class topLevelFunction4GeneratedClass {
+ external FunctionTypesMacro1? method();
+}*/
@FunctionTypesMacro1()
FunctionTypesMacro1? topLevelFunction4() => null;
/*member: topLevelFunction5:
-class topLevelFunction5GeneratedClass<T extends dynamic> {}*/
+class topLevelFunction5GeneratedClass {
+ external dynamic method();
+}*/
@FunctionTypesMacro1()
topLevelFunction5() {}
diff --git a/pkg/front_end/test/macro_application/data/tests/types.dart.expect b/pkg/front_end/test/macro_application/data/tests/types.dart.expect
new file mode 100644
index 0000000..e36ef4f
--- /dev/null
+++ b/pkg/front_end/test/macro_application/data/tests/types.dart.expect
@@ -0,0 +1,55 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "package:macro/macro.dart" as mac;
+
+import "dart:core" as i0;
+import "package:macro/macro.dart" as i1;
+import "package:macro/macro.dart";
+
+class topLevelFunction1GeneratedClass extends core::Object { // from org-dartlang-augmentation:/a/b/c/main.dart-0
+ synthetic constructor •() → self::topLevelFunction1GeneratedClass
+ : super core::Object::•()
+ ;
+ external method method() → void;
+}
+class topLevelFunction2GeneratedClass extends core::Object { // from org-dartlang-augmentation:/a/b/c/main.dart-0
+ synthetic constructor •() → self::topLevelFunction2GeneratedClass
+ : super core::Object::•()
+ ;
+ external method method() → dynamic;
+}
+class topLevelFunction3GeneratedClass extends core::Object { // from org-dartlang-augmentation:/a/b/c/main.dart-0
+ synthetic constructor •() → self::topLevelFunction3GeneratedClass
+ : super core::Object::•()
+ ;
+ external method method() → core::int;
+}
+class topLevelFunction4GeneratedClass extends core::Object { // from org-dartlang-augmentation:/a/b/c/main.dart-0
+ synthetic constructor •() → self::topLevelFunction4GeneratedClass
+ : super core::Object::•()
+ ;
+ external method method() → mac::FunctionTypesMacro1?;
+}
+class topLevelFunction5GeneratedClass extends core::Object { // from org-dartlang-augmentation:/a/b/c/main.dart-0
+ synthetic constructor •() → self::topLevelFunction5GeneratedClass
+ : super core::Object::•()
+ ;
+ external method method() → dynamic;
+}
+@#C1
+static method topLevelFunction1() → void {}
+@#C1
+static method topLevelFunction2() → dynamic {}
+@#C1
+static method topLevelFunction3() → core::int
+ return 0;
+@#C1
+static method topLevelFunction4() → mac::FunctionTypesMacro1?
+ return null;
+@#C1
+static method topLevelFunction5() → dynamic {}
+
+constants {
+ #C1 = mac::FunctionTypesMacro1 {}
+}
diff --git a/pkg/front_end/test/macro_application/macro_application_test.dart b/pkg/front_end/test/macro_application/macro_application_test.dart
index 18e8fdf..9282bac 100644
--- a/pkg/front_end/test/macro_application/macro_application_test.dart
+++ b/pkg/front_end/test/macro_application/macro_application_test.dart
@@ -2,7 +2,7 @@
// 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:io' show Directory, Platform;
+import 'dart:io' show Directory, File, Platform;
import 'package:_fe_analyzer_shared/src/macros/api.dart';
import 'package:_fe_analyzer_shared/src/macros/executor.dart';
@@ -10,7 +10,7 @@
import 'package:_fe_analyzer_shared/src/macros/isolated_executor/isolated_executor.dart'
as isolatedExecutor;
import 'package:_fe_analyzer_shared/src/testing/id.dart'
- show ActualData, ClassId, Id;
+ show ActualData, ClassId, Id, LibraryId;
import 'package:_fe_analyzer_shared/src/testing/id_testing.dart';
import 'package:front_end/src/api_prototype/compiler_options.dart';
import 'package:front_end/src/api_prototype/experimental_flags.dart';
@@ -19,14 +19,20 @@
import 'package:front_end/src/fasta/kernel/macro.dart';
import 'package:front_end/src/fasta/kernel/utils.dart';
import 'package:front_end/src/fasta/source/source_class_builder.dart';
+import 'package:front_end/src/fasta/source/source_library_builder.dart';
+import 'package:front_end/src/testing/compiler_common.dart';
import 'package:front_end/src/testing/id_extractor.dart';
import 'package:front_end/src/testing/id_testing_helper.dart';
import 'package:kernel/ast.dart' hide Arguments;
import 'package:kernel/kernel.dart';
import 'package:kernel/target/targets.dart';
+import 'package:kernel/text/ast_to_text.dart';
import 'package:vm/target/vm.dart';
+import '../utils/kernel_chain.dart';
+
Future<void> main(List<String> args) async {
+ bool generateExpectations = args.contains('-g');
enableMacros = true;
Directory tempDirectory =
@@ -38,8 +44,10 @@
args: args,
createUriForFileName: createUriForFileName,
onFailure: onFailure,
- runTest: runTestFor(
- const MacroDataComputer(), [new MacroTestConfig(tempDirectory)]),
+ runTest: runTestFor(const MacroDataComputer(), [
+ new MacroTestConfig(dataDir, tempDirectory,
+ generateExpectations: generateExpectations)
+ ]),
preserveWhitespaceInAnnotations: true);
} finally {
await tempDirectory.delete(recursive: true);
@@ -47,11 +55,14 @@
}
class MacroTestConfig extends TestConfig {
+ final Directory dataDir;
final Directory tempDirectory;
+ final bool generateExpectations;
int precompiledCount = 0;
final Map<MacroClass, Uri> precompiledMacroUris = {};
- MacroTestConfig(this.tempDirectory)
+ MacroTestConfig(this.dataDir, this.tempDirectory,
+ {required this.generateExpectations})
: super(cfeMarker, 'cfe',
explicitExperimentalFlags: {ExperimentalFlag.macros: true},
packageConfigUri:
@@ -71,6 +82,42 @@
return uri;
};
}
+
+ @override
+ Future<void> onCompilationResult(
+ TestData testData, TestResultData testResultData) async {
+ Component component = testResultData.compilerResult.component!;
+ StringBuffer buffer = new StringBuffer();
+ Printer printer = new Printer(buffer)
+ ..writeProblemsAsJson("Problems in component", component.problemsAsJson);
+ component.libraries.forEach((Library library) {
+ if (isTestUri(library.importUri)) {
+ printer.writeLibraryFile(library);
+ printer.endLine();
+ }
+ });
+ printer.writeConstantTable(component);
+ String actual = buffer.toString();
+ String expectationFileName = '${testData.name}.expect';
+ Uri expectedUri = dataDir.uri.resolve(expectationFileName);
+ File file = new File.fromUri(expectedUri);
+ if (file.existsSync()) {
+ String expected = file.readAsStringSync();
+ if (expected != actual) {
+ if (generateExpectations) {
+ file.writeAsStringSync(actual);
+ } else {
+ String diff = await runDiff(expectedUri, actual);
+ throw "${testData.name} don't match ${expectedUri}\n$diff";
+ }
+ }
+ } else if (generateExpectations) {
+ file.writeAsStringSync(actual);
+ } else {
+ throw 'Please use -g option to create file ${expectedUri} with this '
+ 'content:\n$actual';
+ }
+ }
}
bool _isMember(MemberBuilder memberBuilder, Member member) {
@@ -93,6 +140,34 @@
DataInterpreter<String> get dataValidator => const StringDataInterpreter();
@override
+ void computeLibraryData(TestResultData testResultData, Library library,
+ Map<Id, ActualData<String>> actualMap,
+ {bool? verbose}) {
+ CfeDataRegistry<String> registry =
+ new CfeDataRegistry(testResultData.compilerResult, actualMap);
+ MacroApplicationDataForTesting macroApplicationData = testResultData
+ .compilerResult
+ .kernelTargetForTesting!
+ .loader
+ .dataForTesting!
+ .macroApplicationData;
+ StringBuffer sb = new StringBuffer();
+ for (SourceLibraryBuilder sourceLibraryBuilder
+ in macroApplicationData.libraryTypesResult.keys) {
+ if (sourceLibraryBuilder.library == library) {
+ String source =
+ macroApplicationData.libraryTypesResult[sourceLibraryBuilder]!;
+ sb.write('\n${source}');
+ }
+ }
+ if (sb.isNotEmpty) {
+ Id id = new LibraryId(library.fileUri);
+ registry.registerValue(
+ library.fileUri, library.fileOffset, id, sb.toString(), library);
+ }
+ }
+
+ @override
void computeClassData(TestResultData testResultData, Class cls,
Map<Id, ActualData<String>> actualMap,
{bool? verbose}) {
@@ -105,10 +180,12 @@
.dataForTesting!
.macroApplicationData;
StringBuffer sb = new StringBuffer();
- for (MapEntry<SourceClassBuilder, String> entry
+ for (MapEntry<SourceClassBuilder, List<MacroExecutionResult>> entry
in macroApplicationData.classTypesResults.entries) {
if (entry.key.cls == cls) {
- sb.write('\n${entry.value.trim()}');
+ for (MacroExecutionResult result in entry.value) {
+ sb.write('\n${codeToString(result.augmentations.first)}');
+ }
}
}
for (MapEntry<SourceClassBuilder, List<MacroExecutionResult>> entry
@@ -147,10 +224,12 @@
.dataForTesting!
.macroApplicationData;
StringBuffer sb = new StringBuffer();
- for (MapEntry<MemberBuilder, String> entry
+ for (MapEntry<MemberBuilder, List<MacroExecutionResult>> entry
in macroApplicationData.memberTypesResults.entries) {
if (_isMember(entry.key, member)) {
- sb.write('\n${entry.value.trim()}');
+ for (MacroExecutionResult result in entry.value) {
+ sb.write('\n${codeToString(result.augmentations.first)}');
+ }
}
}
for (MapEntry<MemberBuilder, List<MacroExecutionResult>> entry
diff --git a/pkg/smith/lib/configuration.dart b/pkg/smith/lib/configuration.dart
index bac7ac7..9284ec3 100644
--- a/pkg/smith/lib/configuration.dart
+++ b/pkg/smith/lib/configuration.dart
@@ -624,6 +624,7 @@
static const none = Compiler._('none');
static const dart2js = Compiler._('dart2js');
static const dart2analyzer = Compiler._('dart2analyzer');
+ static const dart2wasm = Compiler._('dart2wasm');
static const compareAnalyzerCfe = Compiler._('compare_analyzer_cfe');
static const dartdevc = Compiler._('dartdevc');
static const dartdevk = Compiler._('dartdevk');
@@ -639,6 +640,7 @@
none,
dart2js,
dart2analyzer,
+ dart2wasm,
compareAnalyzerCfe,
dartdevc,
dartdevk,
@@ -691,6 +693,12 @@
Runtime.safari,
];
+ case Compiler.dart2wasm:
+ return const [
+ Runtime.none,
+ Runtime.d8,
+ Runtime.chrome,
+ ];
case Compiler.dart2analyzer:
case Compiler.compareAnalyzerCfe:
return const [Runtime.none];
@@ -716,6 +724,8 @@
switch (this) {
case Compiler.dart2js:
return Runtime.d8;
+ case Compiler.dart2wasm:
+ return Runtime.d8;
case Compiler.dartdevc:
case Compiler.dartdevk:
return Runtime.chrome;
@@ -742,6 +752,7 @@
case Compiler.dart2analyzer:
case Compiler.compareAnalyzerCfe:
case Compiler.dart2js:
+ case Compiler.dart2wasm:
case Compiler.dartdevc:
case Compiler.dartdevk:
case Compiler.fasta:
diff --git a/pkg/test_runner/lib/src/command.dart b/pkg/test_runner/lib/src/command.dart
index 45fa5d2..7ba599f 100644
--- a/pkg/test_runner/lib/src/command.dart
+++ b/pkg/test_runner/lib/src/command.dart
@@ -186,6 +186,9 @@
if (displayName == 'precompiler' || displayName == 'app_jit') {
return VMCommandOutput(
this, exitCode, timedOut, stdout, stderr, time, pid);
+ } else if (displayName == 'dart2wasm') {
+ return Dart2WasmCompilerCommandOutput(
+ this, exitCode, timedOut, stdout, stderr, time, compilationSkipped);
}
return CompilationCommandOutput(
diff --git a/pkg/test_runner/lib/src/command_output.dart b/pkg/test_runner/lib/src/command_output.dart
index cc65097..03db24c 100644
--- a/pkg/test_runner/lib/src/command_output.dart
+++ b/pkg/test_runner/lib/src/command_output.dart
@@ -1060,6 +1060,45 @@
}
}
+class Dart2WasmCompilerCommandOutput extends CompilationCommandOutput
+ with _StaticErrorOutput {
+ static void parseErrors(String stdout, List<StaticError> errors) {
+ _StaticErrorOutput._parseCfeErrors(
+ ErrorSource.web, _errorRegexp, stdout, errors);
+ }
+
+ /// Matches the location and message of a dart2wasm error message, which looks
+ /// like:
+ ///
+ /// tests/language_2/some_test.dart:9:3: Error: Some message.
+ /// BadThing();
+ /// ^
+ ///
+ /// The test runner only validates the main error message, and not the
+ /// suggested fixes, so we only parse the first line.
+ // TODO(rnystrom): Support validating context messages.
+ static final _errorRegexp =
+ RegExp(r"^([^:]+):(\d+):(\d+): (Error): (.*)$", multiLine: true);
+
+ Dart2WasmCompilerCommandOutput(
+ Command command,
+ int exitCode,
+ bool timedOut,
+ List<int> stdout,
+ List<int> stderr,
+ Duration time,
+ bool compilationSkipped)
+ : super(command, exitCode, timedOut, stdout, stderr, time,
+ compilationSkipped);
+
+ @override
+ void _parseErrors() {
+ var errors = <StaticError>[];
+ parseErrors(decodeUtf8(stdout), errors);
+ errors.forEach(addError);
+ }
+}
+
class DevCompilerCommandOutput extends CommandOutput with _StaticErrorOutput {
/// Matches the first line of a DDC error message. DDC prints errors to
/// stdout that look like:
diff --git a/pkg/test_runner/lib/src/compiler_configuration.dart b/pkg/test_runner/lib/src/compiler_configuration.dart
index 6d72e22..b287bcc 100644
--- a/pkg/test_runner/lib/src/compiler_configuration.dart
+++ b/pkg/test_runner/lib/src/compiler_configuration.dart
@@ -86,6 +86,9 @@
case Compiler.dart2js:
return Dart2jsCompilerConfiguration(configuration);
+ case Compiler.dart2wasm:
+ return Dart2WasmCompilerConfiguration(configuration);
+
case Compiler.dartdevc:
return DevCompilerConfiguration(configuration);
@@ -499,6 +502,83 @@
}
}
+/// Common configuration for dart2wasm-based tools, such as dart2wasm.
+class Dart2WasmCompilerConfiguration extends CompilerConfiguration {
+ Dart2WasmCompilerConfiguration(TestConfiguration configuration)
+ : super._subclass(configuration);
+
+ String computeCompilerPath() {
+ var prefix = 'sdk/bin';
+ if (_isHostChecked) {
+ if (_useSdk) {
+ throw "--host-checked and --use-sdk cannot be used together";
+ }
+ // The script dart2wasm_developer is not included in the
+ // shipped SDK, that is the script is not installed in
+ // "$buildDir/dart-sdk/bin/"
+ return '$prefix/dart2wasm_developer$shellScriptExtension';
+ }
+ if (_useSdk) {
+ prefix = '${_configuration.buildDirectory}/dart-sdk/bin';
+ }
+ return '$prefix/dart2wasm$shellScriptExtension';
+ }
+
+ List<String> computeCompilerArguments(
+ TestFile testFile, List<String> vmOptions, List<String> args) {
+ return [
+ // The file being compiled is the last argument.
+ args.last
+ ];
+ }
+
+ Command computeCompilationCommand(String outputFileName,
+ List<String> arguments, Map<String, String> environmentOverrides) {
+ arguments = arguments.toList();
+ arguments.add('$outputFileName');
+
+ return CompilationCommand(
+ 'dart2wasm',
+ outputFileName,
+ bootstrapDependencies(),
+ computeCompilerPath(),
+ arguments,
+ environmentOverrides,
+ alwaysCompile: !_useSdk);
+ }
+
+ CommandArtifact computeCompilationArtifact(String tempDir,
+ List<String> arguments, Map<String, String> environmentOverrides) {
+ var compilerArguments = [
+ ...arguments,
+ ];
+
+ var inputFile = arguments.last;
+ var inputFilename = Uri.file(inputFile).pathSegments.last;
+ var out = "$tempDir/${inputFilename.replaceAll('.dart', '.wasm')}";
+ var commands = [
+ computeCompilationCommand(out, compilerArguments, environmentOverrides),
+ ];
+
+ return CommandArtifact(commands, out, 'application/wasm');
+ }
+
+ List<String> computeRuntimeArguments(
+ RuntimeConfiguration runtimeConfiguration,
+ TestFile testFile,
+ List<String> vmOptions,
+ List<String> originalArguments,
+ CommandArtifact artifact) {
+ return [
+ '--experimental-wasm-gc',
+ '--wasm-gc-js-interop',
+ 'pkg/dart2wasm/bin/run_wasm.js',
+ '--',
+ artifact.filename,
+ ];
+ }
+}
+
/// Configuration for `dartdevc` and `dartdevk` (DDC with Kernel)
class DevCompilerConfiguration extends CompilerConfiguration {
DevCompilerConfiguration(TestConfiguration configuration)
diff --git a/pkg/test_runner/lib/src/configuration.dart b/pkg/test_runner/lib/src/configuration.dart
index 198c01a..d98b8b8 100644
--- a/pkg/test_runner/lib/src/configuration.dart
+++ b/pkg/test_runner/lib/src/configuration.dart
@@ -203,6 +203,7 @@
Compiler.dartkp,
Compiler.fasta,
Compiler.dart2js,
+ Compiler.dart2wasm,
];
return fastaCompilers.contains(compiler);
}
diff --git a/pkg/test_runner/lib/src/runtime_configuration.dart b/pkg/test_runner/lib/src/runtime_configuration.dart
index 2341361..4cba518 100644
--- a/pkg/test_runner/lib/src/runtime_configuration.dart
+++ b/pkg/test_runner/lib/src/runtime_configuration.dart
@@ -168,7 +168,7 @@
void checkArtifact(CommandArtifact artifact) {
var type = artifact.mimeType;
- if (type != 'application/javascript') {
+ if (type != 'application/javascript' && type != 'application/wasm') {
throw "Runtime '$moniker' cannot run files of type '$type'.";
}
}
diff --git a/pkg/test_runner/lib/src/test_suite.dart b/pkg/test_runner/lib/src/test_suite.dart
index 5aeaf54..faf6d57 100644
--- a/pkg/test_runner/lib/src/test_suite.dart
+++ b/pkg/test_runner/lib/src/test_suite.dart
@@ -597,6 +597,7 @@
'$directory/${name}_analyzer.status',
'$directory/${name}_analyzer2.status',
'$directory/${name}_dart2js.status',
+ '$directory/${name}_dart2wasm.status',
'$directory/${name}_dartdevc.status',
'$directory/${name}_kernel.status',
'$directory/${name}_precompiled.status',
@@ -956,6 +957,7 @@
var commands = <Command>[];
const supportedCompilers = {
Compiler.dart2js,
+ Compiler.dart2wasm,
Compiler.dartdevc,
Compiler.dartdevk
};
diff --git a/pkg/wasm_builder/LICENSE b/pkg/wasm_builder/LICENSE
new file mode 100644
index 0000000..9566a27
--- /dev/null
+++ b/pkg/wasm_builder/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2022, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pkg/wasm_builder/lib/src/instructions.dart b/pkg/wasm_builder/lib/src/instructions.dart
new file mode 100644
index 0000000..24a9556
--- /dev/null
+++ b/pkg/wasm_builder/lib/src/instructions.dart
@@ -0,0 +1,2283 @@
+// Copyright (c) 2022, 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 'module.dart';
+import 'serialize.dart';
+import 'types.dart';
+
+/// Thrown when Wasm bytecode validation fails.
+class ValidationError {
+ final String trace;
+ final String error;
+
+ ValidationError(this.trace, this.error);
+
+ @override
+ String toString() => "$trace\n$error";
+}
+
+/// Label to use as target for branch instructions.
+abstract class Label {
+ final List<ValueType> inputs;
+ final List<ValueType> outputs;
+
+ late final int? ordinal;
+ late final int depth;
+ late final int baseStackHeight;
+ late final bool reachable;
+
+ Label._(this.inputs, this.outputs);
+
+ List<ValueType> get targetTypes;
+
+ bool get hasOrdinal => ordinal != null;
+
+ @override
+ String toString() => "L$ordinal";
+}
+
+class Expression extends Label {
+ Expression(List<ValueType> inputs, List<ValueType> outputs)
+ : super._(inputs, outputs) {
+ ordinal = null;
+ depth = 0;
+ baseStackHeight = 0;
+ reachable = true;
+ }
+
+ List<ValueType> get targetTypes => outputs;
+}
+
+class Block extends Label {
+ Block(List<ValueType> inputs, List<ValueType> outputs)
+ : super._(inputs, outputs);
+
+ List<ValueType> get targetTypes => outputs;
+}
+
+class Loop extends Label {
+ Loop(List<ValueType> inputs, List<ValueType> outputs)
+ : super._(inputs, outputs);
+
+ List<ValueType> get targetTypes => inputs;
+}
+
+class If extends Label {
+ bool hasElse = false;
+
+ If(List<ValueType> inputs, List<ValueType> outputs)
+ : super._(inputs, outputs);
+
+ List<ValueType> get targetTypes => outputs;
+}
+
+/// A sequence of Wasm instructions.
+///
+/// Instructions can be added to the sequence by calling the corresponding
+/// instruction methods.
+///
+/// If asserts are enabled, the instruction methods will perform on-the-fly
+/// validation and throw a [ValidationError] if validation fails.
+class Instructions with SerializerMixin {
+ /// The module containing these instructions.
+ final Module module;
+
+ /// Locals declared in this body, including parameters.
+ final List<Local> locals;
+
+ /// Is this the initializer of a global variable?
+ final bool isGlobalInitializer;
+
+ /// Whether a textual trace of the instruction stream should be recorded when
+ /// emitting instructions (provided asserts are enabled).
+ ///
+ /// This trace can be accessed via the [trace] property and will be part of
+ /// the exception text if a validation error occurs.
+ bool traceEnabled = true;
+
+ /// Whether to print a byte offset for each instruction in the textual trace.
+ bool byteOffsetEnabled = false;
+
+ /// Column width for the instruction byte offset.
+ int byteOffsetWidth = 7;
+
+ /// Column width for the instructions.
+ int instructionColumnWidth = 50;
+
+ int _indent = 1;
+ final List<String> _traceLines = [];
+
+ int _labelCount = 0;
+ final List<Label> _labelStack = [];
+ final List<ValueType> _stackTypes = [];
+ bool _reachable = true;
+
+ /// Create a new instruction sequence.
+ Instructions(this.module, List<ValueType> outputs,
+ {this.locals = const [], this.isGlobalInitializer = false}) {
+ _labelStack.add(Expression(const [], outputs));
+ }
+
+ /// Whether the current point in the instruction stream is reachable.
+ bool get reachable => _reachable;
+
+ /// Whether the instruction sequence has been completed by the final `end`.
+ bool get isComplete => _labelStack.isEmpty;
+
+ /// Textual trace of the instructions.
+ String get trace => _traceLines.join();
+
+ bool _debugTrace(List<Object>? trace,
+ {required bool reachableAfter,
+ int indentBefore = 0,
+ int indentAfter = 0}) {
+ if (traceEnabled && trace != null) {
+ _indent += indentBefore;
+ String byteOffset =
+ byteOffsetEnabled ? "${data.length}".padLeft(byteOffsetWidth) : "";
+ String instr = " " * _indent + " " + trace.join(" ");
+ instr = instr.length > instructionColumnWidth - 2
+ ? instr.substring(0, instructionColumnWidth - 4) + "... "
+ : instr.padRight(instructionColumnWidth);
+ final String stack = reachableAfter ? _stackTypes.join(', ') : "-";
+ final String line = "$byteOffset$instr$stack\n";
+ _indent += indentAfter;
+
+ _traceLines.add(line);
+ }
+ return true;
+ }
+
+ bool _comment(String text) {
+ if (traceEnabled) {
+ final String line = " " * (byteOffsetEnabled ? byteOffsetWidth : 0) +
+ " " * _indent +
+ " ;; $text\n";
+ _traceLines.add(line);
+ }
+ return true;
+ }
+
+ Never _reportError(String error) {
+ throw ValidationError(trace, error);
+ }
+
+ ValueType get _topOfStack {
+ if (!reachable) return RefType.any();
+ if (_stackTypes.isEmpty) _reportError("Stack underflow");
+ return _stackTypes.last;
+ }
+
+ Label get _topOfLabelStack {
+ if (_labelStack.isEmpty) _reportError("Label stack underflow");
+ return _labelStack.last;
+ }
+
+ List<ValueType> _stack(int n) {
+ if (_stackTypes.length < n) _reportError("Stack underflow");
+ return _stackTypes.sublist(_stackTypes.length - n);
+ }
+
+ List<ValueType> _checkStackTypes(List<ValueType> inputs,
+ [List<ValueType>? stack]) {
+ stack ??= _stack(inputs.length);
+ bool typesMatch = true;
+ for (int i = 0; i < inputs.length; i++) {
+ if (!stack[i].isSubtypeOf(inputs[i])) {
+ typesMatch = false;
+ break;
+ }
+ }
+ if (!typesMatch) {
+ final String expected = inputs.join(', ');
+ final String got = stack.join(', ');
+ _reportError("Expected [$expected], but stack contained [$got]");
+ }
+ return stack;
+ }
+
+ bool _verifyTypes(List<ValueType> inputs, List<ValueType> outputs,
+ {List<Object>? trace, bool reachableAfter = true}) {
+ return _verifyTypesFun(inputs, (_) => outputs,
+ trace: trace, reachableAfter: reachableAfter);
+ }
+
+ bool _verifyTypesFun(List<ValueType> inputs,
+ List<ValueType> Function(List<ValueType>) outputsFun,
+ {List<Object>? trace, bool reachableAfter = true}) {
+ if (!reachable) {
+ return _debugTrace(trace, reachableAfter: false);
+ }
+ if (_stackTypes.length - inputs.length < _topOfLabelStack.baseStackHeight) {
+ _reportError("Underflowing base stack of innermost block");
+ }
+ final List<ValueType> stack = _checkStackTypes(inputs);
+ _stackTypes.length -= inputs.length;
+ _stackTypes.addAll(outputsFun(stack));
+ return _debugTrace(trace, reachableAfter: reachableAfter);
+ }
+
+ bool _verifyBranchTypes(Label label,
+ [int popped = 0, List<ValueType> pushed = const []]) {
+ if (!reachable) {
+ return true;
+ }
+ final List<ValueType> inputs = label.targetTypes;
+ if (_stackTypes.length - popped + pushed.length - inputs.length <
+ label.baseStackHeight) {
+ _reportError("Underflowing base stack of target label");
+ }
+ final List<ValueType> stack = inputs.length <= pushed.length
+ ? pushed.sublist(pushed.length - inputs.length)
+ : [
+ ..._stackTypes.sublist(
+ _stackTypes.length - popped + pushed.length - inputs.length,
+ _stackTypes.length - popped),
+ ...pushed
+ ];
+ _checkStackTypes(inputs, stack);
+ return true;
+ }
+
+ bool _verifyStartOfBlock(Label label, {required List<Object> trace}) {
+ return _debugTrace(
+ ["$label:", ...trace, FunctionType(label.inputs, label.outputs)],
+ reachableAfter: reachable, indentAfter: 1);
+ }
+
+ bool _verifyEndOfBlock(List<ValueType> outputs,
+ {required List<Object> trace,
+ required bool reachableAfter,
+ required bool reindent}) {
+ final Label label = _topOfLabelStack;
+ if (reachable) {
+ final int expectedHeight = label.baseStackHeight + label.outputs.length;
+ if (_stackTypes.length != expectedHeight) {
+ _reportError("Incorrect stack height at end of block"
+ " (expected $expectedHeight, actual ${_stackTypes.length})");
+ }
+ _checkStackTypes(label.outputs);
+ }
+ if (label.reachable) {
+ assert(_stackTypes.length >= label.baseStackHeight);
+ _stackTypes.length = label.baseStackHeight;
+ _stackTypes.addAll(outputs);
+ }
+ return _debugTrace([if (label.hasOrdinal) "$label:", ...trace],
+ reachableAfter: reachableAfter,
+ indentBefore: -1,
+ indentAfter: reindent ? 1 : 0);
+ }
+
+ // Meta
+
+ /// Emit a comment.
+ void comment(String text) {
+ assert(_comment(text));
+ }
+
+ // Control instructions
+
+ /// Emit an `unreachable` instruction.
+ void unreachable() {
+ assert(_verifyTypes(const [], const [],
+ trace: const ['unreachable'], reachableAfter: false));
+ _reachable = false;
+ writeByte(0x00);
+ }
+
+ /// Emit a `nop` instruction.
+ void nop() {
+ assert(_verifyTypes(const [], const [], trace: const ['nop']));
+ writeByte(0x01);
+ }
+
+ Label _beginBlock(int encoding, Label label, {required List<Object> trace}) {
+ assert(_verifyTypes(label.inputs, label.inputs));
+ label.ordinal = ++_labelCount;
+ label.depth = _labelStack.length;
+ label.baseStackHeight = _stackTypes.length - label.inputs.length;
+ label.reachable = reachable;
+ _labelStack.add(label);
+ assert(_verifyStartOfBlock(label, trace: trace));
+ writeByte(encoding);
+ if (label.inputs.isEmpty && label.outputs.isEmpty) {
+ writeByte(0x40);
+ } else if (label.inputs.isEmpty && label.outputs.length == 1) {
+ write(label.outputs.single);
+ } else {
+ final type = module.addFunctionType(label.inputs, label.outputs);
+ writeSigned(type.index);
+ }
+ return label;
+ }
+
+ /// Emit a `block` instruction.
+ /// Branching to the returned label will branch to the matching `end`.
+ Label block(
+ [List<ValueType> inputs = const [], List<ValueType> outputs = const []]) {
+ return _beginBlock(0x02, Block(inputs, outputs), trace: const ['block']);
+ }
+
+ /// Emit a `loop` instruction.
+ /// Branching to the returned label will branch to the `loop`.
+ Label loop(
+ [List<ValueType> inputs = const [], List<ValueType> outputs = const []]) {
+ return _beginBlock(0x03, Loop(inputs, outputs), trace: const ['loop']);
+ }
+
+ /// Emit an `if` instruction.
+ /// Branching to the returned label will branch to the matching `end`.
+ Label if_(
+ [List<ValueType> inputs = const [], List<ValueType> outputs = const []]) {
+ assert(_verifyTypes(const [NumType.i32], const []));
+ return _beginBlock(0x04, If(inputs, outputs), trace: const ['if']);
+ }
+
+ /// Emit an `else` instruction.
+ void else_() {
+ assert(_topOfLabelStack is If ||
+ _reportError("Unexpected 'else' (not in 'if' block)"));
+ final If label = _topOfLabelStack as If;
+ assert(!label.hasElse || _reportError("Duplicate 'else' in 'if' block"));
+ assert(_verifyEndOfBlock(label.inputs,
+ trace: const ['else'],
+ reachableAfter: _topOfLabelStack.reachable,
+ reindent: true));
+ label.hasElse = true;
+ _reachable = _topOfLabelStack.reachable;
+ writeByte(0x05);
+ }
+
+ /// Emit an `end` instruction.
+ void end() {
+ assert(_verifyEndOfBlock(_topOfLabelStack.outputs,
+ trace: const ['end'],
+ reachableAfter: _topOfLabelStack.reachable,
+ reindent: false));
+ _reachable = _topOfLabelStack.reachable;
+ _labelStack.removeLast();
+ writeByte(0x0B);
+ }
+
+ int _labelIndex(Label label) {
+ final int index = _labelStack.length - label.depth - 1;
+ assert(_labelStack[label.depth] == label);
+ return index;
+ }
+
+ void _writeLabel(Label label) {
+ writeUnsigned(_labelIndex(label));
+ }
+
+ /// Emit a `br` instruction.
+ void br(Label label) {
+ assert(_verifyTypes(const [], const [],
+ trace: ['br', label], reachableAfter: false));
+ assert(_verifyBranchTypes(label));
+ _reachable = false;
+ writeByte(0x0C);
+ _writeLabel(label);
+ }
+
+ /// Emit a `br_if` instruction.
+ void br_if(Label label) {
+ assert(
+ _verifyTypes(const [NumType.i32], const [], trace: ['br_if', label]));
+ assert(_verifyBranchTypes(label));
+ writeByte(0x0D);
+ _writeLabel(label);
+ }
+
+ /// Emit a `br_table` instruction.
+ void br_table(List<Label> labels, Label defaultLabel) {
+ assert(_verifyTypes(const [NumType.i32], const [],
+ trace: ['br_table', ...labels, defaultLabel], reachableAfter: false));
+ for (var label in labels) {
+ assert(_verifyBranchTypes(label));
+ }
+ assert(_verifyBranchTypes(defaultLabel));
+ _reachable = false;
+ writeByte(0x0E);
+ writeUnsigned(labels.length);
+ for (Label label in labels) {
+ _writeLabel(label);
+ }
+ _writeLabel(defaultLabel);
+ }
+
+ /// Emit a `return` instruction.
+ void return_() {
+ assert(_verifyTypes(_labelStack[0].outputs, const [],
+ trace: const ['return'], reachableAfter: false));
+ _reachable = false;
+ writeByte(0x0F);
+ }
+
+ /// Emit a `call` instruction.
+ void call(BaseFunction function) {
+ assert(_verifyTypes(function.type.inputs, function.type.outputs,
+ trace: ['call', function]));
+ writeByte(0x10);
+ writeUnsigned(function.index);
+ }
+
+ /// Emit a `call_indirect` instruction.
+ void call_indirect(FunctionType type, [Table? table]) {
+ assert(_verifyTypes([...type.inputs, NumType.i32], type.outputs,
+ trace: ['call_indirect', type, if (table != null) table.index]));
+ writeByte(0x11);
+ writeUnsigned(type.index);
+ writeUnsigned(table?.index ?? 0);
+ }
+
+ bool _verifyCallRef() {
+ if (!reachable) {
+ return _debugTrace(const ['call_ref'], reachableAfter: false);
+ }
+ ValueType fun = _topOfStack;
+ if (fun is RefType) {
+ var heapType = fun.heapType;
+ if (heapType is FunctionType) {
+ return _verifyTypes([...heapType.inputs, fun], heapType.outputs,
+ trace: const ['call_ref']);
+ }
+ }
+ _reportError("Expected function type, got $fun");
+ }
+
+ /// Emit a `call_ref` instruction.
+ void call_ref() {
+ assert(_verifyCallRef());
+ writeByte(0x14);
+ }
+
+ // Parametric instructions
+
+ /// Emit a `drop` instruction.
+ void drop() {
+ assert(_verifyTypes([_topOfStack], const [], trace: const ['drop']));
+ writeByte(0x1A);
+ }
+
+ /// Emit a `select` instruction.
+ void select(ValueType type) {
+ assert(_verifyTypes([type, type, NumType.i32], [type],
+ trace: ['select', type]));
+ if (type is NumType) {
+ writeByte(0x1B);
+ } else {
+ writeByte(0x1C);
+ writeUnsigned(1);
+ write(type);
+ }
+ }
+
+ // Variable instructions
+
+ /// Emit a `local.get` instruction.
+ void local_get(Local local) {
+ assert(locals[local.index] == local);
+ assert(_verifyTypes(const [], [local.type], trace: ['local.get', local]));
+ writeByte(0x20);
+ writeUnsigned(local.index);
+ }
+
+ /// Emit a `local.set` instruction.
+ void local_set(Local local) {
+ assert(locals[local.index] == local);
+ assert(_verifyTypes([local.type], const [], trace: ['local.set', local]));
+ writeByte(0x21);
+ writeUnsigned(local.index);
+ }
+
+ /// Emit a `local.tee` instruction.
+ void local_tee(Local local) {
+ assert(locals[local.index] == local);
+ assert(
+ _verifyTypes([local.type], [local.type], trace: ['local.tee', local]));
+ writeByte(0x22);
+ writeUnsigned(local.index);
+ }
+
+ /// Emit a `global.get` instruction.
+ void global_get(Global global) {
+ assert(_verifyTypes(const [], [global.type.type],
+ trace: ['global.get', global]));
+ writeByte(0x23);
+ writeUnsigned(global.index);
+ }
+
+ /// Emit a `global.set` instruction.
+ void global_set(Global global) {
+ assert(global.type.mutable);
+ assert(_verifyTypes([global.type.type], const [],
+ trace: ['global.set', global]));
+ writeByte(0x24);
+ writeUnsigned(global.index);
+ }
+
+ // Memory instructions
+
+ void _writeMemArg(Memory memory, int offset, int align) {
+ assert(align >= 0 && align < 64);
+ if (memory.index == 0) {
+ writeByte(align);
+ writeUnsigned(offset);
+ } else {
+ writeByte(64 + align);
+ writeUnsigned(offset);
+ writeUnsigned(memory.index);
+ }
+ }
+
+ /// Emit an `i32.load` instruction.
+ void i32_load(Memory memory, int offset, [int align = 2]) {
+ assert(align >= 0 && align <= 2);
+ assert(_verifyTypes(const [NumType.i32], const [NumType.i32],
+ trace: ['i32.load', memory.index, offset, align]));
+ writeByte(0x28);
+ _writeMemArg(memory, offset, align);
+ }
+
+ /// Emit an `i64.load` instruction.
+ void i64_load(Memory memory, int offset, [int align = 3]) {
+ assert(align >= 0 && align <= 3);
+ assert(_verifyTypes(const [NumType.i32], const [NumType.i64],
+ trace: ['i64.load', memory.index, offset, align]));
+ writeByte(0x29);
+ _writeMemArg(memory, offset, align);
+ }
+
+ /// Emit an `f32.load` instruction.
+ void f32_load(Memory memory, int offset, [int align = 2]) {
+ assert(align >= 0 && align <= 2);
+ assert(_verifyTypes(const [NumType.i32], const [NumType.f32],
+ trace: ['f32.load', memory.index, offset, align]));
+ writeByte(0x2A);
+ _writeMemArg(memory, offset, align);
+ }
+
+ /// Emit an `f64.load` instruction.
+ void f64_load(Memory memory, int offset, [int align = 3]) {
+ assert(align >= 0 && align <= 3);
+ assert(_verifyTypes(const [NumType.i32], const [NumType.f64],
+ trace: ['f64.load', memory.index, offset, align]));
+ writeByte(0x2B);
+ _writeMemArg(memory, offset, align);
+ }
+
+ /// Emit an `i32.load8_s` instruction.
+ void i32_load8_s(Memory memory, int offset, [int align = 0]) {
+ assert(align == 0);
+ assert(_verifyTypes(const [NumType.i32], const [NumType.i32],
+ trace: ['i32.load8_s', memory.index, offset, align]));
+ writeByte(0x2C);
+ _writeMemArg(memory, offset, align);
+ }
+
+ /// Emit an `i32.load8_u` instruction.
+ void i32_load8_u(Memory memory, int offset, [int align = 0]) {
+ assert(align == 0);
+ assert(_verifyTypes(const [NumType.i32], const [NumType.i32],
+ trace: ['i32.load8_u', memory.index, offset, align]));
+ writeByte(0x2D);
+ _writeMemArg(memory, offset, align);
+ }
+
+ /// Emit an `i32.load16_s` instruction.
+ void i32_load16_s(Memory memory, int offset, [int align = 1]) {
+ assert(align >= 0 && align <= 1);
+ assert(_verifyTypes(const [NumType.i32], const [NumType.i32],
+ trace: ['i32.load16_s', memory.index, offset, align]));
+ writeByte(0x2E);
+ _writeMemArg(memory, offset, align);
+ }
+
+ /// Emit an `i32.load16_u` instruction.
+ void i32_load16_u(Memory memory, int offset, [int align = 1]) {
+ assert(align >= 0 && align <= 1);
+ assert(_verifyTypes(const [NumType.i32], const [NumType.i32],
+ trace: ['i32.load16_u', memory.index, offset, align]));
+ writeByte(0x2F);
+ _writeMemArg(memory, offset, align);
+ }
+
+ /// Emit an `i64.load8_s` instruction.
+ void i64_load8_s(Memory memory, int offset, [int align = 0]) {
+ assert(align == 0);
+ assert(_verifyTypes(const [NumType.i32], const [NumType.i64],
+ trace: ['i64.load8_s', memory.index, offset, align]));
+ writeByte(0x30);
+ _writeMemArg(memory, offset, align);
+ }
+
+ /// Emit an `i64.load8_u` instruction.
+ void i64_load8_u(Memory memory, int offset, [int align = 0]) {
+ assert(align == 0);
+ assert(_verifyTypes(const [NumType.i32], const [NumType.i64],
+ trace: ['i64.load8_u', memory.index, offset, align]));
+ writeByte(0x31);
+ _writeMemArg(memory, offset, align);
+ }
+
+ /// Emit an `i64.load16_s` instruction.
+ void i64_load16_s(Memory memory, int offset, [int align = 1]) {
+ assert(align >= 0 && align <= 1);
+ assert(_verifyTypes(const [NumType.i32], const [NumType.i64],
+ trace: ['i64.load16_s', memory.index, offset, align]));
+ writeByte(0x32);
+ _writeMemArg(memory, offset, align);
+ }
+
+ /// Emit an `i64.load16_u` instruction.
+ void i64_load16_u(Memory memory, int offset, [int align = 1]) {
+ assert(align >= 0 && align <= 1);
+ assert(_verifyTypes(const [NumType.i32], const [NumType.i64],
+ trace: ['i64.load16_u', memory.index, offset, align]));
+ writeByte(0x33);
+ _writeMemArg(memory, offset, align);
+ }
+
+ /// Emit an `i64.load32_s` instruction.
+ void i64_load32_s(Memory memory, int offset, [int align = 2]) {
+ assert(align >= 0 && align <= 2);
+ assert(_verifyTypes(const [NumType.i32], const [NumType.i64],
+ trace: ['i64.load32_s', memory.index, offset, align]));
+ writeByte(0x34);
+ _writeMemArg(memory, offset, align);
+ }
+
+ /// Emit an `i64.load32_u` instruction.
+ void i64_load32_u(Memory memory, int offset, [int align = 2]) {
+ assert(align >= 0 && align <= 2);
+ assert(_verifyTypes(const [NumType.i32], const [NumType.i64],
+ trace: ['i64.load32_u', memory.index, offset, align]));
+ writeByte(0x35);
+ _writeMemArg(memory, offset, align);
+ }
+
+ /// Emit an `i32.store` instruction.
+ void i32_store(Memory memory, int offset, [int align = 2]) {
+ assert(align >= 0 && align <= 2);
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [],
+ trace: ['i32.store', memory.index, offset, align]));
+ writeByte(0x36);
+ _writeMemArg(memory, offset, align);
+ }
+
+ /// Emit an `i64.store` instruction.
+ void i64_store(Memory memory, int offset, [int align = 3]) {
+ assert(align >= 0 && align <= 3);
+ assert(_verifyTypes(const [NumType.i32, NumType.i64], const [],
+ trace: ['i64.store', memory.index, offset, align]));
+ writeByte(0x37);
+ _writeMemArg(memory, offset, align);
+ }
+
+ /// Emit an `f32.store` instruction.
+ void f32_store(Memory memory, int offset, [int align = 2]) {
+ assert(align >= 0 && align <= 2);
+ assert(_verifyTypes(const [NumType.i32, NumType.f32], const [],
+ trace: ['f32.store', memory.index, offset, align]));
+ writeByte(0x38);
+ _writeMemArg(memory, offset, align);
+ }
+
+ /// Emit an `f64.store` instruction.
+ void f64_store(Memory memory, int offset, [int align = 3]) {
+ assert(align >= 0 && align <= 3);
+ assert(_verifyTypes(const [NumType.i32, NumType.f64], const [],
+ trace: ['f64.store', memory.index, offset, align]));
+ writeByte(0x39);
+ _writeMemArg(memory, offset, align);
+ }
+
+ /// Emit an `i32.store8` instruction.
+ void i32_store8(Memory memory, int offset, [int align = 0]) {
+ assert(align == 0);
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [],
+ trace: ['i32.store8', memory.index, offset, align]));
+ writeByte(0x3A);
+ _writeMemArg(memory, offset, align);
+ }
+
+ /// Emit an `i32.store16` instruction.
+ void i32_store16(Memory memory, int offset, [int align = 1]) {
+ assert(align >= 0 && align <= 1);
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [],
+ trace: ['i32.store16', memory.index, offset, align]));
+ writeByte(0x3B);
+ _writeMemArg(memory, offset, align);
+ }
+
+ /// Emit an `i64.store8` instruction.
+ void i64_store8(Memory memory, int offset, [int align = 0]) {
+ assert(align == 0);
+ assert(_verifyTypes(const [NumType.i32, NumType.i64], const [],
+ trace: ['i64.store8', memory.index, offset, align]));
+ writeByte(0x3C);
+ _writeMemArg(memory, offset, align);
+ }
+
+ /// Emit an `i64.store16` instruction.
+ void i64_store16(Memory memory, int offset, [int align = 1]) {
+ assert(align >= 0 && align <= 1);
+ assert(_verifyTypes(const [NumType.i32, NumType.i64], const [],
+ trace: ['i64.store16', memory.index, offset, align]));
+ writeByte(0x3D);
+ _writeMemArg(memory, offset, align);
+ }
+
+ /// Emit an `i64.store32` instruction.
+ void i64_store32(Memory memory, int offset, [int align = 2]) {
+ assert(align >= 0 && align <= 2);
+ assert(_verifyTypes(const [NumType.i32, NumType.i64], const [],
+ trace: ['i64.store32', memory.index, offset, align]));
+ writeByte(0x3E);
+ _writeMemArg(memory, offset, align);
+ }
+
+ /// Emit a `memory.size` instruction.
+ void memory_size(Memory memory) {
+ assert(_verifyTypes(const [], const [NumType.i32]));
+ writeByte(0x3F);
+ writeUnsigned(memory.index);
+ }
+
+ /// Emit a `memory.grow` instruction.
+ void memory_grow(Memory memory) {
+ assert(_verifyTypes(const [NumType.i32], const [NumType.i32]));
+ writeByte(0x40);
+ writeUnsigned(memory.index);
+ }
+
+ // Reference instructions
+
+ /// Emit a `ref.null` instruction.
+ void ref_null(HeapType heapType) {
+ assert(_verifyTypes(const [], [RefType(heapType, nullable: true)],
+ trace: ['ref.null', heapType]));
+ writeByte(0xD0);
+ write(heapType);
+ }
+
+ /// Emit a `ref.is_null` instruction.
+ void ref_is_null() {
+ assert(_verifyTypes(const [RefType.any()], const [NumType.i32],
+ trace: const ['ref.is_null']));
+ writeByte(0xD1);
+ }
+
+ /// Emit a `ref.func` instruction.
+ void ref_func(BaseFunction function) {
+ assert(_verifyTypes(const [], [RefType.def(function.type, nullable: false)],
+ trace: ['ref.func', function]));
+ writeByte(0xD2);
+ writeUnsigned(function.index);
+ }
+
+ /// Emit a `ref.as_non_null` instruction.
+ void ref_as_non_null() {
+ assert(_verifyTypes(
+ const [RefType.any()], [_topOfStack.withNullability(false)],
+ trace: const ['ref.as_non_null']));
+ writeByte(0xD3);
+ }
+
+ /// Emit a `br_on_null` instruction.
+ void br_on_null(Label label) {
+ assert(_verifyTypes(
+ const [RefType.any()], [_topOfStack.withNullability(false)],
+ trace: ['br_on_null', label]));
+ assert(_verifyBranchTypes(label, 1));
+ writeByte(0xD4);
+ _writeLabel(label);
+ }
+
+ /// Emit a `ref.eq` instruction.
+ void ref_eq() {
+ assert(_verifyTypes(const [RefType.eq(), RefType.eq()], const [NumType.i32],
+ trace: const ['ref.eq']));
+ writeByte(0xD5);
+ }
+
+ /// Emit a `br_on_non_null` instruction.
+ void br_on_non_null(Label label) {
+ assert(_verifyBranchTypes(label, 1, [_topOfStack.withNullability(false)]));
+ assert(_verifyTypes(const [RefType.any()], const [],
+ trace: ['br_on_non_null', label]));
+ writeByte(0xD6);
+ _writeLabel(label);
+ }
+
+ /// Emit a `struct.new_with_rtt` instruction.
+ void struct_new_with_rtt(StructType structType) {
+ assert(_verifyTypes(
+ [...structType.fields.map((f) => f.type.unpacked), Rtt(structType)],
+ [RefType.def(structType, nullable: false)],
+ trace: ['struct.new_with_rtt', structType]));
+ writeBytes(const [0xFB, 0x01]);
+ writeUnsigned(structType.index);
+ }
+
+ /// Emit a `struct.new_default_with_rtt` instruction.
+ void struct_new_default_with_rtt(StructType structType) {
+ assert(_verifyTypes(
+ [Rtt(structType)], [RefType.def(structType, nullable: false)],
+ trace: ['struct.new_default_with_rtt', structType]));
+ writeBytes(const [0xFB, 0x02]);
+ writeUnsigned(structType.index);
+ }
+
+ /// Emit a `struct.get` instruction.
+ void struct_get(StructType structType, int fieldIndex) {
+ assert(structType.fields[fieldIndex].type is ValueType);
+ assert(_verifyTypes([RefType.def(structType, nullable: true)],
+ [structType.fields[fieldIndex].type.unpacked],
+ trace: ['struct.get', structType, fieldIndex]));
+ writeBytes(const [0xFB, 0x03]);
+ writeUnsigned(structType.index);
+ writeUnsigned(fieldIndex);
+ }
+
+ /// Emit a `struct.get_s` instruction.
+ void struct_get_s(StructType structType, int fieldIndex) {
+ assert(structType.fields[fieldIndex].type is PackedType);
+ assert(_verifyTypes([RefType.def(structType, nullable: true)],
+ [structType.fields[fieldIndex].type.unpacked],
+ trace: ['struct.get_s', structType, fieldIndex]));
+ writeBytes(const [0xFB, 0x04]);
+ writeUnsigned(structType.index);
+ writeUnsigned(fieldIndex);
+ }
+
+ /// Emit a `struct.get_u` instruction.
+ void struct_get_u(StructType structType, int fieldIndex) {
+ assert(structType.fields[fieldIndex].type is PackedType);
+ assert(_verifyTypes([RefType.def(structType, nullable: true)],
+ [structType.fields[fieldIndex].type.unpacked],
+ trace: ['struct.get_u', structType, fieldIndex]));
+ writeBytes(const [0xFB, 0x05]);
+ writeUnsigned(structType.index);
+ writeUnsigned(fieldIndex);
+ }
+
+ /// Emit a `struct.set` instruction.
+ void struct_set(StructType structType, int fieldIndex) {
+ assert(_verifyTypes([
+ RefType.def(structType, nullable: true),
+ structType.fields[fieldIndex].type.unpacked
+ ], const [], trace: [
+ 'struct.set',
+ structType,
+ fieldIndex
+ ]));
+ writeBytes(const [0xFB, 0x06]);
+ writeUnsigned(structType.index);
+ writeUnsigned(fieldIndex);
+ }
+
+ /// Emit a `struct.new` instruction.
+ void struct_new(StructType structType) {
+ assert(_verifyTypes([...structType.fields.map((f) => f.type.unpacked)],
+ [RefType.def(structType, nullable: false)],
+ trace: ['struct.new', structType]));
+ writeBytes(const [0xFB, 0x07]);
+ writeUnsigned(structType.index);
+ }
+
+ /// Emit a `struct.new_default` instruction.
+ void struct_new_default(StructType structType) {
+ assert(_verifyTypes(const [], [RefType.def(structType, nullable: false)],
+ trace: ['struct.new_default', structType]));
+ writeBytes(const [0xFB, 0x08]);
+ writeUnsigned(structType.index);
+ }
+
+ /// Emit an `array.new_with_rtt` instruction.
+ void array_new_with_rtt(ArrayType arrayType) {
+ assert(_verifyTypes(
+ [arrayType.elementType.type.unpacked, NumType.i32, Rtt(arrayType)],
+ [RefType.def(arrayType, nullable: false)],
+ trace: ['array.new_with_rtt', arrayType]));
+ writeBytes(const [0xFB, 0x11]);
+ writeUnsigned(arrayType.index);
+ }
+
+ /// Emit an `array.new_default_with_rtt` instruction.
+ void array_new_default_with_rtt(ArrayType arrayType) {
+ assert(_verifyTypes([NumType.i32, Rtt(arrayType)],
+ [RefType.def(arrayType, nullable: false)],
+ trace: ['array.new_default_with_rtt', arrayType]));
+ writeBytes(const [0xFB, 0x12]);
+ writeUnsigned(arrayType.index);
+ }
+
+ /// Emit an `array.get` instruction.
+ void array_get(ArrayType arrayType) {
+ assert(arrayType.elementType.type is ValueType);
+ assert(_verifyTypes([RefType.def(arrayType, nullable: true), NumType.i32],
+ [arrayType.elementType.type.unpacked],
+ trace: ['array.get', arrayType]));
+ writeBytes(const [0xFB, 0x13]);
+ writeUnsigned(arrayType.index);
+ }
+
+ /// Emit an `array.get_s` instruction.
+ void array_get_s(ArrayType arrayType) {
+ assert(arrayType.elementType.type is PackedType);
+ assert(_verifyTypes([RefType.def(arrayType, nullable: true), NumType.i32],
+ [arrayType.elementType.type.unpacked],
+ trace: ['array.get_s', arrayType]));
+ writeBytes(const [0xFB, 0x14]);
+ writeUnsigned(arrayType.index);
+ }
+
+ /// Emit an `array.get_u` instruction.
+ void array_get_u(ArrayType arrayType) {
+ assert(arrayType.elementType.type is PackedType);
+ assert(_verifyTypes([RefType.def(arrayType, nullable: true), NumType.i32],
+ [arrayType.elementType.type.unpacked],
+ trace: ['array.get_u', arrayType]));
+ writeBytes(const [0xFB, 0x15]);
+ writeUnsigned(arrayType.index);
+ }
+
+ /// Emit an `array.set` instruction.
+ void array_set(ArrayType arrayType) {
+ assert(_verifyTypes([
+ RefType.def(arrayType, nullable: true),
+ NumType.i32,
+ arrayType.elementType.type.unpacked
+ ], const [], trace: [
+ 'array.set',
+ arrayType
+ ]));
+ writeBytes(const [0xFB, 0x16]);
+ writeUnsigned(arrayType.index);
+ }
+
+ /// Emit an `array.len` instruction.
+ void array_len(ArrayType arrayType) {
+ assert(_verifyTypes(
+ [RefType.def(arrayType, nullable: true)], const [NumType.i32],
+ trace: ['array.len', arrayType]));
+ writeBytes(const [0xFB, 0x17]);
+ writeUnsigned(arrayType.index);
+ }
+
+ /// Emit an `array.init` instruction.
+ void array_init(ArrayType arrayType, int length) {
+ ValueType elementType = arrayType.elementType.type.unpacked;
+ assert(_verifyTypes([...List.filled(length, elementType), Rtt(arrayType)],
+ [RefType.def(arrayType, nullable: false)],
+ trace: ['array.init', arrayType, length]));
+ writeBytes(const [0xFB, 0x19]);
+ writeUnsigned(arrayType.index);
+ writeUnsigned(length);
+ }
+
+ /// Emit an `array.init_static` instruction.
+ void array_init_static(ArrayType arrayType, int length) {
+ ValueType elementType = arrayType.elementType.type.unpacked;
+ assert(_verifyTypes([...List.filled(length, elementType)],
+ [RefType.def(arrayType, nullable: false)],
+ trace: ['array.init_static', arrayType, length]));
+ writeBytes(const [0xFB, 0x1a]);
+ writeUnsigned(arrayType.index);
+ writeUnsigned(length);
+ }
+
+ /// Emit an `array.new` instruction.
+ void array_new(ArrayType arrayType) {
+ assert(_verifyTypes([arrayType.elementType.type.unpacked, NumType.i32],
+ [RefType.def(arrayType, nullable: false)],
+ trace: ['array.new', arrayType]));
+ writeBytes(const [0xFB, 0x1b]);
+ writeUnsigned(arrayType.index);
+ }
+
+ /// Emit an `array.new_default` instruction.
+ void array_new_default(ArrayType arrayType) {
+ assert(_verifyTypes(
+ [NumType.i32], [RefType.def(arrayType, nullable: false)],
+ trace: ['array.new_default', arrayType]));
+ writeBytes(const [0xFB, 0x1c]);
+ writeUnsigned(arrayType.index);
+ }
+
+ /// Emit an `array.init_from_data_static` instruction.
+ void array_init_from_data_static(ArrayType arrayType, DataSegment data) {
+ assert(arrayType.elementType.type.isPrimitive);
+ assert(_verifyTypes(
+ [NumType.i32, NumType.i32], [RefType.def(arrayType, nullable: false)],
+ trace: ['array.init_from_data_static', arrayType, data.index]));
+ writeBytes(const [0xFB, 0x1d]);
+ writeUnsigned(arrayType.index);
+ writeUnsigned(data.index);
+ if (isGlobalInitializer) module.dataReferencedFromGlobalInitializer = true;
+ }
+
+ /// Emit an `array.init_from_data` instruction.
+ void array_init_from_data(ArrayType arrayType, DataSegment data) {
+ assert(arrayType.elementType.type.isPrimitive);
+ assert(_verifyTypes([NumType.i32, NumType.i32, Rtt(arrayType)],
+ [RefType.def(arrayType, nullable: false)],
+ trace: ['array.init_from_data', arrayType, data.index]));
+ writeBytes(const [0xFB, 0x1e]);
+ writeUnsigned(arrayType.index);
+ writeUnsigned(data.index);
+ if (isGlobalInitializer) module.dataReferencedFromGlobalInitializer = true;
+ }
+
+ /// Emit an `i31.new` instruction.
+ void i31_new() {
+ assert(_verifyTypes(const [NumType.i32], const [RefType.i31()],
+ trace: const ['i31.new']));
+ writeBytes(const [0xFB, 0x20]);
+ }
+
+ /// Emit an `i31.get_s` instruction.
+ void i31_get_s() {
+ assert(_verifyTypes(const [RefType.i31()], const [NumType.i32],
+ trace: const ['i31.get_s']));
+ writeBytes(const [0xFB, 0x21]);
+ }
+
+ /// Emit an `i31.get_u` instruction.
+ void i31_get_u() {
+ assert(_verifyTypes(const [RefType.i31()], const [NumType.i32],
+ trace: const ['i31.get_u']));
+ writeBytes(const [0xFB, 0x22]);
+ }
+
+ /// Emit an `rtt.canon` instruction.
+ void rtt_canon(DefType defType) {
+ assert(_verifyTypes(const [], [Rtt(defType, defType.depth)],
+ trace: ['rtt.canon', defType]));
+ writeBytes(const [0xFB, 0x30]);
+ writeSigned(defType.index);
+ }
+
+ bool _verifyRttSub(DefType subType) {
+ if (!reachable) {
+ return _debugTrace(['rtt.sub', subType], reachableAfter: false);
+ }
+ final ValueType input = _topOfStack;
+ if (input is! Rtt) _reportError("Expected rtt, but stack contained $input");
+ final int? depth = input.depth;
+ if (depth == null) _reportError("Expected rtt with known depth");
+ final DefType superType = input.defType;
+ if (!subType.isSubtypeOf(superType)) {
+ _reportError("Expected supertype of $subType, but got $superType");
+ }
+ return _verifyTypes([input], [Rtt(subType, depth + 1)],
+ trace: ['rtt.sub', subType]);
+ }
+
+ /// Emit an `rtt.sub` instruction.
+ void rtt_sub(DefType defType) {
+ assert(_verifyRttSub(defType));
+ writeBytes(const [0xFB, 0x31]);
+ writeSigned(defType.index);
+ }
+
+ bool _verifyCast(List<ValueType> Function(List<ValueType>) outputsFun,
+ {List<Object>? trace}) {
+ if (!reachable) {
+ return _debugTrace(trace, reachableAfter: false);
+ }
+ final stack = _stack(2);
+ final ValueType value = stack[0];
+ final ValueType rtt = stack[1];
+ if (rtt is! Rtt ||
+ !value.isSubtypeOf(const RefType.data(nullable: true)) &&
+ !value.isSubtypeOf(const RefType.func(nullable: true))) {
+ _reportError("Expected [data or func, rtt], but stack contained $stack");
+ }
+ return _verifyTypesFun(stack, outputsFun, trace: trace);
+ }
+
+ /// Emit a `ref.test` instruction.
+ void ref_test() {
+ assert(_verifyCast((_) => const [NumType.i32], trace: const ['ref.test']));
+ writeBytes(const [0xFB, 0x40]);
+ }
+
+ /// Emit a `ref.cast` instruction.
+ void ref_cast() {
+ assert(_verifyCast(
+ (inputs) => [
+ RefType.def((inputs[1] as Rtt).defType,
+ nullable: inputs[0].nullable)
+ ],
+ trace: const ['ref.cast']));
+ writeBytes(const [0xFB, 0x41]);
+ }
+
+ /// Emit a `br_on_cast` instruction.
+ void br_on_cast(Label label) {
+ late final DefType targetType;
+ assert(_verifyCast((inputs) {
+ targetType = (inputs[1] as Rtt).defType;
+ return [inputs[0]];
+ }, trace: ['br_on_cast', label]));
+ assert(_verifyBranchTypes(
+ label, 1, [RefType.def(targetType, nullable: false)]));
+ writeBytes(const [0xFB, 0x42]);
+ _writeLabel(label);
+ }
+
+ /// Emit a `br_on_cast_fail` instruction.
+ void br_on_cast_fail(Label label) {
+ assert(_verifyBranchTypes(label, 1, [_topOfStack]));
+ assert(_verifyCast(
+ (inputs) => [RefType.def((inputs[1] as Rtt).defType, nullable: false)],
+ trace: ['br_on_cast_fail', label]));
+ writeBytes(const [0xFB, 0x43]);
+ _writeLabel(label);
+ }
+
+ bool _verifyCastStatic(List<ValueType> Function(List<ValueType>) outputsFun,
+ {List<Object>? trace}) {
+ if (!reachable) {
+ return _debugTrace(trace, reachableAfter: false);
+ }
+ final ValueType value = _topOfStack;
+ if (!value.isSubtypeOf(const RefType.data(nullable: true)) &&
+ !value.isSubtypeOf(const RefType.func(nullable: true))) {
+ _reportError("Expected [data or func], but stack contained [$value]");
+ }
+ return _verifyTypesFun([value], outputsFun, trace: trace);
+ }
+
+ /// Emit a `ref.test_static` instruction.
+ void ref_test_static(DefType targetType) {
+ assert(_verifyCastStatic((_) => const [NumType.i32],
+ trace: ['ref.test_static', targetType]));
+ writeBytes(const [0xFB, 0x44]);
+ writeSigned(targetType.index);
+ }
+
+ /// Emit a `ref.cast_static` instruction.
+ void ref_cast_static(DefType targetType) {
+ assert(_verifyCastStatic(
+ (inputs) => [RefType.def(targetType, nullable: inputs[0].nullable)],
+ trace: ['ref.cast_static', targetType]));
+ writeBytes(const [0xFB, 0x45]);
+ writeSigned(targetType.index);
+ }
+
+ /// Emit a `br_on_cast_static` instruction.
+ void br_on_cast_static(Label label, DefType targetType) {
+ assert(_verifyCastStatic((inputs) {
+ return [inputs[0]];
+ }, trace: ['br_on_cast_static', label, targetType]));
+ assert(_verifyBranchTypes(
+ label, 1, [RefType.def(targetType, nullable: false)]));
+ writeBytes(const [0xFB, 0x46]);
+ _writeLabel(label);
+ writeSigned(targetType.index);
+ }
+
+ /// Emit a `br_on_cast_static_fail` instruction.
+ void br_on_cast_static_fail(Label label, DefType targetType) {
+ assert(_verifyBranchTypes(label, 1, [_topOfStack]));
+ assert(_verifyCast((inputs) => [RefType.def(targetType, nullable: false)],
+ trace: ['br_on_cast_static_fail', label, targetType]));
+ writeBytes(const [0xFB, 0x47]);
+ _writeLabel(label);
+ writeSigned(targetType.index);
+ }
+
+ /// Emit a `ref.is_func` instruction.
+ void ref_is_func() {
+ assert(_verifyTypes(const [RefType.any()], const [NumType.i32],
+ trace: const ['ref.is_func']));
+ writeBytes(const [0xFB, 0x50]);
+ }
+
+ /// Emit a `ref.is_data` instruction.
+ void ref_is_data() {
+ assert(_verifyTypes(const [RefType.any()], const [NumType.i32],
+ trace: const ['ref.is_data']));
+ writeBytes(const [0xFB, 0x51]);
+ }
+
+ /// Emit a `ref.is_i31` instruction.
+ void ref_is_i31() {
+ assert(_verifyTypes(const [RefType.any()], const [NumType.i32],
+ trace: const ['ref.is_i31']));
+ writeBytes(const [0xFB, 0x52]);
+ }
+
+ /// Emit a `ref.as_func` instruction.
+ void ref_as_func() {
+ assert(_verifyTypes(
+ const [RefType.any()], const [RefType.func(nullable: false)],
+ trace: const ['ref.as_func']));
+ writeBytes(const [0xFB, 0x58]);
+ }
+
+ /// Emit a `ref.as_data` instruction.
+ void ref_as_data() {
+ assert(_verifyTypes(
+ const [RefType.any()], const [RefType.data(nullable: false)],
+ trace: const ['ref.as_data']));
+ writeBytes(const [0xFB, 0x59]);
+ }
+
+ /// Emit a `ref.as_i31` instruction.
+ void ref_as_i31() {
+ assert(_verifyTypes(
+ const [RefType.any()], const [RefType.i31(nullable: false)],
+ trace: const ['ref.as_i31']));
+ writeBytes(const [0xFB, 0x5A]);
+ }
+
+ /// Emit a `br_on_func` instruction.
+ void br_on_func(Label label) {
+ assert(_verifyTypes(const [RefType.any()], [_topOfStack],
+ trace: ['br_on_func', label]));
+ assert(_verifyBranchTypes(label, 1, const [RefType.func(nullable: false)]));
+ writeBytes(const [0xFB, 0x60]);
+ _writeLabel(label);
+ }
+
+ /// Emit a `br_on_data` instruction.
+ void br_on_data(Label label) {
+ assert(_verifyTypes(const [RefType.any()], [_topOfStack],
+ trace: ['br_on_data', label]));
+ assert(_verifyBranchTypes(label, 1, const [RefType.data(nullable: false)]));
+ writeBytes(const [0xFB, 0x61]);
+ _writeLabel(label);
+ }
+
+ /// Emit a `br_on_i31` instruction.
+ void br_on_i31(Label label) {
+ assert(_verifyTypes(const [RefType.any()], [_topOfStack],
+ trace: ['br_on_i31', label]));
+ assert(_verifyBranchTypes(label, 1, const [RefType.i31(nullable: false)]));
+ writeBytes(const [0xFB, 0x62]);
+ _writeLabel(label);
+ }
+
+ /// Emit a `br_on_non_func` instruction.
+ void br_on_non_func(Label label) {
+ assert(_verifyBranchTypes(label, 1, [_topOfStack]));
+ assert(_verifyTypes(
+ const [RefType.any()], const [RefType.func(nullable: false)],
+ trace: ['br_on_non_func', label]));
+ writeBytes(const [0xFB, 0x63]);
+ _writeLabel(label);
+ }
+
+ /// Emit a `br_on_non_data` instruction.
+ void br_on_non_data(Label label) {
+ assert(_verifyBranchTypes(label, 1, [_topOfStack]));
+ assert(_verifyTypes(
+ const [RefType.any()], const [RefType.data(nullable: false)],
+ trace: ['br_on_non_data', label]));
+ writeBytes(const [0xFB, 0x64]);
+ _writeLabel(label);
+ }
+
+ /// Emit a `br_on_non_i31` instruction.
+ void br_on_non_i31(Label label) {
+ assert(_verifyBranchTypes(label, 1, [_topOfStack]));
+ assert(_verifyTypes(
+ const [RefType.any()], const [RefType.i31(nullable: false)],
+ trace: ['br_on_non_i31', label]));
+ writeBytes(const [0xFB, 0x65]);
+ _writeLabel(label);
+ }
+
+ // Numeric instructions
+
+ /// Emit an `i32.const` instruction.
+ void i32_const(int value) {
+ assert(_verifyTypes(const [], const [NumType.i32],
+ trace: ['i32.const', value]));
+ assert(-1 << 31 <= value && value < 1 << 31);
+ writeByte(0x41);
+ writeSigned(value);
+ }
+
+ /// Emit an `i64.const` instruction.
+ void i64_const(int value) {
+ assert(_verifyTypes(const [], const [NumType.i64],
+ trace: ['i64.const', value]));
+ writeByte(0x42);
+ writeSigned(value);
+ }
+
+ /// Emit an `f32.const` instruction.
+ void f32_const(double value) {
+ assert(_verifyTypes(const [], const [NumType.f32],
+ trace: ['f32.const', value]));
+ writeByte(0x43);
+ writeF32(value);
+ }
+
+ /// Emit an `f64.const` instruction.
+ void f64_const(double value) {
+ assert(_verifyTypes(const [], const [NumType.f64],
+ trace: ['f64.const', value]));
+ writeByte(0x44);
+ writeF64(value);
+ }
+
+ /// Emit an `i32.eqz` instruction.
+ void i32_eqz() {
+ assert(_verifyTypes(const [NumType.i32], const [NumType.i32],
+ trace: const ['i32.eqz']));
+ writeByte(0x45);
+ }
+
+ /// Emit an `i32.eq` instruction.
+ void i32_eq() {
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+ trace: const ['i32.eq']));
+ writeByte(0x46);
+ }
+
+ /// Emit an `i32.ne` instruction.
+ void i32_ne() {
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+ trace: const ['i32.ne']));
+ writeByte(0x47);
+ }
+
+ /// Emit an `i32.lt_s` instruction.
+ void i32_lt_s() {
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+ trace: const ['i32.lt_s']));
+ writeByte(0x48);
+ }
+
+ /// Emit an `i32.lt_u` instruction.
+ void i32_lt_u() {
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+ trace: const ['i32.lt_u']));
+ writeByte(0x49);
+ }
+
+ /// Emit an `i32.gt_s` instruction.
+ void i32_gt_s() {
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+ trace: const ['i32.gt_s']));
+ writeByte(0x4A);
+ }
+
+ /// Emit an `i32.gt_u` instruction.
+ void i32_gt_u() {
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+ trace: const ['i32.gt_u']));
+ writeByte(0x4B);
+ }
+
+ /// Emit an `i32.le_s` instruction.
+ void i32_le_s() {
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+ trace: const ['i32.le_s']));
+ writeByte(0x4C);
+ }
+
+ /// Emit an `i32.le_u` instruction.
+ void i32_le_u() {
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+ trace: const ['i32.le_u']));
+ writeByte(0x4D);
+ }
+
+ /// Emit an `i32.ge_s` instruction.
+ void i32_ge_s() {
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+ trace: const ['i32.ge_s']));
+ writeByte(0x4E);
+ }
+
+ /// Emit an `i32.ge_u` instruction.
+ void i32_ge_u() {
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+ trace: const ['i32.ge_u']));
+ writeByte(0x4F);
+ }
+
+ /// Emit an `i64.eqz` instruction.
+ void i64_eqz() {
+ assert(_verifyTypes(const [NumType.i64], const [NumType.i32],
+ trace: const ['i64.eqz']));
+ writeByte(0x50);
+ }
+
+ /// Emit an `i64.eq` instruction.
+ void i64_eq() {
+ assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i32],
+ trace: const ['i64.eq']));
+ writeByte(0x51);
+ }
+
+ /// Emit an `i64.ne` instruction.
+ void i64_ne() {
+ assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i32],
+ trace: const ['i64.ne']));
+ writeByte(0x52);
+ }
+
+ /// Emit an `i64.lt_s` instruction.
+ void i64_lt_s() {
+ assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i32],
+ trace: const ['i64.lt_s']));
+ writeByte(0x53);
+ }
+
+ /// Emit an `i64.lt_u` instruction.
+ void i64_lt_u() {
+ assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i32],
+ trace: const ['i64.lt_u']));
+ writeByte(0x54);
+ }
+
+ /// Emit an `i64.gt_s` instruction.
+ void i64_gt_s() {
+ assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i32],
+ trace: const ['i64.gt_s']));
+ writeByte(0x55);
+ }
+
+ /// Emit an `i64.gt_u` instruction.
+ void i64_gt_u() {
+ assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i32],
+ trace: const ['i64.gt_u']));
+ writeByte(0x56);
+ }
+
+ /// Emit an `i64.le_s` instruction.
+ void i64_le_s() {
+ assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i32],
+ trace: const ['i64.le_s']));
+ writeByte(0x57);
+ }
+
+ /// Emit an `i64.le_u` instruction.
+ void i64_le_u() {
+ assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i32],
+ trace: const ['i64.le_u']));
+ writeByte(0x58);
+ }
+
+ /// Emit an `i64.ge_s` instruction.
+ void i64_ge_s() {
+ assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i32],
+ trace: const ['i64.ge_s']));
+ writeByte(0x59);
+ }
+
+ /// Emit an `i64.ge_u` instruction.
+ void i64_ge_u() {
+ assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i32],
+ trace: const ['i64.ge_u']));
+ writeByte(0x5A);
+ }
+
+ /// Emit an `f32.eq` instruction.
+ void f32_eq() {
+ assert(_verifyTypes(const [NumType.f32, NumType.f32], const [NumType.i32],
+ trace: const ['f32.eq']));
+ writeByte(0x5B);
+ }
+
+ /// Emit an `f32.ne` instruction.
+ void f32_ne() {
+ assert(_verifyTypes(const [NumType.f32, NumType.f32], const [NumType.i32],
+ trace: const ['f32.ne']));
+ writeByte(0x5C);
+ }
+
+ /// Emit an `f32.lt` instruction.
+ void f32_lt() {
+ assert(_verifyTypes(const [NumType.f32, NumType.f32], const [NumType.i32],
+ trace: const ['f32.lt']));
+ writeByte(0x5D);
+ }
+
+ /// Emit an `f32.gt` instruction.
+ void f32_gt() {
+ assert(_verifyTypes(const [NumType.f32, NumType.f32], const [NumType.i32],
+ trace: const ['f32.gt']));
+ writeByte(0x5E);
+ }
+
+ /// Emit an `f32.le` instruction.
+ void f32_le() {
+ assert(_verifyTypes(const [NumType.f32, NumType.f32], const [NumType.i32],
+ trace: const ['f32.le']));
+ writeByte(0x5F);
+ }
+
+ /// Emit an `f32.ge` instruction.
+ void f32_ge() {
+ assert(_verifyTypes(const [NumType.f32, NumType.f32], const [NumType.i32],
+ trace: const ['f32.ge']));
+ writeByte(0x60);
+ }
+
+ /// Emit an `f64.eq` instruction.
+ void f64_eq() {
+ assert(_verifyTypes(const [NumType.f64, NumType.f64], const [NumType.i32],
+ trace: const ['f64.eq']));
+ writeByte(0x61);
+ }
+
+ /// Emit an `f64.ne` instruction.
+ void f64_ne() {
+ assert(_verifyTypes(const [NumType.f64, NumType.f64], const [NumType.i32],
+ trace: const ['f64.ne']));
+ writeByte(0x62);
+ }
+
+ /// Emit an `f64.lt` instruction.
+ void f64_lt() {
+ assert(_verifyTypes(const [NumType.f64, NumType.f64], const [NumType.i32],
+ trace: const ['f64.lt']));
+ writeByte(0x63);
+ }
+
+ /// Emit an `f64.gt` instruction.
+ void f64_gt() {
+ assert(_verifyTypes(const [NumType.f64, NumType.f64], const [NumType.i32],
+ trace: const ['f64.gt']));
+ writeByte(0x64);
+ }
+
+ /// Emit an `f64.le` instruction.
+ void f64_le() {
+ assert(_verifyTypes(const [NumType.f64, NumType.f64], const [NumType.i32],
+ trace: const ['f64.le']));
+ writeByte(0x65);
+ }
+
+ /// Emit an `f64.ge` instruction.
+ void f64_ge() {
+ assert(_verifyTypes(const [NumType.f64, NumType.f64], const [NumType.i32],
+ trace: const ['f64.ge']));
+ writeByte(0x66);
+ }
+
+ /// Emit an `i32.clz` instruction.
+ void i32_clz() {
+ assert(_verifyTypes(const [NumType.i32], const [NumType.i32],
+ trace: const ['i32.clz']));
+ writeByte(0x67);
+ }
+
+ /// Emit an `i32.ctz` instruction.
+ void i32_ctz() {
+ assert(_verifyTypes(const [NumType.i32], const [NumType.i32],
+ trace: const ['i32.ctz']));
+ writeByte(0x68);
+ }
+
+ /// Emit an `i32.popcnt` instruction.
+ void i32_popcnt() {
+ assert(_verifyTypes(const [NumType.i32], const [NumType.i32],
+ trace: const ['i32.popcnt']));
+ writeByte(0x69);
+ }
+
+ /// Emit an `i32.add` instruction.
+ void i32_add() {
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+ trace: const ['i32.add']));
+ writeByte(0x6A);
+ }
+
+ /// Emit an `i32.sub` instruction.
+ void i32_sub() {
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+ trace: const ['i32.sub']));
+ writeByte(0x6B);
+ }
+
+ /// Emit an `i32.mul` instruction.
+ void i32_mul() {
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+ trace: const ['i32.mul']));
+ writeByte(0x6C);
+ }
+
+ /// Emit an `i32.div_s` instruction.
+ void i32_div_s() {
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+ trace: const ['i32.div_s']));
+ writeByte(0x6D);
+ }
+
+ /// Emit an `i32.div_u` instruction.
+ void i32_div_u() {
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+ trace: const ['i32.div_u']));
+ writeByte(0x6E);
+ }
+
+ /// Emit an `i32.rem_s` instruction.
+ void i32_rem_s() {
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+ trace: const ['i32.rem_s']));
+ writeByte(0x6F);
+ }
+
+ /// Emit an `i32.rem_u` instruction.
+ void i32_rem_u() {
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+ trace: const ['i32.rem_u']));
+ writeByte(0x70);
+ }
+
+ /// Emit an `i32.and` instruction.
+ void i32_and() {
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+ trace: const ['i32.and']));
+ writeByte(0x71);
+ }
+
+ /// Emit an `i32.or` instruction.
+ void i32_or() {
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+ trace: const ['i32.or']));
+ writeByte(0x72);
+ }
+
+ /// Emit an `i32.xor` instruction.
+ void i32_xor() {
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+ trace: const ['i32.xor']));
+ writeByte(0x73);
+ }
+
+ /// Emit an `i32.shl` instruction.
+ void i32_shl() {
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+ trace: const ['i32.shl']));
+ writeByte(0x74);
+ }
+
+ /// Emit an `i32.shr_s` instruction.
+ void i32_shr_s() {
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+ trace: const ['i32.shr_s']));
+ writeByte(0x75);
+ }
+
+ /// Emit an `i32.shr_u` instruction.
+ void i32_shr_u() {
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+ trace: const ['i32.shr_u']));
+ writeByte(0x76);
+ }
+
+ /// Emit an `i32.rotl` instruction.
+ void i32_rotl() {
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+ trace: const ['i32.rotl']));
+ writeByte(0x77);
+ }
+
+ /// Emit an `i32.rotr` instruction.
+ void i32_rotr() {
+ assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+ trace: const ['i32.rotr']));
+ writeByte(0x78);
+ }
+
+ /// Emit an `i64.clz` instruction.
+ void i64_clz() {
+ assert(_verifyTypes(const [NumType.i64], const [NumType.i64],
+ trace: const ['i64.clz']));
+ writeByte(0x79);
+ }
+
+ /// Emit an `i64.ctz` instruction.
+ void i64_ctz() {
+ assert(_verifyTypes(const [NumType.i64], const [NumType.i64],
+ trace: const ['i64.ctz']));
+ writeByte(0x7A);
+ }
+
+ /// Emit an `i64.popcnt` instruction.
+ void i64_popcnt() {
+ assert(_verifyTypes(const [NumType.i64], const [NumType.i64],
+ trace: const ['i64.popcnt']));
+ writeByte(0x7B);
+ }
+
+ /// Emit an `i64.add` instruction.
+ void i64_add() {
+ assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+ trace: const ['i64.add']));
+ writeByte(0x7C);
+ }
+
+ /// Emit an `i64.sub` instruction.
+ void i64_sub() {
+ assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+ trace: const ['i64.sub']));
+ writeByte(0x7D);
+ }
+
+ /// Emit an `i64.mul` instruction.
+ void i64_mul() {
+ assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+ trace: const ['i64.mul']));
+ writeByte(0x7E);
+ }
+
+ /// Emit an `i64.div_s` instruction.
+ void i64_div_s() {
+ assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+ trace: const ['i64.div_s']));
+ writeByte(0x7F);
+ }
+
+ /// Emit an `i64.div_u` instruction.
+ void i64_div_u() {
+ assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+ trace: const ['i64.div_u']));
+ writeByte(0x80);
+ }
+
+ /// Emit an `i64.rem_s` instruction.
+ void i64_rem_s() {
+ assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+ trace: const ['i64.rem_s']));
+ writeByte(0x81);
+ }
+
+ /// Emit an `i64.rem_u` instruction.
+ void i64_rem_u() {
+ assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+ trace: const ['i64.rem_u']));
+ writeByte(0x82);
+ }
+
+ /// Emit an `i64.and` instruction.
+ void i64_and() {
+ assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+ trace: const ['i64.and']));
+ writeByte(0x83);
+ }
+
+ /// Emit an `i64.or` instruction.
+ void i64_or() {
+ assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+ trace: const ['i64.or']));
+ writeByte(0x84);
+ }
+
+ /// Emit an `i64.xor` instruction.
+ void i64_xor() {
+ assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+ trace: const ['i64.xor']));
+ writeByte(0x85);
+ }
+
+ /// Emit an `i64.shl` instruction.
+ void i64_shl() {
+ assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+ trace: const ['i64.shl']));
+ writeByte(0x86);
+ }
+
+ /// Emit an `i64.shr_s` instruction.
+ void i64_shr_s() {
+ assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+ trace: const ['i64.shr_s']));
+ writeByte(0x87);
+ }
+
+ /// Emit an `i64.shr_u` instruction.
+ void i64_shr_u() {
+ assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+ trace: const ['i64.shr_u']));
+ writeByte(0x88);
+ }
+
+ /// Emit an `i64.rotl` instruction.
+ void i64_rotl() {
+ assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+ trace: const ['i64.rotl']));
+ writeByte(0x89);
+ }
+
+ /// Emit an `i64.rotr` instruction.
+ void i64_rotr() {
+ assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+ trace: const ['i64.rotr']));
+ writeByte(0x8A);
+ }
+
+ /// Emit an `f32.abs` instruction.
+ void f32_abs() {
+ assert(_verifyTypes(const [NumType.f32], const [NumType.f32],
+ trace: const ['f32.abs']));
+ writeByte(0x8B);
+ }
+
+ /// Emit an `f32.neg` instruction.
+ void f32_neg() {
+ assert(_verifyTypes(const [NumType.f32], const [NumType.f32],
+ trace: const ['f32.neg']));
+ writeByte(0x8C);
+ }
+
+ /// Emit an `f32.ceil` instruction.
+ void f32_ceil() {
+ assert(_verifyTypes(const [NumType.f32], const [NumType.f32],
+ trace: const ['f32.ceil']));
+ writeByte(0x8D);
+ }
+
+ /// Emit an `f32.floor` instruction.
+ void f32_floor() {
+ assert(_verifyTypes(const [NumType.f32], const [NumType.f32],
+ trace: const ['f32.floor']));
+ writeByte(0x8E);
+ }
+
+ /// Emit an `f32.trunc` instruction.
+ void f32_trunc() {
+ assert(_verifyTypes(const [NumType.f32], const [NumType.f32],
+ trace: const ['f32.trunc']));
+ writeByte(0x8F);
+ }
+
+ /// Emit an `f32.nearest` instruction.
+ void f32_nearest() {
+ assert(_verifyTypes(const [NumType.f32], const [NumType.f32],
+ trace: const ['f32.nearest']));
+ writeByte(0x90);
+ }
+
+ /// Emit an `f32.sqrt` instruction.
+ void f32_sqrt() {
+ assert(_verifyTypes(const [NumType.f32], const [NumType.f32],
+ trace: const ['f32.sqrt']));
+ writeByte(0x91);
+ }
+
+ /// Emit an `f32.add` instruction.
+ void f32_add() {
+ assert(_verifyTypes(const [NumType.f32, NumType.f32], const [NumType.f32],
+ trace: const ['f32.add']));
+ writeByte(0x92);
+ }
+
+ /// Emit an `f32.sub` instruction.
+ void f32_sub() {
+ assert(_verifyTypes(const [NumType.f32, NumType.f32], const [NumType.f32],
+ trace: const ['f32.sub']));
+ writeByte(0x93);
+ }
+
+ /// Emit an `f32.mul` instruction.
+ void f32_mul() {
+ assert(_verifyTypes(const [NumType.f32, NumType.f32], const [NumType.f32],
+ trace: const ['f32.mul']));
+ writeByte(0x94);
+ }
+
+ /// Emit an `f32.div` instruction.
+ void f32_div() {
+ assert(_verifyTypes(const [NumType.f32, NumType.f32], const [NumType.f32],
+ trace: const ['f32.div']));
+ writeByte(0x95);
+ }
+
+ /// Emit an `f32.min` instruction.
+ void f32_min() {
+ assert(_verifyTypes(const [NumType.f32, NumType.f32], const [NumType.f32],
+ trace: const ['f32.min']));
+ writeByte(0x96);
+ }
+
+ /// Emit an `f32.max` instruction.
+ void f32_max() {
+ assert(_verifyTypes(const [NumType.f32, NumType.f32], const [NumType.f32],
+ trace: const ['f32.max']));
+ writeByte(0x97);
+ }
+
+ /// Emit an `f32.copysign` instruction.
+ void f32_copysign() {
+ assert(_verifyTypes(const [NumType.f32, NumType.f32], const [NumType.f32],
+ trace: const ['f32.copysign']));
+ writeByte(0x98);
+ }
+
+ /// Emit an `f64.abs` instruction.
+ void f64_abs() {
+ assert(_verifyTypes(const [NumType.f64], const [NumType.f64],
+ trace: const ['f64.abs']));
+ writeByte(0x99);
+ }
+
+ /// Emit an `f64.neg` instruction.
+ void f64_neg() {
+ assert(_verifyTypes(const [NumType.f64], const [NumType.f64],
+ trace: const ['f64.neg']));
+ writeByte(0x9A);
+ }
+
+ /// Emit an `f64.ceil` instruction.
+ void f64_ceil() {
+ assert(_verifyTypes(const [NumType.f64], const [NumType.f64],
+ trace: const ['f64.ceil']));
+ writeByte(0x9B);
+ }
+
+ /// Emit an `f64.floor` instruction.
+ void f64_floor() {
+ assert(_verifyTypes(const [NumType.f64], const [NumType.f64],
+ trace: const ['f64.floor']));
+ writeByte(0x9C);
+ }
+
+ /// Emit an `f64.trunc` instruction.
+ void f64_trunc() {
+ assert(_verifyTypes(const [NumType.f64], const [NumType.f64],
+ trace: const ['f64.trunc']));
+ writeByte(0x9D);
+ }
+
+ /// Emit an `f64.nearest` instruction.
+ void f64_nearest() {
+ assert(_verifyTypes(const [NumType.f64], const [NumType.f64],
+ trace: const ['f64.nearest']));
+ writeByte(0x9E);
+ }
+
+ /// Emit an `f64.sqrt` instruction.
+ void f64_sqrt() {
+ assert(_verifyTypes(const [NumType.f64], const [NumType.f64],
+ trace: const ['f64.sqrt']));
+ writeByte(0x9F);
+ }
+
+ /// Emit an `f64.add` instruction.
+ void f64_add() {
+ assert(_verifyTypes(const [NumType.f64, NumType.f64], const [NumType.f64],
+ trace: const ['f64.add']));
+ writeByte(0xA0);
+ }
+
+ /// Emit an `f64.sub` instruction.
+ void f64_sub() {
+ assert(_verifyTypes(const [NumType.f64, NumType.f64], const [NumType.f64],
+ trace: const ['f64.sub']));
+ writeByte(0xA1);
+ }
+
+ /// Emit an `f64.mul` instruction.
+ void f64_mul() {
+ assert(_verifyTypes(const [NumType.f64, NumType.f64], const [NumType.f64],
+ trace: const ['f64.mul']));
+ writeByte(0xA2);
+ }
+
+ /// Emit an `f64.div` instruction.
+ void f64_div() {
+ assert(_verifyTypes(const [NumType.f64, NumType.f64], const [NumType.f64],
+ trace: const ['f64.div']));
+ writeByte(0xA3);
+ }
+
+ /// Emit an `f64.min` instruction.
+ void f64_min() {
+ assert(_verifyTypes(const [NumType.f64, NumType.f64], const [NumType.f64],
+ trace: const ['f64.min']));
+ writeByte(0xA4);
+ }
+
+ /// Emit an `f64.max` instruction.
+ void f64_max() {
+ assert(_verifyTypes(const [NumType.f64, NumType.f64], const [NumType.f64],
+ trace: const ['f64.max']));
+ writeByte(0xA5);
+ }
+
+ /// Emit an `f64.copysign` instruction.
+ void f64_copysign() {
+ assert(_verifyTypes(const [NumType.f64, NumType.f64], const [NumType.f64],
+ trace: const ['f64.copysign']));
+ writeByte(0xA6);
+ }
+
+ /// Emit an `i32.wrap_i64` instruction.
+ void i32_wrap_i64() {
+ assert(_verifyTypes(const [NumType.i64], const [NumType.i32],
+ trace: const ['i32.wrap_i64']));
+ writeByte(0xA7);
+ }
+
+ /// Emit an `i32.trunc_f32_s` instruction.
+ void i32_trunc_f32_s() {
+ assert(_verifyTypes(const [NumType.f32], const [NumType.i32],
+ trace: const ['i32.trunc_f32_s']));
+ writeByte(0xA8);
+ }
+
+ /// Emit an `i32.trunc_f32_u` instruction.
+ void i32_trunc_f32_u() {
+ assert(_verifyTypes(const [NumType.f32], const [NumType.i32],
+ trace: const ['i32.trunc_f32_u']));
+ writeByte(0xA9);
+ }
+
+ /// Emit an `i32.trunc_f64_s` instruction.
+ void i32_trunc_f64_s() {
+ assert(_verifyTypes(const [NumType.f64], const [NumType.i32],
+ trace: const ['i32.trunc_f64_s']));
+ writeByte(0xAA);
+ }
+
+ /// Emit an `i32.trunc_f64_u` instruction.
+ void i32_trunc_f64_u() {
+ assert(_verifyTypes(const [NumType.f64], const [NumType.i32],
+ trace: const ['i32.trunc_f64_u']));
+ writeByte(0xAB);
+ }
+
+ /// Emit an `i64.extend_i32_s` instruction.
+ void i64_extend_i32_s() {
+ assert(_verifyTypes(const [NumType.i32], const [NumType.i64],
+ trace: const ['i64.extend_i32_s']));
+ writeByte(0xAC);
+ }
+
+ /// Emit an `i64.extend_i32_u` instruction.
+ void i64_extend_i32_u() {
+ assert(_verifyTypes(const [NumType.i32], const [NumType.i64],
+ trace: const ['i64.extend_i32_u']));
+ writeByte(0xAD);
+ }
+
+ /// Emit an `i64.trunc_f32_s` instruction.
+ void i64_trunc_f32_s() {
+ assert(_verifyTypes(const [NumType.f32], const [NumType.i64],
+ trace: const ['i64.trunc_f32_s']));
+ writeByte(0xAE);
+ }
+
+ /// Emit an `i64.trunc_f32_u` instruction.
+ void i64_trunc_f32_u() {
+ assert(_verifyTypes(const [NumType.f32], const [NumType.i64],
+ trace: const ['i64.trunc_f32_u']));
+ writeByte(0xAF);
+ }
+
+ /// Emit an `i64.trunc_f64_s` instruction.
+ void i64_trunc_f64_s() {
+ assert(_verifyTypes(const [NumType.f64], const [NumType.i64],
+ trace: const ['i64.trunc_f64_s']));
+ writeByte(0xB0);
+ }
+
+ /// Emit an `i64.trunc_f64_u` instruction.
+ void i64_trunc_f64_u() {
+ assert(_verifyTypes(const [NumType.f64], const [NumType.i64],
+ trace: const ['i64.trunc_f64_u']));
+ writeByte(0xB1);
+ }
+
+ /// Emit an `f32.convert_i32_s` instruction.
+ void f32_convert_i32_s() {
+ assert(_verifyTypes(const [NumType.i32], const [NumType.f32],
+ trace: const ['f32.convert_i32_s']));
+ writeByte(0xB2);
+ }
+
+ /// Emit an `f32.convert_i32_u` instruction.
+ void f32_convert_i32_u() {
+ assert(_verifyTypes(const [NumType.i32], const [NumType.f32],
+ trace: const ['f32.convert_i32_u']));
+ writeByte(0xB3);
+ }
+
+ /// Emit an `f32.convert_i64_s` instruction.
+ void f32_convert_i64_s() {
+ assert(_verifyTypes(const [NumType.i64], const [NumType.f32],
+ trace: const ['f32.convert_i64_s']));
+ writeByte(0xB4);
+ }
+
+ /// Emit an `f32.convert_i64_u` instruction.
+ void f32_convert_i64_u() {
+ assert(_verifyTypes(const [NumType.i64], const [NumType.f32],
+ trace: const ['f32.convert_i64_u']));
+ writeByte(0xB5);
+ }
+
+ /// Emit an `f32.demote_f64` instruction.
+ void f32_demote_f64() {
+ assert(_verifyTypes(const [NumType.f64], const [NumType.f32],
+ trace: const ['f32.demote_f64']));
+ writeByte(0xB6);
+ }
+
+ /// Emit an `f64.convert_i32_s` instruction.
+ void f64_convert_i32_s() {
+ assert(_verifyTypes(const [NumType.i32], const [NumType.f64],
+ trace: const ['f64.convert_i32_s']));
+ writeByte(0xB7);
+ }
+
+ /// Emit an `f64.convert_i32_u` instruction.
+ void f64_convert_i32_u() {
+ assert(_verifyTypes(const [NumType.i32], const [NumType.f64],
+ trace: const ['f64.convert_i32_u']));
+ writeByte(0xB8);
+ }
+
+ /// Emit an `f64.convert_i64_s` instruction.
+ void f64_convert_i64_s() {
+ assert(_verifyTypes(const [NumType.i64], const [NumType.f64],
+ trace: const ['f64.convert_i64_s']));
+ writeByte(0xB9);
+ }
+
+ /// Emit an `f64.convert_i64_u` instruction.
+ void f64_convert_i64_u() {
+ assert(_verifyTypes(const [NumType.i64], const [NumType.f64],
+ trace: const ['f64.convert_i64_u']));
+ writeByte(0xBA);
+ }
+
+ /// Emit an `f64.promote_f32` instruction.
+ void f64_promote_f32() {
+ assert(_verifyTypes(const [NumType.f32], const [NumType.f64],
+ trace: const ['f64.promote_f32']));
+ writeByte(0xBB);
+ }
+
+ /// Emit an `i32.reinterpret_f32` instruction.
+ void i32_reinterpret_f32() {
+ assert(_verifyTypes(const [NumType.f32], const [NumType.i32],
+ trace: const ['i32.reinterpret_f32']));
+ writeByte(0xBC);
+ }
+
+ /// Emit an `i64.reinterpret_f64` instruction.
+ void i64_reinterpret_f64() {
+ assert(_verifyTypes(const [NumType.f64], const [NumType.i64],
+ trace: const ['i64.reinterpret_f64']));
+ writeByte(0xBD);
+ }
+
+ /// Emit an `f32.reinterpret_i32` instruction.
+ void f32_reinterpret_i32() {
+ assert(_verifyTypes(const [NumType.i32], const [NumType.f32],
+ trace: const ['f32.reinterpret_i32']));
+ writeByte(0xBE);
+ }
+
+ /// Emit an `f64.reinterpret_i64` instruction.
+ void f64_reinterpret_i64() {
+ assert(_verifyTypes(const [NumType.i64], const [NumType.f64],
+ trace: const ['f64.reinterpret_i64']));
+ writeByte(0xBF);
+ }
+
+ /// Emit an `i32.extend8_s` instruction.
+ void i32_extend8_s() {
+ assert(_verifyTypes(const [NumType.i32], const [NumType.i32],
+ trace: const ['i32.extend8_s']));
+ writeByte(0xC0);
+ }
+
+ /// Emit an `i32.extend16_s` instruction.
+ void i32_extend16_s() {
+ assert(_verifyTypes(const [NumType.i32], const [NumType.i32],
+ trace: const ['i32.extend16_s']));
+ writeByte(0xC1);
+ }
+
+ /// Emit an `i64.extend8_s` instruction.
+ void i64_extend8_s() {
+ assert(_verifyTypes(const [NumType.i64], const [NumType.i64],
+ trace: const ['i64.extend8_s']));
+ writeByte(0xC2);
+ }
+
+ /// Emit an `i64.extend16_s` instruction.
+ void i64_extend16_s() {
+ assert(_verifyTypes(const [NumType.i64], const [NumType.i64],
+ trace: const ['i64.extend16_s']));
+ writeByte(0xC3);
+ }
+
+ /// Emit an `i64.extend32_s` instruction.
+ void i64_extend32_s() {
+ assert(_verifyTypes(const [NumType.i64], const [NumType.i64],
+ trace: const ['i64.extend32_s']));
+ writeByte(0xC4);
+ }
+
+ /// Emit an `i32.trunc_sat_f32_s` instruction.
+ void i32_trunc_sat_f32_s() {
+ assert(_verifyTypes(const [NumType.f32], const [NumType.i32],
+ trace: const ['i32.trunc_sat_f32_s']));
+ writeBytes(const [0xFC, 0x00]);
+ }
+
+ /// Emit an `i32.trunc_sat_f32_u` instruction.
+ void i32_trunc_sat_f32_u() {
+ assert(_verifyTypes(const [NumType.f32], const [NumType.i32],
+ trace: const ['i32.trunc_sat_f32_u']));
+ writeBytes(const [0xFC, 0x01]);
+ }
+
+ /// Emit an `i32.trunc_sat_f64_s` instruction.
+ void i32_trunc_sat_f64_s() {
+ assert(_verifyTypes(const [NumType.f64], const [NumType.i32],
+ trace: const ['i32.trunc_sat_f64_s']));
+ writeBytes(const [0xFC, 0x02]);
+ }
+
+ /// Emit an `i32.trunc_sat_f64_u` instruction.
+ void i32_trunc_sat_f64_u() {
+ assert(_verifyTypes(const [NumType.f64], const [NumType.i32],
+ trace: const ['i32.trunc_sat_f64_u']));
+ writeBytes(const [0xFC, 0x03]);
+ }
+
+ /// Emit an `i64.trunc_sat_f32_s` instruction.
+ void i64_trunc_sat_f32_s() {
+ assert(_verifyTypes(const [NumType.f32], const [NumType.i64],
+ trace: const ['i64.trunc_sat_f32_s']));
+ writeBytes(const [0xFC, 0x04]);
+ }
+
+ /// Emit an `i64.trunc_sat_f32_u` instruction.
+ void i64_trunc_sat_f32_u() {
+ assert(_verifyTypes(const [NumType.f32], const [NumType.i64],
+ trace: const ['i64.trunc_sat_f32_u']));
+ writeBytes(const [0xFC, 0x05]);
+ }
+
+ /// Emit an `i64.trunc_sat_f64_s` instruction.
+ void i64_trunc_sat_f64_s() {
+ assert(_verifyTypes(const [NumType.f64], const [NumType.i64],
+ trace: const ['i64.trunc_sat_f64_s']));
+ writeBytes(const [0xFC, 0x06]);
+ }
+
+ /// Emit an `i64.trunc_sat_f64_u` instruction.
+ void i64_trunc_sat_f64_u() {
+ assert(_verifyTypes(const [NumType.f64], const [NumType.i64],
+ trace: const ['i64.trunc_sat_f64_u']));
+ writeBytes(const [0xFC, 0x07]);
+ }
+}
diff --git a/pkg/wasm_builder/lib/src/module.dart b/pkg/wasm_builder/lib/src/module.dart
new file mode 100644
index 0000000..95ad8df
--- /dev/null
+++ b/pkg/wasm_builder/lib/src/module.dart
@@ -0,0 +1,820 @@
+// Copyright (c) 2022, 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 'instructions.dart';
+import 'serialize.dart';
+import 'types.dart';
+
+/// A Wasm module.
+///
+/// Serves as a builder for building new modules.
+class Module with SerializerMixin {
+ final List<int>? watchPoints;
+
+ final Map<_FunctionTypeKey, FunctionType> functionTypeMap = {};
+
+ final List<DefType> defTypes = [];
+ final List<BaseFunction> functions = [];
+ final List<Table> tables = [];
+ final List<Memory> memories = [];
+ final List<DataSegment> dataSegments = [];
+ final List<Global> globals = [];
+ final List<Export> exports = [];
+ BaseFunction? startFunction = null;
+
+ bool anyFunctionsDefined = false;
+ bool anyGlobalsDefined = false;
+ bool dataReferencedFromGlobalInitializer = false;
+
+ int functionNameCount = 0;
+
+ /// Create a new, initially empty, module.
+ ///
+ /// The [watchPoints] is a list of byte offsets within the final module of
+ /// bytes to watch. When the module is serialized, the stack traces leading to
+ /// the production of all watched bytes are printed. This can be used to debug
+ /// runtime errors happening at specific offsets within the module.
+ Module({this.watchPoints}) {
+ if (watchPoints != null) {
+ SerializerMixin.traceEnabled = true;
+ }
+ }
+
+ /// All module imports (functions and globals).
+ Iterable<Import> get imports =>
+ functions.whereType<Import>().followedBy(globals.whereType<Import>());
+
+ /// All functions defined in the module.
+ Iterable<DefinedFunction> get definedFunctions =>
+ functions.whereType<DefinedFunction>();
+
+ /// Add a new function type to the module.
+ ///
+ /// All function types are canonicalized, such that identical types become
+ /// the same type definition in the module, assuming nominal type identity
+ /// of all inputs and outputs.
+ ///
+ /// Inputs and outputs can't be changed after the function type is created.
+ /// This means that recursive function types (without any non-function types
+ /// on the recursion path) are not supported.
+ FunctionType addFunctionType(
+ Iterable<ValueType> inputs, Iterable<ValueType> outputs,
+ {HeapType? superType}) {
+ final List<ValueType> inputList = List.unmodifiable(inputs);
+ final List<ValueType> outputList = List.unmodifiable(outputs);
+ final _FunctionTypeKey key = _FunctionTypeKey(inputList, outputList);
+ return functionTypeMap.putIfAbsent(key, () {
+ final type = FunctionType(inputList, outputList, superType: superType)
+ ..index = defTypes.length;
+ defTypes.add(type);
+ return type;
+ });
+ }
+
+ /// Add a new struct type to the module.
+ ///
+ /// Fields can be added later, by adding to the [fields] list. This enables
+ /// struct types to be recursive.
+ StructType addStructType(String name,
+ {Iterable<FieldType>? fields, HeapType? superType}) {
+ final type = StructType(name, fields: fields, superType: superType)
+ ..index = defTypes.length;
+ defTypes.add(type);
+ return type;
+ }
+
+ /// Add a new array type to the module.
+ ///
+ /// The element type can be specified later. This enables array types to be
+ /// recursive.
+ ArrayType addArrayType(String name,
+ {FieldType? elementType, HeapType? superType}) {
+ final type = ArrayType(name, elementType: elementType, superType: superType)
+ ..index = defTypes.length;
+ defTypes.add(type);
+ return type;
+ }
+
+ /// Add a new function to the module with the given function type.
+ ///
+ /// The [DefinedFunction.body] must be completed (including the terminating
+ /// `end`) before the module can be serialized.
+ DefinedFunction addFunction(FunctionType type, [String? name]) {
+ anyFunctionsDefined = true;
+ if (name != null) functionNameCount++;
+ final function = DefinedFunction(this, functions.length, type, name);
+ functions.add(function);
+ return function;
+ }
+
+ /// Add a new table to the module.
+ Table addTable(int minSize, [int? maxSize]) {
+ final table = Table(tables.length, minSize, maxSize);
+ tables.add(table);
+ return table;
+ }
+
+ /// Add a new memory to the module.
+ Memory addMemory(int minSize, [int? maxSize]) {
+ final memory = Memory(memories.length, minSize, maxSize);
+ memories.add(memory);
+ return memory;
+ }
+
+ /// Add a new data segment to the module.
+ ///
+ /// Either [memory] and [offset] must be both specified or both omitted. If
+ /// they are specified, the segment becomes an *active* segment, otherwise it
+ /// becomes a *passive* segment.
+ ///
+ /// If [initialContent] is specified, it defines the initial content of the
+ /// segment. The content can be extended later.
+ DataSegment addDataSegment(
+ [Uint8List? initialContent, Memory? memory, int? offset]) {
+ initialContent ??= Uint8List(0);
+ assert((memory != null) == (offset != null));
+ assert(memory == null ||
+ offset! >= 0 && offset + initialContent.length <= memory.minSize);
+ final DataSegment data =
+ DataSegment(dataSegments.length, initialContent, memory, offset);
+ dataSegments.add(data);
+ return data;
+ }
+
+ /// Add a global variable to the module.
+ ///
+ /// The [DefinedGlobal.initializer] must be completed (including the
+ /// terminating `end`) before the module can be serialized.
+ DefinedGlobal addGlobal(GlobalType type) {
+ anyGlobalsDefined = true;
+ final global = DefinedGlobal(this, globals.length, type);
+ globals.add(global);
+ return global;
+ }
+
+ /// Import a function into the module.
+ ///
+ /// All imported functions must be specified before any functions are declared
+ /// using [Module.addFunction].
+ ImportedFunction importFunction(String module, String name, FunctionType type,
+ [String? functionName]) {
+ if (anyFunctionsDefined) {
+ throw "All function imports must be specified before any definitions.";
+ }
+ if (functionName != null) functionNameCount++;
+ final function =
+ ImportedFunction(module, name, functions.length, type, functionName);
+ functions.add(function);
+ return function;
+ }
+
+ /// Import a global variable into the module.
+ ///
+ /// All imported globals must be specified before any globals are declared
+ /// using [Module.addGlobal].
+ ImportedGlobal importGlobal(String module, String name, GlobalType type) {
+ if (anyGlobalsDefined) {
+ throw "All global imports must be specified before any definitions.";
+ }
+ final global = ImportedGlobal(module, name, functions.length, type);
+ globals.add(global);
+ return global;
+ }
+
+ void _addExport(Export export) {
+ assert(!exports.any((e) => e.name == export.name), export.name);
+ exports.add(export);
+ }
+
+ /// Export a function from the module.
+ ///
+ /// All exports must have unique names.
+ void exportFunction(String name, BaseFunction function) {
+ function.exportedName = name;
+ _addExport(FunctionExport(name, function));
+ }
+
+ /// Export a global variable from the module.
+ ///
+ /// All exports must have unique names.
+ void exportGlobal(String name, Global global) {
+ exports.add(GlobalExport(name, global));
+ }
+
+ /// Serialize the module to its binary representation.
+ Uint8List encode({bool emitNameSection: true}) {
+ // Wasm module preamble: magic number, version 1.
+ writeBytes(const [0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00]);
+ TypeSection(this).serialize(this);
+ ImportSection(this).serialize(this);
+ FunctionSection(this).serialize(this);
+ TableSection(this).serialize(this);
+ MemorySection(this).serialize(this);
+ if (dataReferencedFromGlobalInitializer) {
+ DataCountSection(this).serialize(this);
+ }
+ GlobalSection(this).serialize(this);
+ ExportSection(this).serialize(this);
+ StartSection(this).serialize(this);
+ ElementSection(this).serialize(this);
+ if (!dataReferencedFromGlobalInitializer) {
+ DataCountSection(this).serialize(this);
+ }
+ CodeSection(this).serialize(this);
+ DataSection(this).serialize(this);
+ if (emitNameSection) {
+ NameSection(this).serialize(this);
+ }
+ return data;
+ }
+}
+
+class _FunctionTypeKey {
+ final List<ValueType> inputs;
+ final List<ValueType> outputs;
+
+ _FunctionTypeKey(this.inputs, this.outputs);
+
+ @override
+ bool operator ==(Object other) {
+ if (other is! _FunctionTypeKey) return false;
+ if (inputs.length != other.inputs.length) return false;
+ if (outputs.length != other.outputs.length) return false;
+ for (int i = 0; i < inputs.length; i++) {
+ if (inputs[i] != other.inputs[i]) return false;
+ }
+ for (int i = 0; i < outputs.length; i++) {
+ if (outputs[i] != other.outputs[i]) return false;
+ }
+ return true;
+ }
+
+ @override
+ int get hashCode {
+ int inputHash = 13;
+ for (var input in inputs) {
+ inputHash = inputHash * 17 + input.hashCode;
+ }
+ int outputHash = 23;
+ for (var output in outputs) {
+ outputHash = outputHash * 29 + output.hashCode;
+ }
+ return (inputHash * 2 + 1) * (outputHash * 2 + 1);
+ }
+}
+
+/// An (imported or defined) Wasm function.
+abstract class BaseFunction {
+ final int index;
+ final FunctionType type;
+ final String? functionName;
+ String? exportedName;
+
+ BaseFunction(this.index, this.type, this.functionName);
+}
+
+/// A function defined in the module.
+class DefinedFunction extends BaseFunction
+ with SerializerMixin
+ implements Serializable {
+ /// All local variables defined in the function, including its inputs.
+ final List<Local> locals = [];
+
+ /// The body of the function.
+ late final Instructions body;
+
+ DefinedFunction(Module module, int index, FunctionType type,
+ [String? functionName])
+ : super(index, type, functionName) {
+ for (ValueType paramType in type.inputs) {
+ addLocal(paramType);
+ }
+ body = Instructions(module, type.outputs, locals: locals);
+ }
+
+ /// Add a local variable to the function.
+ Local addLocal(ValueType type) {
+ Local local = Local(locals.length, type);
+ locals.add(local);
+ return local;
+ }
+
+ @override
+ void serialize(Serializer s) {
+ // Serialize locals internally first in order to compute the total size of
+ // the serialized data.
+ int paramCount = type.inputs.length;
+ int entries = 0;
+ for (int i = paramCount + 1; i <= locals.length; i++) {
+ if (i == locals.length || locals[i - 1].type != locals[i].type) entries++;
+ }
+ writeUnsigned(entries);
+ int start = paramCount;
+ for (int i = paramCount + 1; i <= locals.length; i++) {
+ if (i == locals.length || locals[i - 1].type != locals[i].type) {
+ writeUnsigned(i - start);
+ write(locals[i - 1].type);
+ start = i;
+ }
+ }
+
+ // Bundle locals and body
+ assert(body.isComplete);
+ s.writeUnsigned(data.length + body.data.length);
+ s.writeData(this);
+ s.writeData(body);
+ }
+
+ @override
+ String toString() => exportedName ?? "#$index";
+}
+
+/// A local variable defined in a function.
+class Local {
+ final int index;
+ final ValueType type;
+
+ Local(this.index, this.type);
+
+ @override
+ String toString() => "$index";
+}
+
+/// A table in a module.
+class Table implements Serializable {
+ final int index;
+ final int minSize;
+ final int? maxSize;
+ final List<BaseFunction?> elements;
+
+ Table(this.index, this.minSize, this.maxSize)
+ : elements = List.filled(minSize, null);
+
+ void setElement(int index, BaseFunction function) {
+ elements[index] = function;
+ }
+
+ @override
+ void serialize(Serializer s) {
+ s.writeByte(0x70); // funcref
+ if (maxSize == null) {
+ s.writeByte(0x00);
+ s.writeUnsigned(minSize);
+ } else {
+ s.writeByte(0x01);
+ s.writeUnsigned(minSize);
+ s.writeUnsigned(maxSize!);
+ }
+ }
+}
+
+/// A memory in a module.
+class Memory implements Serializable {
+ final int index;
+ final int minSize;
+ final int? maxSize;
+
+ Memory(this.index, this.minSize, [this.maxSize]);
+
+ @override
+ void serialize(Serializer s) {
+ if (maxSize == null) {
+ s.writeByte(0x00);
+ s.writeUnsigned(minSize);
+ } else {
+ s.writeByte(0x01);
+ s.writeUnsigned(minSize);
+ s.writeUnsigned(maxSize!);
+ }
+ }
+}
+
+/// A data segment in a module.
+class DataSegment implements Serializable {
+ final int index;
+ final BytesBuilder content;
+ final Memory? memory;
+ final int? offset;
+
+ DataSegment(this.index, Uint8List initialContent, this.memory, this.offset)
+ : content = BytesBuilder()..add(initialContent);
+
+ bool get isActive => memory != null;
+ bool get isPassive => memory == null;
+
+ int get length => content.length;
+
+ /// Append content to the data segment.
+ void append(Uint8List data) {
+ content.add(data);
+ assert(isPassive ||
+ offset! >= 0 && offset! + content.length <= memory!.minSize);
+ }
+
+ @override
+ void serialize(Serializer s) {
+ if (memory != null) {
+ // Active segment
+ if (memory!.index == 0) {
+ s.writeByte(0x00);
+ } else {
+ s.writeByte(0x02);
+ s.writeUnsigned(memory!.index);
+ }
+ s.writeByte(0x41); // i32.const
+ s.writeSigned(offset!);
+ s.writeByte(0x0B); // end
+ } else {
+ // Passive segment
+ s.writeByte(0x01);
+ }
+ s.writeUnsigned(content.length);
+ s.writeBytes(content.toBytes());
+ }
+}
+
+/// An (imported or defined) global variable in a module.
+abstract class Global {
+ final int index;
+ final GlobalType type;
+
+ Global(this.index, this.type);
+
+ @override
+ String toString() => "$index";
+}
+
+/// A global variable defined in the module.
+class DefinedGlobal extends Global implements Serializable {
+ final Instructions initializer;
+
+ DefinedGlobal(Module module, int index, GlobalType type)
+ : initializer =
+ Instructions(module, [type.type], isGlobalInitializer: true),
+ super(index, type);
+
+ @override
+ void serialize(Serializer s) {
+ assert(initializer.isComplete);
+ s.write(type);
+ s.writeData(initializer);
+ }
+}
+
+/// Any import (function or global).
+abstract class Import implements Serializable {
+ String get module;
+ String get name;
+}
+
+/// An imported function.
+class ImportedFunction extends BaseFunction implements Import {
+ final String module;
+ final String name;
+
+ ImportedFunction(this.module, this.name, int index, FunctionType type,
+ [String? functionName])
+ : super(index, type, functionName);
+
+ @override
+ void serialize(Serializer s) {
+ s.writeName(module);
+ s.writeName(name);
+ s.writeByte(0x00);
+ s.writeUnsigned(type.index);
+ }
+
+ @override
+ String toString() => "$module.$name";
+}
+
+/// An imported global variable.
+class ImportedGlobal extends Global implements Import {
+ final String module;
+ final String name;
+
+ ImportedGlobal(this.module, this.name, int index, GlobalType type)
+ : super(index, type);
+
+ @override
+ void serialize(Serializer s) {
+ s.writeName(module);
+ s.writeName(name);
+ s.writeByte(0x03);
+ s.write(type);
+ }
+}
+
+abstract class Export implements Serializable {
+ final String name;
+
+ Export(this.name);
+}
+
+class FunctionExport extends Export {
+ final BaseFunction function;
+
+ FunctionExport(String name, this.function) : super(name);
+
+ @override
+ void serialize(Serializer s) {
+ s.writeName(name);
+ s.writeByte(0x00);
+ s.writeUnsigned(function.index);
+ }
+}
+
+class GlobalExport extends Export {
+ final Global global;
+
+ GlobalExport(String name, this.global) : super(name);
+
+ @override
+ void serialize(Serializer s) {
+ s.writeName(name);
+ s.writeByte(0x03);
+ s.writeUnsigned(global.index);
+ }
+}
+
+abstract class Section with SerializerMixin implements Serializable {
+ final Module module;
+
+ Section(this.module);
+
+ void serialize(Serializer s) {
+ if (isNotEmpty) {
+ serializeContents();
+ s.writeByte(id);
+ s.writeUnsigned(data.length);
+ s.writeData(this, module.watchPoints);
+ }
+ }
+
+ int get id;
+
+ bool get isNotEmpty;
+
+ void serializeContents();
+}
+
+class TypeSection extends Section {
+ TypeSection(Module module) : super(module);
+
+ @override
+ int get id => 1;
+
+ @override
+ bool get isNotEmpty => module.defTypes.isNotEmpty;
+
+ @override
+ void serializeContents() {
+ writeUnsigned(module.defTypes.length);
+ for (DefType defType in module.defTypes) {
+ defType.serializeDefinition(this);
+ }
+ }
+}
+
+class ImportSection extends Section {
+ ImportSection(Module module) : super(module);
+
+ @override
+ int get id => 2;
+
+ @override
+ bool get isNotEmpty => module.imports.isNotEmpty;
+
+ @override
+ void serializeContents() {
+ writeList(module.imports.toList());
+ }
+}
+
+class FunctionSection extends Section {
+ FunctionSection(Module module) : super(module);
+
+ @override
+ int get id => 3;
+
+ @override
+ bool get isNotEmpty => module.definedFunctions.isNotEmpty;
+
+ @override
+ void serializeContents() {
+ writeUnsigned(module.definedFunctions.length);
+ for (var function in module.definedFunctions) {
+ writeUnsigned(function.type.index);
+ }
+ }
+}
+
+class TableSection extends Section {
+ TableSection(Module module) : super(module);
+
+ @override
+ int get id => 4;
+
+ @override
+ bool get isNotEmpty => module.tables.isNotEmpty;
+
+ @override
+ void serializeContents() {
+ writeList(module.tables);
+ }
+}
+
+class MemorySection extends Section {
+ MemorySection(Module module) : super(module);
+
+ @override
+ int get id => 5;
+
+ @override
+ bool get isNotEmpty => module.memories.isNotEmpty;
+
+ @override
+ void serializeContents() {
+ writeList(module.memories);
+ }
+}
+
+class GlobalSection extends Section {
+ GlobalSection(Module module) : super(module);
+
+ @override
+ int get id => 6;
+
+ @override
+ bool get isNotEmpty => module.globals.whereType<DefinedGlobal>().isNotEmpty;
+
+ @override
+ void serializeContents() {
+ writeList(module.globals.whereType<DefinedGlobal>().toList());
+ }
+}
+
+class ExportSection extends Section {
+ ExportSection(Module module) : super(module);
+
+ @override
+ int get id => 7;
+
+ @override
+ bool get isNotEmpty => module.exports.isNotEmpty;
+
+ @override
+ void serializeContents() {
+ writeList(module.exports);
+ }
+}
+
+class StartSection extends Section {
+ StartSection(Module module) : super(module);
+
+ @override
+ int get id => 8;
+
+ @override
+ bool get isNotEmpty => module.startFunction != null;
+
+ @override
+ void serializeContents() {
+ writeUnsigned(module.startFunction!.index);
+ }
+}
+
+class _Element implements Serializable {
+ final Table table;
+ final int startIndex;
+ final List<BaseFunction> entries = [];
+
+ _Element(this.table, this.startIndex);
+
+ @override
+ void serialize(Serializer s) {
+ s.writeUnsigned(table.index);
+ s.writeByte(0x41); // i32.const
+ s.writeSigned(startIndex);
+ s.writeByte(0x0B); // end
+ s.writeUnsigned(entries.length);
+ for (var entry in entries) {
+ s.writeUnsigned(entry.index);
+ }
+ }
+}
+
+class ElementSection extends Section {
+ ElementSection(Module module) : super(module);
+
+ @override
+ int get id => 9;
+
+ @override
+ bool get isNotEmpty =>
+ module.tables.any((table) => table.elements.any((e) => e != null));
+
+ @override
+ void serializeContents() {
+ // Group nonempty element entries into contiguous stretches and serialize
+ // each stretch as an element.
+ List<_Element> elements = [];
+ for (Table table in module.tables) {
+ _Element? current = null;
+ for (int i = 0; i < table.elements.length; i++) {
+ BaseFunction? function = table.elements[i];
+ if (function != null) {
+ if (current == null) {
+ current = _Element(table, i);
+ elements.add(current);
+ }
+ current.entries.add(function);
+ } else {
+ current = null;
+ }
+ }
+ }
+ writeList(elements);
+ }
+}
+
+class DataCountSection extends Section {
+ DataCountSection(Module module) : super(module);
+
+ @override
+ int get id => 12;
+
+ @override
+ bool get isNotEmpty => module.dataSegments.isNotEmpty;
+
+ @override
+ void serializeContents() {
+ writeUnsigned(module.dataSegments.length);
+ }
+}
+
+class CodeSection extends Section {
+ CodeSection(Module module) : super(module);
+
+ @override
+ int get id => 10;
+
+ @override
+ bool get isNotEmpty => module.definedFunctions.isNotEmpty;
+
+ @override
+ void serializeContents() {
+ writeList(module.definedFunctions.toList());
+ }
+}
+
+class DataSection extends Section {
+ DataSection(Module module) : super(module);
+
+ @override
+ int get id => 11;
+
+ @override
+ bool get isNotEmpty => module.dataSegments.isNotEmpty;
+
+ @override
+ void serializeContents() {
+ writeList(module.dataSegments);
+ }
+}
+
+abstract class CustomSection extends Section {
+ CustomSection(Module module) : super(module);
+
+ @override
+ int get id => 0;
+}
+
+class NameSection extends CustomSection {
+ NameSection(Module module) : super(module);
+
+ @override
+ bool get isNotEmpty => module.functionNameCount > 0;
+
+ @override
+ void serializeContents() {
+ writeName("name");
+ var functionNameSubsection = _NameSubsection();
+ functionNameSubsection.writeUnsigned(module.functionNameCount);
+ for (int i = 0; i < module.functions.length; i++) {
+ String? functionName = module.functions[i].functionName;
+ if (functionName != null) {
+ functionNameSubsection.writeUnsigned(i);
+ functionNameSubsection.writeName(functionName);
+ }
+ }
+ writeByte(1); // Function names subsection
+ writeUnsigned(functionNameSubsection.data.length);
+ writeData(functionNameSubsection);
+ }
+}
+
+class _NameSubsection with SerializerMixin {}
diff --git a/pkg/wasm_builder/lib/src/serialize.dart b/pkg/wasm_builder/lib/src/serialize.dart
new file mode 100644
index 0000000..007b6e1
--- /dev/null
+++ b/pkg/wasm_builder/lib/src/serialize.dart
@@ -0,0 +1,137 @@
+// Copyright (c) 2022, 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:collection';
+import 'dart:convert';
+import 'dart:typed_data';
+
+abstract class Serializer {
+ void writeByte(int byte);
+ void writeBytes(List<int> bytes);
+ void writeSigned(int value);
+ void writeUnsigned(int value);
+ void writeF32(double value);
+ void writeF64(double value);
+ void writeName(String name);
+ void write(Serializable object);
+ void writeList(List<Serializable> objects);
+ void writeData(Serializer chunk, [List<int>? watchPoints]);
+
+ Uint8List get data;
+}
+
+abstract class Serializable {
+ void serialize(Serializer s);
+}
+
+mixin SerializerMixin implements Serializer {
+ static bool traceEnabled = false;
+
+ // The prefix of `_data` up to `_index` contains the data serialized so far.
+ Uint8List _data = Uint8List(24);
+ int _index = 0;
+
+ // Stack traces or other serializers attached to byte positions within the
+ // chunk of data produced by this serializer.
+ late final SplayTreeMap<int, Object> _traces = SplayTreeMap();
+
+ void _ensure(int size) {
+ // Ensure space for at least `size` additional bytes.
+ if (_data.length < _index + size) {
+ int newLength = _data.length * 2;
+ while (newLength < _index + size) newLength *= 2;
+ _data = Uint8List(newLength)..setRange(0, _data.length, _data);
+ }
+ }
+
+ void _debugTrace(Object data) {
+ _traces[_index] ??= data;
+ }
+
+ void writeByte(int byte) {
+ if (traceEnabled) _debugTrace(StackTrace.current);
+ assert(byte == byte & 0xFF);
+ _ensure(1);
+ _data[_index++] = byte;
+ }
+
+ void writeBytes(List<int> bytes) {
+ if (traceEnabled) _debugTrace(StackTrace.current);
+ _ensure(bytes.length);
+ _data.setRange(_index, _index += bytes.length, bytes);
+ }
+
+ void writeSigned(int value) {
+ while (value < -0x40 || value >= 0x40) {
+ writeByte((value & 0x7F) | 0x80);
+ value >>= 7;
+ }
+ writeByte(value & 0x7F);
+ }
+
+ void writeUnsigned(int value) {
+ assert(value >= 0);
+ while (value >= 0x80) {
+ writeByte((value & 0x7F) | 0x80);
+ value >>= 7;
+ }
+ writeByte(value);
+ }
+
+ void writeF32(double value) {
+ // Get the binary representation of the F32.
+ List<int> bytes = Float32List.fromList([value]).buffer.asUint8List();
+ assert(bytes.length == 4);
+ if (Endian.host == Endian.big) bytes = bytes.reversed.toList();
+ writeBytes(bytes);
+ }
+
+ void writeF64(double value) {
+ // Get the binary representation of the F64.
+ List<int> bytes = Float64List.fromList([value]).buffer.asUint8List();
+ assert(bytes.length == 8);
+ if (Endian.host == Endian.big) bytes = bytes.reversed.toList();
+ writeBytes(bytes);
+ }
+
+ void writeName(String name) {
+ List<int> bytes = utf8.encode(name);
+ writeUnsigned(bytes.length);
+ writeBytes(bytes);
+ }
+
+ void write(Serializable object) {
+ object.serialize(this);
+ }
+
+ void writeList(List<Serializable> objects) {
+ writeUnsigned(objects.length);
+ for (int i = 0; i < objects.length; i++) write(objects[i]);
+ }
+
+ void writeData(Serializer chunk, [List<int>? watchPoints]) {
+ if (traceEnabled) _debugTrace(chunk);
+ if (watchPoints != null) {
+ for (int watchPoint in watchPoints) {
+ if (_index <= watchPoint && watchPoint < _index + chunk.data.length) {
+ int byteValue = chunk.data[watchPoint - _index];
+ Object trace = this;
+ int offset = watchPoint;
+ while (trace is SerializerMixin) {
+ int keyOffset = trace._traces.containsKey(offset)
+ ? offset
+ : trace._traces.lastKeyBefore(offset)!;
+ trace = trace._traces[keyOffset]!;
+ offset -= keyOffset;
+ }
+ String byte = byteValue.toRadixString(16).padLeft(2, '0');
+ print("Watch $watchPoint: 0x$byte\n$trace");
+ }
+ }
+ }
+ writeBytes(chunk.data);
+ }
+
+ Uint8List get data => Uint8List.sublistView(_data, 0, _index);
+}
diff --git a/pkg/wasm_builder/lib/src/types.dart b/pkg/wasm_builder/lib/src/types.dart
new file mode 100644
index 0000000..ecc8b1e
--- /dev/null
+++ b/pkg/wasm_builder/lib/src/types.dart
@@ -0,0 +1,657 @@
+// Copyright (c) 2022, 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 'serialize.dart';
+
+// Representations of all Wasm types.
+
+/// A *storage type*.
+abstract class StorageType implements Serializable {
+ /// Returns whether this type is a subtype of the [other] type, i.e. whether
+ /// it can be used as input where [other] is expected.
+ bool isSubtypeOf(StorageType other);
+
+ /// The *unpacked* form of this storage type, i.e. the *value type* to use
+ /// when reading/writing this storage type from/to memory.
+ ValueType get unpacked;
+
+ /// Whether this is a primitive (i.e. not reference) type.
+ bool get isPrimitive;
+
+ /// For primitive types: the size in bytes of a value of this type.
+ int get byteSize;
+}
+
+/// A *value type*.
+abstract class ValueType implements StorageType {
+ const ValueType();
+
+ @override
+ ValueType get unpacked => this;
+
+ @override
+ bool get isPrimitive => false;
+
+ @override
+ int get byteSize => throw "Size of non-primitive type $runtimeType";
+
+ /// Whether this type is nullable. Primitive types are never nullable.
+ bool get nullable => false;
+
+ /// If this exists in both a nullable and non-nullable version, return the
+ /// version with the given nullability.
+ ValueType withNullability(bool nullable) => this;
+
+ /// Whether this type is defaultable. Primitive types are always defaultable.
+ bool get defaultable => true;
+}
+
+enum NumTypeKind { i32, i64, f32, f64, v128 }
+
+/// A *number type* or *vector type*.
+class NumType extends ValueType {
+ final NumTypeKind kind;
+
+ const NumType._(this.kind);
+
+ /// The `i32` type.
+ static const i32 = NumType._(NumTypeKind.i32);
+
+ /// The `i64` type.
+ static const i64 = NumType._(NumTypeKind.i64);
+
+ /// The `f32` type.
+ static const f32 = NumType._(NumTypeKind.f32);
+
+ /// The `f64` type.
+ static const f64 = NumType._(NumTypeKind.f64);
+
+ /// The `v128` type.
+ static const v128 = NumType._(NumTypeKind.v128);
+
+ @override
+ bool isSubtypeOf(StorageType other) => this == other;
+
+ @override
+ bool get isPrimitive => true;
+
+ @override
+ int get byteSize {
+ switch (kind) {
+ case NumTypeKind.i32:
+ case NumTypeKind.f32:
+ return 4;
+ case NumTypeKind.i64:
+ case NumTypeKind.f64:
+ return 8;
+ case NumTypeKind.v128:
+ return 16;
+ }
+ }
+
+ @override
+ void serialize(Serializer s) {
+ switch (kind) {
+ case NumTypeKind.i32:
+ s.writeByte(0x7F);
+ break;
+ case NumTypeKind.i64:
+ s.writeByte(0x7E);
+ break;
+ case NumTypeKind.f32:
+ s.writeByte(0x7D);
+ break;
+ case NumTypeKind.f64:
+ s.writeByte(0x7C);
+ break;
+ case NumTypeKind.v128:
+ s.writeByte(0x7B);
+ break;
+ }
+ }
+
+ @override
+ String toString() {
+ switch (kind) {
+ case NumTypeKind.i32:
+ return "i32";
+ case NumTypeKind.i64:
+ return "i64";
+ case NumTypeKind.f32:
+ return "f32";
+ case NumTypeKind.f64:
+ return "f64";
+ case NumTypeKind.v128:
+ return "v128";
+ }
+ }
+}
+
+/// An RTT (runtime type) type.
+class Rtt extends ValueType {
+ final DefType defType;
+ final int? depth;
+
+ const Rtt(this.defType, [this.depth]);
+
+ @override
+ bool get defaultable => false;
+
+ @override
+ bool isSubtypeOf(StorageType other) =>
+ other is Rtt &&
+ defType == other.defType &&
+ (other.depth == null || depth == other.depth);
+
+ @override
+ void serialize(Serializer s) {
+ if (depth != null) {
+ s.writeByte(0x69);
+ s.writeUnsigned(depth!);
+ } else {
+ s.writeByte(0x68);
+ }
+ s.writeSigned(defType.index);
+ }
+
+ @override
+ String toString() => depth == null ? "rtt $defType" : "rtt $depth $defType";
+
+ @override
+ bool operator ==(Object other) =>
+ other is Rtt && other.defType == defType && other.depth == depth;
+
+ @override
+ int get hashCode => defType.hashCode * (3 + (depth ?? -3) * 2);
+}
+
+/// A *reference type*.
+class RefType extends ValueType {
+ /// The *heap type* of this reference type.
+ final HeapType heapType;
+
+ /// The nullability of this reference type.
+ final bool nullable;
+
+ RefType(this.heapType, {bool? nullable})
+ : this.nullable = nullable ??
+ heapType.nullableByDefault ??
+ (throw "Unspecified nullability");
+
+ const RefType._(this.heapType, this.nullable);
+
+ /// A (possibly nullable) reference to the `any` heap type.
+ const RefType.any({bool nullable = AnyHeapType.defaultNullability})
+ : this._(HeapType.any, nullable);
+
+ /// A (possibly nullable) reference to the `eq` heap type.
+ const RefType.eq({bool nullable = EqHeapType.defaultNullability})
+ : this._(HeapType.eq, nullable);
+
+ /// A (possibly nullable) reference to the `func` heap type.
+ const RefType.func({bool nullable = FuncHeapType.defaultNullability})
+ : this._(HeapType.func, nullable);
+
+ /// A (possibly nullable) reference to the `data` heap type.
+ const RefType.data({bool nullable = DataHeapType.defaultNullability})
+ : this._(HeapType.data, nullable);
+
+ /// A (possibly nullable) reference to the `i31` heap type.
+ const RefType.i31({bool nullable = I31HeapType.defaultNullability})
+ : this._(HeapType.i31, nullable);
+
+ /// A (possibly nullable) reference to the `extern` heap type.
+ const RefType.extern({bool nullable = ExternHeapType.defaultNullability})
+ : this._(HeapType.extern, nullable);
+
+ /// A (possibly nullable) reference to a custom heap type.
+ RefType.def(DefType defType, {required bool nullable})
+ : this(defType, nullable: nullable);
+
+ @override
+ ValueType withNullability(bool nullable) =>
+ nullable == this.nullable ? this : RefType(heapType, nullable: nullable);
+
+ @override
+ bool get defaultable => nullable;
+
+ @override
+ bool isSubtypeOf(StorageType other) {
+ if (other is! RefType) return false;
+ if (nullable && !other.nullable) return false;
+ return heapType.isSubtypeOf(other.heapType);
+ }
+
+ @override
+ void serialize(Serializer s) {
+ if (nullable != heapType.nullableByDefault) {
+ s.writeByte(nullable ? 0x6C : 0x6B);
+ }
+ s.write(heapType);
+ }
+
+ @override
+ String toString() {
+ if (nullable == heapType.nullableByDefault) return "${heapType}ref";
+ return "ref${nullable ? " null " : " "}${heapType}";
+ }
+
+ @override
+ bool operator ==(Object other) =>
+ other is RefType &&
+ other.heapType == heapType &&
+ other.nullable == nullable;
+
+ @override
+ int get hashCode => heapType.hashCode * (nullable ? -1 : 1);
+}
+
+/// A *heap type*.
+abstract class HeapType implements Serializable {
+ const HeapType();
+
+ /// The `any` heap type.
+ static const any = AnyHeapType._();
+
+ /// The `eq` heap type.
+ static const eq = EqHeapType._();
+
+ /// The `func` heap type.
+ static const func = FuncHeapType._();
+
+ /// The `data` heap type.
+ static const data = DataHeapType._();
+
+ /// The `i31` heap type.
+ static const i31 = I31HeapType._();
+
+ /// The `extern` heap type.
+ static const extern = ExternHeapType._();
+
+ /// Whether this heap type is nullable by default, i.e. when written with the
+ /// -`ref` shorthand. A `null` value here means the heap type has no default
+ /// nullability, so the nullability of a reference has to be specified
+ /// explicitly.
+ bool? get nullableByDefault;
+
+ /// Whether this heap type is a declared subtype of the other heap type.
+ bool isSubtypeOf(HeapType other);
+
+ /// Whether this heap type is a structural subtype of the other heap type.
+ bool isStructuralSubtypeOf(HeapType other) => isSubtypeOf(other);
+}
+
+/// The `any` heap type.
+class AnyHeapType extends HeapType {
+ const AnyHeapType._();
+
+ static const defaultNullability = true;
+
+ @override
+ bool? get nullableByDefault => defaultNullability;
+
+ @override
+ bool isSubtypeOf(HeapType other) => other == HeapType.any;
+
+ @override
+ void serialize(Serializer s) => s.writeByte(0x6E);
+
+ @override
+ String toString() => "any";
+}
+
+/// The `eq` heap type.
+class EqHeapType extends HeapType {
+ const EqHeapType._();
+
+ static const defaultNullability = true;
+
+ @override
+ bool? get nullableByDefault => defaultNullability;
+
+ @override
+ bool isSubtypeOf(HeapType other) =>
+ other == HeapType.any || other == HeapType.eq;
+
+ @override
+ void serialize(Serializer s) => s.writeByte(0x6D);
+
+ @override
+ String toString() => "eq";
+}
+
+/// The `func` heap type.
+class FuncHeapType extends HeapType {
+ const FuncHeapType._();
+
+ static const defaultNullability = true;
+
+ @override
+ bool? get nullableByDefault => defaultNullability;
+
+ @override
+ bool isSubtypeOf(HeapType other) =>
+ other == HeapType.any || other == HeapType.func;
+
+ @override
+ void serialize(Serializer s) => s.writeByte(0x70);
+
+ @override
+ String toString() => "func";
+}
+
+/// The `data` heap type.
+class DataHeapType extends HeapType {
+ const DataHeapType._();
+
+ static const defaultNullability = false;
+
+ @override
+ bool? get nullableByDefault => defaultNullability;
+
+ @override
+ bool isSubtypeOf(HeapType other) =>
+ other == HeapType.any || other == HeapType.eq || other == HeapType.data;
+
+ @override
+ void serialize(Serializer s) => s.writeByte(0x67);
+
+ @override
+ String toString() => "data";
+}
+
+/// The `i31` heap type.
+class I31HeapType extends HeapType {
+ const I31HeapType._();
+
+ static const defaultNullability = false;
+
+ @override
+ bool? get nullableByDefault => defaultNullability;
+
+ @override
+ bool isSubtypeOf(HeapType other) =>
+ other == HeapType.any || other == HeapType.eq || other == HeapType.i31;
+
+ @override
+ void serialize(Serializer s) => s.writeByte(0x6A);
+
+ @override
+ String toString() => "i31";
+}
+
+/// The `extern` heap type.
+class ExternHeapType extends HeapType {
+ const ExternHeapType._();
+
+ static const defaultNullability = true;
+
+ @override
+ bool? get nullableByDefault => defaultNullability;
+
+ @override
+ bool isSubtypeOf(HeapType other) =>
+ other == HeapType.any || other == HeapType.extern;
+
+ @override
+ void serialize(Serializer s) => s.writeByte(0x6F);
+
+ @override
+ String toString() => "extern";
+}
+
+/// A custom heap type.
+abstract class DefType extends HeapType {
+ int? _index;
+
+ /// For nominal types: the declared supertype of this heap type.
+ final HeapType? superType;
+
+ /// The length of the supertype chain of this heap type.
+ final int depth;
+
+ DefType({this.superType})
+ : depth = superType is DefType ? superType.depth + 1 : 0;
+
+ int get index => _index ?? (throw "$runtimeType $this not added to module");
+ set index(int i) => _index = i;
+
+ bool get hasSuperType => superType != null;
+
+ @override
+ bool? get nullableByDefault => null;
+
+ @override
+ bool isSubtypeOf(HeapType other) {
+ if (this == other) return true;
+ if (hasSuperType) {
+ return superType!.isSubtypeOf(other);
+ }
+ return isStructuralSubtypeOf(other);
+ }
+
+ @override
+ void serialize(Serializer s) => s.writeSigned(index);
+
+ void serializeDefinition(Serializer s);
+}
+
+/// A custom function type.
+class FunctionType extends DefType {
+ final List<ValueType> inputs;
+ final List<ValueType> outputs;
+
+ FunctionType(this.inputs, this.outputs, {HeapType? superType})
+ : super(superType: superType);
+
+ @override
+ bool isStructuralSubtypeOf(HeapType other) {
+ if (other == HeapType.any || other == HeapType.func) return true;
+ if (other is! FunctionType) return false;
+ if (inputs.length != other.inputs.length) return false;
+ if (outputs.length != other.outputs.length) return false;
+ for (int i = 0; i < inputs.length; i++) {
+ // Inputs are contravariant.
+ if (!other.inputs[i].isSubtypeOf(inputs[i])) return false;
+ }
+ for (int i = 0; i < outputs.length; i++) {
+ // Outputs are covariant.
+ if (!outputs[i].isSubtypeOf(other.outputs[i])) return false;
+ }
+ return true;
+ }
+
+ @override
+ void serializeDefinition(Serializer s) {
+ s.writeByte(hasSuperType ? 0x5D : 0x60);
+ s.writeList(inputs);
+ s.writeList(outputs);
+ if (hasSuperType) {
+ assert(isStructuralSubtypeOf(superType!));
+ s.write(superType!);
+ }
+ }
+
+ @override
+ String toString() => "(${inputs.join(", ")}) -> (${outputs.join(", ")})";
+}
+
+/// A subtype of the `data` heap type, i.e. `struct` or `array`.
+abstract class DataType extends DefType {
+ final String name;
+
+ DataType(this.name, {HeapType? superType}) : super(superType: superType);
+
+ @override
+ String toString() => name;
+}
+
+/// A custom `struct` type.
+class StructType extends DataType {
+ final List<FieldType> fields = [];
+
+ StructType(String name, {Iterable<FieldType>? fields, HeapType? superType})
+ : super(name, superType: superType) {
+ if (fields != null) this.fields.addAll(fields);
+ }
+
+ @override
+ bool isStructuralSubtypeOf(HeapType other) {
+ if (other == HeapType.any ||
+ other == HeapType.eq ||
+ other == HeapType.data) {
+ return true;
+ }
+ if (other is! StructType) return false;
+ if (fields.length < other.fields.length) return false;
+ for (int i = 0; i < other.fields.length; i++) {
+ if (!fields[i].isSubtypeOf(other.fields[i])) return false;
+ }
+ return true;
+ }
+
+ @override
+ void serializeDefinition(Serializer s) {
+ s.writeByte(hasSuperType ? 0x5C : 0x5F);
+ s.writeList(fields);
+ if (hasSuperType) {
+ assert(isStructuralSubtypeOf(superType!));
+ s.write(superType!);
+ }
+ }
+}
+
+/// A custom `array` type.
+class ArrayType extends DataType {
+ late final FieldType elementType;
+
+ ArrayType(String name, {FieldType? elementType, HeapType? superType})
+ : super(name, superType: superType) {
+ if (elementType != null) this.elementType = elementType;
+ }
+
+ @override
+ bool isStructuralSubtypeOf(HeapType other) {
+ if (other == HeapType.any ||
+ other == HeapType.eq ||
+ other == HeapType.data) {
+ return true;
+ }
+ if (other is! ArrayType) return false;
+ return elementType.isSubtypeOf(other.elementType);
+ }
+
+ @override
+ void serializeDefinition(Serializer s) {
+ s.writeByte(hasSuperType ? 0x5B : 0x5E);
+ s.write(elementType);
+ if (hasSuperType) {
+ assert(isStructuralSubtypeOf(superType!));
+ s.write(superType!);
+ }
+ }
+}
+
+class _WithMutability<T extends StorageType> implements Serializable {
+ final T type;
+ final bool mutable;
+
+ _WithMutability(this.type, this.mutable);
+
+ @override
+ void serialize(Serializer s) {
+ s.write(type);
+ s.writeByte(mutable ? 0x01 : 0x00);
+ }
+
+ @override
+ String toString() => "${mutable ? "var " : "const "}$type";
+}
+
+/// A type for a global.
+///
+/// It consists of a type and a mutability.
+class GlobalType extends _WithMutability<ValueType> {
+ GlobalType(ValueType type, {bool mutable = true}) : super(type, mutable);
+}
+
+/// A type for a struct field or an array element.
+///
+/// It consists of a type and a mutability.
+class FieldType extends _WithMutability<StorageType> {
+ FieldType(StorageType type, {bool mutable = true}) : super(type, mutable);
+
+ /// The `i8` storage type as a field type.
+ FieldType.i8({bool mutable: true}) : this(PackedType.i8, mutable: mutable);
+
+ /// The `i16` storage type as a field type.
+ FieldType.i16({bool mutable: true}) : this(PackedType.i16, mutable: mutable);
+
+ bool isSubtypeOf(FieldType other) {
+ if (mutable != other.mutable) return false;
+ if (mutable) {
+ // Mutable fields are invariant.
+ return type == other.type;
+ } else {
+ // Immutable fields are covariant.
+ return type.isSubtypeOf(other.type);
+ }
+ }
+}
+
+enum PackedTypeKind { i8, i16 }
+
+/// A *packed type*, i.e. a storage type that only exists in memory.
+class PackedType implements StorageType {
+ final PackedTypeKind kind;
+
+ const PackedType._(this.kind);
+
+ /// The `i8` storage type.
+ static const i8 = PackedType._(PackedTypeKind.i8);
+
+ /// The `i16` storage type.
+ static const i16 = PackedType._(PackedTypeKind.i16);
+
+ @override
+ ValueType get unpacked => NumType.i32;
+
+ @override
+ bool isSubtypeOf(StorageType other) => this == other;
+
+ @override
+ bool get isPrimitive => true;
+
+ @override
+ int get byteSize {
+ switch (kind) {
+ case PackedTypeKind.i8:
+ return 1;
+ case PackedTypeKind.i16:
+ return 2;
+ }
+ }
+
+ @override
+ void serialize(Serializer s) {
+ switch (kind) {
+ case PackedTypeKind.i8:
+ s.writeByte(0x7A);
+ break;
+ case PackedTypeKind.i16:
+ s.writeByte(0x79);
+ break;
+ }
+ }
+
+ @override
+ String toString() {
+ switch (kind) {
+ case PackedTypeKind.i8:
+ return "i8";
+ case PackedTypeKind.i16:
+ return "i16";
+ }
+ }
+}
diff --git a/pkg/wasm_builder/lib/wasm_builder.dart b/pkg/wasm_builder/lib/wasm_builder.dart
new file mode 100644
index 0000000..8bf1b98
--- /dev/null
+++ b/pkg/wasm_builder/lib/wasm_builder.dart
@@ -0,0 +1,34 @@
+// Copyright (c) 2022, 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.
+
+export 'src/module.dart'
+ show
+ DataSegment,
+ DefinedFunction,
+ DefinedGlobal,
+ BaseFunction,
+ Global,
+ ImportedFunction,
+ ImportedGlobal,
+ Local,
+ Memory,
+ Module,
+ Table;
+export 'src/types.dart'
+ show
+ ArrayType,
+ DataType,
+ DefType,
+ FieldType,
+ FunctionType,
+ GlobalType,
+ HeapType,
+ NumType,
+ PackedType,
+ RefType,
+ Rtt,
+ StorageType,
+ StructType,
+ ValueType;
+export 'src/instructions.dart' show Instructions, Label, ValidationError;
diff --git a/pkg/wasm_builder/pubspec.yaml b/pkg/wasm_builder/pubspec.yaml
new file mode 100644
index 0000000..af4d545
--- /dev/null
+++ b/pkg/wasm_builder/pubspec.yaml
@@ -0,0 +1,9 @@
+name: wasm_builder
+# This package is not intended for consumption on pub.dev. DO NOT publish.
+publish_to: none
+description: Generate binary Wasm modules
+
+environment:
+ sdk: '>=2.12.0'
+
+dependencies:
diff --git a/sdk/bin/dart2wasm b/sdk/bin/dart2wasm
new file mode 100755
index 0000000..893fcda
--- /dev/null
+++ b/sdk/bin/dart2wasm
@@ -0,0 +1,48 @@
+#!/usr/bin/env bash
+# Copyright (c) 2022, 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.
+
+# Run dart2wasm on the Dart VM. This script assumes the Dart repo's
+# directory structure.
+
+function follow_links() {
+ file="$1"
+ while [ -h "$file" ]; do
+ # On Mac OS, readlink -f doesn't work.
+ file="$(readlink "$file")"
+ done
+ echo "$file"
+}
+
+# Unlike $0, $BASH_SOURCE points to the absolute path of this file.
+PROG_NAME="$(follow_links "$BASH_SOURCE")"
+
+# Handle the case where dart-sdk/bin has been symlinked to.
+BIN_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)"
+SDK_DIR="$(cd "${BIN_DIR}/.." ; pwd -P)"
+
+SDK_ARG="--dart-sdk=$SDK_DIR"
+
+DART="$BIN_DIR/dart"
+
+unset EXTRA_VM_OPTIONS
+declare -a EXTRA_VM_OPTIONS
+
+case $0 in
+ *_developer)
+ EXTRA_VM_OPTIONS+=('--enable_asserts')
+ ;;
+esac
+
+# We allow extra vm options to be passed in through an environment variable.
+if [[ $DART_VM_OPTIONS ]]; then
+ read -a OPTIONS <<< "$DART_VM_OPTIONS"
+ EXTRA_VM_OPTIONS+=("${OPTIONS[@]}")
+fi
+
+DART_ROOT="$(cd "${SDK_DIR}/.." ; pwd -P)"
+
+DART2WASM_COMPILER="$DART_ROOT/pkg/dart2wasm/bin/dart2wasm.dart"
+
+exec "$DART" "--packages=$DART_ROOT/.packages" "${EXTRA_VM_OPTIONS[@]}" "$DART2WASM_COMPILER" "$SDK_ARG" "$@"
diff --git a/sdk/bin/dart2wasm_developer b/sdk/bin/dart2wasm_developer
new file mode 100755
index 0000000..aabf8ea
--- /dev/null
+++ b/sdk/bin/dart2wasm_developer
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+# Copyright (c) 2022, 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.
+
+. ${BASH_SOURCE%_developer}
diff --git a/sdk/lib/_internal/vm/lib/compact_hash.dart b/sdk/lib/_internal/vm/lib/compact_hash.dart
index 6ed0c80..fa4b32d 100644
--- a/sdk/lib/_internal/vm/lib/compact_hash.dart
+++ b/sdk/lib/_internal/vm/lib/compact_hash.dart
@@ -624,6 +624,9 @@
V? operator [](Object? o) => _validKey(o) ? super[o] : null;
V? remove(Object? o) => _validKey(o) ? super.remove(o) : null;
+ @pragma("wasm:entry-point")
+ void operator []=(K key, V value);
+
_CompactLinkedCustomHashMap(this._equality, this._hasher, validKey)
: _validKey = (validKey != null) ? validKey : new _TypeTest<K>().test,
super(_HashBase._INITIAL_INDEX_SIZE);
diff --git a/sdk/lib/_internal/vm/lib/typed_data_patch.dart b/sdk/lib/_internal/vm/lib/typed_data_patch.dart
index 37587c7..68c63b1 100644
--- a/sdk/lib/_internal/vm/lib/typed_data_patch.dart
+++ b/sdk/lib/_internal/vm/lib/typed_data_patch.dart
@@ -2228,6 +2228,7 @@
}
@pragma("vm:entry-point")
+@pragma("wasm:entry-point")
class _Uint8List extends _TypedList
with _IntListMixin, _TypedIntListMixin<Uint8List>
implements Uint8List {
@@ -2281,6 +2282,7 @@
}
@pragma("vm:entry-point")
+@pragma("wasm:entry-point")
class _Uint8ClampedList extends _TypedList
with _IntListMixin, _TypedIntListMixin<Uint8ClampedList>
implements Uint8ClampedList {
@@ -2334,6 +2336,7 @@
}
@pragma("vm:entry-point")
+@pragma("wasm:entry-point")
class _Int16List extends _TypedList
with _IntListMixin, _TypedIntListMixin<Int16List>
implements Int16List {
@@ -2407,6 +2410,7 @@
}
@pragma("vm:entry-point")
+@pragma("wasm:entry-point")
class _Uint16List extends _TypedList
with _IntListMixin, _TypedIntListMixin<Uint16List>
implements Uint16List {
@@ -2480,6 +2484,7 @@
}
@pragma("vm:entry-point")
+@pragma("wasm:entry-point")
class _Int32List extends _TypedList
with _IntListMixin, _TypedIntListMixin<Int32List>
implements Int32List {
@@ -2540,6 +2545,7 @@
}
@pragma("vm:entry-point")
+@pragma("wasm:entry-point")
class _Uint32List extends _TypedList
with _IntListMixin, _TypedIntListMixin<Uint32List>
implements Uint32List {
@@ -2600,6 +2606,7 @@
}
@pragma("vm:entry-point")
+@pragma("wasm:entry-point")
class _Int64List extends _TypedList
with _IntListMixin, _TypedIntListMixin<Int64List>
implements Int64List {
@@ -2660,6 +2667,7 @@
}
@pragma("vm:entry-point")
+@pragma("wasm:entry-point")
class _Uint64List extends _TypedList
with _IntListMixin, _TypedIntListMixin<Uint64List>
implements Uint64List {
@@ -2720,6 +2728,7 @@
}
@pragma("vm:entry-point")
+@pragma("wasm:entry-point")
class _Float32List extends _TypedList
with _DoubleListMixin, _TypedDoubleListMixin<Float32List>
implements Float32List {
@@ -2781,6 +2790,7 @@
}
@pragma("vm:entry-point")
+@pragma("wasm:entry-point")
class _Float64List extends _TypedList
with _DoubleListMixin, _TypedDoubleListMixin<Float64List>
implements Float64List {
@@ -3040,6 +3050,7 @@
}
@pragma("vm:entry-point")
+@pragma("wasm:entry-point")
class _ExternalUint8Array extends _TypedList
with _IntListMixin, _TypedIntListMixin<Uint8List>
implements Uint8List {
@@ -4123,6 +4134,7 @@
}
@pragma("vm:entry-point")
+@pragma("wasm:entry-point")
class _Int8ArrayView extends _TypedListView
with _IntListMixin, _TypedIntListMixin<Int8List>
implements Int8List {
@@ -4164,6 +4176,7 @@
}
@pragma("vm:entry-point")
+@pragma("wasm:entry-point")
class _Uint8ArrayView extends _TypedListView
with _IntListMixin, _TypedIntListMixin<Uint8List>
implements Uint8List {
@@ -4205,6 +4218,7 @@
}
@pragma("vm:entry-point")
+@pragma("wasm:entry-point")
class _Uint8ClampedArrayView extends _TypedListView
with _IntListMixin, _TypedIntListMixin<Uint8ClampedList>
implements Uint8ClampedList {
@@ -4246,6 +4260,7 @@
}
@pragma("vm:entry-point")
+@pragma("wasm:entry-point")
class _Int16ArrayView extends _TypedListView
with _IntListMixin, _TypedIntListMixin<Int16List>
implements Int16List {
@@ -4300,6 +4315,7 @@
}
@pragma("vm:entry-point")
+@pragma("wasm:entry-point")
class _Uint16ArrayView extends _TypedListView
with _IntListMixin, _TypedIntListMixin<Uint16List>
implements Uint16List {
@@ -4355,6 +4371,7 @@
}
@pragma("vm:entry-point")
+@pragma("wasm:entry-point")
class _Int32ArrayView extends _TypedListView
with _IntListMixin, _TypedIntListMixin<Int32List>
implements Int32List {
@@ -4396,6 +4413,7 @@
}
@pragma("vm:entry-point")
+@pragma("wasm:entry-point")
class _Uint32ArrayView extends _TypedListView
with _IntListMixin, _TypedIntListMixin<Uint32List>
implements Uint32List {
@@ -4437,6 +4455,7 @@
}
@pragma("vm:entry-point")
+@pragma("wasm:entry-point")
class _Int64ArrayView extends _TypedListView
with _IntListMixin, _TypedIntListMixin<Int64List>
implements Int64List {
@@ -4478,6 +4497,7 @@
}
@pragma("vm:entry-point")
+@pragma("wasm:entry-point")
class _Uint64ArrayView extends _TypedListView
with _IntListMixin, _TypedIntListMixin<Uint64List>
implements Uint64List {
@@ -4519,6 +4539,7 @@
}
@pragma("vm:entry-point")
+@pragma("wasm:entry-point")
class _Float32ArrayView extends _TypedListView
with _DoubleListMixin, _TypedDoubleListMixin<Float32List>
implements Float32List {
@@ -4560,6 +4581,7 @@
}
@pragma("vm:entry-point")
+@pragma("wasm:entry-point")
class _Float64ArrayView extends _TypedListView
with _DoubleListMixin, _TypedDoubleListMixin<Float64List>
implements Float64List {
@@ -4718,6 +4740,7 @@
}
@pragma("vm:entry-point")
+@pragma("wasm:entry-point")
class _ByteDataView implements ByteData {
@pragma("vm:recognized", "other")
@pragma("vm:exact-result-type", _ByteDataView)
diff --git a/sdk/lib/_internal/wasm/lib/bool.dart b/sdk/lib/_internal/wasm/lib/bool.dart
new file mode 100644
index 0000000..929e519
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/bool.dart
@@ -0,0 +1,21 @@
+// Copyright (c) 2022, 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.
+
+@pragma("wasm:entry-point")
+class _BoxedBool implements bool {
+ // A boxed bool contains an unboxed bool.
+ @pragma("wasm:entry-point")
+ bool value = false;
+
+ @override
+ bool operator ==(Object other) {
+ return other is bool
+ ? this == other // Intrinsic ==
+ : false;
+ }
+
+ bool operator &(bool other) => this & other; // Intrinsic &
+ bool operator ^(bool other) => this ^ other; // Intrinsic ^
+ bool operator |(bool other) => this | other; // Intrinsic |
+}
diff --git a/sdk/lib/_internal/wasm/lib/class_id.dart b/sdk/lib/_internal/wasm/lib/class_id.dart
new file mode 100644
index 0000000..e650509
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/class_id.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2022, 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.
+
+// part of "internal_patch.dart";
+
+@pragma("wasm:entry-point")
+class ClassID {
+ external static int getID(Object value);
+
+ @pragma("wasm:class-id", "dart.typed_data#_ExternalUint8Array")
+ external static int get cidExternalUint8Array;
+ @pragma("wasm:class-id", "dart.typed_data#_Uint8List")
+ external static int get cidUint8Array;
+ @pragma("wasm:class-id", "dart.typed_data#_Uint8ArrayView")
+ external static int get cidUint8ArrayView;
+
+ // Dummy, only used by VM-specific hash table code.
+ static final int numPredefinedCids = 1;
+}
diff --git a/sdk/lib/_internal/wasm/lib/core_patch.dart b/sdk/lib/_internal/wasm/lib/core_patch.dart
new file mode 100644
index 0000000..64b7d9d
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/core_patch.dart
@@ -0,0 +1,51 @@
+// Copyright (c) 2022, 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:_internal" show patch;
+
+import "dart:_internal"
+ show
+ allocateOneByteString,
+ allocateTwoByteString,
+ CodeUnits,
+ copyRangeFromUint8ListToOneByteString,
+ doubleToIntBits,
+ EfficientLengthIterable,
+ FixedLengthListMixin,
+ IterableElementError,
+ ListIterator,
+ Lists,
+ mix64,
+ POWERS_OF_TEN,
+ SubListIterable,
+ UnmodifiableListMixin,
+ has63BitSmis,
+ makeFixedListUnmodifiable,
+ makeListFixedLength,
+ patch,
+ unsafeCast,
+ writeIntoOneByteString,
+ writeIntoTwoByteString;
+
+import "dart:collection"
+ show
+ HashMap,
+ IterableBase,
+ LinkedHashMap,
+ LinkedList,
+ LinkedListEntry,
+ ListBase,
+ MapBase,
+ Maps,
+ UnmodifiableMapBase,
+ UnmodifiableMapView;
+
+import 'dart:math' show Random;
+
+import "dart:typed_data"
+ show Endian, Uint8List, Int64List, Uint16List, Uint32List;
+
+import 'dart:wasm';
+
+typedef _Smi = int; // For compatibility with VM patch files
diff --git a/sdk/lib/_internal/wasm/lib/date_patch.dart b/sdk/lib/_internal/wasm/lib/date_patch.dart
new file mode 100644
index 0000000..f2f0672
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/date_patch.dart
@@ -0,0 +1,542 @@
+// Copyright (c) 2022, 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.
+
+// part of "core_patch.dart";
+
+// This file is identical to the VM `date_patch.dart` except for the
+// implementation of `_getCurrentMicros` and the `_jsDateNow` import.
+// TODO(askesc): Share this file with the VM when the patching mechanism gains
+// support for patching an external member from a patch in a separate patch.
+
+@pragma("wasm:import", "Date.now")
+external double _jsDateNow();
+
+// VM implementation of DateTime.
+@patch
+class DateTime {
+ // Natives.
+ // The natives have been moved up here to work around Issue 10401.
+ @pragma("vm:external-name", "DateTime_currentTimeMicros")
+ static int _getCurrentMicros() =>
+ (_jsDateNow() * Duration.microsecondsPerMillisecond).toInt();
+
+ @pragma("vm:external-name", "DateTime_timeZoneName")
+ external static String _timeZoneNameForClampedSeconds(int secondsSinceEpoch);
+
+ @pragma("vm:external-name", "DateTime_timeZoneOffsetInSeconds")
+ external static int _timeZoneOffsetInSecondsForClampedSeconds(
+ int secondsSinceEpoch);
+
+ // Daylight-savings independent adjustment for the local time zone.
+ @pragma("vm:external-name", "DateTime_localTimeZoneAdjustmentInSeconds")
+ external static int _localTimeZoneAdjustmentInSeconds();
+
+ static const _MICROSECOND_INDEX = 0;
+ static const _MILLISECOND_INDEX = 1;
+ static const _SECOND_INDEX = 2;
+ static const _MINUTE_INDEX = 3;
+ static const _HOUR_INDEX = 4;
+ static const _DAY_INDEX = 5;
+ static const _WEEKDAY_INDEX = 6;
+ static const _MONTH_INDEX = 7;
+ static const _YEAR_INDEX = 8;
+
+ List<int>? __parts;
+
+ @patch
+ DateTime.fromMillisecondsSinceEpoch(int millisecondsSinceEpoch,
+ {bool isUtc: false})
+ : this._withValue(
+ _validateMilliseconds(millisecondsSinceEpoch) *
+ Duration.microsecondsPerMillisecond,
+ isUtc: isUtc);
+
+ @patch
+ DateTime.fromMicrosecondsSinceEpoch(int microsecondsSinceEpoch,
+ {bool isUtc: false})
+ : this._withValue(microsecondsSinceEpoch, isUtc: isUtc);
+
+ @patch
+ DateTime._internal(int year, int month, int day, int hour, int minute,
+ int second, int millisecond, int microsecond, bool isUtc)
+ : this.isUtc = isUtc,
+ this._value = _brokenDownDateToValue(year, month, day, hour, minute,
+ second, millisecond, microsecond, isUtc) ??
+ -1 {
+ if (_value == -1) throw new ArgumentError();
+ if (isUtc == null) throw new ArgumentError();
+ }
+
+ static int _validateMilliseconds(int millisecondsSinceEpoch) =>
+ RangeError.checkValueInInterval(
+ millisecondsSinceEpoch,
+ -_maxMillisecondsSinceEpoch,
+ _maxMillisecondsSinceEpoch,
+ "millisecondsSinceEpoch");
+
+ @patch
+ DateTime._now()
+ : isUtc = false,
+ _value = _getCurrentMicros();
+
+ @patch
+ String get timeZoneName {
+ if (isUtc) return "UTC";
+ return _timeZoneName(microsecondsSinceEpoch);
+ }
+
+ @patch
+ Duration get timeZoneOffset {
+ if (isUtc) return new Duration();
+ int offsetInSeconds = _timeZoneOffsetInSeconds(microsecondsSinceEpoch);
+ return new Duration(seconds: offsetInSeconds);
+ }
+
+ @patch
+ bool operator ==(dynamic other) =>
+ other is DateTime &&
+ _value == other.microsecondsSinceEpoch &&
+ isUtc == other.isUtc;
+
+ @patch
+ bool isBefore(DateTime other) => _value < other.microsecondsSinceEpoch;
+
+ @patch
+ bool isAfter(DateTime other) => _value > other.microsecondsSinceEpoch;
+
+ @patch
+ bool isAtSameMomentAs(DateTime other) =>
+ _value == other.microsecondsSinceEpoch;
+
+ @patch
+ int compareTo(DateTime other) =>
+ _value.compareTo(other.microsecondsSinceEpoch);
+
+ /** The first list contains the days until each month in non-leap years. The
+ * second list contains the days in leap years. */
+ static const List<List<int>> _DAYS_UNTIL_MONTH = const [
+ const [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334],
+ const [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335]
+ ];
+
+ static List<int> _computeUpperPart(int localMicros) {
+ const int DAYS_IN_4_YEARS = 4 * 365 + 1;
+ const int DAYS_IN_100_YEARS = 25 * DAYS_IN_4_YEARS - 1;
+ const int DAYS_IN_400_YEARS = 4 * DAYS_IN_100_YEARS + 1;
+ const int DAYS_1970_TO_2000 = 30 * 365 + 7;
+ const int DAYS_OFFSET =
+ 1000 * DAYS_IN_400_YEARS + 5 * DAYS_IN_400_YEARS - DAYS_1970_TO_2000;
+ const int YEARS_OFFSET = 400000;
+
+ int resultYear = 0;
+ int resultMonth = 0;
+ int resultDay = 0;
+
+ // Always round down.
+ final int daysSince1970 =
+ _flooredDivision(localMicros, Duration.microsecondsPerDay);
+ int days = daysSince1970;
+ days += DAYS_OFFSET;
+ resultYear = 400 * (days ~/ DAYS_IN_400_YEARS) - YEARS_OFFSET;
+ days = unsafeCast<int>(days.remainder(DAYS_IN_400_YEARS));
+ days--;
+ int yd1 = days ~/ DAYS_IN_100_YEARS;
+ days = unsafeCast<int>(days.remainder(DAYS_IN_100_YEARS));
+ resultYear += 100 * yd1;
+ days++;
+ int yd2 = days ~/ DAYS_IN_4_YEARS;
+ days = unsafeCast<int>(days.remainder(DAYS_IN_4_YEARS));
+ resultYear += 4 * yd2;
+ days--;
+ int yd3 = days ~/ 365;
+ days = unsafeCast<int>(days.remainder(365));
+ resultYear += yd3;
+
+ bool isLeap = (yd1 == 0 || yd2 != 0) && yd3 == 0;
+ if (isLeap) days++;
+
+ List<int> daysUntilMonth = _DAYS_UNTIL_MONTH[isLeap ? 1 : 0];
+ for (resultMonth = 12;
+ daysUntilMonth[resultMonth - 1] > days;
+ resultMonth--) {
+ // Do nothing.
+ }
+ resultDay = days - daysUntilMonth[resultMonth - 1] + 1;
+
+ int resultMicrosecond = localMicros % Duration.microsecondsPerMillisecond;
+ int resultMillisecond =
+ _flooredDivision(localMicros, Duration.microsecondsPerMillisecond) %
+ Duration.millisecondsPerSecond;
+ int resultSecond =
+ _flooredDivision(localMicros, Duration.microsecondsPerSecond) %
+ Duration.secondsPerMinute;
+
+ int resultMinute =
+ _flooredDivision(localMicros, Duration.microsecondsPerMinute);
+ resultMinute %= Duration.minutesPerHour;
+
+ int resultHour =
+ _flooredDivision(localMicros, Duration.microsecondsPerHour);
+ resultHour %= Duration.hoursPerDay;
+
+ // In accordance with ISO 8601 a week
+ // starts with Monday. Monday has the value 1 up to Sunday with 7.
+ // 1970-1-1 was a Thursday.
+ int resultWeekday = ((daysSince1970 + DateTime.thursday - DateTime.monday) %
+ DateTime.daysPerWeek) +
+ DateTime.monday;
+
+ List<int> list = new List<int>.filled(_YEAR_INDEX + 1, 0);
+ list[_MICROSECOND_INDEX] = resultMicrosecond;
+ list[_MILLISECOND_INDEX] = resultMillisecond;
+ list[_SECOND_INDEX] = resultSecond;
+ list[_MINUTE_INDEX] = resultMinute;
+ list[_HOUR_INDEX] = resultHour;
+ list[_DAY_INDEX] = resultDay;
+ list[_WEEKDAY_INDEX] = resultWeekday;
+ list[_MONTH_INDEX] = resultMonth;
+ list[_YEAR_INDEX] = resultYear;
+ return list;
+ }
+
+ List<int> get _parts {
+ return __parts ??= _computeUpperPart(_localDateInUtcMicros);
+ }
+
+ @patch
+ DateTime add(Duration duration) {
+ return new DateTime._withValue(_value + duration.inMicroseconds,
+ isUtc: isUtc);
+ }
+
+ @patch
+ DateTime subtract(Duration duration) {
+ return new DateTime._withValue(_value - duration.inMicroseconds,
+ isUtc: isUtc);
+ }
+
+ @patch
+ Duration difference(DateTime other) {
+ return new Duration(microseconds: _value - other.microsecondsSinceEpoch);
+ }
+
+ @patch
+ int get millisecondsSinceEpoch =>
+ _value ~/ Duration.microsecondsPerMillisecond;
+
+ @patch
+ int get microsecondsSinceEpoch => _value;
+
+ @patch
+ int get microsecond => _parts[_MICROSECOND_INDEX];
+
+ @patch
+ int get millisecond => _parts[_MILLISECOND_INDEX];
+
+ @patch
+ int get second => _parts[_SECOND_INDEX];
+
+ @patch
+ int get minute => _parts[_MINUTE_INDEX];
+
+ @patch
+ int get hour => _parts[_HOUR_INDEX];
+
+ @patch
+ int get day => _parts[_DAY_INDEX];
+
+ @patch
+ int get weekday => _parts[_WEEKDAY_INDEX];
+
+ @patch
+ int get month => _parts[_MONTH_INDEX];
+
+ @patch
+ int get year => _parts[_YEAR_INDEX];
+
+ /**
+ * Returns the amount of microseconds in UTC that represent the same values
+ * as [this].
+ *
+ * Say `t` is the result of this function, then
+ * * `this.year == new DateTime.fromMicrosecondsSinceEpoch(t, true).year`,
+ * * `this.month == new DateTime.fromMicrosecondsSinceEpoch(t, true).month`,
+ * * `this.day == new DateTime.fromMicrosecondsSinceEpoch(t, true).day`,
+ * * `this.hour == new DateTime.fromMicrosecondsSinceEpoch(t, true).hour`,
+ * * ...
+ *
+ * Daylight savings is computed as if the date was computed in [1970..2037].
+ * If [this] lies outside this range then it is a year with similar
+ * properties (leap year, weekdays) is used instead.
+ */
+ int get _localDateInUtcMicros {
+ int micros = _value;
+ if (isUtc) return micros;
+ int offset =
+ _timeZoneOffsetInSeconds(micros) * Duration.microsecondsPerSecond;
+ return micros + offset;
+ }
+
+ static int _flooredDivision(int a, int b) {
+ return (a - (a < 0 ? b - 1 : 0)) ~/ b;
+ }
+
+ // Returns the days since 1970 for the start of the given [year].
+ // [year] may be before epoch.
+ static int _dayFromYear(int year) {
+ return 365 * (year - 1970) +
+ _flooredDivision(year - 1969, 4) -
+ _flooredDivision(year - 1901, 100) +
+ _flooredDivision(year - 1601, 400);
+ }
+
+ static bool _isLeapYear(int y) {
+ // (y % 16 == 0) matches multiples of 400, and is faster than % 400.
+ return (y % 4 == 0) && ((y % 16 == 0) || (y % 100 != 0));
+ }
+
+ /// Converts the given broken down date to microseconds.
+ @patch
+ static int? _brokenDownDateToValue(int year, int month, int day, int hour,
+ int minute, int second, int millisecond, int microsecond, bool isUtc) {
+ // Simplify calculations by working with zero-based month.
+ --month;
+ // Deal with under and overflow.
+ if (month >= 12) {
+ year += month ~/ 12;
+ month = month % 12;
+ } else if (month < 0) {
+ int realMonth = month % 12;
+ year += (month - realMonth) ~/ 12;
+ month = realMonth;
+ }
+
+ // First compute the seconds in UTC, independent of the [isUtc] flag. If
+ // necessary we will add the time-zone offset later on.
+ int days = day - 1;
+ days += _DAYS_UNTIL_MONTH[_isLeapYear(year) ? 1 : 0][month];
+ days += _dayFromYear(year);
+ int microsecondsSinceEpoch = days * Duration.microsecondsPerDay +
+ hour * Duration.microsecondsPerHour +
+ minute * Duration.microsecondsPerMinute +
+ second * Duration.microsecondsPerSecond +
+ millisecond * Duration.microsecondsPerMillisecond +
+ microsecond;
+
+ if (!isUtc) {
+ // Since [_timeZoneOffsetInSeconds] will crash if the input is far out of
+ // the valid range we do a preliminary test that weeds out values that can
+ // not become valid even with timezone adjustments.
+ // The timezone adjustment is always less than a day, so adding a security
+ // margin of one day should be enough.
+ if (microsecondsSinceEpoch.abs() >
+ _maxMillisecondsSinceEpoch * Duration.microsecondsPerMillisecond +
+ Duration.microsecondsPerDay) {
+ return null;
+ }
+
+ microsecondsSinceEpoch -= _toLocalTimeOffset(microsecondsSinceEpoch);
+ }
+ if (microsecondsSinceEpoch.abs() >
+ _maxMillisecondsSinceEpoch * Duration.microsecondsPerMillisecond) {
+ return null;
+ }
+ return microsecondsSinceEpoch;
+ }
+
+ static int _weekDay(y) {
+ // 1/1/1970 was a Thursday.
+ return (_dayFromYear(y) + 4) % 7;
+ }
+
+ /**
+ * Returns a year in the range 2008-2035 matching
+ * * leap year, and
+ * * week day of first day.
+ *
+ * Leap seconds are ignored.
+ * Adapted from V8's date implementation. See ECMA 262 - 15.9.1.9.
+ */
+ static int _equivalentYear(int year) {
+ // Returns year y so that _weekDay(y) == _weekDay(year).
+ // _weekDay returns the week day (in range 0 - 6).
+ // 1/1/1956 was a Sunday (i.e. weekday 0). 1956 was a leap-year.
+ // 1/1/1967 was a Sunday (i.e. weekday 0).
+ // Without leap years a subsequent year has a week day + 1 (for example
+ // 1/1/1968 was a Monday). With leap-years it jumps over one week day
+ // (e.g. 1/1/1957 was a Tuesday).
+ // After 12 years the weekdays have advanced by 12 days + 3 leap days =
+ // 15 days. 15 % 7 = 1. So after 12 years the week day has always
+ // (now independently of leap-years) advanced by one.
+ // weekDay * 12 gives thus a year starting with the wanted weekDay.
+ int recentYear = (_isLeapYear(year) ? 1956 : 1967) + (_weekDay(year) * 12);
+ // Close to the year 2008 the calendar cycles every 4 * 7 years (4 for the
+ // leap years, 7 for the weekdays).
+ // Find the year in the range 2008..2037 that is equivalent mod 28.
+ return 2008 + (recentYear - 2008) % 28;
+ }
+
+ /**
+ * Returns the UTC year for the corresponding [secondsSinceEpoch].
+ * It is relatively fast for values in the range 0 to year 2098.
+ *
+ * Code is adapted from V8.
+ */
+ static int _yearsFromSecondsSinceEpoch(int secondsSinceEpoch) {
+ const int DAYS_IN_4_YEARS = 4 * 365 + 1;
+ const int DAYS_IN_100_YEARS = 25 * DAYS_IN_4_YEARS - 1;
+ const int DAYS_YEAR_2098 = DAYS_IN_100_YEARS + 6 * DAYS_IN_4_YEARS;
+
+ int days = secondsSinceEpoch ~/ Duration.secondsPerDay;
+ if (days > 0 && days < DAYS_YEAR_2098) {
+ // According to V8 this fast case works for dates from 1970 to 2099.
+ return 1970 + (4 * days + 2) ~/ DAYS_IN_4_YEARS;
+ }
+ int micros = secondsSinceEpoch * Duration.microsecondsPerSecond;
+ return _computeUpperPart(micros)[_YEAR_INDEX];
+ }
+
+ /**
+ * Returns a date in seconds that is equivalent to the given
+ * date in microseconds [microsecondsSinceEpoch]. An equivalent
+ * date has the same fields (`month`, `day`, etc.) as the given
+ * date, but the `year` is in the range [1901..2038].
+ *
+ * * The time since the beginning of the year is the same.
+ * * If the given date is in a leap year then the returned
+ * seconds are in a leap year, too.
+ * * The week day of given date is the same as the one for the
+ * returned date.
+ */
+ static int _equivalentSeconds(int microsecondsSinceEpoch) {
+ const int CUT_OFF_SECONDS = 0x7FFFFFFF;
+
+ int secondsSinceEpoch = _flooredDivision(
+ microsecondsSinceEpoch, Duration.microsecondsPerSecond);
+
+ if (secondsSinceEpoch.abs() > CUT_OFF_SECONDS) {
+ int year = _yearsFromSecondsSinceEpoch(secondsSinceEpoch);
+ int days = _dayFromYear(year);
+ int equivalentYear = _equivalentYear(year);
+ int equivalentDays = _dayFromYear(equivalentYear);
+ int diffDays = equivalentDays - days;
+ secondsSinceEpoch += diffDays * Duration.secondsPerDay;
+ }
+ return secondsSinceEpoch;
+ }
+
+ static int _timeZoneOffsetInSeconds(int microsecondsSinceEpoch) {
+ int equivalentSeconds = _equivalentSeconds(microsecondsSinceEpoch);
+ return _timeZoneOffsetInSecondsForClampedSeconds(equivalentSeconds);
+ }
+
+ static String _timeZoneName(int microsecondsSinceEpoch) {
+ int equivalentSeconds = _equivalentSeconds(microsecondsSinceEpoch);
+ return _timeZoneNameForClampedSeconds(equivalentSeconds);
+ }
+
+ /// Finds the local time corresponding to a UTC date and time.
+ ///
+ /// The [microsecondsSinceEpoch] represents a particular
+ /// calendar date and clock time in UTC.
+ /// This methods returns a (usually different) point in time
+ /// where the local time had the same calendar date and clock
+ /// time (if such a time exists, otherwise it finds the "best"
+ /// substitute).
+ ///
+ /// A valid result is a point in time `microsecondsSinceEpoch - offset`
+ /// where the local time zone offset is `+offset`.
+ ///
+ /// In some cases there are two valid results, due to a time zone
+ /// change setting the clock back (for example exiting from daylight
+ /// saving time). In that case, we return the *earliest* valid result.
+ ///
+ /// In some cases there are no valid results, due to a time zone
+ /// change setting the clock forward (for example entering daylight
+ /// saving time). In that case, we return the time which would have
+ /// been correct in the earlier time zone (so asking for 2:30 AM
+ /// when clocks move directly from 2:00 to 3:00 will give the
+ /// time that *would have been* 2:30 in the earlier time zone,
+ /// which is now 3:30 in the local time zone).
+ ///
+ /// Returns the point in time as a number of microseconds since epoch.
+ static int _toLocalTimeOffset(int microsecondsSinceEpoch) {
+ // Argument is the UTC time corresponding to the desired
+ // calendar date/wall time.
+ // We now need to find an UTC time where the difference
+ // from `microsecondsSinceEpoch` is the same as the
+ // local time offset at that time. That is, we want to
+ // find `adjustment` in microseconds such that:
+ //
+ // _timeZoneOffsetInSeconds(microsecondsSinceEpoch - offset)
+ // * Duration.microsecondsPerSecond == offset
+ //
+ // Such an offset might not exist, if that wall time
+ // is skipped when a time zone change moves the clock forwards.
+ // In that case we pick a time after the switch which would be
+ // correct in the previous time zone.
+ // Also, there might be more than one solution if a time zone
+ // change moves the clock backwards and the same wall clock
+ // time occurs twice in the same day.
+ // In that case we pick the one in the time zone prior to
+ // the switch.
+
+ // Start with the time zone at the current microseconds since
+ // epoch. It's within one day of the real time we're looking for.
+
+ int offset = _timeZoneOffsetInSeconds(microsecondsSinceEpoch) *
+ Duration.microsecondsPerSecond;
+
+ // If offset is 0 (we're right around the UTC+0, and)
+ // we have found one solution.
+ if (offset != 0) {
+ // If not, try to find an actual solution in the time zone
+ // we just discovered.
+ int offset2 = _timeZoneOffsetInSeconds(microsecondsSinceEpoch - offset) *
+ Duration.microsecondsPerSecond;
+ if (offset2 != offset) {
+ // Also not a solution. We have found a second time zone
+ // within the same day. We assume that's all there are.
+ // Try again with the new time zone.
+ int offset3 =
+ _timeZoneOffsetInSeconds(microsecondsSinceEpoch - offset2) *
+ Duration.microsecondsPerSecond;
+ // Either offset3 is a solution (equal to offset2),
+ // or we have found two different time zones and no solution.
+ // In the latter case we choose the lower offset (latter time).
+ return (offset2 <= offset3 ? offset2 : offset3);
+ }
+ // We have found one solution and one time zone.
+ offset = offset2;
+ }
+ // Try to see if there is an earlier time zone which also
+ // has a solution.
+ // Pretends time zone changes are always at most two hours.
+ // (Double daylight saving happened, fx, in part of Canada in 1988).
+ int offset4 = _timeZoneOffsetInSeconds(microsecondsSinceEpoch -
+ offset -
+ 2 * Duration.microsecondsPerHour) *
+ Duration.microsecondsPerSecond;
+ if (offset4 > offset) {
+ // The time zone at the earlier time had a greater
+ // offset, so it's possible that the desired wall clock
+ // occurs in that time zone too.
+ if (offset4 == offset + 2 * Duration.microsecondsPerHour) {
+ // A second and earlier solution, so use that.
+ return offset4;
+ }
+ // The time zone differs one hour earlier, but not by one
+ // hour, so check again in that time zone.
+ int offset5 = _timeZoneOffsetInSeconds(microsecondsSinceEpoch - offset4) *
+ Duration.microsecondsPerSecond;
+ if (offset5 == offset4) {
+ // Found a second solution earlier than the first solution, so use that.
+ return offset4;
+ }
+ }
+ // Did not find a solution in the earlier time
+ // zone, so just use the original result.
+ return offset;
+ }
+}
diff --git a/sdk/lib/_internal/wasm/lib/developer.dart b/sdk/lib/_internal/wasm/lib/developer.dart
new file mode 100644
index 0000000..ba930b3
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/developer.dart
@@ -0,0 +1,61 @@
+// Copyright (c) 2022, 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.
+
+// This is a stub implementation of `dart:developer`.
+
+import "dart:_internal" show patch;
+
+import "dart:async" show Zone;
+
+// Stubs for `developer.dart`.
+
+@patch
+bool debugger({bool when: true, String? message}) => when;
+
+@patch
+Object? inspect(Object? object) => object;
+
+@patch
+void log(String message,
+ {DateTime? time,
+ int? sequenceNumber,
+ int level: 0,
+ String name: '',
+ Zone? zone,
+ Object? error,
+ StackTrace? stackTrace}) {}
+
+@patch
+void _postEvent(String eventKind, String eventData) {}
+
+@patch
+ServiceExtensionHandler? _lookupExtension(String method) => null;
+
+@patch
+_registerExtension(String method, ServiceExtensionHandler handler) {}
+
+// Stubs for `timeline.dart`.
+
+@patch
+bool _isDartStreamEnabled() => false;
+
+@patch
+int _getTraceClock() => _traceClock++;
+
+int _traceClock = 0;
+
+@patch
+int _getNextAsyncId() => 0;
+
+@patch
+void _reportTaskEvent(int taskId, String phase, String category, String name,
+ String argumentsAsJson) {}
+
+@patch
+void _reportFlowEvent(
+ String category, String name, int type, int id, String argumentsAsJson) {}
+
+@patch
+void _reportInstantEvent(
+ String category, String name, String argumentsAsJson) {}
diff --git a/sdk/lib/_internal/wasm/lib/double.dart b/sdk/lib/_internal/wasm/lib/double.dart
new file mode 100644
index 0000000..ea77a86
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/double.dart
@@ -0,0 +1,290 @@
+// Copyright (c) 2022, 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.
+
+// part of "core_patch.dart";
+
+@pragma("wasm:entry-point")
+class _BoxedDouble implements double {
+ // A boxed double contains an unboxed double.
+ @pragma("wasm:entry-point")
+ double value = 0.0;
+
+ static const int _signMask = 0x8000000000000000;
+ static const int _exponentMask = 0x7FF0000000000000;
+ static const int _mantissaMask = 0x000FFFFFFFFFFFFF;
+
+ int get hashCode {
+ int bits = doubleToIntBits(this);
+ if (bits == _signMask) bits = 0; // 0.0 == -0.0
+ return mix64(bits);
+ }
+
+ int get _identityHashCode => hashCode;
+
+ double operator +(num other) => this + other.toDouble(); // Intrinsic +
+ double operator -(num other) => this - other.toDouble(); // Intrinsic -
+ double operator *(num other) => this * other.toDouble(); // Intrinsic *
+ double operator /(num other) => this / other.toDouble(); // Intrinsic /
+
+ int operator ~/(num other) {
+ return _truncDiv(this, other.toDouble());
+ }
+
+ static int _truncDiv(double a, double b) {
+ return (a / b).toInt();
+ }
+
+ double operator %(num other) {
+ return _modulo(this, other.toDouble());
+ }
+
+ static double _modulo(double a, double b) {
+ double rem = a - (a / b).truncateToDouble() * b;
+ if (rem == 0.0) return 0.0;
+ if (rem < 0.0) {
+ if (b < 0.0) {
+ return rem - b;
+ } else {
+ return rem + b;
+ }
+ }
+ return rem;
+ }
+
+ double remainder(num other) {
+ return _remainder(this, other.toDouble());
+ }
+
+ static double _remainder(double a, double b) {
+ return a - (a / b).truncateToDouble() * b;
+ }
+
+ external double operator -();
+
+ bool operator ==(Object other) {
+ return other is double
+ ? this == other // Intrinsic ==
+ : other is int
+ ? this == other.toDouble() // Intrinsic ==
+ : false;
+ }
+
+ bool operator <(num other) => this < other.toDouble(); // Intrinsic <
+ bool operator >(num other) => this > other.toDouble(); // Intrinsic >
+ bool operator >=(num other) => this >= other.toDouble(); // Intrinsic >=
+ bool operator <=(num other) => this <= other.toDouble(); // Intrinsic <=
+
+ bool get isNegative {
+ int bits = doubleToIntBits(this);
+ return (bits & _signMask) != 0;
+ }
+
+ bool get isInfinite {
+ int bits = doubleToIntBits(this);
+ return (bits & _exponentMask) == _exponentMask &&
+ (bits & _mantissaMask) == 0;
+ }
+
+ bool get isNaN {
+ int bits = doubleToIntBits(this);
+ return (bits & _exponentMask) == _exponentMask &&
+ (bits & _mantissaMask) != 0;
+ }
+
+ bool get isFinite {
+ int bits = doubleToIntBits(this);
+ return (bits & _exponentMask) != _exponentMask;
+ }
+
+ double abs() {
+ // Handle negative 0.0.
+ if (this == 0.0) return 0.0;
+ return this < 0.0 ? -this : this;
+ }
+
+ double get sign {
+ if (this > 0.0) return 1.0;
+ if (this < 0.0) return -1.0;
+ return this; // +/-0.0 or NaN.
+ }
+
+ int round() => roundToDouble().toInt();
+ int floor() => floorToDouble().toInt();
+ int ceil() => ceilToDouble().toInt();
+ int truncate() => truncateToDouble().toInt();
+
+ external double roundToDouble();
+ external double floorToDouble();
+ external double ceilToDouble();
+ external double truncateToDouble();
+
+ num clamp(num lowerLimit, num upperLimit) {
+ if (lowerLimit.compareTo(upperLimit) > 0) {
+ throw new ArgumentError(lowerLimit);
+ }
+ if (lowerLimit.isNaN) return lowerLimit;
+ if (this.compareTo(lowerLimit) < 0) return lowerLimit;
+ if (this.compareTo(upperLimit) > 0) return upperLimit;
+ return this;
+ }
+
+ external int toInt();
+
+ double toDouble() {
+ return this;
+ }
+
+ static const int CACHE_SIZE_LOG2 = 3;
+ static const int CACHE_LENGTH = 1 << (CACHE_SIZE_LOG2 + 1);
+ static const int CACHE_MASK = CACHE_LENGTH - 1;
+ // Each key (double) followed by its toString result.
+ static final List _cache = new List.filled(CACHE_LENGTH, null);
+ static int _cacheEvictIndex = 0;
+
+ external String _toString();
+
+ String toString() {
+ // TODO(koda): Consider starting at most recently inserted.
+ for (int i = 0; i < CACHE_LENGTH; i += 2) {
+ // Need 'identical' to handle negative zero, etc.
+ if (identical(_cache[i], this)) {
+ return _cache[i + 1];
+ }
+ }
+ // TODO(koda): Consider optimizing all small integral values.
+ if (identical(0.0, this)) {
+ return "0.0";
+ }
+ String result = _toString();
+ // Replace the least recently inserted entry.
+ _cache[_cacheEvictIndex] = this;
+ _cache[_cacheEvictIndex + 1] = result;
+ _cacheEvictIndex = (_cacheEvictIndex + 2) & CACHE_MASK;
+ return result;
+ }
+
+ String toStringAsFixed(int fractionDigits) {
+ // See ECMAScript-262, 15.7.4.5 for details.
+
+ // Step 2.
+ if (fractionDigits < 0 || fractionDigits > 20) {
+ throw new RangeError.range(fractionDigits, 0, 20, "fractionDigits");
+ }
+
+ // Step 3.
+ double x = this;
+
+ // Step 4.
+ if (isNaN) return "NaN";
+
+ // Step 5 and 6 skipped. Will be dealt with by native function.
+
+ // Step 7.
+ if (x >= 1e21 || x <= -1e21) {
+ return x.toString();
+ }
+
+ return _toStringAsFixed(fractionDigits);
+ }
+
+ external String _toStringAsFixed(int fractionDigits);
+
+ String toStringAsExponential([int? fractionDigits]) {
+ // See ECMAScript-262, 15.7.4.6 for details.
+
+ // The EcmaScript specification checks for NaN and Infinity before looking
+ // at the fractionDigits. In Dart we are consistent with toStringAsFixed and
+ // look at the fractionDigits first.
+
+ // Step 7.
+ if (fractionDigits != null) {
+ if (fractionDigits < 0 || fractionDigits > 20) {
+ throw new RangeError.range(fractionDigits, 0, 20, "fractionDigits");
+ }
+ }
+
+ if (isNaN) return "NaN";
+ if (this == double.infinity) return "Infinity";
+ if (this == -double.infinity) return "-Infinity";
+
+ // The dart function prints the shortest representation when fractionDigits
+ // equals null. The native function wants -1 instead.
+ fractionDigits = (fractionDigits == null) ? -1 : fractionDigits;
+
+ return _toStringAsExponential(fractionDigits);
+ }
+
+ external String _toStringAsExponential(int fractionDigits);
+
+ String toStringAsPrecision(int precision) {
+ // See ECMAScript-262, 15.7.4.7 for details.
+
+ // The EcmaScript specification checks for NaN and Infinity before looking
+ // at the fractionDigits. In Dart we are consistent with toStringAsFixed and
+ // look at the fractionDigits first.
+
+ // Step 8.
+ if (precision < 1 || precision > 21) {
+ throw new RangeError.range(precision, 1, 21, "precision");
+ }
+
+ if (isNaN) return "NaN";
+ if (this == double.infinity) return "Infinity";
+ if (this == -double.infinity) return "-Infinity";
+
+ return _toStringAsPrecision(precision);
+ }
+
+ external String _toStringAsPrecision(int fractionDigits);
+
+ // Order is: NaN > Infinity > ... > 0.0 > -0.0 > ... > -Infinity.
+ int compareTo(num other) {
+ const int EQUAL = 0, LESS = -1, GREATER = 1;
+ if (this < other) {
+ return LESS;
+ } else if (this > other) {
+ return GREATER;
+ } else if (this == other) {
+ if (this == 0.0) {
+ bool thisIsNegative = isNegative;
+ bool otherIsNegative = other.isNegative;
+ if (thisIsNegative == otherIsNegative) {
+ return EQUAL;
+ }
+ return thisIsNegative ? LESS : GREATER;
+ } else if (other is int) {
+ // Compare as integers as it is more precise if the integer value is
+ // outside of MIN_EXACT_INT_TO_DOUBLE..MAX_EXACT_INT_TO_DOUBLE range.
+ const int MAX_EXACT_INT_TO_DOUBLE = 9007199254740992; // 2^53.
+ const int MIN_EXACT_INT_TO_DOUBLE = -MAX_EXACT_INT_TO_DOUBLE;
+ if ((MIN_EXACT_INT_TO_DOUBLE <= other) &&
+ (other <= MAX_EXACT_INT_TO_DOUBLE)) {
+ return EQUAL;
+ }
+ const bool limitIntsTo64Bits = ((1 << 64) == 0);
+ if (limitIntsTo64Bits) {
+ // With integers limited to 64 bits, double.toInt() clamps
+ // double value to fit into the MIN_INT64..MAX_INT64 range.
+ // MAX_INT64 is not precisely representable as double, so
+ // integers near MAX_INT64 compare as equal to (MAX_INT64 + 1) when
+ // represented as doubles.
+ // There is no similar problem with MIN_INT64 as it is precisely
+ // representable as double.
+ const double maxInt64Plus1AsDouble = 9223372036854775808.0;
+ if (this >= maxInt64Plus1AsDouble) {
+ return GREATER;
+ }
+ }
+ return toInt().compareTo(other);
+ } else {
+ return EQUAL;
+ }
+ } else if (isNaN) {
+ return other.isNaN ? EQUAL : GREATER;
+ } else {
+ // Other is NaN.
+ return LESS;
+ }
+ }
+}
diff --git a/sdk/lib/_internal/wasm/lib/expando_patch.dart b/sdk/lib/_internal/wasm/lib/expando_patch.dart
new file mode 100644
index 0000000..2be5189
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/expando_patch.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2022, 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:_internal" show patch;
+
+// Stub Expando implementation to make the Expando class compile.
+
+@patch
+class Expando<T> {
+ @patch
+ Expando([String? name]) : name = name;
+}
diff --git a/sdk/lib/_internal/wasm/lib/function.dart b/sdk/lib/_internal/wasm/lib/function.dart
new file mode 100644
index 0000000..b698573
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/function.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2022, 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.
+
+/// Base class for closure objects.
+@pragma("wasm:entry-point")
+class _Function {
+ @pragma("wasm:entry-point")
+ WasmDataRef context;
+
+ _Function._(this.context);
+}
diff --git a/sdk/lib/_internal/wasm/lib/growable_list.dart b/sdk/lib/_internal/wasm/lib/growable_list.dart
new file mode 100644
index 0000000..7b4912b
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/growable_list.dart
@@ -0,0 +1,305 @@
+// Copyright (c) 2022, 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.
+
+// part of "core_patch.dart";
+
+@pragma("wasm:entry-point")
+class _GrowableList<E> extends _ModifiableList<E> {
+ void insert(int index, E element) {
+ if ((index < 0) || (index > length)) {
+ throw new RangeError.range(index, 0, length);
+ }
+ int oldLength = this.length;
+ add(element);
+ if (index == oldLength) {
+ return;
+ }
+ Lists.copy(this, index, this, index + 1, oldLength - index);
+ this[index] = element;
+ }
+
+ E removeAt(int index) {
+ var result = this[index];
+ int newLength = this.length - 1;
+ if (index < newLength) {
+ Lists.copy(this, index + 1, this, index, newLength - index);
+ }
+ this.length = newLength;
+ return result;
+ }
+
+ bool remove(Object? element) {
+ for (int i = 0; i < this.length; i++) {
+ if (this[i] == element) {
+ removeAt(i);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void insertAll(int index, Iterable<E> iterable) {
+ if (index < 0 || index > length) {
+ throw new RangeError.range(index, 0, length);
+ }
+ if (iterable is! _ListBase) {
+ // Read out all elements before making room to ensure consistency of the
+ // modified list in case the iterator throws.
+ iterable = _List.of(iterable);
+ }
+ int insertionLength = iterable.length;
+ int capacity = _capacity;
+ int newLength = length + insertionLength;
+ if (newLength > capacity) {
+ do {
+ capacity = _nextCapacity(capacity);
+ } while (newLength > capacity);
+ _grow(capacity);
+ }
+ _setLength(newLength);
+ setRange(index + insertionLength, this.length, this, index);
+ setAll(index, iterable);
+ }
+
+ void removeRange(int start, int end) {
+ RangeError.checkValidRange(start, end, this.length);
+ Lists.copy(this, end, this, start, this.length - end);
+ this.length = this.length - (end - start);
+ }
+
+ _GrowableList._(int length, int capacity) : super(length, capacity);
+
+ factory _GrowableList(int length) {
+ return _GrowableList<E>._(length, length);
+ }
+
+ factory _GrowableList.withCapacity(int capacity) {
+ return _GrowableList<E>._(0, capacity);
+ }
+
+ // Specialization of List.empty constructor for growable == true.
+ // Used by pkg/vm/lib/transformations/list_factory_specializer.dart.
+ factory _GrowableList.empty() => _GrowableList(0);
+
+ // Specialization of List.filled constructor for growable == true.
+ // Used by pkg/vm/lib/transformations/list_factory_specializer.dart.
+ factory _GrowableList.filled(int length, E fill) {
+ final result = _GrowableList<E>(length);
+ if (fill != null) {
+ for (int i = 0; i < result.length; i++) {
+ result[i] = fill;
+ }
+ }
+ return result;
+ }
+
+ // Specialization of List.generate constructor for growable == true.
+ // Used by pkg/vm/lib/transformations/list_factory_specializer.dart.
+ factory _GrowableList.generate(int length, E generator(int index)) {
+ final result = _GrowableList<E>(length);
+ for (int i = 0; i < result.length; ++i) {
+ result[i] = generator(i);
+ }
+ return result;
+ }
+
+ // Specialization of List.of constructor for growable == true.
+ factory _GrowableList.of(Iterable<E> elements) {
+ if (elements is _ListBase) {
+ return _GrowableList._ofListBase(unsafeCast(elements));
+ }
+ if (elements is EfficientLengthIterable) {
+ return _GrowableList._ofEfficientLengthIterable(unsafeCast(elements));
+ }
+ return _GrowableList._ofOther(elements);
+ }
+
+ factory _GrowableList._ofListBase(_ListBase<E> elements) {
+ final int length = elements.length;
+ final list = _GrowableList<E>(length);
+ for (int i = 0; i < length; i++) {
+ list[i] = elements[i];
+ }
+ return list;
+ }
+
+ factory _GrowableList._ofEfficientLengthIterable(
+ EfficientLengthIterable<E> elements) {
+ final int length = elements.length;
+ final list = _GrowableList<E>(length);
+ if (length > 0) {
+ int i = 0;
+ for (var element in elements) {
+ list[i++] = element;
+ }
+ if (i != length) throw ConcurrentModificationError(elements);
+ }
+ return list;
+ }
+
+ factory _GrowableList._ofOther(Iterable<E> elements) {
+ final list = _GrowableList<E>(0);
+ for (var elements in elements) {
+ list.add(elements);
+ }
+ return list;
+ }
+
+ _GrowableList._withData(WasmObjectArray<Object?> data)
+ : super._withData(data.length, data);
+
+ int get _capacity => _data.length;
+
+ void set length(int new_length) {
+ if (new_length > length) {
+ // Verify that element type is nullable.
+ null as E;
+ if (new_length > _capacity) {
+ _grow(new_length);
+ }
+ _setLength(new_length);
+ return;
+ }
+ final int new_capacity = new_length;
+ // We are shrinking. Pick the method which has fewer writes.
+ // In the shrink-to-fit path, we write |new_capacity + new_length| words
+ // (null init + copy).
+ // In the non-shrink-to-fit path, we write |length - new_length| words
+ // (null overwrite).
+ final bool shouldShrinkToFit =
+ (new_capacity + new_length) < (length - new_length);
+ if (shouldShrinkToFit) {
+ _shrink(new_capacity, new_length);
+ } else {
+ for (int i = new_length; i < length; i++) {
+ _data.write(i, null);
+ }
+ }
+ _setLength(new_length);
+ }
+
+ void _setLength(int new_length) {
+ _length = new_length;
+ }
+
+ void add(E value) {
+ var len = length;
+ if (len == _capacity) {
+ _growToNextCapacity();
+ }
+ _setLength(len + 1);
+ this[len] = value;
+ }
+
+ void addAll(Iterable<E> iterable) {
+ var len = length;
+ if (iterable is EfficientLengthIterable) {
+ if (identical(iterable, this)) {
+ throw new ConcurrentModificationError(this);
+ }
+ var cap = _capacity;
+ // Pregrow if we know iterable.length.
+ var iterLen = iterable.length;
+ if (iterLen == 0) {
+ return;
+ }
+ var newLen = len + iterLen;
+ if (newLen > cap) {
+ do {
+ cap = _nextCapacity(cap);
+ } while (newLen > cap);
+ _grow(cap);
+ }
+ }
+ Iterator it = iterable.iterator;
+ if (!it.moveNext()) return;
+ do {
+ while (len < _capacity) {
+ int newLen = len + 1;
+ this._setLength(newLen);
+ this[len] = it.current;
+ if (!it.moveNext()) return;
+ if (this.length != newLen) throw new ConcurrentModificationError(this);
+ len = newLen;
+ }
+ _growToNextCapacity();
+ } while (true);
+ }
+
+ E removeLast() {
+ var len = length - 1;
+ var elem = this[len];
+ this.length = len;
+ return elem;
+ }
+
+ // Shared array used as backing for new empty growable lists.
+ static final WasmObjectArray<Object?> _emptyData =
+ WasmObjectArray<Object?>(0);
+
+ static WasmObjectArray<Object?> _allocateData(int capacity) {
+ if (capacity == 0) {
+ // Use shared empty list as backing.
+ return _emptyData;
+ }
+ return new WasmObjectArray<Object?>(capacity);
+ }
+
+ // Grow from 0 to 3, and then double + 1.
+ int _nextCapacity(int old_capacity) => (old_capacity * 2) | 3;
+
+ void _grow(int new_capacity) {
+ var newData = WasmObjectArray<Object?>(new_capacity);
+ for (int i = 0; i < length; i++) {
+ newData.write(i, this[i]);
+ }
+ _data = newData;
+ }
+
+ void _growToNextCapacity() {
+ _grow(_nextCapacity(_capacity));
+ }
+
+ void _shrink(int new_capacity, int new_length) {
+ var newData = _allocateData(new_capacity);
+ for (int i = 0; i < new_length; i++) {
+ newData.write(i, this[i]);
+ }
+ _data = newData;
+ }
+
+ Iterator<E> get iterator {
+ return new _GrowableListIterator<E>(this);
+ }
+}
+
+// Iterator for growable lists.
+class _GrowableListIterator<E> implements Iterator<E> {
+ final _GrowableList<E> _list;
+ final int _length; // Cache list length for modification check.
+ int _index;
+ E? _current;
+
+ _GrowableListIterator(_GrowableList<E> list)
+ : _list = list,
+ _length = list.length,
+ _index = 0 {
+ assert(list is _List<E> || list is _ImmutableList<E>);
+ }
+
+ E get current => _current as E;
+
+ bool moveNext() {
+ if (_list.length != _length) {
+ throw ConcurrentModificationError(_list);
+ }
+ if (_index >= _length) {
+ _current = null;
+ return false;
+ }
+ _current = unsafeCast(_list._data.read(_index));
+ _index++;
+ return true;
+ }
+}
diff --git a/sdk/lib/_internal/wasm/lib/hash_factories.dart b/sdk/lib/_internal/wasm/lib/hash_factories.dart
new file mode 100644
index 0000000..4217e38
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/hash_factories.dart
@@ -0,0 +1,51 @@
+// Copyright (c) 2022, 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.
+
+@patch
+class LinkedHashMap<K, V> {
+ @patch
+ factory LinkedHashMap(
+ {bool equals(K key1, K key2)?,
+ int hashCode(K key)?,
+ bool isValidKey(potentialKey)?}) {
+ if (isValidKey == null) {
+ if (identical(identityHashCode, hashCode) &&
+ identical(identical, equals)) {
+ return new _CompactLinkedIdentityHashMap<K, V>();
+ }
+ }
+ hashCode ??= _defaultHashCode;
+ equals ??= _defaultEquals;
+ return new _CompactLinkedCustomHashMap<K, V>(equals, hashCode, isValidKey);
+ }
+
+ @pragma("wasm:entry-point")
+ factory LinkedHashMap._default() =>
+ _CompactLinkedCustomHashMap<K, V>(_defaultEquals, _defaultHashCode, null);
+
+ @patch
+ factory LinkedHashMap.identity() => new _CompactLinkedIdentityHashMap<K, V>();
+}
+
+@patch
+class LinkedHashSet<E> {
+ @patch
+ factory LinkedHashSet(
+ {bool equals(E e1, E e2)?,
+ int hashCode(E e)?,
+ bool isValidKey(potentialKey)?}) {
+ if (isValidKey == null) {
+ if (identical(identityHashCode, hashCode) &&
+ identical(identical, equals)) {
+ return new _CompactLinkedIdentityHashSet<E>();
+ }
+ }
+ hashCode ??= _defaultHashCode;
+ equals ??= _defaultEquals;
+ return new _CompactLinkedCustomHashSet<E>(equals, hashCode, isValidKey);
+ }
+
+ @patch
+ factory LinkedHashSet.identity() => new _CompactLinkedIdentityHashSet<E>();
+}
diff --git a/sdk/lib/_internal/wasm/lib/identical_patch.dart b/sdk/lib/_internal/wasm/lib/identical_patch.dart
new file mode 100644
index 0000000..614bef6
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/identical_patch.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2022, 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.
+
+// part of "core_patch.dart";
+
+@patch
+external bool identical(Object? a, Object? b);
+
+@patch
+int identityHashCode(Object? object) =>
+ object == null ? Null._HASH_CODE : object._identityHashCode;
diff --git a/sdk/lib/_internal/wasm/lib/immutable_map.dart b/sdk/lib/_internal/wasm/lib/immutable_map.dart
new file mode 100644
index 0000000..74127f4
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/immutable_map.dart
@@ -0,0 +1,221 @@
+// Copyright (c) 2022, 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.
+
+// part of "core_patch.dart";
+
+/// Immutable map class for compiler generated map literals.
+// TODO(lrn): Extend MapBase with UnmodifiableMapMixin when mixins
+// support forwarding const constructors.
+@pragma("wasm:entry-point")
+class _ImmutableMap<K, V> implements Map<K, V> {
+ final _ImmutableList _kvPairs;
+
+ @pragma("wasm:entry-point")
+ const _ImmutableMap._create(_ImmutableList keyValuePairs)
+ : _kvPairs = keyValuePairs;
+
+ Map<K2, V2> cast<K2, V2>() => Map.castFrom<K, V, K2, V2>(this);
+
+ V? operator [](Object? key) {
+ // To preserve the key-value order of the map literal, the keys are
+ // not sorted. Need to do linear search or implement an additional
+ // lookup table.
+ for (int i = 0; i < _kvPairs.length - 1; i += 2) {
+ if (key == _kvPairs[i]) {
+ return _kvPairs[i + 1];
+ }
+ }
+ return null;
+ }
+
+ bool get isEmpty {
+ return _kvPairs.length == 0;
+ }
+
+ bool get isNotEmpty => !isEmpty;
+
+ int get length {
+ return _kvPairs.length ~/ 2;
+ }
+
+ void forEach(void f(K key, V value)) {
+ for (int i = 0; i < _kvPairs.length; i += 2) {
+ f(_kvPairs[i], _kvPairs[i + 1]);
+ }
+ }
+
+ Iterable<K> get keys {
+ return new _ImmutableMapKeyIterable<K>(this);
+ }
+
+ Iterable<V> get values {
+ return new _ImmutableMapValueIterable<V>(this);
+ }
+
+ bool containsKey(Object? key) {
+ for (int i = 0; i < _kvPairs.length; i += 2) {
+ if (key == _kvPairs[i]) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool containsValue(Object? value) {
+ for (int i = 1; i < _kvPairs.length; i += 2) {
+ if (value == _kvPairs[i]) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void operator []=(K key, V value) {
+ throw new UnsupportedError("Cannot set value in unmodifiable Map");
+ }
+
+ void addAll(Map<K, V> other) {
+ throw new UnsupportedError("Cannot set value in unmodifiable Map");
+ }
+
+ V putIfAbsent(K key, V ifAbsent()) {
+ throw new UnsupportedError("Cannot set value in unmodifiable Map");
+ }
+
+ void clear() {
+ throw new UnsupportedError("Cannot clear unmodifiable Map");
+ }
+
+ V? remove(Object? key) {
+ throw new UnsupportedError("Cannot remove from unmodifiable Map");
+ }
+
+ Iterable<MapEntry<K, V>> get entries =>
+ new _ImmutableMapEntryIterable<K, V>(this);
+
+ Map<K2, V2> map<K2, V2>(MapEntry<K2, V2> f(K key, V value)) {
+ var result = <K2, V2>{};
+ for (int i = 0; i < _kvPairs.length; i += 2) {
+ var entry = f(_kvPairs[i], _kvPairs[i + 1]);
+ result[entry.key] = entry.value;
+ }
+ return result;
+ }
+
+ void addEntries(Iterable<MapEntry<K, V>> newEntries) {
+ throw new UnsupportedError("Cannot modify an unmodifiable Map");
+ }
+
+ V update(K key, V update(V value), {V ifAbsent()?}) {
+ throw new UnsupportedError("Cannot modify an unmodifiable Map");
+ }
+
+ void updateAll(V update(K key, V value)) {
+ throw new UnsupportedError("Cannot modify an unmodifiable Map");
+ }
+
+ void removeWhere(bool predicate(K key, V value)) {
+ throw new UnsupportedError("Cannot modify an unmodifiable Map");
+ }
+
+ String toString() => MapBase.mapToString(this);
+}
+
+class _ImmutableMapKeyIterable<E> extends EfficientLengthIterable<E> {
+ final _ImmutableMap _map;
+ _ImmutableMapKeyIterable(this._map);
+
+ Iterator<E> get iterator {
+ return new _ImmutableMapKeyIterator<E>(_map);
+ }
+
+ int get length => _map.length;
+}
+
+class _ImmutableMapValueIterable<E> extends EfficientLengthIterable<E> {
+ final _ImmutableMap _map;
+ _ImmutableMapValueIterable(this._map);
+
+ Iterator<E> get iterator {
+ return new _ImmutableMapValueIterator<E>(_map);
+ }
+
+ int get length => _map.length;
+}
+
+class _ImmutableMapEntryIterable<K, V>
+ extends EfficientLengthIterable<MapEntry<K, V>> {
+ final _ImmutableMap _map;
+ _ImmutableMapEntryIterable(this._map);
+
+ Iterator<MapEntry<K, V>> get iterator {
+ return new _ImmutableMapEntryIterator<K, V>(_map);
+ }
+
+ int get length => _map.length;
+}
+
+class _ImmutableMapKeyIterator<E> implements Iterator<E> {
+ _ImmutableMap _map;
+ int _nextIndex = 0;
+ E? _current;
+
+ _ImmutableMapKeyIterator(this._map);
+
+ bool moveNext() {
+ int newIndex = _nextIndex;
+ if (newIndex < _map.length) {
+ _nextIndex = newIndex + 1;
+ _current = _map._kvPairs[newIndex * 2];
+ return true;
+ }
+ _current = null;
+ return false;
+ }
+
+ E get current => _current as E;
+}
+
+class _ImmutableMapValueIterator<E> implements Iterator<E> {
+ _ImmutableMap _map;
+ int _nextIndex = 0;
+ E? _current;
+
+ _ImmutableMapValueIterator(this._map);
+
+ bool moveNext() {
+ int newIndex = _nextIndex;
+ if (newIndex < _map.length) {
+ _nextIndex = newIndex + 1;
+ _current = _map._kvPairs[newIndex * 2 + 1];
+ return true;
+ }
+ _current = null;
+ return false;
+ }
+
+ E get current => _current as E;
+}
+
+class _ImmutableMapEntryIterator<K, V> implements Iterator<MapEntry<K, V>> {
+ _ImmutableMap _map;
+ int _nextIndex = 0;
+ MapEntry<K, V>? _current;
+
+ _ImmutableMapEntryIterator(this._map);
+
+ bool moveNext() {
+ int newIndex = _nextIndex;
+ if (newIndex < _map.length) {
+ _nextIndex = newIndex + 1;
+ _current = new MapEntry<K, V>(
+ _map._kvPairs[newIndex * 2], _map._kvPairs[newIndex * 2 + 1]);
+ return true;
+ }
+ _current = null;
+ return false;
+ }
+
+ MapEntry<K, V> get current => _current as MapEntry<K, V>;
+}
diff --git a/sdk/lib/_internal/wasm/lib/int.dart b/sdk/lib/_internal/wasm/lib/int.dart
new file mode 100644
index 0000000..e41f186
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/int.dart
@@ -0,0 +1,610 @@
+// Copyright (c) 2022, 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.
+
+// part of "core_patch.dart";
+
+@pragma("wasm:entry-point")
+class _BoxedInt implements int {
+ // A boxed int contains an unboxed int.
+ @pragma("wasm:entry-point")
+ int value = 0;
+
+ external num operator +(num other);
+ external num operator -(num other);
+ external num operator *(num other);
+
+ double operator /(num other) {
+ return this.toDouble() / other.toDouble();
+ }
+
+ int operator ~/(num other) => other is int
+ ? this ~/ other
+ : _BoxedDouble._truncDiv(toDouble(), unsafeCast<double>(other));
+
+ num operator %(num other) => other is int
+ ? _modulo(this, other)
+ : _BoxedDouble._modulo(toDouble(), unsafeCast<double>(other));
+
+ static int _modulo(int a, int b) {
+ int rem = a - (a ~/ b) * b;
+ if (rem < 0) {
+ if (b < 0) {
+ return rem - b;
+ } else {
+ return rem + b;
+ }
+ }
+ return rem;
+ }
+
+ num remainder(num other) => other is int
+ ? this - (this ~/ other) * other
+ : _BoxedDouble._remainder(toDouble(), unsafeCast<double>(other));
+
+ external int operator -();
+
+ external int operator &(int other);
+ external int operator |(int other);
+ external int operator ^(int other);
+
+ external int operator >>(int other);
+ external int operator >>>(int other);
+ external int operator <<(int other);
+
+ external bool operator <(num other);
+ external bool operator >(num other);
+ external bool operator >=(num other);
+ external bool operator <=(num other);
+
+ bool operator ==(Object other) {
+ return other is int
+ ? this == other // Intrinsic ==
+ : other is double
+ ? this.toDouble() == other // Intrinsic ==
+ : false;
+ }
+
+ int abs() {
+ return this < 0 ? -this : this;
+ }
+
+ int get sign {
+ return (this > 0)
+ ? 1
+ : (this < 0)
+ ? -1
+ : 0;
+ }
+
+ bool get isEven => (this & 1) == 0;
+ bool get isOdd => (this & 1) != 0;
+ bool get isNaN => false;
+ bool get isNegative => this < 0;
+ bool get isInfinite => false;
+ bool get isFinite => true;
+
+ int toUnsigned(int width) {
+ return this & ((1 << width) - 1);
+ }
+
+ int toSigned(int width) {
+ // The value of binary number weights each bit by a power of two. The
+ // twos-complement value weights the sign bit negatively. We compute the
+ // value of the negative weighting by isolating the sign bit with the
+ // correct power of two weighting and subtracting it from the value of the
+ // lower bits.
+ int signMask = 1 << (width - 1);
+ return (this & (signMask - 1)) - (this & signMask);
+ }
+
+ int compareTo(num other) {
+ const int EQUAL = 0, LESS = -1, GREATER = 1;
+ if (other is double) {
+ const int MAX_EXACT_INT_TO_DOUBLE = 9007199254740992; // 2^53.
+ const int MIN_EXACT_INT_TO_DOUBLE = -MAX_EXACT_INT_TO_DOUBLE;
+ // With int limited to 64 bits, double.toInt() clamps
+ // double value to fit into the MIN_INT64..MAX_INT64 range.
+ // Check if the double value is outside of this range.
+ // This check handles +/-infinity as well.
+ const double minInt64AsDouble = -9223372036854775808.0;
+ // MAX_INT64 is not precisely representable in doubles, so
+ // check against (MAX_INT64 + 1).
+ const double maxInt64Plus1AsDouble = 9223372036854775808.0;
+ if (other < minInt64AsDouble) {
+ return GREATER;
+ } else if (other >= maxInt64Plus1AsDouble) {
+ return LESS;
+ }
+ if (other.isNaN) {
+ return LESS;
+ }
+ if (MIN_EXACT_INT_TO_DOUBLE <= this && this <= MAX_EXACT_INT_TO_DOUBLE) {
+ // Let the double implementation deal with -0.0.
+ return -(other.compareTo(this.toDouble()));
+ } else {
+ // If abs(other) > MAX_EXACT_INT_TO_DOUBLE, then other has an integer
+ // value (no bits below the decimal point).
+ other = other.toInt();
+ }
+ }
+ if (this < other) {
+ return LESS;
+ } else if (this > other) {
+ return GREATER;
+ } else {
+ return EQUAL;
+ }
+ }
+
+ int round() {
+ return this;
+ }
+
+ int floor() {
+ return this;
+ }
+
+ int ceil() {
+ return this;
+ }
+
+ int truncate() {
+ return this;
+ }
+
+ double roundToDouble() {
+ return this.toDouble();
+ }
+
+ double floorToDouble() {
+ return this.toDouble();
+ }
+
+ double ceilToDouble() {
+ return this.toDouble();
+ }
+
+ double truncateToDouble() {
+ return this.toDouble();
+ }
+
+ num clamp(num lowerLimit, num upperLimit) {
+ // Special case for integers.
+ if (lowerLimit is int && upperLimit is int && lowerLimit <= upperLimit) {
+ if (this < lowerLimit) return lowerLimit;
+ if (this > upperLimit) return upperLimit;
+ return this;
+ }
+ // Generic case involving doubles, and invalid integer ranges.
+ if (lowerLimit.compareTo(upperLimit) > 0) {
+ throw new ArgumentError(lowerLimit);
+ }
+ if (lowerLimit.isNaN) return lowerLimit;
+ // Note that we don't need to care for -0.0 for the lower limit.
+ if (this < lowerLimit) return lowerLimit;
+ if (this.compareTo(upperLimit) > 0) return upperLimit;
+ return this;
+ }
+
+ int toInt() {
+ return this;
+ }
+
+ external double toDouble();
+
+ String toStringAsFixed(int fractionDigits) {
+ return this.toDouble().toStringAsFixed(fractionDigits);
+ }
+
+ String toStringAsExponential([int? fractionDigits]) {
+ return this.toDouble().toStringAsExponential(fractionDigits);
+ }
+
+ String toStringAsPrecision(int precision) {
+ return this.toDouble().toStringAsPrecision(precision);
+ }
+
+ static const _digits = "0123456789abcdefghijklmnopqrstuvwxyz";
+
+ String toRadixString(int radix) {
+ if (radix < 2 || 36 < radix) {
+ throw new RangeError.range(radix, 2, 36, "radix");
+ }
+ if (radix & (radix - 1) == 0) {
+ return _toPow2String(radix);
+ }
+ if (radix == 10) return this.toString();
+ final bool isNegative = this < 0;
+ int value = isNegative ? -this : this;
+ if (value < 0) {
+ // With int limited to 64 bits, the value
+ // MIN_INT64 = -0x8000000000000000 overflows at negation:
+ // -MIN_INT64 == MIN_INT64, so it requires special handling.
+ return _minInt64ToRadixString(radix);
+ }
+ var temp = <int>[];
+ do {
+ int digit = value % radix;
+ value ~/= radix;
+ temp.add(_digits.codeUnitAt(digit));
+ } while (value > 0);
+ if (isNegative) temp.add(0x2d); // '-'.
+
+ _OneByteString string = _OneByteString._allocate(temp.length);
+ for (int i = 0, j = temp.length; j > 0; i++) {
+ string._setAt(i, temp[--j]);
+ }
+ return string;
+ }
+
+ String _toPow2String(int radix) {
+ int value = this;
+ if (value == 0) return "0";
+ assert(radix & (radix - 1) == 0);
+ var negative = value < 0;
+ var bitsPerDigit = radix.bitLength - 1;
+ var length = 0;
+ if (negative) {
+ value = -value;
+ length = 1;
+ if (value < 0) {
+ // With int limited to 64 bits, the value
+ // MIN_INT64 = -0x8000000000000000 overflows at negation:
+ // -MIN_INT64 == MIN_INT64, so it requires special handling.
+ return _minInt64ToRadixString(radix);
+ }
+ }
+ // Integer division, rounding up, to find number of _digits.
+ length += (value.bitLength + bitsPerDigit - 1) ~/ bitsPerDigit;
+ _OneByteString string = _OneByteString._allocate(length);
+ string._setAt(0, 0x2d); // '-'. Is overwritten if not negative.
+ var mask = radix - 1;
+ do {
+ string._setAt(--length, _digits.codeUnitAt(value & mask));
+ value >>= bitsPerDigit;
+ } while (value > 0);
+ return string;
+ }
+
+ /// Converts negative value to radix string.
+ /// This method is only used to handle corner case of
+ /// MIN_INT64 = -0x8000000000000000.
+ String _minInt64ToRadixString(int radix) {
+ var temp = <int>[];
+ int value = this;
+ assert(value < 0);
+ do {
+ int digit = -unsafeCast<int>(value.remainder(radix));
+ value ~/= radix;
+ temp.add(_digits.codeUnitAt(digit));
+ } while (value != 0);
+ temp.add(0x2d); // '-'.
+
+ _OneByteString string = _OneByteString._allocate(temp.length);
+ for (int i = 0, j = temp.length; j > 0; i++) {
+ string._setAt(i, temp[--j]);
+ }
+ return string;
+ }
+
+ // Returns pow(this, e) % m.
+ int modPow(int e, int m) {
+ if (e < 0) throw new RangeError.range(e, 0, null, "exponent");
+ if (m <= 0) throw new RangeError.range(m, 1, null, "modulus");
+ if (e == 0) return 1;
+
+ // This is floor(sqrt(2^63)).
+ const int maxValueThatCanBeSquaredWithoutTruncation = 3037000499;
+ if (m > maxValueThatCanBeSquaredWithoutTruncation) {
+ // Use BigInt version to avoid truncation in multiplications below.
+ return BigInt.from(this).modPow(BigInt.from(e), BigInt.from(m)).toInt();
+ }
+
+ int b = this;
+ if (b < 0 || b > m) {
+ b %= m;
+ }
+ int r = 1;
+ while (e > 0) {
+ if (e.isOdd) {
+ r = (r * b) % m;
+ }
+ e >>= 1;
+ b = (b * b) % m;
+ }
+ return r;
+ }
+
+ // If inv is false, returns gcd(x, y).
+ // If inv is true and gcd(x, y) = 1, returns d, so that c*x + d*y = 1.
+ // If inv is true and gcd(x, y) != 1, throws Exception("Not coprime").
+ static int _binaryGcd(int x, int y, bool inv) {
+ int s = 0;
+ if (!inv) {
+ while (x.isEven && y.isEven) {
+ x >>= 1;
+ y >>= 1;
+ s++;
+ }
+ if (y.isOdd) {
+ var t = x;
+ x = y;
+ y = t;
+ }
+ }
+ final bool ac = x.isEven;
+ int u = x;
+ int v = y;
+ int a = 1, b = 0, c = 0, d = 1;
+ do {
+ while (u.isEven) {
+ u >>= 1;
+ if (ac) {
+ if (!a.isEven || !b.isEven) {
+ a += y;
+ b -= x;
+ }
+ a >>= 1;
+ } else if (!b.isEven) {
+ b -= x;
+ }
+ b >>= 1;
+ }
+ while (v.isEven) {
+ v >>= 1;
+ if (ac) {
+ if (!c.isEven || !d.isEven) {
+ c += y;
+ d -= x;
+ }
+ c >>= 1;
+ } else if (!d.isEven) {
+ d -= x;
+ }
+ d >>= 1;
+ }
+ if (u >= v) {
+ u -= v;
+ if (ac) a -= c;
+ b -= d;
+ } else {
+ v -= u;
+ if (ac) c -= a;
+ d -= b;
+ }
+ } while (u != 0);
+ if (!inv) return v << s;
+ if (v != 1) {
+ throw new Exception("Not coprime");
+ }
+ if (d < 0) {
+ d += x;
+ if (d < 0) d += x;
+ } else if (d > x) {
+ d -= x;
+ if (d > x) d -= x;
+ }
+ return d;
+ }
+
+ // Returns 1/this % m, with m > 0.
+ int modInverse(int m) {
+ if (m <= 0) throw new RangeError.range(m, 1, null, "modulus");
+ if (m == 1) return 0;
+ int t = this;
+ if ((t < 0) || (t >= m)) t %= m;
+ if (t == 1) return 1;
+ if ((t == 0) || (t.isEven && m.isEven)) {
+ throw new Exception("Not coprime");
+ }
+ return _binaryGcd(m, t, true);
+ }
+
+ // Returns gcd of abs(this) and abs(other).
+ int gcd(int other) {
+ int x = this.abs();
+ int y = other.abs();
+ if (x == 0) return y;
+ if (y == 0) return x;
+ if ((x == 1) || (y == 1)) return 1;
+ return _binaryGcd(x, y, false);
+ }
+
+ int get hashCode => this;
+ int get _identityHashCode => this;
+
+ external int operator ~();
+ external int get bitLength;
+
+ /**
+ * The digits of '00', '01', ... '99' as a single array.
+ *
+ * Get the digits of `n`, with `0 <= n < 100`, as
+ * `_digitTable[n * 2]` and `_digitTable[n * 2 + 1]`.
+ */
+ static const _digitTable = const [
+ 0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, //
+ 0x30, 0x34, 0x30, 0x35, 0x30, 0x36, 0x30, 0x37, //
+ 0x30, 0x38, 0x30, 0x39, 0x31, 0x30, 0x31, 0x31, //
+ 0x31, 0x32, 0x31, 0x33, 0x31, 0x34, 0x31, 0x35, //
+ 0x31, 0x36, 0x31, 0x37, 0x31, 0x38, 0x31, 0x39, //
+ 0x32, 0x30, 0x32, 0x31, 0x32, 0x32, 0x32, 0x33, //
+ 0x32, 0x34, 0x32, 0x35, 0x32, 0x36, 0x32, 0x37, //
+ 0x32, 0x38, 0x32, 0x39, 0x33, 0x30, 0x33, 0x31, //
+ 0x33, 0x32, 0x33, 0x33, 0x33, 0x34, 0x33, 0x35, //
+ 0x33, 0x36, 0x33, 0x37, 0x33, 0x38, 0x33, 0x39, //
+ 0x34, 0x30, 0x34, 0x31, 0x34, 0x32, 0x34, 0x33, //
+ 0x34, 0x34, 0x34, 0x35, 0x34, 0x36, 0x34, 0x37, //
+ 0x34, 0x38, 0x34, 0x39, 0x35, 0x30, 0x35, 0x31, //
+ 0x35, 0x32, 0x35, 0x33, 0x35, 0x34, 0x35, 0x35, //
+ 0x35, 0x36, 0x35, 0x37, 0x35, 0x38, 0x35, 0x39, //
+ 0x36, 0x30, 0x36, 0x31, 0x36, 0x32, 0x36, 0x33, //
+ 0x36, 0x34, 0x36, 0x35, 0x36, 0x36, 0x36, 0x37, //
+ 0x36, 0x38, 0x36, 0x39, 0x37, 0x30, 0x37, 0x31, //
+ 0x37, 0x32, 0x37, 0x33, 0x37, 0x34, 0x37, 0x35, //
+ 0x37, 0x36, 0x37, 0x37, 0x37, 0x38, 0x37, 0x39, //
+ 0x38, 0x30, 0x38, 0x31, 0x38, 0x32, 0x38, 0x33, //
+ 0x38, 0x34, 0x38, 0x35, 0x38, 0x36, 0x38, 0x37, //
+ 0x38, 0x38, 0x38, 0x39, 0x39, 0x30, 0x39, 0x31, //
+ 0x39, 0x32, 0x39, 0x33, 0x39, 0x34, 0x39, 0x35, //
+ 0x39, 0x36, 0x39, 0x37, 0x39, 0x38, 0x39, 0x39, //
+ ];
+
+ /**
+ * Result of int.toString for -99, -98, ..., 98, 99.
+ */
+ static const _smallLookupTable = const [
+ "-99", "-98", "-97", "-96", "-95", "-94", "-93", "-92", "-91", "-90", //
+ "-89", "-88", "-87", "-86", "-85", "-84", "-83", "-82", "-81", "-80", //
+ "-79", "-78", "-77", "-76", "-75", "-74", "-73", "-72", "-71", "-70", //
+ "-69", "-68", "-67", "-66", "-65", "-64", "-63", "-62", "-61", "-60", //
+ "-59", "-58", "-57", "-56", "-55", "-54", "-53", "-52", "-51", "-50", //
+ "-49", "-48", "-47", "-46", "-45", "-44", "-43", "-42", "-41", "-40", //
+ "-39", "-38", "-37", "-36", "-35", "-34", "-33", "-32", "-31", "-30", //
+ "-29", "-28", "-27", "-26", "-25", "-24", "-23", "-22", "-21", "-20", //
+ "-19", "-18", "-17", "-16", "-15", "-14", "-13", "-12", "-11", "-10", //
+ "-9", "-8", "-7", "-6", "-5", "-4", "-3", "-2", "-1", "0", //
+ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", //
+ "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", //
+ "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", //
+ "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", //
+ "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", //
+ "51", "52", "53", "54", "55", "56", "57", "58", "59", "60", //
+ "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", //
+ "71", "72", "73", "74", "75", "76", "77", "78", "79", "80", //
+ "81", "82", "83", "84", "85", "86", "87", "88", "89", "90", //
+ "91", "92", "93", "94", "95", "96", "97", "98", "99" //
+ ];
+
+ // Powers of 10 above 1000000 are indistinguishable by eye.
+ static const int _POW_10_7 = 10000000;
+ static const int _POW_10_8 = 100000000;
+ static const int _POW_10_9 = 1000000000;
+
+ // Find the number of decimal digits in a positive smi.
+ // Never called with numbers < 100. These are handled before calling.
+ static int _positiveBase10Length(int smi) {
+ // A positive smi has length <= 19 if 63-bit, <=10 if 31-bit.
+ // Avoid comparing a 31-bit smi to a non-smi.
+ if (smi < 1000) return 3;
+ if (smi < 10000) return 4;
+ if (smi < _POW_10_7) {
+ if (smi < 100000) return 5;
+ if (smi < 1000000) return 6;
+ return 7;
+ }
+ if (smi < _POW_10_8) return 8;
+ if (smi < _POW_10_9) return 9;
+ smi = smi ~/ _POW_10_9;
+ // Handle numbers < 100 before calling recursively.
+ if (smi < 10) return 10;
+ if (smi < 100) return 11;
+ return 9 + _positiveBase10Length(smi);
+ }
+
+ String toString() {
+ if (this < 100 && this > -100) {
+ // Issue(https://dartbug.com/39639): The analyzer incorrectly reports the
+ // result type as `num`.
+ return _smallLookupTable[unsafeCast<int>(this + 99)];
+ }
+ if (this < 0) return _negativeToString(this);
+ // Inspired by Andrei Alexandrescu: "Three Optimization Tips for C++"
+ // Avoid expensive remainder operation by doing it on more than
+ // one digit at a time.
+ const int DIGIT_ZERO = 0x30;
+ int length = _positiveBase10Length(this);
+ _OneByteString result = _OneByteString._allocate(length);
+ int index = length - 1;
+ int smi = this;
+ do {
+ // Two digits at a time.
+ final int twoDigits = smi.remainder(100);
+ smi = smi ~/ 100;
+ int digitIndex = twoDigits * 2;
+ result._setAt(index, _digitTable[digitIndex + 1]);
+ result._setAt(index - 1, _digitTable[digitIndex]);
+ index -= 2;
+ } while (smi >= 100);
+ if (smi < 10) {
+ // Character code for '0'.
+ // Issue(https://dartbug.com/39639): The analyzer incorrectly reports the
+ // result type as `num`.
+ result._setAt(index, DIGIT_ZERO + smi);
+ } else {
+ // No remainder for this case.
+ // Issue(https://dartbug.com/39639): The analyzer incorrectly reports the
+ // result type as `num`.
+ int digitIndex = smi * 2;
+ result._setAt(index, _digitTable[digitIndex + 1]);
+ result._setAt(index - 1, _digitTable[digitIndex]);
+ }
+ return result;
+ }
+
+ // Find the number of decimal digits in a negative smi.
+ // Never called with numbers > -100. These are handled before calling.
+ static int _negativeBase10Length(int negSmi) {
+ // A negative smi has length <= 19 if 63-bit, <=10 if 31-bit.
+ // Avoid comparing a 31-bit smi to a non-smi.
+ if (negSmi > -1000) return 3;
+ if (negSmi > -10000) return 4;
+ if (negSmi > -_POW_10_7) {
+ if (negSmi > -100000) return 5;
+ if (negSmi > -1000000) return 6;
+ return 7;
+ }
+ if (negSmi > -_POW_10_8) return 8;
+ if (negSmi > -_POW_10_9) return 9;
+ negSmi = negSmi ~/ _POW_10_9;
+ // Handle numbers > -100 before calling recursively.
+ if (negSmi > -10) return 10;
+ if (negSmi > -100) return 11;
+ return 9 + _negativeBase10Length(negSmi);
+ }
+
+ // Convert a negative smi to a string.
+ // Doesn't negate the smi to avoid negating the most negative smi, which
+ // would become a non-smi.
+ static String _negativeToString(int negSmi) {
+ // Character code for '-'
+ const int MINUS_SIGN = 0x2d;
+ // Character code for '0'.
+ const int DIGIT_ZERO = 0x30;
+ if (negSmi > -10) {
+ return _OneByteString._allocate(2)
+ .._setAt(0, MINUS_SIGN)
+ .._setAt(1, DIGIT_ZERO - negSmi);
+ }
+ if (negSmi > -100) {
+ int digitIndex = 2 * -negSmi;
+ return _OneByteString._allocate(3)
+ .._setAt(0, MINUS_SIGN)
+ .._setAt(1, _digitTable[digitIndex])
+ .._setAt(2, _digitTable[digitIndex + 1]);
+ }
+ // Number of digits, not including minus.
+ int digitCount = _negativeBase10Length(negSmi);
+ _OneByteString result = _OneByteString._allocate(digitCount + 1);
+ result._setAt(0, MINUS_SIGN); // '-'.
+ int index = digitCount;
+ do {
+ int twoDigits = unsafeCast<int>(negSmi.remainder(100));
+ negSmi = negSmi ~/ 100;
+ int digitIndex = -twoDigits * 2;
+ result._setAt(index, _digitTable[digitIndex + 1]);
+ result._setAt(index - 1, _digitTable[digitIndex]);
+ index -= 2;
+ } while (negSmi <= -100);
+ if (negSmi > -10) {
+ result._setAt(index, DIGIT_ZERO - negSmi);
+ } else {
+ // No remainder necessary for this case.
+ int digitIndex = -negSmi * 2;
+ result._setAt(index, _digitTable[digitIndex + 1]);
+ result._setAt(index - 1, _digitTable[digitIndex]);
+ }
+ return result;
+ }
+}
diff --git a/sdk/lib/_internal/wasm/lib/internal_patch.dart b/sdk/lib/_internal/wasm/lib/internal_patch.dart
new file mode 100644
index 0000000..3cd8846
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/internal_patch.dart
@@ -0,0 +1,128 @@
+// Copyright (c) 2022, 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" show Uint8List;
+
+/// The returned string is a [_OneByteString] with uninitialized content.
+external String allocateOneByteString(int length);
+
+/// The [string] must be a [_OneByteString]. The [index] must be valid.
+external void writeIntoOneByteString(String string, int index, int codePoint);
+
+/// It is assumed that [from] is a native [Uint8List] class and [to] is a
+/// [_OneByteString]. The [fromStart] and [toStart] indices together with the
+/// [length] must specify ranges within the bounds of the list / string.
+void copyRangeFromUint8ListToOneByteString(
+ Uint8List from, String to, int fromStart, int toStart, int length) {
+ for (int i = 0; i < length; i++) {
+ writeIntoOneByteString(to, toStart + i, from[fromStart + i]);
+ }
+}
+
+/// The returned string is a [_TwoByteString] with uninitialized content.
+external String allocateTwoByteString(int length);
+
+/// The [string] must be a [_TwoByteString]. The [index] must be valid.
+external void writeIntoTwoByteString(String string, int index, int codePoint);
+
+// String accessors used to perform Dart<->JS string conversion
+
+@pragma("wasm:export", "\$stringLength")
+double _stringLength(String string) {
+ return string.length.toDouble();
+}
+
+@pragma("wasm:export", "\$stringRead")
+double _stringRead(String string, double index) {
+ return string.codeUnitAt(index.toInt()).toDouble();
+}
+
+@pragma("wasm:export", "\$stringAllocate1")
+String _stringAllocate1(double length) {
+ return allocateOneByteString(length.toInt());
+}
+
+@pragma("wasm:export", "\$stringWrite1")
+void _stringWrite1(String string, double index, double codePoint) {
+ writeIntoOneByteString(string, index.toInt(), codePoint.toInt());
+}
+
+@pragma("wasm:export", "\$stringAllocate2")
+String _stringAllocate2(double length) {
+ return allocateTwoByteString(length.toInt());
+}
+
+@pragma("wasm:export", "\$stringWrite2")
+void _stringWrite2(String string, double index, double codePoint) {
+ writeIntoTwoByteString(string, index.toInt(), codePoint.toInt());
+}
+
+const bool has63BitSmis = false;
+
+class Lists {
+ static void copy(List src, int srcStart, List dst, int dstStart, int count) {
+ // TODO(askesc): Intrinsify for efficient copying
+ if (srcStart < dstStart) {
+ for (int i = srcStart + count - 1, j = dstStart + count - 1;
+ i >= srcStart;
+ i--, j--) {
+ dst[j] = src[i];
+ }
+ } else {
+ for (int i = srcStart, j = dstStart; i < srcStart + count; i++, j++) {
+ dst[j] = src[i];
+ }
+ }
+ }
+}
+
+// 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.
+//
+// Important: this is unsafe and must be used with care.
+external T unsafeCast<T>(Object? v);
+
+// Thomas Wang 64-bit mix.
+// https://gist.github.com/badboy/6267743
+int mix64(int n) {
+ n = (~n) + (n << 21); // n = (n << 21) - n - 1;
+ n = n ^ (n >>> 24);
+ n = n * 265; // n = (n + (n << 3)) + (n << 8);
+ n = n ^ (n >>> 14);
+ n = n * 21; // n = (n + (n << 2)) + (n << 4);
+ n = n ^ (n >>> 28);
+ n = n + (n << 31);
+ return n;
+}
+
+external int floatToIntBits(double value);
+external double intBitsToFloat(int value);
+external int doubleToIntBits(double value);
+external double intBitsToDouble(int value);
+
+// Exported call stubs to enable JS to call Dart closures. Since all closure
+// parameters and returns are boxed (their Wasm type is #Top) the Wasm type of
+// the closure will be the same as with all parameters and returns as dynamic.
+// Thus, the unsafeCast succeeds, and as long as the passed argumnets have the
+// correct types, the argument casts inside the closure will also succeed.
+
+@pragma("wasm:export", "\$call0")
+dynamic _callClosure0(dynamic closure) {
+ return unsafeCast<dynamic Function()>(closure)();
+}
+
+@pragma("wasm:export", "\$call1")
+dynamic _callClosure1(dynamic closure, dynamic arg1) {
+ return unsafeCast<dynamic Function(dynamic)>(closure)(arg1);
+}
+
+@pragma("wasm:export", "\$call2")
+dynamic _callClosure2(dynamic closure, dynamic arg1, dynamic arg2) {
+ return unsafeCast<dynamic Function(dynamic, dynamic)>(closure)(arg1, arg2);
+}
+
+// Schedule a callback from JS via setTimeout.
+@pragma("wasm:import", "dart2wasm.scheduleCallback")
+external void scheduleCallback(double millis, dynamic Function() callback);
diff --git a/sdk/lib/_internal/wasm/lib/list.dart b/sdk/lib/_internal/wasm/lib/list.dart
new file mode 100644
index 0000000..13eeff3
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/list.dart
@@ -0,0 +1,223 @@
+// Copyright (c) 2022, 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.
+
+// part of "core_patch.dart";
+
+@pragma("wasm:entry-point")
+abstract class _ListBase<E> extends ListBase<E> {
+ @pragma("wasm:entry-point")
+ int _length;
+ @pragma("wasm:entry-point")
+ WasmObjectArray<Object?> _data;
+
+ _ListBase(int length, int capacity)
+ : _length = length,
+ _data = WasmObjectArray<Object?>(capacity);
+
+ _ListBase._withData(this._length, this._data);
+
+ E operator [](int index) {
+ return unsafeCast(_data.read(index));
+ }
+
+ int get length => _length;
+
+ List<E> sublist(int start, [int? end]) {
+ final int listLength = this.length;
+ final int actualEnd = RangeError.checkValidRange(start, end, listLength);
+ int length = actualEnd - start;
+ if (length == 0) return <E>[];
+ return _GrowableList<E>(length)..setRange(0, length, this);
+ }
+
+ void forEach(f(E element)) {
+ final length = this.length;
+ for (int i = 0; i < length; i++) {
+ f(this[i]);
+ }
+ }
+
+ List<E> toList({bool growable: true}) {
+ return List.from(this, growable: growable);
+ }
+}
+
+@pragma("wasm:entry-point")
+abstract class _ModifiableList<E> extends _ListBase<E> {
+ _ModifiableList(int length, int capacity) : super(length, capacity);
+
+ _ModifiableList._withData(int length, WasmObjectArray<Object?> data)
+ : super._withData(length, data);
+
+ void operator []=(int index, E value) {
+ _data.write(index, value);
+ }
+
+ // List interface.
+ void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) {
+ if (start < 0 || start > this.length) {
+ throw new RangeError.range(start, 0, this.length);
+ }
+ if (end < start || end > this.length) {
+ throw new RangeError.range(end, start, this.length);
+ }
+ int length = end - start;
+ if (length == 0) return;
+ if (identical(this, iterable)) {
+ Lists.copy(this, skipCount, this, start, length);
+ } else if (iterable is List<E>) {
+ Lists.copy(iterable, skipCount, this, start, length);
+ } else {
+ Iterator<E> it = iterable.iterator;
+ while (skipCount > 0) {
+ if (!it.moveNext()) return;
+ skipCount--;
+ }
+ for (int i = start; i < end; i++) {
+ if (!it.moveNext()) return;
+ this[i] = it.current;
+ }
+ }
+ }
+
+ void setAll(int index, Iterable<E> iterable) {
+ if (index < 0 || index > this.length) {
+ throw new RangeError.range(index, 0, this.length, "index");
+ }
+ List<E> iterableAsList;
+ if (identical(this, iterable)) {
+ iterableAsList = this;
+ } else if (iterable is List<E>) {
+ iterableAsList = iterable;
+ } else {
+ for (var value in iterable) {
+ this[index++] = value;
+ }
+ return;
+ }
+ int length = iterableAsList.length;
+ if (index + length > this.length) {
+ throw new RangeError.range(index + length, 0, this.length);
+ }
+ Lists.copy(iterableAsList, 0, this, index, length);
+ }
+}
+
+@pragma("wasm:entry-point")
+class _List<E> extends _ModifiableList<E> with FixedLengthListMixin<E> {
+ _List._(int length) : super(length, length);
+
+ factory _List(int length) => _List._(length);
+
+ // Specialization of List.empty constructor for growable == false.
+ // Used by pkg/vm/lib/transformations/list_factory_specializer.dart.
+ factory _List.empty() => _List<E>(0);
+
+ // Specialization of List.filled constructor for growable == false.
+ // Used by pkg/vm/lib/transformations/list_factory_specializer.dart.
+ factory _List.filled(int length, E fill) {
+ final result = _List<E>(length);
+ if (fill != null) {
+ for (int i = 0; i < result.length; i++) {
+ result[i] = fill;
+ }
+ }
+ return result;
+ }
+
+ // Specialization of List.generate constructor for growable == false.
+ // Used by pkg/vm/lib/transformations/list_factory_specializer.dart.
+ factory _List.generate(int length, E generator(int index)) {
+ final result = _List<E>(length);
+ for (int i = 0; i < result.length; ++i) {
+ result[i] = generator(i);
+ }
+ return result;
+ }
+
+ // Specialization of List.of constructor for growable == false.
+ factory _List.of(Iterable<E> elements) {
+ if (elements is _ListBase) {
+ return _List._ofListBase(unsafeCast(elements));
+ }
+ if (elements is EfficientLengthIterable) {
+ return _List._ofEfficientLengthIterable(unsafeCast(elements));
+ }
+ return _List._ofOther(elements);
+ }
+
+ factory _List._ofListBase(_ListBase<E> elements) {
+ final int length = elements.length;
+ final list = _List<E>(length);
+ for (int i = 0; i < length; i++) {
+ list[i] = elements[i];
+ }
+ return list;
+ }
+
+ factory _List._ofEfficientLengthIterable(
+ EfficientLengthIterable<E> elements) {
+ final int length = elements.length;
+ final list = _List<E>(length);
+ if (length > 0) {
+ int i = 0;
+ for (var element in elements) {
+ list[i++] = element;
+ }
+ if (i != length) throw ConcurrentModificationError(elements);
+ }
+ return list;
+ }
+
+ factory _List._ofOther(Iterable<E> elements) {
+ // The static type of `makeListFixedLength` is `List<E>`, not `_List<E>`,
+ // but we know that is what it does. `makeListFixedLength` is too generally
+ // typed since it is available on the web platform which has different
+ // system List types.
+ return unsafeCast(makeListFixedLength(_GrowableList<E>._ofOther(elements)));
+ }
+
+ Iterator<E> get iterator {
+ return new _FixedSizeListIterator<E>(this);
+ }
+}
+
+@pragma("wasm:entry-point")
+class _ImmutableList<E> extends _ListBase<E> with UnmodifiableListMixin<E> {
+ factory _ImmutableList._uninstantiable() {
+ throw new UnsupportedError(
+ "_ImmutableList can only be allocated by the runtime");
+ }
+
+ Iterator<E> get iterator {
+ return new _FixedSizeListIterator<E>(this);
+ }
+}
+
+// Iterator for lists with fixed size.
+class _FixedSizeListIterator<E> implements Iterator<E> {
+ final _ListBase<E> _list;
+ final int _length; // Cache list length for faster access.
+ int _index;
+ E? _current;
+
+ _FixedSizeListIterator(_ListBase<E> list)
+ : _list = list,
+ _length = list.length,
+ _index = 0 {
+ assert(list is _List<E> || list is _ImmutableList<E>);
+ }
+
+ E get current => _current as E;
+
+ bool moveNext() {
+ if (_index >= _length) {
+ _current = null;
+ return false;
+ }
+ _current = unsafeCast(_list._data.read(_index));
+ _index++;
+ return true;
+ }
+}
diff --git a/sdk/lib/_internal/wasm/lib/math_patch.dart b/sdk/lib/_internal/wasm/lib/math_patch.dart
new file mode 100644
index 0000000..b26fd6d
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/math_patch.dart
@@ -0,0 +1,262 @@
+// Copyright (c) 2022, 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:_internal" show mix64, patch;
+
+import "dart:typed_data" show Uint32List;
+
+/// There are no parts of this patch library.
+
+@patch
+T min<T extends num>(T a, T b) {
+ if (a > b) return b;
+ if (a < b) return a;
+ if (b is double) {
+ // Special case for NaN and -0.0. If one argument is NaN return NaN.
+ // [min] must also distinguish between -0.0 and 0.0.
+ if (a is double) {
+ if (a == 0.0) {
+ // a is either 0.0 or -0.0. b is either 0.0, -0.0 or NaN.
+ // The following returns -0.0 if either a or b is -0.0, and it
+ // returns NaN if b is NaN.
+ num n = (a + b) * a * b;
+ return n as T;
+ }
+ }
+ // Check for NaN and b == -0.0.
+ if (a == 0 && b.isNegative || b.isNaN) return b;
+ return a;
+ }
+ return a;
+}
+
+@patch
+T max<T extends num>(T a, T b) {
+ if (a > b) return a;
+ if (a < b) return b;
+ if (b is double) {
+ // Special case for NaN and -0.0. If one argument is NaN return NaN.
+ // [max] must also distinguish between -0.0 and 0.0.
+ if (a is double) {
+ if (a == 0.0) {
+ // a is either 0.0 or -0.0. b is either 0.0, -0.0, or NaN.
+ // The following returns 0.0 if either a or b is 0.0, and it
+ // returns NaN if b is NaN.
+ num n = a + b;
+ return n as T;
+ }
+ }
+ // Check for NaN.
+ if (b.isNaN) return b;
+ return a;
+ }
+ // max(-0.0, 0) must return 0.
+ if (b == 0 && a.isNegative) return b;
+ return a;
+}
+
+// If [x] is an [int] and [exponent] is a non-negative [int], the result is
+// an [int], otherwise the result is a [double].
+@patch
+num pow(num x, num exponent) {
+ if ((x is int) && (exponent is int) && (exponent >= 0)) {
+ return _intPow(x, exponent);
+ }
+ return _doublePow(x.toDouble(), exponent.toDouble());
+}
+
+@pragma("wasm:import", "Math.pow")
+external double _doublePow(double base, double exponent);
+
+int _intPow(int base, int exponent) {
+ // Exponentiation by squaring.
+ int result = 1;
+ while (exponent != 0) {
+ if ((exponent & 1) == 1) {
+ result *= base;
+ }
+ exponent >>= 1;
+ // Skip unnecessary operation (can overflow to Mint).
+ if (exponent != 0) {
+ base *= base;
+ }
+ }
+ return result;
+}
+
+@patch
+double atan2(num a, num b) => _atan2(a.toDouble(), b.toDouble());
+@patch
+double sin(num radians) => _sin(radians.toDouble());
+@patch
+double cos(num radians) => _cos(radians.toDouble());
+@patch
+double tan(num radians) => _tan(radians.toDouble());
+@patch
+double acos(num x) => _acos(x.toDouble());
+@patch
+double asin(num x) => _asin(x.toDouble());
+@patch
+double atan(num x) => _atan(x.toDouble());
+@patch
+double sqrt(num x) => _sqrt(x.toDouble());
+@patch
+double exp(num x) => _exp(x.toDouble());
+@patch
+double log(num x) => _log(x.toDouble());
+
+@pragma("wasm:import", "Math.atan2")
+external double _atan2(double a, double b);
+@pragma("wasm:import", "Math.sin")
+external double _sin(double x);
+@pragma("wasm:import", "Math.cos")
+external double _cos(double x);
+@pragma("wasm:import", "Math.tan")
+external double _tan(double x);
+@pragma("wasm:import", "Math.acos")
+external double _acos(double x);
+@pragma("wasm:import", "Math.asin")
+external double _asin(double x);
+@pragma("wasm:import", "Math.atan")
+external double _atan(double x);
+@pragma("wasm:import", "Math.sqrt")
+external double _sqrt(double x);
+@pragma("wasm:import", "Math.exp")
+external double _exp(double x);
+@pragma("wasm:import", "Math.log")
+external double _log(double x);
+
+// TODO(iposva): Handle patch methods within a patch class correctly.
+@patch
+class Random {
+ static final Random _secureRandom = _SecureRandom();
+
+ @patch
+ factory Random([int? seed]) {
+ var state = _Random._setupSeed((seed == null) ? _Random._nextSeed() : seed);
+ // Crank a couple of times to distribute the seed bits a bit further.
+ return new _Random._withState(state)
+ .._nextState()
+ .._nextState()
+ .._nextState()
+ .._nextState();
+ }
+
+ @patch
+ factory Random.secure() => _secureRandom;
+}
+
+class _Random implements Random {
+ // Internal state of the random number generator.
+ int _state;
+
+ int get _stateLow => _state & 0xFFFFFFFF;
+ int get _stateHigh => _state >>> 32;
+
+ _Random._withState(this._state);
+
+ // The algorithm used here is Multiply with Carry (MWC) with a Base b = 2^32.
+ // http://en.wikipedia.org/wiki/Multiply-with-carry
+ // The constant A is selected from "Numerical Recipes 3rd Edition" p.348 B1.
+
+ // Implements:
+ // const _A = 0xffffda61;
+ // var state =
+ // ((_A * (_state[_kSTATE_LO])) + _state[_kSTATE_HI]) & ((1 << 64) - 1);
+ // _state[_kSTATE_LO] = state & ((1 << 32) - 1);
+ // _state[_kSTATE_HI] = state >> 32;
+ // This is a native to prevent 64-bit operations in Dart, which
+ // fail with --throw_on_javascript_int_overflow.
+ // TODO(regis): Implement in Dart and remove Random_nextState in math.cc.
+ void _nextState() {
+ const _A = 0xffffda61;
+ _state = _A * _stateLow + _stateHigh;
+ }
+
+ int nextInt(int max) {
+ if (max <= 0 || max > _POW2_32) {
+ throw new RangeError.range(
+ max, 1, _POW2_32, "max", "Must be positive and <= 2^32");
+ }
+ if ((max & -max) == max) {
+ // Fast case for powers of two.
+ _nextState();
+ return _state & (max - 1);
+ }
+
+ int rnd32;
+ int result;
+ do {
+ _nextState();
+ rnd32 = _stateLow;
+ result = rnd32 % max;
+ } while ((rnd32 - result + max) > _POW2_32);
+ return result;
+ }
+
+ double nextDouble() {
+ return ((nextInt(1 << 26) * _POW2_27_D) + nextInt(1 << 27)) / _POW2_53_D;
+ }
+
+ bool nextBool() {
+ return nextInt(2) == 0;
+ }
+
+ // Constants used by the algorithm.
+ static const _POW2_32 = 1 << 32;
+ static const _POW2_53_D = 1.0 * (1 << 53);
+ static const _POW2_27_D = 1.0 * (1 << 27);
+
+ // Use a singleton Random object to get a new seed if no seed was passed.
+ static final _prng = new _Random._withState(_initialSeed());
+
+ static int _setupSeed(int seed) => mix64(seed);
+
+ // TODO: Make this actually random
+ static int _initialSeed() => 0xCAFEBABEDEADBEEF;
+
+ static int _nextSeed() {
+ // Trigger the PRNG once to change the internal state.
+ _prng._nextState();
+ return _prng._stateLow;
+ }
+}
+
+class _SecureRandom implements Random {
+ _SecureRandom() {
+ // Throw early in constructor if entropy source is not hooked up.
+ _getBytes(1);
+ }
+
+ // Return count bytes of entropy as a positive integer; count <= 8.
+ external static int _getBytes(int count);
+
+ int nextInt(int max) {
+ RangeError.checkValueInInterval(
+ max, 1, _POW2_32, "max", "Must be positive and <= 2^32");
+ final byteCount = ((max - 1).bitLength + 7) >> 3;
+ if (byteCount == 0) {
+ return 0; // Not random if max == 1.
+ }
+ var rnd;
+ var result;
+ do {
+ rnd = _getBytes(byteCount);
+ result = rnd % max;
+ } while ((rnd - result + max) > (1 << (byteCount << 3)));
+ return result;
+ }
+
+ double nextDouble() {
+ return (_getBytes(7) >> 3) / _POW2_53_D;
+ }
+
+ bool nextBool() {
+ return _getBytes(1).isEven;
+ }
+
+ // Constants used by the algorithm.
+ static const _POW2_32 = 1 << 32;
+ static const _POW2_53_D = 1.0 * (1 << 53);
+}
diff --git a/sdk/lib/_internal/wasm/lib/object_patch.dart b/sdk/lib/_internal/wasm/lib/object_patch.dart
new file mode 100644
index 0000000..d38ca14
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/object_patch.dart
@@ -0,0 +1,49 @@
+// Copyright (c) 2022, 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.
+
+// part of "core_patch.dart";
+
+// Access hidden identity hash code field
+external int _getHash(Object obj);
+external void _setHash(Object obj, int hash);
+
+@patch
+class Object {
+ @patch
+ external bool operator ==(Object other);
+
+ // Random number generator used to generate identity hash codes.
+ static final _hashCodeRnd = new Random();
+
+ static int _objectHashCode(Object obj) {
+ var result = _getHash(obj);
+ if (result == 0) {
+ // We want the hash to be a Smi value greater than 0.
+ do {
+ result = _hashCodeRnd.nextInt(0x40000000);
+ } while (result == 0);
+
+ _setHash(obj, result);
+ return result;
+ }
+ return result;
+ }
+
+ @patch
+ int get hashCode => _objectHashCode(this);
+ int get _identityHashCode => _objectHashCode(this);
+
+ @patch
+ String toString() => _toString(this);
+ // A statically dispatched version of Object.toString.
+ static String _toString(obj) => "Instance of '${obj.runtimeType}'";
+
+ @patch
+ dynamic noSuchMethod(Invocation invocation) {
+ throw new NoSuchMethodError.withInvocation(this, invocation);
+ }
+
+ @patch
+ external Type get runtimeType;
+}
diff --git a/sdk/lib/_internal/wasm/lib/patch.dart b/sdk/lib/_internal/wasm/lib/patch.dart
new file mode 100644
index 0000000..f69ddc4
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/patch.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2022, 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.
+
+class _Patch {
+ const _Patch();
+}
+
+const _Patch patch = const _Patch();
diff --git a/sdk/lib/_internal/wasm/lib/print_patch.dart b/sdk/lib/_internal/wasm/lib/print_patch.dart
new file mode 100644
index 0000000..e08b7e2
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/print_patch.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2022, 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.
+
+// part of "internal_patch.dart";
+
+@patch
+@pragma("wasm:import", "dart2wasm.printToConsole")
+external void printToConsole(String line);
diff --git a/sdk/lib/_internal/wasm/lib/string_buffer_patch.dart b/sdk/lib/_internal/wasm/lib/string_buffer_patch.dart
new file mode 100644
index 0000000..ac22cba
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/string_buffer_patch.dart
@@ -0,0 +1,212 @@
+// Copyright (c) 2022, 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.
+
+// part of "core_patch.dart";
+
+// This file is identical to the VM `string_buffer_patch.dart` except for the
+// implementation of `StringBuffer._create`.
+// TODO(askesc): Share this file with the VM when the patching mechanism gains
+// support for patching an external member from a patch in a separate patch.
+
+@patch
+class StringBuffer {
+ static const int _BUFFER_SIZE = 64;
+ static const int _PARTS_TO_COMPACT = 128;
+ static const int _PARTS_TO_COMPACT_SIZE_LIMIT = _PARTS_TO_COMPACT * 8;
+
+ /**
+ * When strings are written to the string buffer, we add them to a
+ * list of string parts.
+ */
+ List<String>? _parts;
+
+ /**
+ * Total number of code units in the string parts. Does not include
+ * the code units added to the buffer.
+ */
+ int _partsCodeUnits = 0;
+
+ /**
+ * To preserve memory, we sometimes compact the parts. This combines
+ * several smaller parts into a single larger part to cut down on the
+ * cost that comes from the per-object memory overhead. We keep track
+ * of the last index where we ended our compaction and the number of
+ * code units added since the last compaction.
+ */
+ int _partsCompactionIndex = 0;
+ int _partsCodeUnitsSinceCompaction = 0;
+
+ /**
+ * The buffer is used to build up a string from code units. It is
+ * used when writing short strings or individual char codes to the
+ * buffer. The buffer is allocated on demand.
+ */
+ Uint16List? _buffer;
+ int _bufferPosition = 0;
+
+ /**
+ * Collects the approximate maximal magnitude of the code units added
+ * to the buffer.
+ *
+ * The value of each added code unit is or'ed with this variable, so the
+ * most significant bit set in any code unit is also set in this value.
+ * If below 256, the string in the buffer is a Latin-1 string.
+ */
+ int _bufferCodeUnitMagnitude = 0;
+
+ /// Creates the string buffer with an initial content.
+ @patch
+ StringBuffer([Object content = ""]) {
+ write(content);
+ }
+
+ @patch
+ int get length => _partsCodeUnits + _bufferPosition;
+
+ @patch
+ void write(Object? obj) {
+ String str = "$obj";
+ if (str.isEmpty) return;
+ _consumeBuffer();
+ _addPart(str);
+ }
+
+ @patch
+ void writeCharCode(int charCode) {
+ if (charCode <= 0xFFFF) {
+ if (charCode < 0) {
+ throw new RangeError.range(charCode, 0, 0x10FFFF);
+ }
+ _ensureCapacity(1);
+ final localBuffer = _buffer!;
+ localBuffer[_bufferPosition++] = charCode;
+ _bufferCodeUnitMagnitude |= charCode;
+ } else {
+ if (charCode > 0x10FFFF) {
+ throw new RangeError.range(charCode, 0, 0x10FFFF);
+ }
+ _ensureCapacity(2);
+ int bits = charCode - 0x10000;
+ final localBuffer = _buffer!;
+ localBuffer[_bufferPosition++] = 0xD800 | (bits >> 10);
+ localBuffer[_bufferPosition++] = 0xDC00 | (bits & 0x3FF);
+ _bufferCodeUnitMagnitude |= 0xFFFF;
+ }
+ }
+
+ @patch
+ void writeAll(Iterable objects, [String separator = ""]) {
+ Iterator iterator = objects.iterator;
+ if (!iterator.moveNext()) return;
+ if (separator.isEmpty) {
+ do {
+ write(iterator.current);
+ } while (iterator.moveNext());
+ } else {
+ write(iterator.current);
+ while (iterator.moveNext()) {
+ write(separator);
+ write(iterator.current);
+ }
+ }
+ }
+
+ @patch
+ void writeln([Object? obj = ""]) {
+ write(obj);
+ write("\n");
+ }
+
+ /** Makes the buffer empty. */
+ @patch
+ void clear() {
+ _parts = null;
+ _partsCodeUnits = _bufferPosition = _bufferCodeUnitMagnitude = 0;
+ }
+
+ /** Returns the contents of buffer as a string. */
+ @patch
+ String toString() {
+ _consumeBuffer();
+ final localParts = _parts;
+ return (_partsCodeUnits == 0 || localParts == null)
+ ? ""
+ : _StringBase._concatRange(localParts, 0, localParts.length);
+ }
+
+ /** Ensures that the buffer has enough capacity to add n code units. */
+ void _ensureCapacity(int n) {
+ final localBuffer = _buffer;
+ if (localBuffer == null) {
+ _buffer = new Uint16List(_BUFFER_SIZE);
+ } else if (_bufferPosition + n > localBuffer.length) {
+ _consumeBuffer();
+ }
+ }
+
+ /**
+ * Consumes the content of the buffer by turning it into a string
+ * and adding it as a part. After calling this the buffer position
+ * will be reset to zero.
+ */
+ void _consumeBuffer() {
+ if (_bufferPosition == 0) return;
+ bool isLatin1 = _bufferCodeUnitMagnitude <= 0xFF;
+ String str = _create(_buffer!, _bufferPosition, isLatin1);
+ _bufferPosition = _bufferCodeUnitMagnitude = 0;
+ _addPart(str);
+ }
+
+ /**
+ * Adds a new part to this string buffer and keeps track of how
+ * many code units are contained in the parts.
+ */
+ void _addPart(String str) {
+ final localParts = _parts;
+ int length = str.length;
+ _partsCodeUnits += length;
+ _partsCodeUnitsSinceCompaction += length;
+
+ if (localParts == null) {
+ // Empirically this is a good capacity to minimize total bytes allocated.
+ _parts = new _GrowableList.withCapacity(10)..add(str);
+ } else {
+ localParts.add(str);
+ int partsSinceCompaction = localParts.length - _partsCompactionIndex;
+ if (partsSinceCompaction == _PARTS_TO_COMPACT) {
+ _compact();
+ }
+ }
+ }
+
+ /**
+ * Compacts the last N parts if their average size allows us to save a
+ * lot of memory by turning them all into a single part.
+ */
+ void _compact() {
+ final localParts = _parts!;
+ if (_partsCodeUnitsSinceCompaction < _PARTS_TO_COMPACT_SIZE_LIMIT) {
+ String compacted = _StringBase._concatRange(
+ localParts,
+ _partsCompactionIndex, // Start
+ _partsCompactionIndex + _PARTS_TO_COMPACT // End
+ );
+ localParts.length = localParts.length - _PARTS_TO_COMPACT;
+ localParts.add(compacted);
+ }
+ _partsCodeUnitsSinceCompaction = 0;
+ _partsCompactionIndex = localParts.length;
+ }
+
+ /**
+ * Create a [String] from the UFT-16 code units in buffer.
+ */
+ static String _create(Uint16List buffer, int length, bool isLatin1) {
+ if (isLatin1) {
+ return _StringBase._createOneByteString(buffer, 0, length);
+ } else {
+ return _TwoByteString._allocateFromTwoByteList(buffer, 0, length);
+ }
+ }
+}
diff --git a/sdk/lib/_internal/wasm/lib/string_patch.dart b/sdk/lib/_internal/wasm/lib/string_patch.dart
new file mode 100644
index 0000000..a9c0222
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/string_patch.dart
@@ -0,0 +1,1391 @@
+// Copyright (c) 2022, 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.
+
+// part of "core_patch.dart";
+
+// Much of this patch file is similar to the VM `string_patch.dart`. It may make
+// sense to share some of the code when the patching mechanism supports patching
+// the same class in multiple patch files.
+
+const int _maxAscii = 0x7f;
+const int _maxLatin1 = 0xff;
+const int _maxUtf16 = 0xffff;
+const int _maxUnicode = 0x10ffff;
+
+@patch
+class String {
+ @patch
+ factory String.fromCharCodes(Iterable<int> charCodes,
+ [int start = 0, int? end]) {
+ return _StringBase.createFromCharCodes(charCodes, start, end, null);
+ }
+
+ @patch
+ factory String.fromCharCode(int charCode) {
+ if (charCode >= 0) {
+ if (charCode <= 0xff) {
+ return _OneByteString._allocate(1).._setAt(0, charCode);
+ }
+ if (charCode <= 0xffff) {
+ return _TwoByteString._allocate(1).._setAt(0, charCode);
+ }
+ if (charCode <= 0x10ffff) {
+ var low = 0xDC00 | (charCode & 0x3ff);
+ int bits = charCode - 0x10000;
+ var high = 0xD800 | (bits >> 10);
+ return _StringBase._createFromCodePoints(
+ new _List(2)
+ ..[0] = high
+ ..[1] = low,
+ 0,
+ 2);
+ }
+ }
+ throw new RangeError.range(charCode, 0, 0x10ffff);
+ }
+
+ @patch
+ external const factory String.fromEnvironment(String name,
+ {String defaultValue = ""});
+
+ bool get _isOneByte;
+ String _substringUnchecked(int startIndex, int endIndex);
+}
+
+/**
+ * [_StringBase] contains common methods used by concrete String
+ * implementations, e.g., _OneByteString.
+ */
+abstract class _StringBase implements String {
+ bool _isWhitespace(int codeUnit);
+
+ // Constants used by replaceAll encoding of string slices between matches.
+ // A string slice (start+length) is encoded in a single Smi to save memory
+ // overhead in the common case.
+ // We use fewer bits for length (11 bits) than for the start index (19+ bits).
+ // For long strings, it's possible to have many large indices,
+ // but it's unlikely to have many long lengths since slices don't overlap.
+ // If there are few matches in a long string, then there are few long slices,
+ // and if there are many matches, there'll likely be many short slices.
+ //
+ // Encoding is: 0((start << _lengthBits) | length)
+
+ // Number of bits used by length.
+ // This is the shift used to encode and decode the start index.
+ static const int _lengthBits = 11;
+ // The maximal allowed length value in an encoded slice.
+ static const int _maxLengthValue = (1 << _lengthBits) - 1;
+ // Mask of length in encoded smi value.
+ static const int _lengthMask = _maxLengthValue;
+ static const int _startBits = _maxUnsignedSmiBits - _lengthBits;
+ // Maximal allowed start index value in an encoded slice.
+ static const int _maxStartValue = (1 << _startBits) - 1;
+ // We pick 30 as a safe lower bound on available bits in a negative smi.
+ // TODO(lrn): Consider allowing more bits for start on 64-bit systems.
+ static const int _maxUnsignedSmiBits = 30;
+
+ // For longer strings, calling into C++ to create the result of a
+ // [replaceAll] is faster than [_joinReplaceAllOneByteResult].
+ // TODO(lrn): See if this limit can be tweaked.
+ static const int _maxJoinReplaceOneByteStringLength = 500;
+
+ _StringBase._();
+
+ int get hashCode {
+ int hash = _getHash(this);
+ if (hash != 0) return hash;
+ hash = _computeHashCode();
+ _setHash(this, hash);
+ return hash;
+ }
+
+ int _computeHashCode();
+
+ int get _identityHashCode => hashCode;
+
+ bool get _isOneByte {
+ // Alternatively return false and override it on one-byte string classes.
+ return this is _OneByteString;
+ }
+
+ /**
+ * Create the most efficient string representation for specified
+ * [charCodes].
+ *
+ * Only uses the character codes between index [start] and index [end] of
+ * `charCodes`. They must satisfy `0 <= start <= end <= charCodes.length`.
+ *
+ * The [limit] is an upper limit on the character codes in the iterable.
+ * It's `null` if unknown.
+ */
+ static String createFromCharCodes(
+ Iterable<int> charCodes, int start, int? end, int? limit) {
+ // TODO(srdjan): Also skip copying of wide typed arrays.
+ if (charCodes is Uint8List) {
+ final actualEnd =
+ RangeError.checkValidRange(start, end, charCodes.length);
+ return _createOneByteString(charCodes, start, actualEnd - start);
+ } else if (charCodes is! Uint16List) {
+ return _createStringFromIterable(charCodes, start, end);
+ }
+ final int codeCount = charCodes.length;
+ final actualEnd = RangeError.checkValidRange(start, end, codeCount);
+ final len = actualEnd - start;
+ if (len == 0) return "";
+
+ final typedCharCodes = unsafeCast<List<int>>(charCodes);
+
+ final int actualLimit =
+ limit ?? _scanCodeUnits(typedCharCodes, start, actualEnd);
+ if (actualLimit < 0) {
+ throw new ArgumentError(typedCharCodes);
+ }
+ if (actualLimit <= _maxLatin1) {
+ return _createOneByteString(typedCharCodes, start, len);
+ }
+ if (actualLimit <= _maxUtf16) {
+ return _TwoByteString._allocateFromTwoByteList(
+ typedCharCodes, start, actualEnd);
+ }
+ // TODO(lrn): Consider passing limit to _createFromCodePoints, because
+ // the function is currently fully generic and doesn't know that its
+ // charCodes are not all Latin-1 or Utf-16.
+ return _createFromCodePoints(typedCharCodes, start, actualEnd);
+ }
+
+ static int _scanCodeUnits(List<int> charCodes, int start, int end) {
+ int bits = 0;
+ for (int i = start; i < end; i++) {
+ int code = charCodes[i];
+ bits |= code;
+ }
+ return bits;
+ }
+
+ static String _createStringFromIterable(
+ Iterable<int> charCodes, int start, int? end) {
+ // Treat charCodes as Iterable.
+ if (charCodes is EfficientLengthIterable) {
+ int length = charCodes.length;
+ final endVal = RangeError.checkValidRange(start, end, length);
+ final charCodeList = new List<int>.from(
+ charCodes.take(endVal).skip(start),
+ growable: false);
+ return createFromCharCodes(charCodeList, 0, charCodeList.length, null);
+ }
+ // Don't know length of iterable, so iterate and see if all the values
+ // are there.
+ if (start < 0) throw new RangeError.range(start, 0, charCodes.length);
+ var it = charCodes.iterator;
+ for (int i = 0; i < start; i++) {
+ if (!it.moveNext()) {
+ throw new RangeError.range(start, 0, i);
+ }
+ }
+ List<int> charCodeList;
+ int bits = 0; // Bitwise-or of all char codes in list.
+ final endVal = end;
+ if (endVal == null) {
+ var list = <int>[];
+ while (it.moveNext()) {
+ int code = it.current;
+ bits |= code;
+ list.add(code);
+ }
+ charCodeList = makeListFixedLength<int>(list);
+ } else {
+ if (endVal < start) {
+ throw new RangeError.range(endVal, start, charCodes.length);
+ }
+ int len = endVal - start;
+ charCodeList = new List<int>.generate(len, (int i) {
+ if (!it.moveNext()) {
+ throw new RangeError.range(endVal, start, start + i);
+ }
+ int code = it.current;
+ bits |= code;
+ return code;
+ });
+ }
+ int length = charCodeList.length;
+ if (bits < 0) {
+ throw new ArgumentError(charCodes);
+ }
+ bool isOneByteString = (bits <= _maxLatin1);
+ if (isOneByteString) {
+ return _createOneByteString(charCodeList, 0, length);
+ }
+ return createFromCharCodes(charCodeList, 0, length, bits);
+ }
+
+ // Inlining is disabled as a workaround to http://dartbug.com/37800.
+
+ static String _createOneByteString(List<int> charCodes, int start, int len) {
+ // It's always faster to do this in Dart than to call into the runtime.
+ var s = _OneByteString._allocate(len);
+
+ // Special case for native Uint8 typed arrays.
+ if (charCodes is Uint8List) {
+ copyRangeFromUint8ListToOneByteString(charCodes, s, start, 0, len);
+ return s;
+ }
+
+ // Fall through to normal case.
+ for (int i = 0; i < len; i++) {
+ s._setAt(i, charCodes[start + i]);
+ }
+ return s;
+ }
+
+ external static String _createFromCodePoints(
+ List<int> codePoints, int start, int end);
+
+ String operator [](int index) => String.fromCharCode(codeUnitAt(index));
+
+ int codeUnitAt(int index); // Implemented in the subclasses.
+
+ int get length; // Implemented in the subclasses.
+
+ bool get isEmpty {
+ return this.length == 0;
+ }
+
+ bool get isNotEmpty => !isEmpty;
+
+ String operator +(String other) => _interpolate([this, other]);
+
+ String toString() {
+ return this;
+ }
+
+ bool operator ==(Object other) {
+ if (identical(this, other)) {
+ return true;
+ }
+ if (other is String && this.length == other.length) {
+ final len = this.length;
+ for (int i = 0; i < len; i++) {
+ if (this.codeUnitAt(i) != other.codeUnitAt(i)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ int compareTo(String other) {
+ int thisLength = this.length;
+ int otherLength = other.length;
+ int len = (thisLength < otherLength) ? thisLength : otherLength;
+ for (int i = 0; i < len; i++) {
+ int thisCodeUnit = this.codeUnitAt(i);
+ int otherCodeUnit = other.codeUnitAt(i);
+ if (thisCodeUnit < otherCodeUnit) {
+ return -1;
+ }
+ if (thisCodeUnit > otherCodeUnit) {
+ return 1;
+ }
+ }
+ if (thisLength < otherLength) return -1;
+ if (thisLength > otherLength) return 1;
+ return 0;
+ }
+
+ bool _substringMatches(int start, String other) {
+ if (other.isEmpty) return true;
+ final len = other.length;
+ if ((start < 0) || (start + len > this.length)) {
+ return false;
+ }
+ for (int i = 0; i < len; i++) {
+ if (this.codeUnitAt(i + start) != other.codeUnitAt(i)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ bool endsWith(String other) {
+ return _substringMatches(this.length - other.length, other);
+ }
+
+ bool startsWith(Pattern pattern, [int index = 0]) {
+ if ((index < 0) || (index > this.length)) {
+ throw new RangeError.range(index, 0, this.length);
+ }
+ if (pattern is String) {
+ return _substringMatches(index, pattern);
+ }
+ return pattern.matchAsPrefix(this, index) != null;
+ }
+
+ int indexOf(Pattern pattern, [int start = 0]) {
+ if ((start < 0) || (start > this.length)) {
+ throw new RangeError.range(start, 0, this.length, "start");
+ }
+ if (pattern is String) {
+ String other = pattern;
+ int maxIndex = this.length - other.length;
+ // TODO: Use an efficient string search (e.g. BMH).
+ for (int index = start; index <= maxIndex; index++) {
+ if (_substringMatches(index, other)) {
+ return index;
+ }
+ }
+ return -1;
+ }
+ for (int i = start; i <= this.length; i++) {
+ // TODO(11276); This has quadratic behavior because matchAsPrefix tries
+ // to find a later match too. Optimize matchAsPrefix to avoid this.
+ if (pattern.matchAsPrefix(this, i) != null) return i;
+ }
+ return -1;
+ }
+
+ int lastIndexOf(Pattern pattern, [int? start]) {
+ if (start == null) {
+ start = this.length;
+ } else if (start < 0 || start > this.length) {
+ throw new RangeError.range(start, 0, this.length);
+ }
+ if (pattern is String) {
+ String other = pattern;
+ int maxIndex = this.length - other.length;
+ if (maxIndex < start) start = maxIndex;
+ for (int index = start; index >= 0; index--) {
+ if (_substringMatches(index, other)) {
+ return index;
+ }
+ }
+ return -1;
+ }
+ for (int i = start; i >= 0; i--) {
+ // TODO(11276); This has quadratic behavior because matchAsPrefix tries
+ // to find a later match too. Optimize matchAsPrefix to avoid this.
+ if (pattern.matchAsPrefix(this, i) != null) return i;
+ }
+ return -1;
+ }
+
+ String substring(int startIndex, [int? endIndex]) {
+ endIndex = RangeError.checkValidRange(startIndex, endIndex, this.length);
+ return _substringUnchecked(startIndex, endIndex);
+ }
+
+ String _substringUnchecked(int startIndex, int endIndex) {
+ assert((startIndex >= 0) && (startIndex <= this.length));
+ assert((endIndex >= 0) && (endIndex <= this.length));
+ assert(startIndex <= endIndex);
+
+ if (startIndex == endIndex) {
+ return "";
+ }
+ if ((startIndex == 0) && (endIndex == this.length)) {
+ return this;
+ }
+ if ((startIndex + 1) == endIndex) {
+ return this[startIndex];
+ }
+ return _substringUncheckedNative(startIndex, endIndex);
+ }
+
+ external String _substringUncheckedNative(int startIndex, int endIndex);
+
+ // Checks for one-byte whitespaces only.
+ static bool _isOneByteWhitespace(int codeUnit) {
+ if (codeUnit <= 32) {
+ return ((codeUnit == 32) || // Space.
+ ((codeUnit <= 13) && (codeUnit >= 9))); // CR, LF, TAB, etc.
+ }
+ return (codeUnit == 0x85) || (codeUnit == 0xA0); // NEL, NBSP.
+ }
+
+ // Characters with Whitespace property (Unicode 6.3).
+ // 0009..000D ; White_Space # Cc <control-0009>..<control-000D>
+ // 0020 ; White_Space # Zs SPACE
+ // 0085 ; White_Space # Cc <control-0085>
+ // 00A0 ; White_Space # Zs NO-BREAK SPACE
+ // 1680 ; White_Space # Zs OGHAM SPACE MARK
+ // 2000..200A ; White_Space # Zs EN QUAD..HAIR SPACE
+ // 2028 ; White_Space # Zl LINE SEPARATOR
+ // 2029 ; White_Space # Zp PARAGRAPH SEPARATOR
+ // 202F ; White_Space # Zs NARROW NO-BREAK SPACE
+ // 205F ; White_Space # Zs MEDIUM MATHEMATICAL SPACE
+ // 3000 ; White_Space # Zs IDEOGRAPHIC SPACE
+ //
+ // BOM: 0xFEFF
+ static bool _isTwoByteWhitespace(int codeUnit) {
+ if (codeUnit <= 32) {
+ return (codeUnit == 32) || ((codeUnit <= 13) && (codeUnit >= 9));
+ }
+ if (codeUnit < 0x85) return false;
+ if ((codeUnit == 0x85) || (codeUnit == 0xA0)) return true;
+ return (codeUnit <= 0x200A)
+ ? ((codeUnit == 0x1680) || (0x2000 <= codeUnit))
+ : ((codeUnit == 0x2028) ||
+ (codeUnit == 0x2029) ||
+ (codeUnit == 0x202F) ||
+ (codeUnit == 0x205F) ||
+ (codeUnit == 0x3000) ||
+ (codeUnit == 0xFEFF));
+ }
+
+ int _firstNonWhitespace() {
+ final len = this.length;
+ int first = 0;
+ for (; first < len; first++) {
+ if (!_isWhitespace(this.codeUnitAt(first))) {
+ break;
+ }
+ }
+ return first;
+ }
+
+ int _lastNonWhitespace() {
+ int last = this.length - 1;
+ for (; last >= 0; last--) {
+ if (!_isWhitespace(this.codeUnitAt(last))) {
+ break;
+ }
+ }
+ return last;
+ }
+
+ String trim() {
+ final len = this.length;
+ int first = _firstNonWhitespace();
+ if (len == first) {
+ // String contains only whitespaces.
+ return "";
+ }
+ int last = _lastNonWhitespace() + 1;
+ if ((first == 0) && (last == len)) {
+ // Returns this string since it does not have leading or trailing
+ // whitespaces.
+ return this;
+ }
+ return _substringUnchecked(first, last);
+ }
+
+ String trimLeft() {
+ final len = this.length;
+ int first = 0;
+ for (; first < len; first++) {
+ if (!_isWhitespace(this.codeUnitAt(first))) {
+ break;
+ }
+ }
+ if (len == first) {
+ // String contains only whitespaces.
+ return "";
+ }
+ if (first == 0) {
+ // Returns this string since it does not have leading or trailing
+ // whitespaces.
+ return this;
+ }
+ return _substringUnchecked(first, len);
+ }
+
+ String trimRight() {
+ final len = this.length;
+ int last = len - 1;
+ for (; last >= 0; last--) {
+ if (!_isWhitespace(this.codeUnitAt(last))) {
+ break;
+ }
+ }
+ if (last == -1) {
+ // String contains only whitespaces.
+ return "";
+ }
+ if (last == (len - 1)) {
+ // Returns this string since it does not have trailing whitespaces.
+ return this;
+ }
+ return _substringUnchecked(0, last + 1);
+ }
+
+ String operator *(int times) {
+ if (times <= 0) return "";
+ if (times == 1) return this;
+ StringBuffer buffer = new StringBuffer(this);
+ for (int i = 1; i < times; i++) {
+ buffer.write(this);
+ }
+ return buffer.toString();
+ }
+
+ String padLeft(int width, [String padding = ' ']) {
+ int delta = width - this.length;
+ if (delta <= 0) return this;
+ StringBuffer buffer = new StringBuffer();
+ for (int i = 0; i < delta; i++) {
+ buffer.write(padding);
+ }
+ buffer.write(this);
+ return buffer.toString();
+ }
+
+ String padRight(int width, [String padding = ' ']) {
+ int delta = width - this.length;
+ if (delta <= 0) return this;
+ StringBuffer buffer = new StringBuffer(this);
+ for (int i = 0; i < delta; i++) {
+ buffer.write(padding);
+ }
+ return buffer.toString();
+ }
+
+ bool contains(Pattern pattern, [int startIndex = 0]) {
+ if (pattern is String) {
+ if (startIndex < 0 || startIndex > this.length) {
+ throw new RangeError.range(startIndex, 0, this.length);
+ }
+ return indexOf(pattern, startIndex) >= 0;
+ }
+ return pattern.allMatches(this.substring(startIndex)).isNotEmpty;
+ }
+
+ String replaceFirst(Pattern pattern, String replacement,
+ [int startIndex = 0]) {
+ RangeError.checkValueInInterval(startIndex, 0, this.length, "startIndex");
+ Iterator iterator = startIndex == 0
+ ? pattern.allMatches(this).iterator
+ : pattern.allMatches(this, startIndex).iterator;
+ if (!iterator.moveNext()) return this;
+ Match match = iterator.current;
+ return replaceRange(match.start, match.end, replacement);
+ }
+
+ String replaceRange(int start, int? end, String replacement) {
+ final length = this.length;
+ final localEnd = RangeError.checkValidRange(start, end, length);
+ bool replacementIsOneByte = replacement._isOneByte;
+ if (start == 0 && localEnd == length) return replacement;
+ int replacementLength = replacement.length;
+ int totalLength = start + (length - localEnd) + replacementLength;
+ if (replacementIsOneByte && this._isOneByte) {
+ var result = _OneByteString._allocate(totalLength);
+ int index = 0;
+ index = result._setRange(index, this, 0, start);
+ index = result._setRange(start, replacement, 0, replacementLength);
+ result._setRange(index, this, localEnd, length);
+ return result;
+ }
+ List slices = [];
+ _addReplaceSlice(slices, 0, start);
+ if (replacement.length > 0) slices.add(replacement);
+ _addReplaceSlice(slices, localEnd, length);
+ return _joinReplaceAllResult(
+ this, slices, totalLength, replacementIsOneByte);
+ }
+
+ static int _addReplaceSlice(List matches, int start, int end) {
+ int length = end - start;
+ if (length > 0) {
+ if (length <= _maxLengthValue && start <= _maxStartValue) {
+ matches.add(-((start << _lengthBits) | length));
+ } else {
+ matches.add(start);
+ matches.add(end);
+ }
+ }
+ return length;
+ }
+
+ String replaceAll(Pattern pattern, String replacement) {
+ int startIndex = 0;
+ // String fragments that replace the prefix [this] up to [startIndex].
+ List matches = [];
+ int length = 0; // Length of all fragments.
+ int replacementLength = replacement.length;
+
+ if (replacementLength == 0) {
+ for (Match match in pattern.allMatches(this)) {
+ length += _addReplaceSlice(matches, startIndex, match.start);
+ startIndex = match.end;
+ }
+ } else {
+ for (Match match in pattern.allMatches(this)) {
+ length += _addReplaceSlice(matches, startIndex, match.start);
+ matches.add(replacement);
+ length += replacementLength;
+ startIndex = match.end;
+ }
+ }
+ // No match, or a zero-length match at start with zero-length replacement.
+ if (startIndex == 0 && length == 0) return this;
+ length += _addReplaceSlice(matches, startIndex, this.length);
+ bool replacementIsOneByte = replacement._isOneByte;
+ if (replacementIsOneByte &&
+ length < _maxJoinReplaceOneByteStringLength &&
+ this._isOneByte) {
+ // TODO(lrn): Is there a cut-off point, or is runtime always faster?
+ return _joinReplaceAllOneByteResult(this, matches, length);
+ }
+ return _joinReplaceAllResult(this, matches, length, replacementIsOneByte);
+ }
+
+ /**
+ * As [_joinReplaceAllResult], but knowing that the result
+ * is always a [_OneByteString].
+ */
+ static String _joinReplaceAllOneByteResult(
+ String base, List matches, int length) {
+ _OneByteString result = _OneByteString._allocate(length);
+ int writeIndex = 0;
+ for (int i = 0; i < matches.length; i++) {
+ var entry = matches[i];
+ if (entry is _Smi) {
+ int sliceStart = entry;
+ int sliceEnd;
+ if (sliceStart < 0) {
+ int bits = -sliceStart;
+ int sliceLength = bits & _lengthMask;
+ sliceStart = bits >> _lengthBits;
+ sliceEnd = sliceStart + sliceLength;
+ } else {
+ i++;
+ // This function should only be called with valid matches lists.
+ // If the list is short, or sliceEnd is not an integer, one of
+ // the next few lines will throw anyway.
+ assert(i < matches.length);
+ sliceEnd = matches[i];
+ }
+ for (int j = sliceStart; j < sliceEnd; j++) {
+ result._setAt(writeIndex++, base.codeUnitAt(j));
+ }
+ } else {
+ // Replacement is a one-byte string.
+ String replacement = entry;
+ for (int j = 0; j < replacement.length; j++) {
+ result._setAt(writeIndex++, replacement.codeUnitAt(j));
+ }
+ }
+ }
+ assert(writeIndex == length);
+ return result;
+ }
+
+ /**
+ * Combine the results of a [replaceAll] match into a new string.
+ *
+ * The [matches] lists contains Smi index pairs representing slices of
+ * [base] and [String]s to be put in between the slices.
+ *
+ * The total [length] of the resulting string is known, as is
+ * whether the replacement strings are one-byte strings.
+ * If they are, then we have to check the base string slices to know
+ * whether the result must be a one-byte string.
+ */
+
+ external static String _joinReplaceAllResult(
+ String base, List matches, int length, bool replacementStringsAreOneByte);
+
+ String replaceAllMapped(Pattern pattern, String replace(Match match)) {
+ List matches = [];
+ int length = 0;
+ int startIndex = 0;
+ bool replacementStringsAreOneByte = true;
+ for (Match match in pattern.allMatches(this)) {
+ length += _addReplaceSlice(matches, startIndex, match.start);
+ var replacement = "${replace(match)}";
+ matches.add(replacement);
+ length += replacement.length;
+ replacementStringsAreOneByte =
+ replacementStringsAreOneByte && replacement._isOneByte;
+ startIndex = match.end;
+ }
+ if (matches.isEmpty) return this;
+ length += _addReplaceSlice(matches, startIndex, this.length);
+ if (replacementStringsAreOneByte &&
+ length < _maxJoinReplaceOneByteStringLength &&
+ this._isOneByte) {
+ return _joinReplaceAllOneByteResult(this, matches, length);
+ }
+ return _joinReplaceAllResult(
+ this, matches, length, replacementStringsAreOneByte);
+ }
+
+ String replaceFirstMapped(Pattern pattern, String replace(Match match),
+ [int startIndex = 0]) {
+ RangeError.checkValueInInterval(startIndex, 0, this.length, "startIndex");
+
+ var matches = pattern.allMatches(this, startIndex).iterator;
+ if (!matches.moveNext()) return this;
+ var match = matches.current;
+ var replacement = "${replace(match)}";
+ return replaceRange(match.start, match.end, replacement);
+ }
+
+ static String _matchString(Match match) => match[0]!;
+ static String _stringIdentity(String string) => string;
+
+ String _splitMapJoinEmptyString(
+ String onMatch(Match match), String onNonMatch(String nonMatch)) {
+ // Pattern is the empty string.
+ StringBuffer buffer = new StringBuffer();
+ int length = this.length;
+ int i = 0;
+ buffer.write(onNonMatch(""));
+ while (i < length) {
+ buffer.write(onMatch(new _StringMatch(i, this, "")));
+ // Special case to avoid splitting a surrogate pair.
+ int code = this.codeUnitAt(i);
+ if ((code & ~0x3FF) == 0xD800 && length > i + 1) {
+ // Leading surrogate;
+ code = this.codeUnitAt(i + 1);
+ if ((code & ~0x3FF) == 0xDC00) {
+ // Matching trailing surrogate.
+ buffer.write(onNonMatch(this.substring(i, i + 2)));
+ i += 2;
+ continue;
+ }
+ }
+ buffer.write(onNonMatch(this[i]));
+ i++;
+ }
+ buffer.write(onMatch(new _StringMatch(i, this, "")));
+ buffer.write(onNonMatch(""));
+ return buffer.toString();
+ }
+
+ String splitMapJoin(Pattern pattern,
+ {String onMatch(Match match)?, String onNonMatch(String nonMatch)?}) {
+ onMatch ??= _matchString;
+ onNonMatch ??= _stringIdentity;
+ if (pattern is String) {
+ String stringPattern = pattern;
+ if (stringPattern.isEmpty) {
+ return _splitMapJoinEmptyString(onMatch, onNonMatch);
+ }
+ }
+ StringBuffer buffer = new StringBuffer();
+ int startIndex = 0;
+ for (Match match in pattern.allMatches(this)) {
+ buffer.write(onNonMatch(this.substring(startIndex, match.start)));
+ buffer.write(onMatch(match).toString());
+ startIndex = match.end;
+ }
+ buffer.write(onNonMatch(this.substring(startIndex)));
+ return buffer.toString();
+ }
+
+ /**
+ * Convert all objects in [values] to strings and concat them
+ * into a result string.
+ * Modifies the input list if it contains non-`String` values.
+ */
+ @pragma("wasm:entry-point", "call")
+ static String _interpolate(final List values) {
+ final numValues = values.length;
+ int totalLength = 0;
+ int i = 0;
+ while (i < numValues) {
+ final e = values[i];
+ final s = e.toString();
+ values[i] = s;
+ if (s is _OneByteString) {
+ totalLength += s.length;
+ i++;
+ } else {
+ // Handle remaining elements without checking for one-byte-ness.
+ while (++i < numValues) {
+ final e = values[i];
+ values[i] = e.toString();
+ }
+ return _concatRangeNative(values, 0, numValues);
+ }
+ }
+ // All strings were one-byte strings.
+ return _OneByteString._concatAll(values, totalLength);
+ }
+
+ static ArgumentError _interpolationError(Object? o, Object? result) {
+ // Since Dart 2.0, [result] can only be null.
+ return new ArgumentError.value(
+ o, "object", "toString method returned 'null'");
+ }
+
+ Iterable<Match> allMatches(String string, [int start = 0]) {
+ if (start < 0 || start > string.length) {
+ throw new RangeError.range(start, 0, string.length, "start");
+ }
+ return new _StringAllMatchesIterable(string, this, start);
+ }
+
+ Match? matchAsPrefix(String string, [int start = 0]) {
+ if (start < 0 || start > string.length) {
+ throw new RangeError.range(start, 0, string.length);
+ }
+ if (start + this.length > string.length) return null;
+ for (int i = 0; i < this.length; i++) {
+ if (string.codeUnitAt(start + i) != this.codeUnitAt(i)) {
+ return null;
+ }
+ }
+ return new _StringMatch(start, string, this);
+ }
+
+ List<String> split(Pattern pattern) {
+ if ((pattern is String) && pattern.isEmpty) {
+ List<String> result =
+ new List<String>.generate(this.length, (int i) => this[i]);
+ return result;
+ }
+ int length = this.length;
+ Iterator iterator = pattern.allMatches(this).iterator;
+ if (length == 0 && iterator.moveNext()) {
+ // A matched empty string input returns the empty list.
+ return <String>[];
+ }
+ List<String> result = <String>[];
+ int startIndex = 0;
+ int previousIndex = 0;
+ // 'pattern' may not be implemented correctly and therefore we cannot
+ // call _substringUnhchecked unless it is a trustworthy type (e.g. String).
+ while (true) {
+ if (startIndex == length || !iterator.moveNext()) {
+ result.add(this.substring(previousIndex, length));
+ break;
+ }
+ Match match = iterator.current;
+ if (match.start == length) {
+ result.add(this.substring(previousIndex, length));
+ break;
+ }
+ int endIndex = match.end;
+ if (startIndex == endIndex && endIndex == previousIndex) {
+ ++startIndex; // empty match, advance and restart
+ continue;
+ }
+ result.add(this.substring(previousIndex, match.start));
+ startIndex = previousIndex = endIndex;
+ }
+ return result;
+ }
+
+ List<int> get codeUnits => new CodeUnits(this);
+
+ Runes get runes => new Runes(this);
+
+ external String toUpperCase();
+
+ external String toLowerCase();
+
+ // Concatenate ['start', 'end'[ elements of 'strings'.
+ static String _concatRange(List<String> strings, int start, int end) {
+ if ((end - start) == 1) {
+ return strings[start];
+ }
+ return _concatRangeNative(strings, start, end);
+ }
+
+ // Call this method if all elements of [strings] are known to be strings
+ // but not all are known to be OneByteString(s).
+ static String _concatRangeNative(List strings, int start, int end) {
+ int totalLength = 0;
+ for (int i = start; i < end; i++) {
+ totalLength += unsafeCast<_StringBase>(strings[i]).length;
+ }
+ _TwoByteString result = _TwoByteString._allocate(totalLength);
+ int offset = 0;
+ for (int i = start; i < end; i++) {
+ _StringBase s = unsafeCast<_StringBase>(strings[i]);
+ offset = s._copyIntoTwoByteString(result, offset);
+ }
+ return result;
+ }
+
+ int _copyIntoTwoByteString(_TwoByteString result, int offset);
+
+ static int _combineHashes(int hash, int other_hash) {
+ hash += other_hash;
+ hash += hash << 10;
+ hash ^= (hash & 0xFFFFFFFF) >>> 6;
+ return hash;
+ }
+
+ static int _finalizeHash(int hash) {
+ hash += hash << 3;
+ hash ^= (hash & 0xFFFFFFFF) >>> 11;
+ hash += hash << 15;
+ hash &= 0x3FFFFFFF;
+ return hash == 0 ? 1 : hash;
+ }
+}
+
+@pragma("wasm:entry-point")
+class _OneByteString extends _StringBase {
+ @pragma("wasm:entry-point")
+ WasmIntArray<WasmI8> _array;
+
+ _OneByteString._withLength(int length)
+ : _array = WasmIntArray<WasmI8>(length),
+ super._();
+
+ // Same hash as VM
+ int _computeHashCode() {
+ WasmIntArray<WasmI8> array = _array;
+ int length = array.length;
+ int hash = 0;
+ for (int i = 0; i < length; i++) {
+ hash = _StringBase._combineHashes(hash, array.readUnsigned(i));
+ }
+ return _StringBase._finalizeHash(hash);
+ }
+
+ int codeUnitAt(int index) => _array.readUnsigned(index);
+
+ int get length => _array.length;
+
+ bool _isWhitespace(int codeUnit) {
+ return _StringBase._isOneByteWhitespace(codeUnit);
+ }
+
+ bool operator ==(Object other) {
+ return super == other;
+ }
+
+ String _substringUncheckedNative(int startIndex, int endIndex) {
+ int length = endIndex - startIndex;
+ var result = _OneByteString._withLength(length);
+ for (int i = 0; i < length; i++) {
+ result._setAt(i, codeUnitAt(startIndex + i));
+ }
+ return result;
+ }
+
+ List<String> _splitWithCharCode(int charCode) {
+ final parts = <String>[];
+ int i = 0;
+ int start = 0;
+ for (i = 0; i < this.length; ++i) {
+ if (this.codeUnitAt(i) == charCode) {
+ parts.add(this._substringUnchecked(start, i));
+ start = i + 1;
+ }
+ }
+ parts.add(this._substringUnchecked(start, i));
+ return parts;
+ }
+
+ List<String> split(Pattern pattern) {
+ if (pattern is _OneByteString && pattern.length == 1) {
+ return _splitWithCharCode(pattern.codeUnitAt(0));
+ }
+ return super.split(pattern);
+ }
+
+ // All element of 'strings' must be OneByteStrings.
+ static _concatAll(List strings, int totalLength) {
+ final result = _OneByteString._allocate(totalLength);
+ final to = result._array;
+ final stringsLength = strings.length;
+ int j = 0;
+ for (int s = 0; s < stringsLength; s++) {
+ final _OneByteString e = unsafeCast<_OneByteString>(strings[s]);
+ final from = e._array;
+ final length = from.length;
+ for (int i = 0; i < length; i++) {
+ to.write(j++, from.readUnsigned(i));
+ }
+ }
+ return result;
+ }
+
+ int _copyIntoTwoByteString(_TwoByteString result, int offset) {
+ final from = _array;
+ final int length = from.length;
+ final to = result._array;
+ int j = offset;
+ for (int i = 0; i < length; i++) {
+ to.write(j++, from.readUnsigned(i));
+ }
+ return j;
+ }
+
+ int indexOf(Pattern pattern, [int start = 0]) {
+ final len = this.length;
+ // Specialize for single character pattern.
+ if (pattern is String && pattern.length == 1 && start >= 0 && start < len) {
+ final patternCu0 = pattern.codeUnitAt(0);
+ if (patternCu0 > 0xFF) {
+ return -1;
+ }
+ for (int i = start; i < len; i++) {
+ if (this.codeUnitAt(i) == patternCu0) {
+ return i;
+ }
+ }
+ return -1;
+ }
+ return super.indexOf(pattern, start);
+ }
+
+ bool contains(Pattern pattern, [int start = 0]) {
+ final len = this.length;
+ if (pattern is String && pattern.length == 1 && start >= 0 && start < len) {
+ final patternCu0 = pattern.codeUnitAt(0);
+ if (patternCu0 > 0xFF) {
+ return false;
+ }
+ for (int i = start; i < len; i++) {
+ if (this.codeUnitAt(i) == patternCu0) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return super.contains(pattern, start);
+ }
+
+ String operator *(int times) {
+ if (times <= 0) return "";
+ if (times == 1) return this;
+ int length = this.length;
+ if (this.isEmpty) return this; // Don't clone empty string.
+ _OneByteString result = _OneByteString._allocate(length * times);
+ int index = 0;
+ for (int i = 0; i < times; i++) {
+ for (int j = 0; j < length; j++) {
+ result._setAt(index++, this.codeUnitAt(j));
+ }
+ }
+ return result;
+ }
+
+ String padLeft(int width, [String padding = ' ']) {
+ if (!padding._isOneByte) {
+ return super.padLeft(width, padding);
+ }
+ int length = this.length;
+ int delta = width - length;
+ if (delta <= 0) return this;
+ int padLength = padding.length;
+ int resultLength = padLength * delta + length;
+ _OneByteString result = _OneByteString._allocate(resultLength);
+ int index = 0;
+ if (padLength == 1) {
+ int padChar = padding.codeUnitAt(0);
+ for (int i = 0; i < delta; i++) {
+ result._setAt(index++, padChar);
+ }
+ } else {
+ for (int i = 0; i < delta; i++) {
+ for (int j = 0; j < padLength; j++) {
+ result._setAt(index++, padding.codeUnitAt(j));
+ }
+ }
+ }
+ for (int i = 0; i < length; i++) {
+ result._setAt(index++, this.codeUnitAt(i));
+ }
+ return result;
+ }
+
+ String padRight(int width, [String padding = ' ']) {
+ if (!padding._isOneByte) {
+ return super.padRight(width, padding);
+ }
+ int length = this.length;
+ int delta = width - length;
+ if (delta <= 0) return this;
+ int padLength = padding.length;
+ int resultLength = length + padLength * delta;
+ _OneByteString result = _OneByteString._allocate(resultLength);
+ int index = 0;
+ for (int i = 0; i < length; i++) {
+ result._setAt(index++, this.codeUnitAt(i));
+ }
+ if (padLength == 1) {
+ int padChar = padding.codeUnitAt(0);
+ for (int i = 0; i < delta; i++) {
+ result._setAt(index++, padChar);
+ }
+ } else {
+ for (int i = 0; i < delta; i++) {
+ for (int j = 0; j < padLength; j++) {
+ result._setAt(index++, padding.codeUnitAt(j));
+ }
+ }
+ }
+ return result;
+ }
+
+ // Lower-case conversion table for Latin-1 as string.
+ // Upper-case ranges: 0x41-0x5a ('A' - 'Z'), 0xc0-0xd6, 0xd8-0xde.
+ // Conversion to lower case performed by adding 0x20.
+ static const _LC_TABLE =
+ "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
+ "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
+ "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
+ "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
+ "\x40\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f"
+ "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x5b\x5c\x5d\x5e\x5f"
+ "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f"
+ "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f"
+ "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"
+ "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
+ "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf"
+ "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
+ "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef"
+ "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xd7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xdf"
+ "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef"
+ "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff";
+
+ // Upper-case conversion table for Latin-1 as string.
+ // Lower-case ranges: 0x61-0x7a ('a' - 'z'), 0xe0-0xff.
+ // The characters 0xb5 (µ) and 0xff (ÿ) have upper case variants
+ // that are not Latin-1. These are both marked as 0x00 in the table.
+ // The German "sharp s" \xdf (ß) should be converted into two characters (SS),
+ // and is also marked with 0x00.
+ // Conversion to lower case performed by subtracting 0x20.
+ static const _UC_TABLE =
+ "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
+ "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
+ "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
+ "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
+ "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f"
+ "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"
+ "\x60\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f"
+ "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x7b\x7c\x7d\x7e\x7f"
+ "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"
+ "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
+ "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf"
+ "\xb0\xb1\xb2\xb3\xb4\x00\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
+ "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
+ "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\x00"
+ "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
+ "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xf7\xd8\xd9\xda\xdb\xdc\xdd\xde\x00";
+
+ String toLowerCase() {
+ for (int i = 0; i < this.length; i++) {
+ final c = this.codeUnitAt(i);
+ if (c == _LC_TABLE.codeUnitAt(c)) continue;
+ // Upper-case character found.
+ final result = _allocate(this.length);
+ for (int j = 0; j < i; j++) {
+ result._setAt(j, this.codeUnitAt(j));
+ }
+ for (int j = i; j < this.length; j++) {
+ result._setAt(j, _LC_TABLE.codeUnitAt(this.codeUnitAt(j)));
+ }
+ return result;
+ }
+ return this;
+ }
+
+ String toUpperCase() {
+ for (int i = 0; i < this.length; i++) {
+ final c = this.codeUnitAt(i);
+ // Continue loop if character is unchanged by upper-case conversion.
+ if (c == _UC_TABLE.codeUnitAt(c)) continue;
+
+ // Check rest of string for characters that do not convert to
+ // single-characters in the Latin-1 range.
+ for (int j = i; j < this.length; j++) {
+ final c = this.codeUnitAt(j);
+ if ((_UC_TABLE.codeUnitAt(c) == 0x00) && (c != 0x00)) {
+ // We use the 0x00 value for characters other than the null character,
+ // that don't convert to a single Latin-1 character when upper-cased.
+ // In that case, call the generic super-class method.
+ return super.toUpperCase();
+ }
+ }
+ // Some lower-case characters found, but all upper-case to single Latin-1
+ // characters.
+ final result = _allocate(this.length);
+ for (int j = 0; j < i; j++) {
+ result._setAt(j, this.codeUnitAt(j));
+ }
+ for (int j = i; j < this.length; j++) {
+ result._setAt(j, _UC_TABLE.codeUnitAt(this.codeUnitAt(j)));
+ }
+ return result;
+ }
+ return this;
+ }
+
+ // Allocates a string of given length, expecting its content to be
+ // set using _setAt.
+
+ static _OneByteString _allocate(int length) {
+ return unsafeCast<_OneByteString>(allocateOneByteString(length));
+ }
+
+ external static _OneByteString _allocateFromOneByteList(
+ List<int> list, int start, int end);
+
+ // This is internal helper method. Code point value must be a valid
+ // Latin1 value (0..0xFF), index must be valid.
+
+ void _setAt(int index, int codePoint) {
+ writeIntoOneByteString(this, index, codePoint);
+ }
+
+ // Should be optimizable to a memory move.
+ // Accepts both _OneByteString and _ExternalOneByteString as argument.
+ // Returns index after last character written.
+ int _setRange(int index, String oneByteString, int start, int end) {
+ assert(oneByteString._isOneByte);
+ assert(0 <= start);
+ assert(start <= end);
+ assert(end <= oneByteString.length);
+ assert(0 <= index);
+ assert(index + (end - start) <= length);
+ for (int i = start; i < end; i++) {
+ _setAt(index, oneByteString.codeUnitAt(i));
+ index += 1;
+ }
+ return index;
+ }
+}
+
+@pragma("wasm:entry-point")
+class _TwoByteString extends _StringBase {
+ @pragma("wasm:entry-point")
+ WasmIntArray<WasmI16> _array;
+
+ _TwoByteString._withLength(int length)
+ : _array = WasmIntArray<WasmI16>(length),
+ super._();
+
+ // Same hash as VM
+ int _computeHashCode() {
+ WasmIntArray<WasmI16> array = _array;
+ int length = array.length;
+ int hash = 0;
+ for (int i = 0; i < length; i++) {
+ hash = _StringBase._combineHashes(hash, array.readUnsigned(i));
+ }
+ return _StringBase._finalizeHash(hash);
+ }
+
+ // Allocates a string of given length, expecting its content to be
+ // set using _setAt.
+
+ static _TwoByteString _allocate(int length) {
+ return unsafeCast<_TwoByteString>(allocateTwoByteString(length));
+ }
+
+ static String _allocateFromTwoByteList(List<int> list, int start, int end) {
+ final int length = end - start;
+ final s = _allocate(length);
+ final array = s._array;
+ for (int i = 0; i < length; i++) {
+ array.write(i, list[start + i]);
+ }
+ return s;
+ }
+
+ // This is internal helper method. Code point value must be a valid
+ // UTF-16 value (0..0xFFFF), index must be valid.
+
+ void _setAt(int index, int codePoint) {
+ writeIntoTwoByteString(this, index, codePoint);
+ }
+
+ bool _isWhitespace(int codeUnit) {
+ return _StringBase._isTwoByteWhitespace(codeUnit);
+ }
+
+ int codeUnitAt(int index) => _array.readUnsigned(index);
+
+ int get length => _array.length;
+
+ bool operator ==(Object other) {
+ return super == other;
+ }
+
+ int _copyIntoTwoByteString(_TwoByteString result, int offset) {
+ final from = _array;
+ final int length = from.length;
+ final to = result._array;
+ int j = offset;
+ for (int i = 0; i < length; i++) {
+ to.write(j++, from.readUnsigned(i));
+ }
+ return j;
+ }
+}
+
+class _StringMatch implements Match {
+ const _StringMatch(this.start, this.input, this.pattern);
+
+ int get end => start + pattern.length;
+ String operator [](int g) => group(g);
+ int get groupCount => 0;
+
+ String group(int group) {
+ if (group != 0) {
+ throw new RangeError.value(group);
+ }
+ return pattern;
+ }
+
+ List<String> groups(List<int> groups) {
+ List<String> result = <String>[];
+ for (int g in groups) {
+ result.add(group(g));
+ }
+ return result;
+ }
+
+ final int start;
+ final String input;
+ final String pattern;
+}
+
+class _StringAllMatchesIterable extends Iterable<Match> {
+ final String _input;
+ final String _pattern;
+ final int _index;
+
+ _StringAllMatchesIterable(this._input, this._pattern, this._index);
+
+ Iterator<Match> get iterator =>
+ new _StringAllMatchesIterator(_input, _pattern, _index);
+
+ Match get first {
+ int index = _input.indexOf(_pattern, _index);
+ if (index >= 0) {
+ return new _StringMatch(index, _input, _pattern);
+ }
+ throw IterableElementError.noElement();
+ }
+}
+
+class _StringAllMatchesIterator implements Iterator<Match> {
+ final String _input;
+ final String _pattern;
+ int _index;
+ Match? _current;
+
+ _StringAllMatchesIterator(this._input, this._pattern, this._index);
+
+ bool moveNext() {
+ if (_index + _pattern.length > _input.length) {
+ _current = null;
+ return false;
+ }
+ var index = _input.indexOf(_pattern, _index);
+ if (index < 0) {
+ _index = _input.length + 1;
+ _current = null;
+ return false;
+ }
+ int end = index + _pattern.length;
+ _current = new _StringMatch(index, _input, _pattern);
+ // Empty match, don't start at same location again.
+ if (end == _index) end++;
+ _index = end;
+ return true;
+ }
+
+ Match get current => _current as Match;
+}
diff --git a/sdk/lib/_internal/wasm/lib/timer_patch.dart b/sdk/lib/_internal/wasm/lib/timer_patch.dart
new file mode 100644
index 0000000..47c7f03
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/timer_patch.dart
@@ -0,0 +1,82 @@
+// Copyright (c) 2022, 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.
+
+// part of "async_patch.dart";
+
+// Implementation of `Timer` and `scheduleMicrotask` via the JS event loop.
+
+import 'dart:_internal' show patch, scheduleCallback;
+
+@patch
+class Timer {
+ @patch
+ static Timer _createTimer(Duration duration, void callback()) {
+ return _OneShotTimer(duration, callback);
+ }
+
+ @patch
+ static Timer _createPeriodicTimer(
+ Duration duration, void callback(Timer timer)) {
+ return _PeriodicTimer(duration, callback);
+ }
+}
+
+abstract class _Timer implements Timer {
+ final double milliseconds;
+ bool isActive;
+ int tick;
+
+ _Timer(Duration duration)
+ : milliseconds = duration.inMilliseconds.toDouble(),
+ isActive = true,
+ tick = 0 {
+ _schedule();
+ }
+
+ void _schedule() {
+ scheduleCallback(milliseconds, () {
+ if (isActive) {
+ tick++;
+ _run();
+ }
+ });
+ }
+
+ void _run();
+
+ @override
+ void cancel() {
+ isActive = false;
+ }
+}
+
+class _OneShotTimer extends _Timer {
+ final void Function() callback;
+
+ _OneShotTimer(Duration duration, this.callback) : super(duration);
+
+ void _run() {
+ isActive = false;
+ callback();
+ }
+}
+
+class _PeriodicTimer extends _Timer {
+ final void Function(Timer) callback;
+
+ _PeriodicTimer(Duration duration, this.callback) : super(duration);
+
+ void _run() {
+ _schedule();
+ callback(this);
+ }
+}
+
+@patch
+class _AsyncRun {
+ @patch
+ static void _scheduleImmediate(void callback()) {
+ scheduleCallback(0, callback);
+ }
+}
diff --git a/sdk/lib/_internal/wasm/lib/type.dart b/sdk/lib/_internal/wasm/lib/type.dart
new file mode 100644
index 0000000..6db370d
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/type.dart
@@ -0,0 +1,47 @@
+// Copyright (c) 2022, 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.
+
+// Representation of runtime types. Can only represent interface types so far,
+// and does not capture nullability.
+
+@pragma("wasm:entry-point")
+class _Type implements Type {
+ final int classId;
+ final List<_Type> typeArguments;
+
+ @pragma("wasm:entry-point")
+ const _Type(this.classId, [this.typeArguments = const []]);
+
+ bool operator ==(Object other) {
+ if (other is! _Type) return false;
+ if (classId != other.classId) return false;
+ for (int i = 0; i < typeArguments.length; i++) {
+ if (typeArguments[i] != other.typeArguments[i]) return false;
+ }
+ return true;
+ }
+
+ int get hashCode {
+ int hash = mix64(classId);
+ for (int i = 0; i < typeArguments.length; i++) {
+ hash = mix64(hash ^ typeArguments[i].hashCode);
+ }
+ return hash;
+ }
+
+ String toString() {
+ StringBuffer s = StringBuffer();
+ s.write("Type");
+ s.write(classId);
+ if (typeArguments.isNotEmpty) {
+ s.write("<");
+ for (int i = 0; i < typeArguments.length; i++) {
+ if (i > 0) s.write(",");
+ s.write(typeArguments[i]);
+ }
+ s.write(">");
+ }
+ return s.toString();
+ }
+}
diff --git a/sdk/lib/libraries.json b/sdk/lib/libraries.json
index 8573ca4..12aee71 100644
--- a/sdk/lib/libraries.json
+++ b/sdk/lib/libraries.json
@@ -154,6 +154,81 @@
}
}
},
+ "wasm": {
+ "libraries": {
+ "_internal": {
+ "uri": "internal/internal.dart",
+ "patches": [
+ "_internal/wasm/lib/internal_patch.dart",
+ "_internal/wasm/lib/class_id.dart",
+ "_internal/wasm/lib/patch.dart",
+ "_internal/wasm/lib/print_patch.dart",
+ "_internal/vm/lib/symbol_patch.dart"
+ ]
+ },
+ "async": {
+ "uri": "async/async.dart",
+ "patches": "_internal/wasm/lib/timer_patch.dart"
+ },
+ "collection": {
+ "uri": "collection/collection.dart",
+ "patches": [
+ "_internal/vm/lib/collection_patch.dart",
+ "_internal/vm/lib/compact_hash.dart",
+ "_internal/wasm/lib/hash_factories.dart"
+ ]
+ },
+ "convert": {
+ "uri": "convert/convert.dart",
+ "patches": "_internal/vm/lib/convert_patch.dart"
+ },
+ "core": {
+ "uri": "core/core.dart",
+ "patches": [
+ "_internal/wasm/lib/core_patch.dart",
+ "_internal/vm/lib/array_patch.dart",
+ "_internal/wasm/lib/bool.dart",
+ "_internal/vm/lib/bool_patch.dart",
+ "_internal/wasm/lib/date_patch.dart",
+ "_internal/wasm/lib/double.dart",
+ "_internal/wasm/lib/expando_patch.dart",
+ "_internal/wasm/lib/function.dart",
+ "_internal/wasm/lib/growable_list.dart",
+ "_internal/wasm/lib/identical_patch.dart",
+ "_internal/wasm/lib/immutable_map.dart",
+ "_internal/wasm/lib/int.dart",
+ "_internal/vm/lib/integers_patch.dart",
+ "_internal/wasm/lib/list.dart",
+ "_internal/vm/lib/null_patch.dart",
+ "_internal/vm/lib/map_patch.dart",
+ "_internal/wasm/lib/object_patch.dart",
+ "_internal/wasm/lib/string_buffer_patch.dart",
+ "_internal/wasm/lib/string_patch.dart",
+ "_internal/wasm/lib/type.dart"
+ ]
+ },
+ "developer": {
+ "uri": "developer/developer.dart",
+ "patches": [
+ "_internal/wasm/lib/developer.dart"
+ ]
+ },
+ "isolate": {
+ "uri": "isolate/isolate.dart"
+ },
+ "math": {
+ "uri": "math/math.dart",
+ "patches": "_internal/wasm/lib/math_patch.dart"
+ },
+ "typed_data": {
+ "uri": "typed_data/typed_data.dart",
+ "patches": "_internal/vm/lib/typed_data_patch.dart"
+ },
+ "wasm": {
+ "uri": "wasm/wasm_types.dart"
+ }
+ }
+ },
"dart2js": {
"include": [
{
diff --git a/sdk/lib/libraries.yaml b/sdk/lib/libraries.yaml
index dcf81c0..a67349c 100644
--- a/sdk/lib/libraries.yaml
+++ b/sdk/lib/libraries.yaml
@@ -152,6 +152,66 @@
vmservice_io:
uri: "_internal/vm/bin/vmservice_io.dart"
+wasm:
+ libraries:
+ _internal:
+ uri: internal/internal.dart
+ patches:
+ - _internal/wasm/lib/internal_patch.dart
+ - _internal/wasm/lib/class_id.dart
+ - _internal/wasm/lib/patch.dart
+ - _internal/wasm/lib/print_patch.dart
+ - _internal/vm/lib/symbol_patch.dart
+ async:
+ uri: async/async.dart
+ patches: _internal/wasm/lib/timer_patch.dart
+ collection:
+ uri: collection/collection.dart
+ patches:
+ - _internal/vm/lib/collection_patch.dart
+ - _internal/vm/lib/compact_hash.dart
+ - _internal/wasm/lib/hash_factories.dart
+ convert:
+ uri: convert/convert.dart
+ patches: _internal/vm/lib/convert_patch.dart
+ core:
+ uri: core/core.dart
+ patches:
+ - _internal/wasm/lib/core_patch.dart
+ - _internal/vm/lib/array_patch.dart
+ - _internal/wasm/lib/bool.dart
+ - _internal/vm/lib/bool_patch.dart
+ - _internal/wasm/lib/date_patch.dart
+ - _internal/wasm/lib/double.dart
+ - _internal/wasm/lib/expando_patch.dart
+ - _internal/wasm/lib/function.dart
+ - _internal/wasm/lib/growable_list.dart
+ - _internal/wasm/lib/identical_patch.dart
+ - _internal/wasm/lib/immutable_map.dart
+ - _internal/wasm/lib/int.dart
+ - _internal/vm/lib/integers_patch.dart
+ - _internal/wasm/lib/list.dart
+ - _internal/vm/lib/null_patch.dart
+ - _internal/vm/lib/map_patch.dart
+ - _internal/wasm/lib/object_patch.dart
+ - _internal/wasm/lib/string_buffer_patch.dart
+ - _internal/wasm/lib/string_patch.dart
+ - _internal/wasm/lib/type.dart
+ developer:
+ uri: developer/developer.dart
+ patches:
+ - _internal/wasm/lib/developer.dart
+ isolate:
+ uri: isolate/isolate.dart
+ math:
+ uri: math/math.dart
+ patches: _internal/wasm/lib/math_patch.dart
+ typed_data:
+ uri: typed_data/typed_data.dart
+ patches: _internal/vm/lib/typed_data_patch.dart
+ wasm:
+ uri: wasm/wasm_types.dart
+
dart2js:
include:
- target: "_dart2js_common"
diff --git a/sdk/lib/wasm/wasm_types.dart b/sdk/lib/wasm/wasm_types.dart
new file mode 100644
index 0000000..1ec7bb3
--- /dev/null
+++ b/sdk/lib/wasm/wasm_types.dart
@@ -0,0 +1,90 @@
+// Copyright (c) 2022, 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.
+
+library dart.wasm;
+
+// A collection a special Dart tytpes that are mapped directly to Wasm types
+// by the dart2wasm compiler. These types have a number of constraints:
+//
+// - They can only be used directly as types of local variables, fields, or
+// parameter/return of static functions. No other uses of the types are valid.
+// - They are not assignable to or from any ordinary Dart types.
+// - The integer and float types can't be nullable.
+//
+// TODO(askesc): Give an error message if any of these constraints are violated.
+
+@pragma("wasm:entry-point")
+abstract class _WasmBase {}
+
+abstract class _WasmInt extends _WasmBase {}
+
+abstract class _WasmFloat extends _WasmBase {}
+
+/// The Wasm `anyref` type.
+@pragma("wasm:entry-point")
+class WasmAnyRef extends _WasmBase {}
+
+/// The Wasm `eqref` type.
+@pragma("wasm:entry-point")
+class WasmEqRef extends WasmAnyRef {}
+
+/// The Wasm `dataref` type.
+@pragma("wasm:entry-point")
+class WasmDataRef extends WasmEqRef {}
+
+abstract class _WasmArray extends WasmDataRef {
+ external int get length;
+}
+
+/// The Wasm `i8` storage type.
+@pragma("wasm:entry-point")
+class WasmI8 extends _WasmInt {}
+
+/// The Wasm `i16` storage type.
+@pragma("wasm:entry-point")
+class WasmI16 extends _WasmInt {}
+
+/// The Wasm `i32` type.
+@pragma("wasm:entry-point")
+class WasmI32 extends _WasmInt {}
+
+/// The Wasm `i64` type.
+@pragma("wasm:entry-point")
+class WasmI64 extends _WasmInt {}
+
+/// The Wasm `f32` type.
+@pragma("wasm:entry-point")
+class WasmF32 extends _WasmFloat {}
+
+/// The Wasm `f64` type.
+@pragma("wasm:entry-point")
+class WasmF64 extends _WasmFloat {}
+
+/// A Wasm array with integer element type.
+@pragma("wasm:entry-point")
+class WasmIntArray<T extends _WasmInt> extends _WasmArray {
+ external factory WasmIntArray(int length);
+
+ external int readSigned(int index);
+ external int readUnsigned(int index);
+ external void write(int index, int value);
+}
+
+/// A Wasm array with float element type.
+@pragma("wasm:entry-point")
+class WasmFloatArray<T extends _WasmFloat> extends _WasmArray {
+ external factory WasmFloatArray(int length);
+
+ external double read(int index);
+ external void write(int index, double value);
+}
+
+/// A Wasm array with reference element type, containing Dart objects.
+@pragma("wasm:entry-point")
+class WasmObjectArray<T extends Object?> extends _WasmArray {
+ external factory WasmObjectArray(int length);
+
+ external T read(int index);
+ external void write(int index, T value);
+}
diff --git a/tests/co19/co19-dart2js.status b/tests/co19/co19-dart2js.status
index 277a778..75ebe8c 100644
--- a/tests/co19/co19-dart2js.status
+++ b/tests/co19/co19-dart2js.status
@@ -42,6 +42,7 @@
LanguageFeatures/Abstract-external-fields/static_analysis_external_A05_t03: SkipByDesign # Non-JS-interop external members are not supported
LanguageFeatures/Abstract-external-fields/syntax_A01_t03: SkipByDesign # Non-JS-interop external members are not supported
LanguageFeatures/Abstract-external-fields/syntax_A02_t03: SkipByDesign # Non-JS-interop external members are not supported
+LanguageFeatures/FinalizationRegistry/ffi/*: SkipByDesign # dart:ffi is not supported
LibTest/core/DateTime/DateTime.fromMicrosecondsSinceEpoch_A01_t01: SkipByDesign # microseconds are not supported in JavaScript
LibTest/core/DateTime/microsecond_A01_t01: SkipByDesign # microseconds are not supported in JavaScript
LibTest/core/DateTime/microsecondsSinceEpoch_A01_t01: SkipByDesign # microseconds are not supported in JavaScript
diff --git a/tests/co19/co19-dartdevc.status b/tests/co19/co19-dartdevc.status
index 3458c5c..94d2a50 100644
--- a/tests/co19/co19-dartdevc.status
+++ b/tests/co19/co19-dartdevc.status
@@ -39,6 +39,7 @@
LanguageFeatures/Abstract-external-fields/static_analysis_external_A05_t03: SkipByDesign # External variables are not supported
LanguageFeatures/Abstract-external-fields/syntax_A01_t03: SkipByDesign # External variables are not supported
LanguageFeatures/Abstract-external-fields/syntax_A02_t03: SkipByDesign # External variables are not supported
+LanguageFeatures/FinalizationRegistry/ffi/*: SkipByDesign # dart:ffi is not supported
LibTest/core/DateTime/DateTime.fromMicrosecondsSinceEpoch_A01_t01: SkipByDesign # microseconds are not supported in JavaScript
LibTest/core/DateTime/microsecond_A01_t01: SkipByDesign # microseconds are not supported in JavaScript
LibTest/core/DateTime/microsecondsSinceEpoch_A01_t01: SkipByDesign # microseconds are not supported in JavaScript
diff --git a/tests/co19/co19-kernel.status b/tests/co19/co19-kernel.status
index 4e3c7d4..3254ec0 100644
--- a/tests/co19/co19-kernel.status
+++ b/tests/co19/co19-kernel.status
@@ -27,10 +27,16 @@
LibTest/core/List/List_class_A01_t02: Slow, Pass
LibTest/core/List/List_class_A01_t03: Slow, Pass
+[ $runtime == dart_precompiled && $system == windows ]
+LanguageFeatures/FinalizationRegistry/ffi/*: SkipByDesign # https://dartbug.com/40579 Dart C API symbols not available.
+
[ $runtime == dart_precompiled && ($arch == simarm64 || $arch == simarm64c || $arch == simriscv32 || $arch == simriscv64) ]
LibTest/collection/ListBase/ListBase_class_A01_t01: SkipSlow # Issue 43036
LibTest/collection/ListMixin/ListMixin_class_A01_t01: SkipSlow # Issue 43036
+[ $arch == simarm || $arch == simarm64 ]
+LanguageFeatures/FinalizationRegistry/ffi/*: SkipByDesign # https://github.com/dart-lang/sdk/issues/37299
+
# It makes no sense to run any test that uses spawnURI under the simulator
# as that would involve running CFE (the front end) in simulator mode
# to compile the URI file specified in spawnURI code.
diff --git a/tests/language/language_dart2wasm.status b/tests/language/language_dart2wasm.status
new file mode 100644
index 0000000..3dea313
--- /dev/null
+++ b/tests/language/language_dart2wasm.status
@@ -0,0 +1,10 @@
+# Copyright (c) 2022, 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.
+# Sections in this file should contain "$compiler == dart2wasm".
+
+[ $compiler == dart2wasm ]
+vm/*: SkipByDesign # Tests for the VM.
+
+[ $compiler == dart2wasm && $runtime == d8 ]
+import/conditional_string_test: SkipByDesign # No XHR in d8
diff --git a/tools/VERSION b/tools/VERSION
index 17b79a3..9cab9b0 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 17
PATCH 0
-PRERELEASE 112
+PRERELEASE 113
PRERELEASE_PATCH 0
\ No newline at end of file
diff --git a/tools/bots/test_matrix.json b/tools/bots/test_matrix.json
index 37803b4..724d094 100644
--- a/tools/bots/test_matrix.json
+++ b/tools/bots/test_matrix.json
@@ -185,6 +185,41 @@
"xcodebuild/ReleaseX64/dart2js_platform.dill",
"xcodebuild/ReleaseX64/dart2js_platform_unsound.dill"
],
+ "dart2wasm_hostasserts": [
+ ".packages",
+ ".dart_tool/package_config.json",
+ "out/ReleaseX64/dart",
+ "out/ReleaseX64/dart2wasm_platform.dill",
+ "pkg/",
+ "runtime/tests/",
+ "samples-dev/",
+ "samples/",
+ "sdk/",
+ "tests/.dart_tool/package_config.json",
+ "tests/angular/",
+ "tests/co19/co19-analyzer.status",
+ "tests/co19/co19-co19.status",
+ "tests/co19/co19-dart2js.status",
+ "tests/co19/co19-dartdevc.status",
+ "tests/co19/co19-kernel.status",
+ "tests/co19/co19-runtime.status",
+ "tests/corelib/",
+ "tests/web/",
+ "tests/dartdevc/",
+ "tests/language/",
+ "tests/language_2/",
+ "tests/lib/",
+ "tests/light_unittest.dart",
+ "tests/search/",
+ "tests/ffi/",
+ "third_party/d8/",
+ "third_party/pkg/",
+ "third_party/pkg_tested/",
+ "third_party/requirejs/",
+ "tools/",
+ "xcodebuild/ReleaseX64/dart",
+ "xcodebuild/ReleaseX64/dart2wasm_platform.dill"
+ ],
"front-end": [
".packages",
".dart_tool/package_config.json",
@@ -705,6 +740,13 @@
"builder-tag": "dart2js-strong"
}
},
+ "dart2wasm-hostasserts-linux-x64-d8": {
+ "options": {
+ "host-checked": true,
+ "timeout": 240,
+ "builder-tag": "dart2wasm"
+ }
+ },
"dartkp-android-(debug|product|release)-arm_x64": {
"options": {
"builder-tag": "crossword",
@@ -2888,6 +2930,33 @@
},
{
"builders": [
+ "dart2wasm-linux-x64-d8"
+ ],
+ "meta": {
+ "description": "dart2wasm tests."
+ },
+ "steps": [
+ {
+ "name": "build dart",
+ "script": "tools/build.py",
+ "arguments": [
+ "create_sdk"
+ ]
+ },
+ {
+ "name": "dart2wasm d8 tests",
+ "arguments": [
+ "-ndart2wasm-hostasserts-linux-x64-d8",
+ "language",
+ "corelib"
+ ],
+ "shards": 6,
+ "fileset": "dart2wasm_hostasserts"
+ }
+ ]
+ },
+ {
+ "builders": [
"dart-sdk-linux"
],
"meta": {