mirror of
https://github.com/dart-lang/sdk
synced 2024-10-04 16:04:53 +00:00
[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:
parent
5e4f36eda6
commit
6faa5f3bd0
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
99
pkg/dart2wasm/bin/dart2wasm.dart
Normal file
99
pkg/dart2wasm/bin/dart2wasm.dart
Normal 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;
|
||||
}
|
63
pkg/dart2wasm/bin/run_wasm.js
Normal file
63
pkg/dart2wasm/bin/run_wasm.js
Normal 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);
|
65
pkg/dart2wasm/dart2wasm.md
Normal file
65
pkg/dart2wasm/dart2wasm.md
Normal 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.
|
377
pkg/dart2wasm/lib/class_info.dart
Normal file
377
pkg/dart2wasm/lib/class_info.dart
Normal 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);
|
||||
}
|
||||
}
|
318
pkg/dart2wasm/lib/closures.dart
Normal file
318
pkg/dart2wasm/lib/closures.dart
Normal 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);
|
||||
}
|
||||
}
|
1940
pkg/dart2wasm/lib/code_generator.dart
Normal file
1940
pkg/dart2wasm/lib/code_generator.dart
Normal file
File diff suppressed because it is too large
Load diff
69
pkg/dart2wasm/lib/compile.dart
Normal file
69
pkg/dart2wasm/lib/compile.dart
Normal 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();
|
||||
}
|
637
pkg/dart2wasm/lib/constants.dart
Normal file
637
pkg/dart2wasm/lib/constants.dart
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
110
pkg/dart2wasm/lib/constants_backend.dart
Normal file
110
pkg/dart2wasm/lib/constants_backend.dart
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
308
pkg/dart2wasm/lib/dispatch_table.dart
Normal file
308
pkg/dart2wasm/lib/dispatch_table.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
216
pkg/dart2wasm/lib/functions.dart
Normal file
216
pkg/dart2wasm/lib/functions.dart
Normal 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);
|
||||
}
|
||||
}
|
198
pkg/dart2wasm/lib/globals.dart
Normal file
198
pkg/dart2wasm/lib/globals.dart
Normal 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;
|
||||
}
|
||||
}
|
931
pkg/dart2wasm/lib/intrinsics.dart
Normal file
931
pkg/dart2wasm/lib/intrinsics.dart
Normal 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;
|
||||
}
|
||||
}
|
93
pkg/dart2wasm/lib/param_info.dart
Normal file
93
pkg/dart2wasm/lib/param_info.dart
Normal 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
62
pkg/dart2wasm/lib/reference_extensions.dart
Normal file
62
pkg/dart2wasm/lib/reference_extensions.dart
Normal 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;
|
||||
}
|
||||
}
|
191
pkg/dart2wasm/lib/target.dart
Normal file
191
pkg/dart2wasm/lib/target.dart
Normal 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:");
|
||||
}
|
108
pkg/dart2wasm/lib/transformers.dart
Normal file
108
pkg/dart2wasm/lib/transformers.dart
Normal 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);
|
||||
}
|
||||
}
|
819
pkg/dart2wasm/lib/translator.dart
Normal file
819
pkg/dart2wasm/lib/translator.dart
Normal 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);
|
||||
}
|
||||
}
|
26
pkg/dart2wasm/pubspec.yaml
Normal file
26
pkg/dart2wasm/pubspec.yaml
Normal 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
|
|
@ -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:
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -203,6 +203,7 @@ class TestConfiguration {
|
|||
Compiler.dartkp,
|
||||
Compiler.fasta,
|
||||
Compiler.dart2js,
|
||||
Compiler.dart2wasm,
|
||||
];
|
||||
return fastaCompilers.contains(compiler);
|
||||
}
|
||||
|
|
|
@ -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'.";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
26
pkg/wasm_builder/LICENSE
Normal 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.
|
2283
pkg/wasm_builder/lib/src/instructions.dart
Normal file
2283
pkg/wasm_builder/lib/src/instructions.dart
Normal file
File diff suppressed because it is too large
Load diff
820
pkg/wasm_builder/lib/src/module.dart
Normal file
820
pkg/wasm_builder/lib/src/module.dart
Normal 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 {}
|
137
pkg/wasm_builder/lib/src/serialize.dart
Normal file
137
pkg/wasm_builder/lib/src/serialize.dart
Normal 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);
|
||||
}
|
657
pkg/wasm_builder/lib/src/types.dart
Normal file
657
pkg/wasm_builder/lib/src/types.dart
Normal 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";
|
||||
}
|
||||
}
|
||||
}
|
34
pkg/wasm_builder/lib/wasm_builder.dart
Normal file
34
pkg/wasm_builder/lib/wasm_builder.dart
Normal 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;
|
9
pkg/wasm_builder/pubspec.yaml
Normal file
9
pkg/wasm_builder/pubspec.yaml
Normal 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
48
sdk/bin/dart2wasm
Executable 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
6
sdk/bin/dart2wasm_developer
Executable 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}
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
21
sdk/lib/_internal/wasm/lib/bool.dart
Normal file
21
sdk/lib/_internal/wasm/lib/bool.dart
Normal 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 |
|
||||
}
|
20
sdk/lib/_internal/wasm/lib/class_id.dart
Normal file
20
sdk/lib/_internal/wasm/lib/class_id.dart
Normal 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;
|
||||
}
|
51
sdk/lib/_internal/wasm/lib/core_patch.dart
Normal file
51
sdk/lib/_internal/wasm/lib/core_patch.dart
Normal 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
|
542
sdk/lib/_internal/wasm/lib/date_patch.dart
Normal file
542
sdk/lib/_internal/wasm/lib/date_patch.dart
Normal 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;
|
||||
}
|
||||
}
|
61
sdk/lib/_internal/wasm/lib/developer.dart
Normal file
61
sdk/lib/_internal/wasm/lib/developer.dart
Normal 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) {}
|
290
sdk/lib/_internal/wasm/lib/double.dart
Normal file
290
sdk/lib/_internal/wasm/lib/double.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
13
sdk/lib/_internal/wasm/lib/expando_patch.dart
Normal file
13
sdk/lib/_internal/wasm/lib/expando_patch.dart
Normal 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;
|
||||
}
|
12
sdk/lib/_internal/wasm/lib/function.dart
Normal file
12
sdk/lib/_internal/wasm/lib/function.dart
Normal 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);
|
||||
}
|
305
sdk/lib/_internal/wasm/lib/growable_list.dart
Normal file
305
sdk/lib/_internal/wasm/lib/growable_list.dart
Normal 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;
|
||||
}
|
||||
}
|
51
sdk/lib/_internal/wasm/lib/hash_factories.dart
Normal file
51
sdk/lib/_internal/wasm/lib/hash_factories.dart
Normal 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>();
|
||||
}
|
12
sdk/lib/_internal/wasm/lib/identical_patch.dart
Normal file
12
sdk/lib/_internal/wasm/lib/identical_patch.dart
Normal 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;
|
221
sdk/lib/_internal/wasm/lib/immutable_map.dart
Normal file
221
sdk/lib/_internal/wasm/lib/immutable_map.dart
Normal 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>;
|
||||
}
|
610
sdk/lib/_internal/wasm/lib/int.dart
Normal file
610
sdk/lib/_internal/wasm/lib/int.dart
Normal 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;
|
||||
}
|
||||
}
|
128
sdk/lib/_internal/wasm/lib/internal_patch.dart
Normal file
128
sdk/lib/_internal/wasm/lib/internal_patch.dart
Normal 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);
|
223
sdk/lib/_internal/wasm/lib/list.dart
Normal file
223
sdk/lib/_internal/wasm/lib/list.dart
Normal 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;
|
||||
}
|
||||
}
|
262
sdk/lib/_internal/wasm/lib/math_patch.dart
Normal file
262
sdk/lib/_internal/wasm/lib/math_patch.dart
Normal 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);
|
||||
}
|
49
sdk/lib/_internal/wasm/lib/object_patch.dart
Normal file
49
sdk/lib/_internal/wasm/lib/object_patch.dart
Normal 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;
|
||||
}
|
9
sdk/lib/_internal/wasm/lib/patch.dart
Normal file
9
sdk/lib/_internal/wasm/lib/patch.dart
Normal 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();
|
9
sdk/lib/_internal/wasm/lib/print_patch.dart
Normal file
9
sdk/lib/_internal/wasm/lib/print_patch.dart
Normal 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);
|
212
sdk/lib/_internal/wasm/lib/string_buffer_patch.dart
Normal file
212
sdk/lib/_internal/wasm/lib/string_buffer_patch.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
1391
sdk/lib/_internal/wasm/lib/string_patch.dart
Normal file
1391
sdk/lib/_internal/wasm/lib/string_patch.dart
Normal file
File diff suppressed because it is too large
Load diff
82
sdk/lib/_internal/wasm/lib/timer_patch.dart
Normal file
82
sdk/lib/_internal/wasm/lib/timer_patch.dart
Normal 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);
|
||||
}
|
||||
}
|
47
sdk/lib/_internal/wasm/lib/type.dart
Normal file
47
sdk/lib/_internal/wasm/lib/type.dart
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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": [
|
||||
{
|
||||
|
|
|
@ -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"
|
||||
|
|
90
sdk/lib/wasm/wasm_types.dart
Normal file
90
sdk/lib/wasm/wasm_types.dart
Normal 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);
|
||||
}
|
10
tests/language/language_dart2wasm.status
Normal file
10
tests/language/language_dart2wasm.status
Normal 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
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue