Commit Graph

115 Commits

Author SHA1 Message Date
Martin Kustermann 2d78883f27 [dart2wasm] Simplify handling of JS interop callbacks
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>
2026-06-11 12:24:16 -07:00
Martin Kustermann 5cd42e6b21 [dart2wasm] Start emitting binaryen.inline custom section
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>
2026-06-10 15:51:47 -07:00
Nate Biggs 595cc71901 [wasm_builder] Add support for including extra custom sections when serializing a wasm module.
Allows the caller to extend `CustomSection` with their own subclasses
and include them when serializing the module.

Change-Id: I2bda659eab531def94abd0dae682ea24f717d1af
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/504840
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2026-05-20 18:55:10 -07:00
Martin Kustermann 9fd82be4ff [dart2wasm] Disable type branding by default
This reduces e main module by around 1.5% compressed and 0.7%
uncompressed.

Except for a few special wasm struct types, we don't rely on type
uniqueness as we never use e.g. `ref.test` on normal wasm structs of
dart classes, closures, closure contexts, ...

So for otherwise structurally equivalent types, we can allow merging the
types. This can lead to confusion when reading wasm files because two
unrelated Dart classes may end up being represented with the same wasm
struct. To avoid this confusion when reading .wat files, we make it
clear in the type name and field names that it can be any of the
original dart classes / fields.

One can enforce unique types by passing `--no-unique-types`.

In IR tests we default to `--unique-types` to make the IR tests more
readable, except for 2 newly added tests that test this flag.

In essence this is the follow-up work on [0] which started sharing wasm
structs between classes in a hierarchy.

We enable `--unique-types` in opt>=2 (just like in --minify)

The dart2wasm compiler may now emit two identical recursion groups. We
could avoid that, but that would be a bigger refactoring of the type
building code in `package:wasm_builder` and `wasm-opt` will remove the
duplicate recursion groups anyway.

[0] https://dart-review.googlesource.com/c/sdk/+/472181

Change-Id: I36a5b7cfc3abb5318658fa67dae36e1c0a1e4d19
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/501140
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2026-05-08 00:32:15 -07:00
Martin Kustermann 1d8d507384 [dart2wasm] More precise inlining
This reduces e main module by around -0.3%.

We make the inliner have more precision when estimating the
callee size, especially around constructors but also other
cases.

We are also less generous with budget for iterators, modulo list
iterators which we want to be always inlined.

We also print the inlining decisions in `--print-wasm`

In deferred loading scenario this now inlines constructor bodies
more often than before as the size estimate is better. That in
return avoids us e.g. exporting empty constructor body functions
in the main module to be imported by other modules.

CoreLibraryReviewExempt: Backend-specific function changes.
Change-Id: I320db6f976a5d8c036b40908a5f72e7019d420ae
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/498562
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2026-04-29 05:16:01 -07:00
Martin Kustermann 4201b2677e [dart2wasm] Dart format pkg/wasm_builder
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>
2026-04-21 05:52:14 -07:00
Martin Kustermann b1df65ded8 [dart2wasm] Use table based dispatch for dynamic calls
This reduces e mail module by -4% in compressed form. It does have
a increase of 2.5% in uncompressed form. This is due to repeated
entries in the element section of the dynamic dispatch table and
will be addressed in future CLs.

Currently if the main module of an app has a dynamic call, it will have
a caller-shape specific dynamic dispatcher that checks (via class ids)
all possible targets in the app and issues calls to them.

That means the code size is O(targets) - even if most targets reside in
deferred modules.

We change this now to use a row displacement table based dispatch,
just as our normal typed dispatch table. Though there's a few
differences

* In a typed call we know the target exists, in a dynamic call we don't
  know whether the target exists (it may be a NSM case). To accomodate
  for this we make 2 wasm tables of the same layout: First we load a
  table of i31refs and check if the value is in agreement with the
  receiver class id. If so we have a match and can load the actual
  destination from the funcref table.

