[dart2wasm] Initial commit for the Dart-to-WasmGC compiler.

This is work in progress. Several language features are still
unimplemented or only partially implemented.

Instructions for running the compiler and its output can be found in
pkg/dart2wasm/dart2wasm.md. These procedures are preliminary and
expected to change.

The best version of d8 to use for this version of dart2wasm is 10.0.40,
as explained here: https://dart-review.googlesource.com/c/sdk/+/232097

This commit also adds a dart2wasm-hostasserts-linux-x64-d8 testing
configuration to run the compiler over the test suite.

The history of the prototype that this is based on can be seen here:

https://github.com/askeksa-google/sdk/tree/wasm_prototype

Issue: https://github.com/dart-lang/sdk/issues/32894

Change-Id: I910b6ff239ef9c5f66863e4ca97b39b8202cce85
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/175728
Reviewed-by: Joshua Litt <joshualitt@google.com>
Commit-Queue: Aske Simon Christensen <askesc@google.com>
This commit is contained in:
Aske Simon Christensen 2022-02-16 11:11:14 +00:00 committed by Commit Bot
parent 5e4f36eda6
commit 6faa5f3bd0
67 changed files with 15752 additions and 1 deletions

View file

@ -228,6 +228,12 @@
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "dart2wasm",
"rootUri": "../pkg/dart2wasm",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "dart_internal",
"rootUri": "../pkg/dart_internal",
@ -780,6 +786,12 @@
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "wasm_builder",
"rootUri": "../pkg/wasm_builder",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "watcher",
"rootUri": "../third_party/pkg/watcher",

View file

