dart-sdk/pkg/dart2wasm/README.md

85 lines
4.8 KiB
Markdown
Raw Normal View History

# Compiling Dart to WebAssembly
WebAssembly (commonly abbreviated to Wasm) is a
_"binary instruction format for a stack-based virtual machine"_.
Although Wasm was originally designed for running native code on the web,
Wasm has since evolved into a general technology for running
compiled code across multiple platforms.
The Dart team is currently investigating support for compiling Dart to Wasm,
in conjunction with support [in Flutter](https://flutter.dev/wasm).
**Note:** This feature is under active development,
and is currently considered experimental.
The tracking issue is [#32894](https://github.com/dart-lang/sdk/issues/32894).
## Running dart2wasm
You don't need to build the Dart SDK to run dart2wasm, as long as you have a Dart SDK installed and have the [Dart SDK repository checked out](https://github.com/dart-lang/sdk/wiki/Building#getting-the-source). NB: the SDK must be checked out using depot tools and not just cloned from this repo.
To compile a Dart file to Wasm, in a checkout of the Dart SDK repository, 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.
| `--platform=`*path* | none | The location of the platform `dill` file containing the compiled core libraries.
| `--depfile=`*path* | none | Write a Ninja depfile listing the input sources for the compilation.
| `--`[`no-`]`export-all` | no | Export all functions; otherwise, just export `main`.
| `--`[`no-`]`import-shared-memory` | no | Import a shared memory buffer. If this is on, `--shared-memory-max-pages` must also be specified.
| `--`[`no-`]`inlining` | yes | Enable function inlining.
| `--inlining-limit` *size* | 0 | Always inline functions no larger than this number of AST nodes, if inlining is enabled.
| `--`[`no-`]`name-section` | yes | Emit Name Section with function names.
| `--`[`no-`]`omit-type-checks` | no | Omit runtime type checks, such as covariance checks and downcasts.
| `--`[`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-`]`stringref` | no | Use the experimental stringref Wasm proposal.
| `--`[`no-`]`enable-asserts` | no | Enable assertions at runtime.
| `--shared-memory-max-pages` *pagecount* | | Max size of the imported memory buffer. If `--shared-import-memory` is specified, this must also be specified.
| `--watch` *offset* | | Print stack trace leading to the byte at offset *offset* in the `.wasm` output file. Can be specified multiple times.
Dart2Wasm will output a `wasm` file, containing Dart compiled to Wasm, as well as an `mjs` file containing the runtime. The result can be run with:
[dart2wasm] New async implementation This CL re-implements the async function compilation without using JSPI or any other platform features. This implementation is faster than the JSPI-based one in all benchmarks, in some cases up to 200x (benchmark results at the end). So we remove the JSPI-based implementation as there's no point in maintaining a much slower implementation and supporting two implementations at the same time (which is tricky because these implementations need different libraries, all scripts need to support two modes etc.) that also requires experimental platform features. # Main changes - A new pass `AwaitTransformer` transforms `await` expressions to top-level statements in form `var <fresh variable> = await <simple expr>`, where `<simple expr>` is an expression without `await`. After this pass all `await` expressions have the simple continuation of "assign the value of the awaited future to this variable and continue with the next statement". This simplifies `await` compilation. - A new code generator `AsyncCodeGenerator` (inherits from `CodeGenerator`) compiles `async` functions. The `_YieldFinder` class is copied from `sync*` code generator but modified to handle `async` expressions. - Mentions to the V8 flag `--experimental-wasm-stack-switching` is removed from all scripts and documents. # Future work - Control flow handling in `AsyncCodeGenerator` needs to be implemented in a similar way in `SyncStarCodeGenerator`. Doing this without duplicating a lot of code will require some refactoring. # New passing tests - co19/Language/Statements/Yield_and_Yield_Each/Yield/execution_async_A05_t01 - co19/Language/Statements/For/Asynchronous_For_in/execution_A02_t02 - language/regress/regress23996_test - language/sync_star/dcall_type_test # Benchmarks Current implementation: ``` AsyncLiveVars.LiveObj1(RunTime): 1586000.0 us. AsyncLiveVars.LiveObj2(RunTime): 2114000.0 us. AsyncLiveVars.LiveObj4(RunTime): 1972500.0 us. AsyncLiveVars.LiveObj8(RunTime): 2212000.0 us. AsyncLiveVars.LiveObj16(RunTime): 2238000.0 us. AsyncLiveVars.LiveInt1(RunTime): 2362000.0 us. AsyncLiveVars.LiveInt4(RunTime): 2470000.0 us. AsyncLiveVars.LiveObj2Int2(RunTime): 2575000.0 us. AsyncLiveVars.LiveObj4Int4(RunTime): 2820000.0 us. Calls.AwaitAsyncCall(RunTimeRaw): 35676.15658362989 ns. Calls.AwaitAsyncCallClosureTargetPolymorphic(RunTimeRaw): 38934.108527131786 ns. Calls.AwaitAsyncCallInstanceTargetPolymorphic(RunTimeRaw): 42617.02127659575 ns. Calls.AwaitFutureCall(RunTimeRaw): 2832.058906825262 ns. Calls.AwaitFutureCallClosureTargetPolymorphic(RunTimeRaw): 3665.8125915080527 ns. Calls.AwaitFutureCallInstanceTargetPolymorphic(RunTimeRaw): 4420.449537241076 ns. Calls.AwaitFutureOrCall(RunTimeRaw): 3692.7621861152143 ns. Calls.AwaitFutureOrCallClosureTargetPolymorphic(RunTimeRaw): 4625.346901017576 ns. Calls.AwaitFutureOrCallInstanceTargetPolymorphic(RunTimeRaw): 4514.6726862302485 ns. Calls.AwaitFutureOrCallInstanceTargetPolymorphicManyAwaits(RunTimeRaw): 345172.4137931034 ns. Calls.AwaitForAsyncStarStreamPolymorphic(RunTimeRaw): 697000.0 ns. Calls.AwaitForAsyncStarStreamPolymorphicManyYields(RunTimeRaw): 704666.6666666666 ns. Calls.AwaitForManualStreamPolymorphic(RunTimeRaw): 11010.989010989011 ns. Calls.SyncCall(RunTimeRaw): 0.40275240996973316 ns. Calls.SyncCallClosureTarget(RunTimeRaw): 0.3989591156672242 ns. Calls.SyncCallInstanceTargetPolymorphic(RunTimeRaw): 3.2632549336335526 ns. Calls.IterableSyncStarIterablePolymorphic(RunTimeRaw): 353.3980582524272 ns. Calls.IterableManualIterablePolymorphic(RunTimeRaw): 332.1161825726141 ns. Calls.IterableManualIterablePolymorphicManyYields(RunTimeRaw): 354.28067078552516 ns. ``` New implementation: ``` AsyncLiveVars.LiveObj1(RunTime): 11327.683615819209 us. AsyncLiveVars.LiveObj2(RunTime): 10923.91304347826 us. AsyncLiveVars.LiveObj4(RunTime): 10956.284153005465 us. AsyncLiveVars.LiveObj8(RunTime): 11286.516853932584 us. AsyncLiveVars.LiveObj16(RunTime): 11445.714285714286 us. AsyncLiveVars.LiveInt1(RunTime): 11016.483516483517 us. AsyncLiveVars.LiveInt4(RunTime): 11327.683615819209 us. AsyncLiveVars.LiveObj2Int2(RunTime): 10918.478260869566 us. AsyncLiveVars.LiveObj4Int4(RunTime): 10737.967914438503 us. Calls.AwaitAsyncCall(RunTimeRaw): 1082.2510822510822 ns. Calls.AwaitAsyncCallClosureTargetPolymorphic(RunTimeRaw): 1056.4124234100993 ns. Calls.AwaitAsyncCallInstanceTargetPolymorphic(RunTimeRaw): 1134.1726210729273 ns. Calls.AwaitFutureCall(RunTimeRaw): 865.6509695290858 ns. Calls.AwaitFutureCallClosureTargetPolymorphic(RunTimeRaw): 841.3967185527977 ns. Calls.AwaitFutureCallInstanceTargetPolymorphic(RunTimeRaw): 839.066957543212 ns. Calls.AwaitFutureOrCall(RunTimeRaw): 397.9941096871766 ns. Calls.AwaitFutureOrCallClosureTargetPolymorphic(RunTimeRaw): 406.17384240454913 ns. Calls.AwaitFutureOrCallInstanceTargetPolymorphic(RunTimeRaw): 393.7472929873607 ns. Calls.AwaitFutureOrCallInstanceTargetPolymorphicManyAwaits(RunTimeRaw): 1095.0503723171266 ns. Calls.AwaitForAsyncStarStreamPolymorphic(RunTimeRaw): 6643.426294820717 ns. Calls.AwaitForAsyncStarStreamPolymorphicManyYields(RunTimeRaw): 7178.750897343863 ns. Calls.AwaitForManualStreamPolymorphic(RunTimeRaw): 1456.23998835008 ns. Calls.SyncCall(RunTimeRaw): 0.3919935321067202 ns. Calls.SyncCallClosureTarget(RunTimeRaw): 0.3906669661780074 ns. Calls.SyncCallInstanceTargetPolymorphic(RunTimeRaw): 3.1676143112814583 ns. Calls.IterableSyncStarIterablePolymorphic(RunTimeRaw): 104.4932079414838 ns. Calls.IterableManualIterablePolymorphic(RunTimeRaw): 104.57516339869281 ns. Calls.IterableManualIterablePolymorphicManyYields(RunTimeRaw): 116.92487576731949 ns. ``` TEST=ci CoreLibraryReviewExempt: Added entry-point pragmas. Change-Id: I02fbd08141f51c00fb37b6fa0304dc25d6afdb71 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/301020 Commit-Queue: Ömer Ağacan <omersa@google.com> Reviewed-by: William Hesse <whesse@google.com> Reviewed-by: Johnni Winther <johnniwinther@google.com> Reviewed-by: Joshua Litt <joshualitt@google.com>
2023-05-22 08:32:12 +00:00
`d8 --experimental-wasm-gc --experimental-wasm-type-reflection pkg/dart2wasm/bin/run_wasm.js -- `*outfile*`.wasm` /abs/path/to/`*outfile*`.mjs
Where `d8` is the [V8 developer shell](https://v8.dev/docs/d8).
## 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 JS objects, use a JS interop type, e.g. `JSAny`, which translates to the Wasm `externref` type. These can be passed around and stored as opaque values on the Dart side.
- For Dart objects, use the corresponding Dart type. This will be emitted as `anyref` and automatically converted to and from the Dart type at the boundary.