* In typed calls we use one row in the table for each selector. In the
  dynamic invocation case we'll use one row per dynamic selector + call
  shape.

* The wasm module that contains the actual instance method will also
  hold all it's dynamic forwarder functions (one per dynamic caller
  shape). The elements section of that module will cause initialization
  of the two slots in the two tables mentioned above.

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

Change-Id: I4e631f98fe9c58e2110bac34c3f5ff6d11bef909
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/487660
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2026-03-18 03:17:42 -07:00
Martin Kustermann f99f5b1a2b [dart2wasm] Allow marking functions as pure functions
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>
2026-02-23 01:39:37 -08:00
Kevin Moore e0b028808a [dart2wasm] Expand Wasm SIMD intrinsics for vector operations
Add more Wasm SIMD intrinsics and instructions to support efficient
vector operations, specifically targeting the requirements of types
like Offset and Size.

- Implement new intrinsics in `dart2wasm`:
  - `anyTrue` for V128.
  - `allTrue` for I64x2.
  - `pmin`/`pmax` (pseudo-minimum/maximum) for F64x2.
  - `fromLaneValues` for F32x4 and F64x2.
  - `shuffle` for F64x2.
- Update `wasm_builder` with support for the following instructions:
  - `v128.any_true`
  - `i8x16.all_true`, `i16x8.all_true`, `i32x4.all_true`, `i64x2.all_true`
  - `i8x16.shuffle` (including serialization and deserialization)
- Add `tests/web/wasm/simd/vector_test.dart`, which implements `Offset`
  and `Size` using these new SIMD intrinsics, demonstrating their
  practical utility and correctness.
- Update `tests/web/wasm/simd/simd_test.dart` with additional coverage.

Change-Id: Ifa5ba0fb265b0fa46c0a3469d9331f32a025c9ec
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/478860
Commit-Queue: Kevin Moore <kevmoo@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2026-02-17 16:05:32 -08:00
Kevin Moore ef8682a9f8 [dart:wasm] implement most useful floating point SIMD ops
Change-Id: I44958086921812c7eb1a4be6b826c1be9a50738b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/478340
Reviewed-by: Nate Biggs <natebiggs@google.com>
Auto-Submit: Kevin Moore <kevmoo@google.com>
Commit-Queue: Kevin Moore <kevmoo@google.com>
2026-02-04 20:31:45 -08:00
Kevin Moore 59a862e788 [dart2wasm] DRY up a LOT of wasm v128 types
Change-Id: I40ee7f9350b0f4bec66aeeb85a50e7d33a694233
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/478245
Commit-Queue: Kevin Moore <kevmoo@google.com>
Reviewed-by: Nate Biggs <natebiggs@google.com>
2026-02-03 22:34:15 -08:00
Kevin Moore 152e01ff69 [dart2wasm] "publicly" implement WasmV128
And add corresponding operations

Change-Id: Ib39ec04969281a35615e1b4ae41d1b4ec1c100ef
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/478242
Reviewed-by: Nate Biggs <natebiggs@google.com>
Commit-Queue: Kevin Moore <kevmoo@google.com>
2026-02-03 22:17:49 -08:00
Kevin Moore 44cfe5b3fa [dart2wasm] Add equality to the v128 types
Change-Id: I6a07d1bec1ccc4b3a528399f9201c661bcef3e03
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/478083
Reviewed-by: Nate Biggs <natebiggs@google.com>
Commit-Queue: Kevin Moore <kevmoo@google.com>
2026-02-03 20:51:11 -08:00
Nate Biggs 5a02a7a019 [dart2wasm] SIMD instructions template
SIMD proposal:
https://github.com/WebAssembly/spec/blob/main/proposals/simd/SIMD.md

This only implements a few of the available SIMD instructions as a
template for further contribution. kevmoo@ plans to continue
implementing these.

Bug: https://github.com/dart-lang/sdk/issues/62516
Change-Id: Ib229feecf58714e92fcb1461f968919658cbce29
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/476540
Commit-Queue: Nate Biggs <natebiggs@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2026-02-03 10:01:43 -08:00
Martin Kustermann ea6fb0a16f [dart2wasm] Allow using table slots for storing values of dart globals
This seems to result in -0.6% compressed main module and a bit less
in uncompressed mode.

Sometimes we have many fields with lazy initializers of the same type.
That led us to emit 1 nullable wasm global for each such field. For
example all proto classes have a `static BuilderInfo i_` field. This has
led to thousands of `(mut (ref null $BuilderInfo))` wasm globals.

Now we use a wasm table for this, which is a O(1) in the binary as they
all get `null` by default, saving us all these globals. The downside is
that accesses have an extra instruction now, but overall this is a win.

The CL also cleans up `globals.dart` by separating the concept of a wasm
global and reading/writing to it from the concept of a Dart global - as
Dart globals can now be backed by wasm globals or table slots.

This CL uses the new capability made possible by the refactoring
in [0] - namely to emit element sections which initialize wasm table
slots with non-function expressions.

[0] https://dart-review.googlesource.com/c/sdk/+/459440

Change-Id: Ie57206dff8c0a57a1df5e48c0808167f822bc4a2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/475800
Reviewed-by: Nate Biggs <natebiggs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2026-01-30 03:34:42 -08:00
Ömer Ağacan 992a07a552 [dart2wasm] Generate dart2js sourcemap extensions to map minified class names to full names
Reuse dart2js's source map extension format to map minified class names
shown in error messages and runtime type strings ("minified:Class123")
to original class names.

The extension field is only generated when minifying, and with multiple
modules, only in the main module's source map.

- Without minification runtime type strings and error messages already
  include the full class name, so mapping is not necessary.

- With multiple modules, the existing tools use the main module's source
  map to deobfuscate errors.

Because wasm-opt is not aware of this custom section and it also changes
the names section, when optimizing, we read the original names before
calling wasm-opt, and write the section back to the source map file for
the optimized Wasm.

When not optimizing we generate the source map with the custom section
directly.

Example deobfuscation using the new source maps:

    $ dart pkg/dart2js_tools/bin/lookup_name.dart test.wasm.map Class123
    Class123 => AsyncError (a global name)

The custom section is a bit verbose for what dart2wasm needs: we could
map numbers to name indices instead of strings to name indices, as
dart2wasm minifies class names to numbers. However to avoid updating a
bunch of tools in g3, SDK, maybe also in Flutter and devtools, we reuse
dart2js's format, at least for now.

Issue: https://github.com/dart-lang/sdk/issues/60711
Change-Id: I2cda331723c6f0c7e7ef5f4772feaf420dc8c6ba
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/474660
Commit-Queue: Ömer Ağacan <omersa@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2026-01-28 02:48:30 -08:00
Martin Kustermann 6767453d49 [dart2wasm] Define single table for cross module calls
This reduces essentials main module by 3.5% and the type section by
37%.

So far if there was a call from any module Mi to a function F in module
Mj the main module had to add a table whose type is the function type of
F. Encoding this function type also means encoding any type the
function type transitively refers to.

=> That means that if classes are contained in deferred modules we still
   pay the price of encoding their types in the main module -
   which we really don't want.

Instead of doing this, we now make a single table containing values of
type `HeapType.func`. The module containing the definition of the
function will initialize the table with the slot. Any caller can use
`call_indirect` to call the function.

One may argue that the table having a less precise type means more
overhead at runtime. Though for several reasons I think this is the
right call to make:

* the performance of cross module direct call will now be similar to a
  interface call via dispatch table
* the call site will have the identical function type as the callee and
  as such the type check should be cheap / a pointer comparison
* there's no polymorphism at runtime, a call always goes to the same
  destination and as such it's a very predictable branch for the CPU
* it has to be an indirect call anyway and some engines have similar
  support for both `call_ref` and `call_indirect` (e.g. speculative
  inlining)

Change-Id: Ibc7680afd9acb4f969019d27e9f13c24aa6a5706
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/475200
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Nate Biggs <natebiggs@google.com>
2026-01-23 11:15:51 -08:00
Martin Kustermann b5dbc67c1d [dart2wasm] Prune compiler output of empty modules
For a variety of reasons the dart2wasm comiler can currently emit empty
modules (e.g. function is assigned to a module but actually the function
is dead but wasn't tree shaken by TFA, all functions in a module got
inlined to callers, etc)

=> Only emit modules that are not empty.
=> Prune `loadids.json` of those empty modules.

This results in compilation of essentials containing 263 less modules.

Change-Id: I31e9277450d5738da08f09220dbbabc270c1a53b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/475100
Reviewed-by: Nate Biggs <natebiggs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2026-01-22 15:24:42 -08:00
Kevin Moore a820015bb5 [wasm_builder] enable and fix some new lints
dart_flutter_team_lints is more thorough and used in most ecosystem packages
I ignored some of the ones with the most violations

Change-Id: I825203b1a60ed44d6190aaa59bfce2a18af8bc82
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/473540
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2026-01-16 04:56:01 -08:00
Kevin Moore e37343fa6d [wasm_builder] Use switch expressions in many places
Improves readability

Change-Id: I7a4e44138e43dd286c3183989241675f6e82c58d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/473443
Commit-Queue: Kevin Moore <kevmoo@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
2026-01-15 16:50:41 -08:00
Simon Binder 7d8cd032dc [dart2wasm] Interfaces for accessing memories
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>
2026-01-15 03:45:21 -08:00
Martin Kustermann a7ffe9d5d5 [dart2wasm] Roll binaryen with fix for duplicate sourceMappingUrl section
See https://github.com/WebAssembly/binaryen/issues/8190

Change-Id: If2f6860a2f812e57c69bee3849142cda6b4dc441
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/472581
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2026-01-13 05:49:45 -08:00
Nate Biggs 909786e1b9 [dart2wasm] Update optimized modules source map URLs.
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>
2026-01-12 05:09:16 -08:00
Ömer Ağacan 8453e3370b [dart2wasm] Improve debug traces:
- When generating `end` instructions starting a state machine block,
  generate a comment showing
  - State index
  - Type of AST node that the state is for
  - Whether the block is for the "inner" or "outer" (i.e. continuation)
    of the AST node

- When a local has a name, show it in `local.{get,set,tee}` traces.

Also removes an old TODO comment.

Change-Id: I80848f2fbb4d25458e5cdb27ed1f7def4ca9899e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/470160
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
2026-01-02 05:45:56 -08:00
Ömer Ağacan 02a3163219 [dart2wasm] Catch JS exceptions with the right tag, improve toString and stack traces
Example:

    import 'dart:js_interop';

    @JS()
    external void eval(String code);

    @JS()
    external void throwFunction();

    void main() {
      eval('''
        self.throwFunction = function() {
          throw new Error('Hi from JS');
        }
      ''');
      try {
        throwFunction();
      } catch (e, st) {
        print(e);
        print(st);
      }
    }

Output before: ("..." parts are code locations, omitted)

    JavaScriptError
        at module0.main ...
        at module0._invokeMain ...
        at InstantiatedApp.invokeMain ...
        at main ...
        at async action ...
        at async eventLoop ...

Output after:

    Error: Hi from JS
        at self.throwFunction ...
        at _277 ...
        at module0.main ...
        at module0._invokeMain ...
        at InstantiatedApp.invokeMain ...
        at main ...
        at async action ...
        at async eventLoop ...

Fixes #62218.

Issue: https://github.com/dart-lang/sdk/issues/62218
Change-Id: Ia9347e938af209b8b87752479d35b6236f721acf
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/469062
Reviewed-by: Martin Kustermann <kustermann@google.com>
2026-01-02 02:27:58 -08:00
Martin Kustermann 27f26e5189 [dart2wasm] Optimize initialization of dispatch table
This reduces essentials main module by 8.3% (-730 KB)

The dispatch table contains displaced selector rows. Each selector row
contains an entry for each class that provides the selector.

This can lead to very large dispatch tables with repeated elements:
Especially common is a base class with selectors that get inherited
by many subclasses where few subclasses override the selector.

This is common e.g. for `Object.{hashCode,operator==,noSuchMethod}` but
also for user defined base classes that have many subclasses.

This led to the element section being very large: It often contains
large consecutive sub-ranges which refer to the same target function.

To shrink the element size we instead move the initialization of those
large regions homogenious regions to the module init function: We can
utilize the `table.fill` instruction which can initialize a large range
of the table with the same value.

Change-Id: I9fc308969264f4855a514e7d36c761891bb0cb52
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/467843
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
2025-12-15 06:25:41 -08:00
Martin Kustermann 3030208174 [dart2wasm] Implement fine grained deferred module splitting
This shrinks essentials main module by 13% (-1.4 MB)

Before this CL the splitting of the application into wasm modules
was done on a library granularity level.

Now we split the application based on "static element" granularity -
those elements are:

  * Static fields
  * Static getters, setters, methods
  * Constructors
  * Class (all instance fields & methods)

This means that moving a class or static methods/fields from one library
to another will have no effect on the partitioning.

Differences to dart2js:

  * No tracking of local functions
  * No tracking of types
  * No split constraint support (yet)
  * (Works on Kernel AST instead of dart2js element/entity model)

The code is organized into

* `pkg/dart2wasm/lib/deferred_load/import_set.dart`
   This is almost identical to the dartj2s version with minor differences:
    - works on `LibraryDependency` objects
    - does not assign names to parts
    - no split constraint support (yet)

* `pkg/dart2wasm/lib/deferred_load/dependencies.dart`
   This is a new implementation that collects dependencies of
   `Reference`s/`Constant`s and in case of `Reference` whether the
   dependencies are deferred or not.

* `pkg/dart2wasm/lib/deferred_load/partition.dart`
   This is the main algorithm (core logic is the same as in dart2js)
   `Reference`s/`Constant`s and (in case of `Reference`) whether the
   dependencies are accessed under deferred loading guard or not.

Change-Id: I0fcdf86f5226060738671a1f69bb14cfffd631e8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/467041
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-12-11 04:50:39 -08:00
Martin Kustermann 053f0ed005 [dart2wasm] Make IR tests more stable
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>
2025-12-02 03:06:51 -08:00
Martin Kustermann f02fd0cd21 [dart2wasm] Change types builder to not use record types in maps
This reduces the time the codegen phase takes when compiling the
essentials app by 33% (1 min 43 sec -> 1 min 8)

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

Change-Id: Ic26231a5823eeee24b562364e5cc6b03359792ba
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/461822
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-11-14 02:52:30 -08:00
Martin Kustermann 76d56d74db [dart2wasm] Refactor the way we handle elements segments
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>
2025-11-07 11:23:41 -08:00
Martin Kustermann 293c4b95dc [dart2wasm] Some fixes for the binary wasm deserializer
* Assign type indices when deserializing the type section
* Fix decoding of tag imports
* Add data segment support to IR printer
* Handle input+output variants of loop/if/try_table instructions

This allows us to now read & print all wasm modules of ACX gallery when
compiled in deferred loading mode.

Change-Id: Iaba30a6337aa2afd5d4894ca73d305cd2b7befea
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/459480
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-11-04 11:48:14 -08:00
Martin Kustermann 6184c447ab [dart2wasm] Introduce unresolved accesses & link phase
This reduces ACX Gallery's main module from 8.4 MB to 2.75 MB.