@ -33,6 +33,7 @@ dart2js_info:pkg/dart2js_info/lib
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 @@ vector_math:third_party/pkg/vector_math/lib
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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -624,6 +624,7 @@ class Compiler extends NamedEnum {
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 @@ class Compiler extends NamedEnum {
none,
dart2js,
dart2analyzer,
dart2wasm,
compareAnalyzerCfe,
dartdevc,
dartdevk,
@ -691,6 +693,12 @@ class Compiler extends NamedEnum {
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 @@ class Compiler extends NamedEnum {
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 @@ class Compiler extends NamedEnum {
case Compiler.dart2analyzer:
case Compiler.compareAnalyzerCfe:
case Compiler.dart2js:
case Compiler.dart2wasm:
case Compiler.dartdevc:
case Compiler.dartdevk:
case Compiler.fasta:

View file

@ -186,6 +186,9 @@ class CompilationCommand extends ProcessCommand {
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(

View file

@ -1060,6 +1060,45 @@ class Dart2jsCompilerCommandOutput extends CompilationCommandOutput
}
}
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:

View file

@ -86,6 +86,9 @@ abstract class CompilerConfiguration {
case Compiler.dart2js:
return Dart2jsCompilerConfiguration(configuration);
case Compiler.dart2wasm:
return Dart2WasmCompilerConfiguration(configuration);
case Compiler.dartdevc:
return DevCompilerConfiguration(configuration);
@ -499,6 +502,83 @@ class Dart2jsCompilerConfiguration extends CompilerConfiguration {
}
}
/// 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)

View file

@ -203,6 +203,7 @@ class TestConfiguration {
Compiler.dartkp,
Compiler.fasta,
Compiler.dart2js,
Compiler.dart2wasm,
];
return fastaCompilers.contains(compiler);
}

View file

@ -168,7 +168,7 @@ class CommandLineJavaScriptRuntime extends RuntimeConfiguration {
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'.";
}
}

View file

@ -597,6 +597,7 @@ class StandardTestSuite extends TestSuite {
'$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 @@ class StandardTestSuite extends TestSuite {
var commands = <Command>[];
const supportedCompilers = {
Compiler.dart2js,
Compiler.dart2wasm,
Compiler.dartdevc,
Compiler.dartdevk
};

26
pkg/wasm_builder/LICENSE Normal file
View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

48
sdk/bin/dart2wasm Executable file
View file

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

6
sdk/bin/dart2wasm_developer Executable file
View file

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

View file

@ -624,6 +624,9 @@ class _CompactLinkedCustomHashMap<K, V> extends _HashFieldBase
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);

View file

@ -2228,6 +2228,7 @@ class Uint8List {
}
@pragma("vm:entry-point")
@pragma("wasm:entry-point")
class _Uint8List extends _TypedList
with _IntListMixin, _TypedIntListMixin<Uint8List>
implements Uint8List {
@ -2281,6 +2282,7 @@ class Uint8ClampedList {
}
@pragma("vm:entry-point")
@pragma("wasm:entry-point")
class _Uint8ClampedList extends _TypedList
with _IntListMixin, _TypedIntListMixin<Uint8ClampedList>
implements Uint8ClampedList {
@ -2334,6 +2336,7 @@ class Int16List {
}
@pragma("vm:entry-point")
@pragma("wasm:entry-point")
class _Int16List extends _TypedList
with _IntListMixin, _TypedIntListMixin<Int16List>
implements Int16List {
@ -2407,6 +2410,7 @@ class Uint16List {
}
@pragma("vm:entry-point")
@pragma("wasm:entry-point")
class _Uint16List extends _TypedList
with _IntListMixin, _TypedIntListMixin<Uint16List>
implements Uint16List {
@ -2480,6 +2484,7 @@ class Int32List {
}
@pragma("vm:entry-point")
@pragma("wasm:entry-point")
class _Int32List extends _TypedList
with _IntListMixin, _TypedIntListMixin<Int32List>
implements Int32List {
@ -2540,6 +2545,7 @@ class Uint32List {
}
@pragma("vm:entry-point")
@pragma("wasm:entry-point")
class _Uint32List extends _TypedList
with _IntListMixin, _TypedIntListMixin<Uint32List>
implements Uint32List {
@ -2600,6 +2606,7 @@ class Int64List {
}
@pragma("vm:entry-point")
@pragma("wasm:entry-point")
class _Int64List extends _TypedList
with _IntListMixin, _TypedIntListMixin<Int64List>
implements Int64List {
@ -2660,6 +2667,7 @@ class Uint64List {
}
@pragma("vm:entry-point")
@pragma("wasm:entry-point")
class _Uint64List extends _TypedList
with _IntListMixin, _TypedIntListMixin<Uint64List>
implements Uint64List {
@ -2720,6 +2728,7 @@ class Float32List {
}
@pragma("vm:entry-point")
@pragma("wasm:entry-point")
class _Float32List extends _TypedList
with _DoubleListMixin, _TypedDoubleListMixin<Float32List>
implements Float32List {
@ -2781,6 +2790,7 @@ class Float64List {
}
@pragma("vm:entry-point")
@pragma("wasm:entry-point")
class _Float64List extends _TypedList
with _DoubleListMixin, _TypedDoubleListMixin<Float64List>
implements Float64List {
@ -3040,6 +3050,7 @@ class _ExternalInt8Array extends _TypedList
}
@pragma("vm:entry-point")
@pragma("wasm:entry-point")
class _ExternalUint8Array extends _TypedList
with _IntListMixin, _TypedIntListMixin<Uint8List>
implements Uint8List {
@ -4123,6 +4134,7 @@ abstract class _TypedListView extends _TypedListBase implements TypedData {
}
@pragma("vm:entry-point")
@pragma("wasm:entry-point")
class _Int8ArrayView extends _TypedListView
with _IntListMixin, _TypedIntListMixin<Int8List>
implements Int8List {
@ -4164,6 +4176,7 @@ class _Int8ArrayView extends _TypedListView
}
@pragma("vm:entry-point")
@pragma("wasm:entry-point")
class _Uint8ArrayView extends _TypedListView
with _IntListMixin, _TypedIntListMixin<Uint8List>
implements Uint8List {
@ -4205,6 +4218,7 @@ class _Uint8ArrayView extends _TypedListView
}
@pragma("vm:entry-point")
@pragma("wasm:entry-point")
class _Uint8ClampedArrayView extends _TypedListView
with _IntListMixin, _TypedIntListMixin<Uint8ClampedList>
implements Uint8ClampedList {
@ -4246,6 +4260,7 @@ class _Uint8ClampedArrayView extends _TypedListView
}
@pragma("vm:entry-point")
@pragma("wasm:entry-point")
class _Int16ArrayView extends _TypedListView
with _IntListMixin, _TypedIntListMixin<Int16List>
implements Int16List {
@ -4300,6 +4315,7 @@ class _Int16ArrayView extends _TypedListView
}
@pragma("vm:entry-point")
@pragma("wasm:entry-point")
class _Uint16ArrayView extends _TypedListView
with _IntListMixin, _TypedIntListMixin<Uint16List>
implements Uint16List {
@ -4355,6 +4371,7 @@ class _Uint16ArrayView extends _TypedListView
}
@pragma("vm:entry-point")
@pragma("wasm:entry-point")
class _Int32ArrayView extends _TypedListView
with _IntListMixin, _TypedIntListMixin<Int32List>
implements Int32List {
@ -4396,6 +4413,7 @@ class _Int32ArrayView extends _TypedListView
}
@pragma("vm:entry-point")
@pragma("wasm:entry-point")
class _Uint32ArrayView extends _TypedListView
with _IntListMixin, _TypedIntListMixin<Uint32List>
implements Uint32List {
@ -4437,6 +4455,7 @@ class _Uint32ArrayView extends _TypedListView
}
@pragma("vm:entry-point")
@pragma("wasm:entry-point")
class _Int64ArrayView extends _TypedListView
with _IntListMixin, _TypedIntListMixin<Int64List>
implements Int64List {
@ -4478,6 +4497,7 @@ class _Int64ArrayView extends _TypedListView
}
@pragma("vm:entry-point")
@pragma("wasm:entry-point")
class _Uint64ArrayView extends _TypedListView
with _IntListMixin, _TypedIntListMixin<Uint64List>
implements Uint64List {
@ -4519,6 +4539,7 @@ class _Uint64ArrayView extends _TypedListView
}
@pragma("vm:entry-point")
@pragma("wasm:entry-point")
class _Float32ArrayView extends _TypedListView
with _DoubleListMixin, _TypedDoubleListMixin<Float32List>
implements Float32List {
@ -4560,6 +4581,7 @@ class _Float32ArrayView extends _TypedListView
}
@pragma("vm:entry-point")
@pragma("wasm:entry-point")
class _Float64ArrayView extends _TypedListView
with _DoubleListMixin, _TypedDoubleListMixin<Float64List>
implements Float64List {
@ -4718,6 +4740,7 @@ class _Float64x2ArrayView extends _TypedListView
}
@pragma("vm:entry-point")
@pragma("wasm:entry-point")
class _ByteDataView implements ByteData {
@pragma("vm:recognized", "other")
@pragma("vm:exact-result-type", _ByteDataView)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

@ -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": [
{

View file

@ -152,6 +152,66 @@ vm:
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"

View file

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

View file

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

View file

@ -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",
@ -2886,6 +2928,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"