Right now a JS interop callback works like this:
* Each wasm module that gets instantiated will be given it's module
instance (JS calls Dart to set it) via `setThisModule`
* When Dart code calls JS and gives it a callback to invoke, it gave it
this module instance. It will also make the callback wasm function
weakly exported.
* The JS trampoline code, when invoked, would then call the weakly
exported wasm function from the module instance.
We simplify this now by making the Dart code simply give the wasm
function reference to JS, then JS can later on invoke it. No need to
weakly export a function and call back via
`module.exports.<weaklyExportedCallback>`
To ensure binaryen is aware that the wasm function may be called from
JS, we annotate it via the `(@binaryen.js.called)` annotation.
Change-Id: I828dd0cf8d3b36db338792c4e277a4bb94c76faf
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/511080
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Binaryen introduced `binaryen.inline` which allows us to tell it
inlining hints now, including "never inline" hint (see [0])
This allows us to remove the ugly mangling of wasm function names with
`<noInline>` postfix.
We also now pass `--strip-toolchain-annotations`: The annotations
occupy size in the wasm binary and wasm runtimes ignore them (they are
for `wams-opt` only).
Except for IR tests: Here we want to see the annotations, so we keep
them there.
We also rename the package:wasm_builder classes to clearly indicate
those are binaryen specific sections.
We also make the ir_test.dart put it's options first, allowing the IR
tests to override options if needed.
[0] https://github.com/WebAssembly/binaryen/commit/3c25487214600a9
Change-Id: I96688bfaeba403a39cd5e7376f8d2889bcbae030
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/510000
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Srujan Gaddam <srujzs@google.com>
The change in [0] increased the language version of pkg/dart2wasm. That
in return changes how the package is formatted by the autoformatter.
This CL runs now the formatter to re-format the code. Unfortunately this
makes blame lists worse. But not doing it will make us have to disable
auto-formatting before saving files which is very annoying.
[0] https://dart-review.googlesource.com/c/sdk/+/487944
Change-Id: Ie3cbdc58bbd7f5aba187470554b7c958719fa795
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/496940
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
If a function doesn't have an effect we can now mark it via
`@pragma('wasm:pure-function')`. We'll then emit this as metadata
in the `binaryen.remove.if.unused` custom section.
This allows `wasm-opt` to remove calls to such functions if the result
of the call isn't used.
For now we mark a few string functions as pure.
Closes https://github.com/dart-lang/sdk/issues/62665
Change-Id: I8d38fb5894fd98248dc4d648d99c8cdcddc271a5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/481802
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Nate Biggs <natebiggs@google.com>
This adds the `Memory` class to `dart:_wasm`, allowing Dart code to
load and store numeric types in linear memory.
Since `dart2wasm` doesn't generate a memory instance by default, there
is no singleton instance of `Memory`. Instead, memories are defined as
`external` top-level getters annotated with a pragma like
`@pragma('wasm:memory-tyype', MemoryType(limits: Limits(1, 10)))` to
declare their type.
Interop happens in a static way: Methods on `Memory` cannot be torn-off
and, since the target memory is encoded directly in the store/load
instruction, there's also no polymorphism for memories in Dart.
Attempting to call methods on a memory instance that isn't a direct
reference to its definition is a compile-time error.
Memories can also be imported and exported through the existing
`wasm:import` and `wasm:export` pragmas.
TEST=tests/web/wasm/memory_test.dart
Change-Id: I726f33ac2ec04afab55c5a2b6bc09079d0193e02
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/470020
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Prior to recent changes that moved wasm-opt into a phase of dart2wasm, the optimized module always had the same filename as the unoptimized one. So even though wasm-opt was not updating the source map URL in the module, the URL was still correct.
However, now we can specify a new name when we run wasm-opt on its own. So we need wasm-opt to update the URL in the module.
We always pass this flag but when the names match wasm-opt will just update the URL to the same value.
Change-Id: I1e59a6189cd537b1e614431ba1ef7bc5e70cb088
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/471380
Commit-Queue: Nate Biggs <natebiggs@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
To avoid depending on exact ordering binaryen uses in the wasm module,
we start
* enqueue functions/types/... based on sort name instead of index
in wasm module
* only assign increasing ids in `Namer` if we actually want to print
the name
* print functions/types/... in sorted order
for IR tests.
We also update the `pkg/dart2wasm/bin/wasm2wat.dart` tool to be
able to dump wat for multiple wasm modules.
Change-Id: Ifbfc9149a629cf5ceacd95b7cb4892e9de3d4d9c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/465341
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
Until now the element segments were hidden from the core IR & Builder
classes. This has a few issues as outlined here:
* It required `ir.DefinedTable` and `ir.ImportedTable` to have entries
=> Having element segments as core concept means they can refer
to a `ir.Table` (irrespective of wether it's a `ir.DefinedTable` or
`ir.ImportedTable`)
* It assumed that table entries have to be functions
=> We can have tables of other reference types as well
* If we ever wanted to use the `table.init` instruction then we'd be
having an issue as that instruction refers to an element segment
(which didn't exist in our IR)
=> Now we represent `ir.ElementSegment` as a concept which a
`ir.TableInit` instruction could reference.
* The encoding of function tables was so far either using the
`vec[ref.func]` form if the table index was 0 or it
would use `vec[expr]`.
=> The active segment encoding with/without table index is somewhat
orthogonal to whether to use ref.func/expression.
=> If we encode a function table with index != 0 now we can still
use `vec[ref.func]` (which is smaller encoding)
=> If we encode non-functions in a table we can now use `vec[expr]`
encoding.
* Makes IR, serializer & deserializer cleaner.
=> removes `ir.Functions.withoutDeclared`
=> removes deserializing functions with side-effects
Change-Id: I75544ab3b1f0c721f0585a2c187a81415e978fe3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/459440
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
Changes to IR printing implementation:
* Emit omitted `<...>` marker to make difference between function
without body and function with omitted body clear.
* Extend omtting capability to globals, types and tables
* Extend filtering capability to globals, types, tables
* Better table names: Try to use import or export names if available.
* Option to print globals & types always multiline
* Option to scrub absolute file uris (which aren't stable across machines)
Changes to IR binary / text:
* Extend binary parsing of element section to support imported tables
* Add printing of those (which shows how imported tables are patched)
(The patching of importing tables is used in deferred modules)
Changes to IR test framework:
* Allow more filters (see above) in the test files.
* Allow tests to use helper libraries (containing `.h.` in their name)
without them being considered tests themselves.
* Allow using deferred loading in tests and write expectation files for
main & deferred modules as wat files.
Other things:
* Share more code between `pkg/dart2wasm/bin/wasm2dart.dart` and
`pkg/dart2wasm/test/ir_test.dart`
* Add baseline test for deferred loading using tear off constants
Change-Id: Ica6666f23f5aa6fb2174f414c9082039940f8dc5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/456460
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Nate Biggs <natebiggs@google.com>
This adds support for printing module IR as text format.
For convenience we add a `pkg/dart2wasm/bin/wami.dart` that
produces very similar output to V8's `wami`.
The goal is to use this to write size/perf optimization tests
by dumping IR into expectation files (will add this
infrastructure in a future CL)
Issue https://github.com/dart-lang/sdk/issues/60928
Change-Id: I42d19c2b8c6242f55693d6ed5d844a5c1ecb1f39
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/454600
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
This adds a wasm binary reader that produces an `ir.Module`.
We also make a few changes to existing code
* Represent the import section with an `ir.Imports` object (similar to
`ir.Exports`, `ir.Functions`, ...)
* We make a bunch of data structures allocatable in uninitialized state
(the fields being usually uninitialized `late final` fields) where the
deserializer can create those objects and then fill in details later.
=> This comes partly due to the way wasm binaries are structured
themselves: The "data count" section comes first so a reader knows
how many data sections there will be, then the "code section" can
refer to those data sections. Then afterwards the actual "data
segment" comes that fills in the data of the section.
* We make names consistently optional: Wasm objects don't have to have
names, so the names should be optional, so we make them `String?`. We
also make them non-final as that's consistent with other names.
* We make the `ir.Types`, `ir.Functions`, ... objects have `operator[]`
and the index used is the same index used e.g. in wasm instructions.
* We make static constants for section ids and custom section names.
Issue https://github.com/dart-lang/sdk/issues/60928
Change-Id: I5394d6b82cf4dc68d24cea1dee66c5b33eb2f60f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/452144
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
Currently the IR has some serialization information attached to it (e.g.
it keeps track of the number of types that have names, etc).
This is redundant/derived information as it can be computed by looking
at the actual IR (e.g. iterate types and count the number of them that
have names).
=> We get rid of this impurity by making the serializer compute the
information it needs to serialize.
I suspect it has been done as an optimization, to avoid an extra pass
over the IR data structures. But this can be handled in another way
witout extra passes as well:
* a single pass traverses and writes the (index, name) pairs
* it keeps track of the number of (index, name) pairs written
* if the number is > 0 then we add the section, the count and the pairs
We do have to traverse all the functions, types, ..., at least once now,
but that's fine: Firstly we always assign names so in reality we cannot
skip these traversals (we instruct binaryen to emit or skip the names
when it runs). Secondly if we wanted to have core dart2wasm not emit
name section we can just skip serializing it if e.g. a
`--no-name-section` flag was passed.
We do a few more changes in this CL:
* We make `ir.DefType.name` be an optional string
=> This aligns it with `ir.Global.globalName`, `ir.BaseFunction.name`,
...
* Only assign `localNames[local] = ...` and `fieldNames[field] ` ...`
if name is not empty string
* We put the `sourceMapUrl` section behind the name section (to align
with the order used in binaryen)
* We remove redundancy in the section serializer: Instead of having
`isEmpty` that has to be kept in sync with `serializeContents` we
only have `serializeContents` and make the caller check if the bytes
are non empty.
Issue https://github.com/dart-lang/sdk/issues/60928
Change-Id: Ie9d153fefc25e8277fb30b83e45d2549731797c4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/452040
Reviewed-by: Ömer Ağacan <omersa@google.com>
We have a relatively clean separation between the data structures
representing a wasm module (e.g. `Module`) and the classes used to
build such a module (e.g. `ModuleBuilder`)
Though with deferred loading changes the core IR data structures started
to get a `ModuleBuilder get enclosingModule` getter.
This CL makes the IR classes self-contained again.
=> The `enclosingModule` getter will now return a `Module` instead of a
`ModuleBuilder`
=> The `*Builder` classes that need a `ModuleBuilder` can store that
themselves but only pass up `Module` to IR classes.
Since the IR data structures are cyclic (the `Module` has various parts
that refer back to it via `enclosingModule`), we allow the `Module`
object to be constructed in an uninitialized state and initialize it
after building the module is done.
Issue https://github.com/dart-lang/sdk/issues/60928
Change-Id: I5fddd6ca43b662a6e6c328230e91b1bc75332deb
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/451720
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
We should use `Uint8List` to represent bytes in the wasm serialization
code. For cases where we only write two fixed bytes, we use `writeByte`
instead (which is faster than `Uint8List.setRange(<int>[a, b])`. We use
a cached `ByteData` to write little-endian float32/float64.
Change-Id: Id5662c1b7a3ba15f087341b13a99bbfdfa37059e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/403864
Reviewed-by: Slava Egorov <vegorov@google.com>
This annotates each code location in wasm stacktraces with the name of the referenced module. By default v8 only includes an opaque hash to name each module.
For tests and debugging where we want to analyze stacktraces the opaque hash alone makes it impossible to associate each code location back to a file. With multiple modules we will need this additional information.
Change-Id: I464780d9234e0685d08f4f1925f65ce073b3f322
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/385400
Reviewed-by: Martin Kustermann <kustermann@google.com>
Golem results verifying no regressions from this change:
https://golem.corp.goog/Revision?repository=dart&revision=111273&patch=19368
This modifies the wasm_builder to emit types in minimally sized recursive groups.
The main motivation of these changes is to minimize the size of the type section when dart2wasm produces multiple modules. If we included every type in every module, for small modules the type section would be a very significant % of the total file size (in a small example it was ~98% for a deferred module).
The two main changes are:
1) Type tree-shaking for each module.
We detect which types are actually used in that module (via instructions, function signatures, etc.) and only include the detected types in the module's type section. This simplifies the compilation pipeline as we don't need to worry about assigning types to modules, we can just build the module as before and post-process the IR to collect the set of used types.
2) Create minimally-sized recursive groups.
In order for wasm type-checking to work across modules, equal types have to be in equal recursive groups. Tree-shaking therefore has to occur at the rec group level as opposed to the individual type level. So to make tree-shaking effective, we need the smallest possible rec groups.
We achieve this by creating a graph of the wasm types' dependencies and then calculating the set of strongly connected components for that graph. Each component represents a minimally-sized recursive group. The DAG formed by the components is the order to emit them so that definitions come before usages.
Importantly, by separating types into different rec groups, we are also changing the equivalence relationship between them. This can have a meaningful impact on binaryen's ability to optimize the module, two types that were distinguishable might not be anymore. To avoid this regression we group together any types that are structurally equivalent. This way binaryen will differentiate them as separate types as they were in the original Dart source.
Change-Id: I67acdd21a89ff2718e8bbd6360f342c150494a9a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/378764
Reviewed-by: Ömer Ağacan <omersa@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
None of these changes modify the generated wasm in any way yet. But all these changes allow dart2wasm to generate multiple modules to implement deferred loading and eventually dynamic modules.
1) Exposes tag export functionality.
This functionality is supported by the exception handling spec/proposal but just wasn't added to wasm_builder originally:
https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/Exceptions.md
The exception tag needs to be exported from the main module and then imported into subsequent modules so that exception handling works in a multi-module environment.
2) Support updates to imported tables via the element section.
The element section provides an easy way to assign functions to indices in a table. This assignment can be applied to either a defined table or an imported table. wasm_builder only supported tracking element assignments for defined tables but deferred loading will make use of imported tables to which we will also want to assign functions.
Change-Id: If23fbbb1e60a67d80d11924aaa718c50e68ecb98
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/378840
Reviewed-by: Martin Kustermann <kustermann@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
This implements generating source maps for the generated Wasm files.
Copying dart2js's command line interface, a source map file with the
name `<program name>.wasm.map` is generated unless `--no-source-maps` is
passed.
When a source map is generated, the generated .wasm file gets a new
section `sourceMappingURL` with the contents `<program name>.wasm.map`.
This section seems to be undocumented, but Chrome and binaryen recognize
it as the URI to the source map file. Chrome is then loads it
automatically in the DevTools.
## Implementation
- `wasm_builder` package is updated with the new `source_map` library,
which describes the source mapping entries.
- `wasm_builder`'s `InstructionsBuilder` is updated with the new public
members:
- `startSourceMapping`: starts mapping the instructions generated to
the given source code.
- `stopSourceMapping`: stops mapping the instructions generated to a
source code. These instructions won't have a mapping in the source
map.
- `CodeGenerator` sets the source file URI and location in the file
when:
- Starting compiling a new member
- Compiling an expression and statement
Change-Id: Ic8f723f7a154402c0d34710689db57d640b83b86
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/370500
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
- Imported things (modules, globals, tables, functions) don't need to
declared before defining things, as we don't assign indices to things
before finalizing the IR, and when finalizing we assign ids to
imported things before defined things, in
`finalizeImportsAndBuilders`.
Remove documentation saying imports should be declared before
definitions.
`FunctionsBuilder._functions` list was used in the name section, and
required imports to come before definitions. This list is now removed,
instead we pass `[...imported, ...defined]` to the names section.
- Stop upcasting imported things to `Import` before we need to upcast.
- Fix global lists passed to `NameSection`.
These lists need to include imports as well (even though we never name
them, wasm_builder doesn't allow naming them yet) otherwise the
indices in the name section will be incorrect.
wasm_builder doesn't allow importing types yet, so we don't need to do
the same for the types list.
Change-Id: Id05632c3af7937bd66d7581d89d538137020f6e6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/366601
Commit-Queue: Ömer Ağacan <omersa@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
As of 2025/05/14, `array.new_data` is not a constant instruction, so
can't be used in a global initializer.
Also, data count section needs to appear right before the code section.
Without `array.new_data` we don't need to generate it before the global
section.
Remove the code related to handling `array.new_data` in global sections.
When `array.new_data` becomes constant, we can generate the data count
section in its new place always, without having to specially handle it
when it's used in a global section.
Change-Id: If11824e171d7f21848e82c102f0a4e8257f02c6f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/366341
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
Currently no globals are named, so the subsection will be empty. Three
extra bytes will be generated in the names section for the empty
subsection.
When debugging we can now name globals with the optional positional
argument:
// "my global" is new:
m.globals.define(w.GlobalType(type), "my global")
wasm-opt and v8 (d8, wami) support the subsection, so the global will
now appear as "my global" in the debugger and Wast outputs.
Change-Id: I5988ab792209c5c82593b85c48fead65ad536031
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/355120
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
There's no difference between a module watchpoints field being `null` or
`[]`, so the type `List<int>? watchPoints` can be simplified as
`List<int> watchPoints` and by treating empty list the same way as
previous `null`.
Change-Id: I9be8c872c4336b1c0bf5157af5e8587136cf2088
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/325261
Commit-Queue: Ömer Ağacan <omersa@google.com>
Reviewed-by: Aske Simon Christensen <askesc@google.com>
This CL mostly just moves code around. There are three broad changes in this CL:
1) Reify the builder / built pattern that exists implicitly in the existing code. Builders now live in `src/builder`, while the built ir lives in `src/ir`.
2) Reify the module subsections.
3) `pkg/dart2wasm` has been updated to use the new API.
There is only one minor logic change in the entire CL, we now defer serialization of a module until the bytes are actually required, as opposed to serializing eagerly.
This change is designed to make the wasm_builder more robust. By clearly delineating which parts of the AST are mutable and which parts are immutable, then it should make it easier for users of the wasm_builder to avoid undefined behavior, i.e. holding on to something that can change.
Change-Id: I676107b867aa74fabf413108673e170126bdb5c1
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/316280
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Joshua Litt <joshualitt@google.com>