This CL adds new infrastructure to our compiler: We allow codegen to
emit unresolved instructions which will get patched up later on in a
final link phase.

We use this for constants: Generating the body of a function (or
initializer of a global) may need to access a constant. Though in
deferred loading mode we may not have decided (yet) into which module
to place the constant.

* We emit an unresolved constant access (kind of dummy instructions
  which still maintain stack machine) as a patchable region in the
  instruction stream and remember that the constant (and any constant it
  refers to, directly or indirectly) was used by the corresponding module.

* After code generation we have collected all constant uses (and the
  modules they are used in) and have therefore all knowledge to decide
  where to place constants. (See below on placement logic)

* In a final link phase that will walk over any instructions with
  unresolved constant accesses (patchable regions) and patch them up
  with the actual instructions to access the constants.

Part 1) Determination of global order

So far the creation order of globals determined the order in the global
section. But now we emit unresolved global uses and later on have to
define (or import) the globals in modules.

=> To allow this we now determine the order of globals when we build the
   globals section instead.

=> This would also allow other things: Choose ordering of globals based
   on usage count, etc.

Part 2) Separation of concerns in `constants.ensureConstant()`

So far the `constants.ensureConstant()` has done several things:

  * performed constant lowering
  * analyzing whether the constant should be lazy or eager
  * determine the type of the global of the constant
  * actual creation of global & initializer function (if needed)
  * doing the above for all transitive constants

=> The result was the `ConstantInfo` object.

We now separate these things:

The first part will lower constants, determine lazy or not, determine
type. This will recursively walk the constant DAGs and create
`Constantinfo` as needed for all of them.

=> Each `ConstantInfo` (representing information about a `Constant`)
   will now also remember the child constants (in the form of
   `List<ConstantInfo> children`) it will use when defining the constant.

=> When code generation uses a constant we will remember that that
   module-use of the constant and all it's child constants.

=> Representing this as `constantInfo.children` avoids recursive AST
   visiting, avoids re-lowering the constants and ensures we don't have
   to keep two AST visitors in sync.

Part 3) Tracking constant uses

When the code generation uses a constant, we remember it being used in
the module being currently compiled. We use this usage information in
the final stage to determine where to place constants.

Special situation: If we have constant uses across modules where
deferred loading is involved. For example here:
    ```
    import 'foo.d.dart' deferred as foo;

    main() {
      ...
      print(foo.topLevelConstant);
    }
    ```
which gets lowered to something like this
    ```
      StaticInvocation(target=print, args=[
        let
          _ = StaticInvocation(target=checkLibraryIsLoaded, args=[StringLiteral('foo')])
        in
          ConstantExpression(topLevelConstant)
      )
    ```

Even though the main module is using the `topLevelConstant` it does so
under what I call a deferred loading "load guard": The code accessing
the constant will never be executed unless the `foo` deferred library
was successfully loaded.

=> We make our usage tracking consider such uses not a usage of the main
   module but rather the module containing deferred library of the
   "load guard".
=> The `CodeGenerator` will track the active "load guard" when it goes
   down the tree.
=> This allows pushing constants to deferred modules even if they are
   used in main module code.

Part 4) Defining of constants

During code generation we (generally speaking) emit a patchable region &
record the constant use of the constant DAG (see above).

During the linking phase we then have global knowldge of constant uses
and start defining them.

Theoretically we want to define a constant in a wasm module in the
loading graph where all using modules have it as direct or indirect
dependency but the dependency being the closest one to the uses.

=> As simplification for now: If two different modules use a constant we
   place it in the main module. We can later on make this more precise if
   complexity is warrented.

To avoid emitting many patchable regions that we later on have to fix
up we add an optimization during code generation:

=> As soon as a use is in the main module, we define the constant DAG
   in the main module.
=> As soon as there's 2 uses in different modules, we define the
   constant DAG in the main module.

Misc

* We separate constant definition from importing / exporting them.

* The new architecture changes constant visiting slightly so the names
  of constants in expectation files change as a side-effect of this.

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

Change-Id: Ib44dee4c2514fb4af871e7078f5bfe43077922fd
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/458240
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Nate Biggs <natebiggs@google.com>
2025-10-30 16:30:28 -07:00
Martin Kustermann bf156859c0 [dart2wasm] Unify deferred loading implementation
This CL does a few things

* only have one way to lower deferred loading constructs
* only have one runtime implementation
* only use load ids now and refer to them as integers instead of strings
* remove AST repository for load ids (no need to serialize them across
  kernel serialization - as we assign them during codegen phase now)
* make runtime metadata smaller for deferred loading (wasm arrays)
* inject the loading map (runtime data structure) after codegen
* it fixes the stress test module strategy to inject `LoadLibrary`
  AST nodes (as the CFE does) instead of calls to the lowered form

Overall this simplifies the code significantly, removes complexity &
code from the codebase.

This refactoring opens up for the possibility for the codegen phase
to add more modules (e.g. if two deferred modules use the same constant,
we could *create* module to contain them).

Closes https://github.com/dart-lang/sdk/issues/61844
Issue https://github.com/dart-lang/sdk/issues/61727

Change-Id: Idfefee25d5f84f8717808a0aee1b189d8f15d16d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/458000
Reviewed-by: Nate Biggs <natebiggs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-10-30 03:40:00 -07:00
Martin Kustermann 92f500b7b7 [dart2wasm] Make IR test framework more flexible, add deferred loading baseline test
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>
2025-10-23 01:11:22 -07:00
Stephen Adams bf3e8e923a [wasm_builder] Add method to deserialize just the sourceMapUrl
It is useful to be able to extract the sourceMapUrl without parsing
the rest of the module.

Change-Id: I595b615b07015597fc1b082aa320887d86f16204
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/455542
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Stephen Adams <sra@google.com>
2025-10-17 12:01:43 -07:00
Martin Kustermann 6c228bff4a [dart2wasm] Add simple way to test IR changes
In order to test generated code for a function one can

  * place a dart file in `pkg/dart2wasm/tets/ir_tests`
  * annotate functions that shouldn't be inlined
  * describe which functions we want to dump in the expectation file
  * generate an expectation file.

This will allow generating renatively small expectation files for only
functions we care about and types/globals/... those functions need.

Change-Id: Ic7b6b6dece16ab453202aa2c4f9412de2fc251ae
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/454840
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-10-15 14:25:20 -07:00
Martin Kustermann e63dcafc0c [dart2wasm] Add wasm module printing functionality.
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>
2025-10-15 12:36:50 -07:00
Martin Kustermann 8322e6af37 [dart2wasm] Add support for reading wasm files to package:wasm_builder
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>
2025-10-01 03:37:31 -07:00
Martin Kustermann 53b35e6f9d [dart2wasm] Fix missing local names in wasm serializer
There was a missing increment of an integer for the local names to be
written out in the [0] refactoring.

Once we have the wasm module reader checked-in, I could even write a
regression test for it.

[0] https://dart-review.googlesource.com/c/sdk/+/452040

Change-Id: I65367b6796a50f16db1c013c94bb1d413691e980
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/452600
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-10-01 01:01:15 -07:00
Martin Kustermann b53d3b79ba [dart2wasm] Write typeidx as ULEB128
Most call, struct and array wasm instructions refer to the type using an
unsigned index. Currently the serializer uses a signed index, which
emits extra bytes.

Change-Id: Id8a55497c45692956bc8ea36268d9b38c8025933
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/452442
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-09-30 06:57:09 -07:00
Martin Kustermann fafff1e976 [dart2wasm] Simplify handling of name section
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>
2025-09-30 01:38:14 -07:00
Martin Kustermann e502e9049c [dart2wasm] Make <xxx>.enclosingModule an ir.Module.
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>
2025-09-26 04:11:19 -07:00
Nate Biggs 6751903449 [dart2wasm] Expand space of available brand types in wasm builder type model.
Bug: https://github.com/dart-lang/sdk/issues/61035
Change-Id: I8911c4613d3753b7dd59edccaa315b8de338ba6b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/438440
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2025-07-02 14:21:32 -07:00
Ömer Ağacan 26a065c60d [wasm_builder] Update names of instructions extern.internalize/externalize
These instructions were renamed to any.convert_extern and extern.convert_any in
Wasm GC spec commit:
https://github.com/WebAssembly/gc/commit/5bca3f7de6ad117a0d4349c138b5e32981c88d2f

Change-Id: Ie696541f75974c69828fb317754e3b613691eb4d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/420141
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
2025-04-03 14:24:31 -07:00
Ömer Ağacan 82282b6287 [dart2wasm] Generate local names in the names section
Local names for function value parameters and for "precise this", return
values are generated, state indices in `async` and `sync*` functions are
generated.

We can generate names for more locals as needed.

Change-Id: Ie919f030f0bfae8adbca90408509dd04a7414278
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/419200
Commit-Queue: Ömer Ağacan <omersa@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2025-04-02 01:52:18 -07:00
Ömer Sinan Ağacan 99d5a32aa1 [dart2wasm] Generate field names in the names section
Change-Id: Ia2fdbb63c20cde2fed432fd89f93acb9373239f4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/381840
Commit-Queue: Ömer Ağacan <omersa@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2025-03-31 04:52:11 -07:00
Ömer Ağacan c0e8f907e3 [wasm_builder] Add new exception handling instructions
These instructions are not used yet as they're not enabled by default
in Chrome yet.

This CL is mainly tested by the child CL, which uses instructions added
in this CL for exception handling.

Change-Id: I04d767599f47cdb6abc9cca02974647d9e5421fb
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/411581
Commit-Queue: Ömer Ağacan <omersa@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2025-03-20 10:10:22 -07:00
Nate Biggs 7b28c5a94f [dart2wasm] Update serialization strategy for dynamic modules.
Introduces some more machinery to simplify the serialization pipeline. Now the logic to index kernel nodes is hidden away in the serializer. Now throughout the compiler if entities (i.e. classes, members and references) need to be serialized they can be passed to the serializer directly.

Also adds support for tracking brand type assignments from the main module to the dynamic module. This is necessary to decouple the execution order of both compilations which is a WIP.

Change-Id: I38dd0ed11811ea12a2230959588b261719976592
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/412461
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2025-03-07 16:45:35 -08:00
Ömer Sinan Ağacan 26f08dd036 [wasm_builder] Replace parts with libs
Parts make it difficult to see dependencies between files and which
files can be imported. Replace them with libs.

Change-Id: I0a07b48bc3c4a545a420f3efae23f61c4918bdcf
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/410400
Commit-Queue: Ömer Ağacan <omersa@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2025-02-18 02:35:11 -08:00
Nate Biggs a4a4ca8a41 [dart2wasm] Dynamic modules
Missing from this implementation:
- Closure/dynamic calls with differing signatures
- Overrides with extra optional parameters
- Records with same shape defined in different dynamic modules
- Avoiding running TFA on dynamic module.
- Recompilation of only updateable functions from main module.
- Persist wasm def types from main module.

Testing is currently done locally via the dynamic_modules package test suite:
dart pkg/dynamic_modules/test/runner/main.dart --runtime=dart2wasm

Immediately after this lands we can introduce a new step to one of the wasm test matrix configurations that runs the above test suite (the VM has a similar configuration).

Change-Id: I3386d84be11b773842d45f4268a62a54c47e352b
Tested: Tested via new tests in dynamic_modules package. Tests run locally but will add to existing config.
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/397721
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2025-02-11 13:59:46 -08:00