Commit Graph

1118 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 892e6a8b53 [dart2wasm] Pass on @pragma('wasm:prefer-inline') to wasm-opt
Now that we have a way to tell binaryen to inline functions (see [0],
which added support for `(@binaryen.inline <0...127>)` annotations) we
can tell it to inline functions that were annotated with
`@pragma('wasm:prefer-inline')`

Since binaryen sometimes can devirtualize call sites that TFA cannot, it
can then inline those devirtualized calls.

[0] https://github.com/WebAssembly/binaryen/issues/7972

Change-Id: I139bd43976a1ddb83afe756d4fbac09419f7199e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/510822
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2026-06-10 15:59:53 -07:00
Martin Kustermann 8e751fbc02 [dart2wasm] Remove deprecated mjs API functions
This is a re-land of [0] from 1.5 years ago.

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

Change-Id: I92747494f7c0d9dc43c09040ce06a523453994f3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/510823
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2026-06-10 15:52:03 -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
Martin Kustermann 034b90af5d [dart2wasm] Allow using experimental dart:ffi in deferred modules
The experimental `dart:ffi` support predates deferred loading
implementation. Any use of FFI features would lazily import `ffi.memory`
into the (fixed coded) main module.

This CL now lazily imports the `ffi.memory` module into all modules that
operate on the memory.

=> This will now allow a flutter app that is compiled with dart2wasm
   in deferred loading mode to possibly defer parts of flutter
   engine code that uses FFI into deferred modules.

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

Change-Id: Idf821ce2dd92c36ac1be9dae24307516a1ae86d9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/510760
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2026-06-10 02:34:44 -07:00
Nate Biggs 92ae8b61ec [dart2wasm] Fix switch statements that include a Type expression case.
'canInvokeTypeEquality' was assuming that if a single case was a Type expression, then type equality should be used for the whole switch statement. This is incorrect because if a single case is a type expression but the rest are, for example, ints (as in the fixed test) then the int cases should be compared with identical while only the type expression should be compared with ==.

More importantly, the expected type of the case expression constants should be top type rather than the Type type. Top type is the union of Type and the other case type.

Switches that only include Type expressions will now use "top" type as well but in general switching on a Type expression is an antipattern we shouldn't optimize for. And the impact of not specializing the switch type to Type should be very minimal.

Fixes: https://github.com/dart-lang/sdk/issues/63476
Change-Id: Ib96172f157d8cf3093199e5127d4835e7b25011c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/509920
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2026-06-09 20:39:04 -07:00
Martin Kustermann 9edafc75f7 [dart2wasm] Disallow inlining of static field initializers
Static field initializers are only run once and as such are cold code.
They should never be inlined into hot code that accesses the static
field.

Though right now `wasm-opt` will inline the static field initializers
e.g. if there's only one use of a static field (as it makes size a bit
smaller if we avoid the extra wasm function). That has the problem that
it may inline cold code into hot code. That hot code in return then gets
big and may not be allowed to be inlined into it's caller.

One can instruct binaryen to only inline functions with one caller up to
some size by passing `--one-caller-inline-max-function-size` (see [0]).
Though that can regress size.

So instead we specifically mark the function we know are cold to not be
inlined, namely static field initializer functions above a certain size.

This changes performance of a benchmark from [1] from
  cascadeUnhoisted: 390000 us
to
  cascadeUnhoisted: 138000 us

NOTE: This will cause some size regressions for e.g. e main module by
0.4-0.5% - but seems to be the right thing to do.

[0] https://github.com/WebAssembly/binaryen/issues/8619
[1] https://github.com/dart-lang/sdk/issues/63484

Change-Id: I626b8cd879a804aef638901ede190f74276d8e8b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/509960
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2026-06-08 12:21:42 -07:00
Kevin Moore 23a289417d [dart2wasm] Fix unsound type-argument check optimization on covariance checks
When casting a getter return value that requires a covariance check, the
CFE inserts an AsExpression where both the operand type and
tested-against type are statically identical (the instantiated member
return type, e.g. Callable<void Function(num)>).

The types.dart optimizer previously assumed that because the static
types matched, the runtime type arguments must also match, and optimized
away the type-argument checks. However, in a covariance check, the
dynamic value returned is a supertype (e.g. Callable<void
Function(int)>) due to class parameter covariance.

This change safely rewrites the static operand type of a covariance check
by preserving the InterfaceType structure but using calculateBounds to
rewrite its type arguments to their upper bounds (falling back to Object?
or Object for non-interface types). This allows us to keep class-check
optimizations active while remaining sound.

TEST=tests/language/covariant/callable_class_field_getter_test.dart
Fixes https://github.com/dart-lang/sdk/issues/53091

Change-Id: Ia64ea90b1bad2f7c1dab81cc3385103507b97b3e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/508425
Reviewed-by: Martin Kustermann <kustermann@google.com>
Auto-Submit: Kevin Moore <kevmoo@google.com>
Commit-Queue: Kevin Moore <kevmoo@google.com>
2026-06-07 21:51:07 -07:00
Martin Kustermann 56e0c9fca3 [dart2wasm] Exclude value types from deferred loading partitioning
This reduces e main module by around -0.4% uncompressed
and -0.5% compressed

Value types don't have identiy, they are compared by value. As such
there's no need to have a unique int/double box for the same value -
each module can have their own box: The box identity cannot observed.

That in return means also we avoid exporting those from main module &
importing into deferred modules.

Change-Id: Ib63949b59263b7381396323210b4218662f56525
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/509321
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Srujan Gaddam <srujzs@google.com>
2026-06-05 02:10:35 -07:00
Martin Kustermann e3cf529f87 [dart2wasm] More compact encoding of deferred load lists
Measured on size of e main module (baseline is we don't embed
it in application code):

* embedding before: +16.5% uncompressed / +9.1% compressed
* embedding with this CL: +4% uncompressed / +4.3% compressed

When embeddeding deferred load list information into the app
(as opposed to a separate json file) we now use a more compact
encoding.

Specifically: Instead of encoding it as an array of an array of
strings (which are module names), we encode it as an array of an
array of module ids and construct the module name from the id.

To make the array of module ids more compact we utilize the fact
that we can sort them and encode in delta encoding (i.e. instead
of absolute module ids, encode the diff between previous module
id in the list).

We put the encoded module id lists in a data section and create
`WasmArray<WasmI8>`s from them at startup. When we trigger a load
we then decode them into the list of module names.

There's more opportunity to optimize it, but it's good to do
this as a first step.

Change-Id: I293fb8879d992fc370786f6c9b258ccd27e1559b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/508980
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2026-06-05 00:50:46 -07:00
Martin Kustermann 3f7dd5c075 [dart2wasm] Do not use CFE lowering for constructor tear-offs
The CFE constructor tear-off lowerings introduce extra static methods.
Tear-off constants refer to them as `StaticTearOffConstant`s. They
just forward the call to the actual generative, factory or
redirecting factory constructors.

We can avoid these intermediary methods by not using the CFE lowerings.

Change-Id: I1dc1203d6b3023cf1f13bc204a2628ec8a3aebc4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/508562
Reviewed-by: Nate Biggs <natebiggs@google.com>
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2026-06-02 13:21:23 -07:00
Simon Binder 567bf3337b [dart2wasm] Document standalone platform and include it in SDK
Given that the standalone target for dart2wasm is feature-complete now,
it makes sense to include it in released SDKs.

This adds the platform and outline files to built SDKs and exposes the
`--standalone` flag in `dart compile wasm`.
It also documents the standalone target in `pkg/dart2wasm/doc`, which
should be helpful as a starting point to use these compiled modules.

Change-Id: I5bd86e9670f03f2955e31789095dd5c462bf149e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/506920
Reviewed-by: Slava Egorov <vegorov@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2026-06-02 00:56:35 -07:00
Hakim Jonas Ghoula f710c4338a [dart2wasm] Intrinsify math.min() and math.max() for double and int
Issue #55173.

Exposes signed min/max on WasmI64 and f64.min/f64.max on WasmF64 in
dart:_wasm, mirroring how WasmF64.sqrt is exposed today. The dart:math
min/max patches in math_patch.dart dispatch to them via runtime
`is`-checks, with @pragma('wasm:prefer-inline') so the inliner folds
the chain to the bare instruction sequence at each call site:

  T min<T extends num>(T a, T b) {
    if (a is int && b is int) return unsafeCast<T>((a as int).minS(b));
    if (a is double && b is double) return unsafeCast<T>((a as double).min(b));
    return _minSlow<T>(a, b);
  }

Wasm has no native i64 min_s/max_s, so WasmI64.minS/maxS emit the
same local.tee + i64.le_s/ge_s + select sequence. WasmF64.min/max emit
f64.min/f64.max directly.

The NaN- and signed-zero-aware fallback ladder is preserved in
`_minSlow` / `_maxSlow` (out-of-line, no pragma) and called for the
mixed and num cases. tests/lib/math/min_max_test.dart requires type
preservation between equal int and double arguments (e.g.
min(-499, -499.0) is int at line 113; max(499, 499.0) is int at
line 382), which a toDouble().max(toDouble()) fallback would not
satisfy.

Adds pkg/dart2wasm/test/ir_tests/math_min_max.dart covering min/max
for static int/int, double/double, mixed int/double, and num/num.
The .wat locks in f64.min/f64.max for the f64 paths, i64.le_s/i64.ge_s
+ select for the i64 paths, and `call $_minSlow` / `call $_maxSlow`
for mixed and num/num.

Measurements on a probe with four typed call sites (one each for
min<double>, max<double>, min<int>, max<int>, all marked
@pragma('wasm:never-inline')):

* .wasm size: 27,112 → 25,848 bytes (-4.66%). Generic $min and $max
  are eliminated by DCE.
* Runtime, 100M iterations per operation on d8, median of 10 runs:
  min<double> 543 → 213 ms (2.55x), max<double> 550 → 213 ms (2.58x),
  min<int>    552 →  65 ms (8.49x), max<int>    555 →  73 ms (7.61x).
  Checksums match between baseline and patched.

tests/lib/math/min_max_test.dart passes.

R=mkustermann@google.com, osa1@google.com

Change-Id: If8cf0a4df976f2d7f2230308905ff68491311c97
Bug: https://github.com/dart-lang/sdk/issues/55173
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/503740
Reviewed-by: Martin Kustermann <kustermann@google.com>
Reviewed-by: Kevin Moore <kevmoo@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2026-05-29 00:03:32 -07:00
Martin Kustermann 5423762c21 [dart2wasm] Fix unreachable instance call issue
When a call is unreachable we used to generate sometimes a block with no
outputs, but callers expected an output which they may drop. That caused
stack discipline mismatch.

Instead we should simply emit an unreachable and tell the caller there's
no value.

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

Change-Id: I81be6729fd578e237c4e6483539a9d53a9ed355e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/506960
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
2026-05-28 03:37:13 -07:00
Johnni Winther 6b693e00bc [kernel][Contexts] Add VariableDeclaration
This adds a VariableDeclaration node which is used in ForStatement instead of VariableStatement.

This is a step towards removing the initializer from Variable. Long term, VariableDeclaration will own the initializer expression for variables and function parameters will have a defaultValue property instead of using the initializer property for the default value.

TEST=existing

Change-Id: I4a663eeb6006a0f9f098fb2b3e3b502d2ae583b0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/505681
Reviewed-by: Stephen Adams <sra@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
Reviewed-by: Nicholas Shahan <nshahan@google.com>
2026-05-27 00:41:34 -07:00
Simon Binder 3196571c9b [dart2wasm] Move wasm-js interop to dart:_js_interop_wasm
Some features in `dart:_wasm` are relevant for all WebAssembly targets
(like interop types or memory intrinsics). Others, in particular the
helper methods to convert between `WasmExternRef` and `JSAny`, don't
make sense for the standalone target.

To be able to remove the `js_interop` library from the standalone
target, we need to stop importing it from `dart:_wasm`. To prepare that
step, this moves the helpers to a new library (`dart:_js_interop_wasm`)
and re-exports them from `dart:_wasm`.

Once Flutter has migrated to the new import, the export can be removed.

Bug: https://github.com/dart-lang/sdk/issues/63166
Change-Id: I13fe875e509a13d16c48d420a5927d3130b90736
Cq-Include-Trybots: luci.dart.try:flutter-analyze-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/505080
Reviewed-by: Martin Kustermann <kustermann@google.com>
Auto-Submit: Simon Binder <oss@simonbinder.eu>
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Slava Egorov <vegorov@google.com>
2026-05-22 04:06:27 -07:00
Johnni Winther d18803c5be [kernel][Contexts] Move initializer from VariableInitialization to Variable
This is a step back towards the end goal of separating Variable its initializer. This is done in order to normalize the encoding between the old and the new variable model, such that the split can be perform in both the old and new model simultaneously.

TEST=existing

Change-Id: I3ad6595613c06812d95dff3cabbca6eb05dc1c98
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/505000
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
2026-05-21 01:51:10 -07:00
Johnni Winther 80c9d57250 [kernel][Contexts] Rename VariableDeclaration to Variable
This is a step towards split variables from their declarations as part of encoding variables and their usage more precisely.

TEST=existing

Change-Id: I4a0eeb2947bdebce3667afda4e6cfbfdf5d7de18
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/504201
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Nicholas Shahan <nshahan@google.com>
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
SLSA-Policy-Verified: SLSA Policy Verification Service <devtools-gerritcodereview-exitgate@google.com>
2026-05-21 01:51:10 -07:00
Simon Binder ff25d85758 [dart2wasm, standalone] Rename JSStringImpl to EmbedderStringImpl
The standalone target used `JSStringImpl` as the name for its string
implementation even though JavaScript isn't involved in that at all.
This was to simplify parts of the compiler which can then refer to both
classes with the same name.

Changing this in the compiler is not that complicated however, so it
makes sense to align the string implementation name with the embedder
terminology we also use for other host imports.

TEST=pkg/dart2wasm/test/ir_tests/standalone.{dart,wat}

Change-Id: I1e112c8a72bb43a7edfa73ff7205d353edc7403a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/504581
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
2026-05-21 01:35:53 -07:00
Simon Binder 58f9d88fb2 [dart2wasm, standalone] Migrate String
This migrates the `String` implementation from using JS interop to
explicit host imports for the standalone target.

This moves a few helper methods shared between the JS and standalone
targets to `dart:_string_helper`. This also moves the embedder regexp
implementation to `dart:_string` to be able to access internals in some
string methods (similar to how the JS implementation special-cases
`JSSyntaxRegExp`).

This removes the final real use of JS-interop in the standalone target.
So, we can:

 - Remove internal JS helper libraries from the target.
 - Skip JS-interop transformations in the compiler.
 - Stop emitting a helper module and support script.

Because `js_interop` is imported in `dart:_wasm`, we can't remove the
library entirely. This replaces it with a stub to avoid compilation
errors, a proper removal is tracked in dartbug.com/63166.

Change-Id: Ide495c210c3a272438deebf8fe4f3f44ba314ffa
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/501960
Reviewed-by: Martin Kustermann <kustermann@google.com>
Reviewed-by: Kevin Moore <kevmoo@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2026-05-20 00:22:27 -07:00
Johnni Winther e30cd0322c [cfe][Contexts] Split VariableDeclaration and VariableStatement
This separates VariableDeclaration from Statement. VariableDeclaration no longer implements Statement and variable declared in a block or in a for-statement are now wrapped by a VariableStatement.

Currently there are two VariableStatement implementations; LegacyVariableStatement for variables in the current model, called LegacyVariable, and VariableInitialization for variables used in the new, still experimental, encoding that supports scope computation.

This CL is a step towards realigning the AST nodes to the new model in which each kind of variable has its own distinct subclass. (LocalVariable, PositionalParameter, NamedParameter, SyntheticVariable, etc.)

Note that it is not the intent to use VariableStatement in ForStatement going forward but that will be handled in a follow-up.

TEST=existing.

Change-Id: I5b309cd62c9b138f95b74fb054686edffa49a393
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/502681
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
Reviewed-by: Nicholas Shahan <nshahan@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
2026-05-18 05:49:28 -07:00
Martin Kustermann 147ed24717 [dart2wasm] Explicitly model functions that do not return
Similar to [0] which recognizes functions that return always nulls, we
recognize functions that never return and

* make the wasm function have no outputs
* make call sites emit `unreachable()` after the call (to inform
  binaryen & wasm runtime that this is unreachable)

Before we had an artificial construct where we made such functions
have a `w.RefType(HeapType.none, nullable: false)` return type (i.e.
bottom, i.e. no values) and encoded that way it's unreachable.

We also change some exported wasm functions to explicitly use wasm
types in the signature (namely `WasmVoid` instead of Dart `void`).

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

Change-Id: I3724e777cda23c0cf2c8a7dd2e473f3fef0a4f54
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/499240
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Srujan Gaddam <srujzs@google.com>
2026-05-13 00:23:45 -07:00
Martin Kustermann d0e0290702 [dart2wasm] Remove synthesizing values from nothing
When there's no expression on the stack but we expect something on the
stack, then the code should be unreachable.

Though the current code would just synthesize a value that matches
the expected type (`convertType(voidMarker, <some type>)`). This
is problematic: If we ever used that synthesized value we may
have incorrect program behavior.

Now there were some valid uses where we synthesize values

* A function that has `void` return type but no explicit return
  => Here we should synthesize `null`
* Synthesize `null` in cases where we know it's not going to be used
  => e.g. for CFE desugaring of `a[i] = b` is roughly
     `let tmp = b in (let ignored = a.[]=(tmp) in b)`
     where we synthesize `null` as `a.[]=(tmp)` result,
     `ignored` isn't used.
* ...

With this CL we no longer allow synthesizing a value of a type
out of thin air, instead all the places where this occurs have
to do that explicitly.

There's some impurities around how setters and index setters
are handled today (and even after this CL). Those impurities
start all the way at CFE, which treats setters and index
setters very differently. See the CFE issue [0].

For those we have two choices:

* special case all call sites that require synthesizing
  null values
* special case all call sites that require dropping an
  auto synthesized null value

This CL now marks instance setter/index-setter methods as
requiring auto-synthesizeing null values on usage sites and
make code that doesn't need them explicitly drop them.

Somewhat related to this change is how we deal with `void`
on the Dart <-> Wasm Import / Wasm Export boundary: When we
call an imported wasm function that has `void` as return
type (meaning no return values) we have to synthesize a `null`
(as the caller may "use"/"observe" the `void`).

=> We now are more strict and instead use `WasmVoid` as type
   instead of allowing `void` as type on the import/export
   functions.

[0] https://github.com/dart-lang/sdk/issues/63360

Change-Id: Ie30df3bd68553724437607bab3163c98f5467efe
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/501061
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2026-05-13 00:22:52 -07:00
Nate Biggs 63d042fe0d [dart2wasm] Fix dynamic dispatch table population for multiple modules.
When deferred loading was enabled the dynamic dispatch class ID table
was incorrectly assigning class IDs to the wrong module. If a contiguous
target segment included classes/targets from different modules, all of
them were getting assigned to the module of the first class/target in
that segment.

This was causing spurious NSM exceptions as the necessary rows in the
table might not be populated for a dynamic call if the module the
segment was assigned to wasn't loaded yet.

To fix this we end the segment if the next target does not belong to the
same module as the active segment.

The new test fails with an NSM exception prior to this fix.

Change-Id: I07bc4fdb5a8bff1bfad5fe17f45c8076a965a775
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/502860
Commit-Queue: Nate Biggs <natebiggs@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2026-05-12 12:54:38 -07:00
Nate Biggs c2a1b4452d [dart2wasm] Handle directory creation better when emitting compiler outputs.
Add checks to create enclosing directory before writing any files. If
the directory does not exist, these file creations will end up in an IO
exception.

Since dart2wasm might be writing many files to the same directory, keep
track of directories we know exist in memory to skip the extra file I/O.

Change-Id: If2d4a4973fe58f12c26d98578418420d964c3f84
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/502600
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2026-05-12 08:18:06 -07:00
Modestas Valauskas 754239b077 [core] Add trailingZeroBitCount and oneBitCount to int
Adds two new getters to int for bit-counting: trailingZeroBitCount
(ctz) and oneBitCount (popcount). On native platforms they operate
on the full 64-bit two's-complement representation; on the web they
operate on the least-significant 32 bits.

Implementations:
- VM: unified C++ natives Integer_trailingZeroBitCount /
  Integer_oneBitCount on _IntegerImplementation, using
  Utils::CountTrailingZeros64 and Utils::CountOneBits64. The receiver
  may be _Smi or _Mint at runtime.
- dart2js / DDC: clz32-based ctz and a SWAR popcount.
- dart2wasm: inlined i64.ctz and i64.popcnt intrinsics.

leadingZeroBitCount (clz) is intentionally excluded from this CL: its
result depends on the platform integer width (e.g. 1.leadingZeroBitCount
is 31 on web, 63 on native), and the same value can be derived from
the existing bitLength getter when needed.

Asm intrinsification on native architectures is intentionally left for
a separate follow-up CL.

Work towards https://github.com/dart-lang/sdk/issues/6486 (this CL
covers popcount and ctz from the bit-twiddling list; clz, rotate,
reverse, and others remain).

Work towards https://github.com/dart-lang/sdk/issues/1053 (efficient
BitSet implementation).

Bug: https://github.com/dart-lang/sdk/issues/52673
Bug: https://github.com/dart-lang/sdk/issues/38346
TEST=tests/corelib/int_bit_count_test

Change-Id: I8a5cdb5c91360478f47bbd6b9c84ca1c477aa8c7
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/498041
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Slava Egorov <vegorov@google.com>
Reviewed-by: Stephen Adams <sra@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Auto-Submit: Modestas Valauskas <valauskasmodestas@gmail.com>
Reviewed-by: Lasse Nielsen <lrn@google.com>
2026-05-12 05:49:31 -07:00
Martin Kustermann 96d8359e32 [dart2wasm] Cleanup closure related things
Move the creation of

* name of a lambda to `translator.functions.getLambdaFunctionName`
* type of a lambda to `translator.functions.getLambdaFunctionType`

Removes dependency on `w.FunctionBuilder` in the lambda code generators
- as they don't need access to the wasm function. This may allow
inlining closure calls in the future.

Avoid creating `w.FunctionBuilder` eagerly when analyzing closures and
instead create it only when there's a call to it (or the closure object
gets instantiated).

Use `CallTarget` abstraction when invoking lambdas.

Avoid carrying around
`(lambda, enclosingMember, enclosingMemberClosures)` throughout
the codebase and instead store this information on `Lambda`.

=> All these changes make the codebase more uniform between
   lambdas and normal functions and also is net code removal.

Change-Id: Ib69566ea9580827be0ed52c0c179884e84599d88
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/501983
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Srujan Gaddam <srujzs@google.com>
2026-05-11 01:48:14 -07:00
kevmoo b4f88f537b [dart2wasm] Fix spelling mistakes in the output mjs template
Fixes https://github.com/dart-lang/sdk/issues/63357

Change-Id: I8dec270e80616950ddb36ee3a1ac8b60798247c4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/502440
Reviewed-by: Nate Biggs <natebiggs@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
Auto-Submit: Kevin Moore <kevmoo@google.com>
2026-05-10 17:02:12 -07:00
Nate Biggs f31923beee [dart2wasm] Fix async* stream not forwarding errors to listener cancel.
Set up a cancellation Completer that we forward errors to if the stream
has been cancelled.

Also add `isDone` which will avoid forwarding registering the
cancellation logic if the stream has already finished.

Fixes test
co19/Language/Expressions/Function_Invocation/async_generator_invokation_t10.dart
on wasm targets. This test already passes for all other backends.

Fixes: https://github.com/dart-lang/sdk/issues/63123
Change-Id: I8a3d87c3ea7b4ebb3a7b82ab064fb57034aa6f4a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/495200
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2026-05-08 15:39:19 -07:00
Martin Kustermann 28f200b97d [dart2wasm] Calculate element stride for class id table and target table separately
It reduces e main module by around -0.3% compressed/uncompressed.

The dynamic calls use 2 tables. One of them stores class ids and one
stores targets. We can compute the table initialization for these two
tables independently.

The targets table may contain different targets but the class ids may
still be consecutive and therefore allow a larger stride which may move
initialization from element section to a loop in the #start function.

Change-Id: Ief5415f035ce1b854aecaa635a5e916938217b8e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/501420
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Srujan Gaddam <srujzs@google.com>
2026-05-08 00:46:52 -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
Nate Biggs 0af31e37bb [dart2wasm] Fix context collection emptiness false positive.
In some cases, closure contexts were being left out of the parent chain
of their children because they were empty at the time the child closure
was created. If a usage appeared later in the visit of the parent, the
context would no longer be empty but the child would already be created
without a parent.

This was easiest to recreate in sync* function because unlike async, it
doesn't introduce hoisted helper variables (these immediately mark the
parent as non-empty).

In the attached bug the repro only happens with named parameters because
TFA transforms the named parameter into a Let that introduces a variable
before the closure with the usage in the let body after the closure. The
new test explicitly introduces the same pattern of a variable declared
before the closure and used after it.

The fix here is to not eagerly check for emptiness of the parents.
Instead we post-process the Contexts and relink the parent tree skipping
any empty nodes.

Bug: https://github.com/dart-lang/sdk/issues/63264
Change-Id: I2f75506b9fa879544b1a606d8f157fbd44ba8ce2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/500680
Commit-Queue: Nate Biggs <natebiggs@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2026-05-07 09:21:19 -07:00
Martin Kustermann 0f9ce46c6d [dart2wasm] Ensure the field in BoxedBool is non mutable
Change-Id: Idfa251e647f9d0205086d17334103a511366df7a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/501460
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
2026-05-07 04:05:45 -07:00
Martin Kustermann a3deacfd58 [dart2wasm] Remove unused annotations and field
Change-Id: I83a9bd6a8aedb5312381b975c49e287ee76bc11b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/501141
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2026-05-07 03:46:02 -07:00
Simon Binder 9514710249 [dart2wasm,standalone] Migrate everything except String
This migrates remaining dart2wasm patches to avoid js-interop outside
of passing strings.

Change-Id: I92a9bb6cb97305a51858901d27966c3bd0af8fc6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/491540
Reviewed-by: Martin Kustermann <kustermann@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2026-05-06 06:41:53 -07:00
Martin Kustermann b10062cbbf [dart2wasm] Synthesize null when calling JS functions without result, Remove inlining in js interop transformer
When we call out to JS and the function type is declared as `void` we
don't observe the result from JS. That means we can import the JS
function as a wasm function type without results.

We then synthesize a `null` value, in case a caller of the external JS
function uses/observes the `void` result value.

=> This CL does the same for `JS<void>(...)` calls.

We also remove the "inline" replacement optimization in the js interop
transformer. That makes all `JS<...>()` calls to be handled in a uniform
way.

=> Our backend's inliner should inline it iff beneficial for size.

Change-Id: I78032aae0dcdcaaf52701c628cea71af1de0d5e6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/499880
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Srujan Gaddam <srujzs@google.com>
2026-05-05 06:55:07 -07:00
Martin Kustermann 6f45aa5329 [dart2wasm] Address comments from element section CL
Addresses comments from https://dart-review.googlesource.com/c/sdk/+/499560

Change-Id: I7207420441151436be51ab6dc636f8cb7aac409a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/499840
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Srujan Gaddam <srujzs@google.com>
2026-05-01 11:28:24 -07:00
Martin Kustermann 95c3cab03b [dart2wasm] Reuse dispatch table entries for static calls
This has slight e main size improvements (-0.1% uncompressed, -0.2%
compressed)

Significant portion of instance calls can be devirtualized. Doing so
will make us issue direct calls to the target. But there may also be
non-devirtualized calls to the selector, so the target may be in the
dispatch table.

If we can issue a direct call to the target but the target is in a
different module we have to go via a wasm table. Currently we use an
extra wasm table for such static calls across modules.

=> If the function we want to call is already in the dispatch table we
   may call it via the existing slot instead of having to add it to
   another table as well.
=> This shrinks the size of the static wasm table.

Change-Id: Icc594b9490f2f0573190ea5a3a21b63b3ddb388a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/499580
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2026-05-01 00:02:45 -07:00
Martin Kustermann 3662682cd0 [dart2wasm] Significantly reduce size of element section
This gives in e main module

  * 2.7% size reduction uncompressed
  * 1.7% size reduction compressed

The element section goes from 300 kb to 175 kb and #init function also
shrinks in size.

We perform two optimizations

* If the dispatch table contains `null` entries, then they are
  unreachable. That means if we have long strides of the same target and
  possibly gaps in-between, we can use one `table.fill` instruction to
  initialize the entire segment. Entries that are supposed to be `null`
  are then occupied but unused.

* If the dispatch table entries of the main module contain gaps but are
  not `null` but will be filled in later by a deferred module, we can
  also use a `table.fill` with large area. The entries that are then
  occupied, but should be `null` until deferred module initializes it.
  That's not a problem because they will be unused until the deferred
  module loads, and when it does it will override those (incorrect)
  entries with valid ones.

Change-Id: I356faff4204d04dfe42cbd1282f23090229a8503
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/499560
Reviewed-by: Srujan Gaddam <srujzs@google.com>
2026-05-01 00:02:45 -07:00
Nate Biggs 9f33810b01 [dart2wasm] Separate deferred map json creation from use of load ids.
Currently the 'load-ids' controls both how deferred modules are loaded and whether or not the compiler emits a deferred map JSON file.

This change separates these 2 actions into 2 separate flags. 'deferred-map' now provides the URI for the JSON file. And 'use-load-ids' controls how deferred modules are loaded.

This is needed for some tools that might not want to use load ids but still need to process the deferred map file.

Change-Id: I6909307f058d8f97dd8987e421f693ee97d6572f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/499520
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2026-04-30 09:10:58 -07:00
Simon Binder fe8f4cd369 [dart2wasm, standalone] Port dart:core patches except String
This replaces js-interop and `JS(...)` usages in patches for
`dart:core` in the dart2wasm standalone target with explicit host
imports.

This still uses JS strings as a string implementation, so js-interop
from `dart:core` hasn't been removed completely. Migrating strings will
require additional changes - mainly to `dart:js_interop` itself, which
we want to remove from the standalone target anyway. So, I believe it
makes sense to migrate strings last.

In most cases, these imports match the manual JavaScript we've used
before. `StringBuffer`s are an exception here, the default platform
implements them via string concatenation but some embedders might
benefit from explicit string buffers.

TEST=tests/corelib/**

Change-Id: I1ea18ac30bac24b30e528b2c28d925fda886c988
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/491480
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
2026-04-30 03:46:55 -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 ae33e9c12c [dart2wasm] Filter out user-provided program constraints
When users provide program constraints, some of the library import
prefixes will be unreachable. Though the core partitioning algorithm in
dart2wasm will only work on imports sets of alive / reachable prefixes.

=> Make us filter out unreachable library prefixes before processing
   user-provided program constraints.
=> Add a check that those we filter out are still valid library prefixes
   from the Kernel AST.

Change-Id: Id55064ee03e682a04d5e9117e466ac9573e66a39
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/498901
Reviewed-by: Nate Biggs <natebiggs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2026-04-29 02:26:33 -07:00
Nate Biggs 6ebcada9ff [dart2wasm] Fix loading unit lookup for top-level constants.
Change-Id: Ia7de84a88efbc53f170ec156046a892310399f58
Tested: Added function_invocation.dart test
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/498380
Commit-Queue: Nate Biggs <natebiggs@google.com>
Reviewed-by: Daco Harkes <dacoharkes@google.com>
2026-04-28 07:36:44 -07:00
Martin Kustermann bb52def063 [dart2wasm] Cleanup a bit more code after dynamic module support was removed
Change-Id: Ic9ffcda3f8a15f06780e78accd29d4e3616e214d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/498560
Reviewed-by: Nate Biggs <natebiggs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2026-04-28 00:20:21 -07:00
Martin Kustermann b8b0cad67a [dart2wasm] Make most void functions have no return values
This gives around 0.2% improvement in compressed e main module.

In Dart a function with `void` return type can actually return values
that callers can observe. But most of the time this doesn't happen, most
times those functions return `null` values and callers don't observe
them.

Let's use inferred return value information to see if a function is
guaranteed to only return `null`. If so we make the wasm function
signature not return any values. Callers will then synthesize a `null`
which may immediatly be dropped or (in rare cases) actually be used.

This leads to less less instructions in the callee (as a callee doesn't
need to push the null onto the stack) and the caller (as the caller
doesn't have to drop it from the stack).

Change-Id: I3ed1be7592798ad0c697c5bc3ab2c4b64c156f03
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/497620
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Srujan Gaddam <srujzs@google.com>
2026-04-28 00:20:02 -07:00
Nate Biggs 761ece74b6 [dart2wasm] Remove assertion that named param Symbol cannot be private.
Though named parameters cannot be private (this is checked by the
CFE/analyzer) it is not a static error to specify a named argument
beginning with "_". Instead this should be a runtime error (resulting in
an NSM since the caller signature won't match the target callee's).

We could move this assert to the call sites but there are several that
would need to have this check. And the CFE should be validating this
anyway so it should be safe/simplest to just remove this assert.

Fixes: https://github.com/dart-lang/sdk/issues/63201
Change-Id: I511b56c7a3b0964ef3db744a75b574dd9551e6ab
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/498340
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2026-04-27 19:12:03 -07:00
Nate Biggs 3d2d6492c1 Add 'external-effect' pragma support to all the backends.
Call sites targeting a procedure annotated with `external-effect` will
not produce any code, including the argument which will not be
evaluated.

However, the single parameter will be treated as 'live' for the purposes
of any global analysis the backends do. This is useful for things like
protobuf shaking where a user may want to retain certain protobuf
messages without actually emitting the code that retains those messages.

Today this functionality is available internally in the vm and wasm SDK
libraries. dart2js has similar functionality represented via the
opaqueTrue and opaqueFalse booleans (which will cause conditional
branches to get shaken after analysis). This will replace dart2js's
opaque(True/False).

This also adds validation to the frontend to ensure a method annotated
with 'external-effect' is well-formed.

Change-Id: If1c4096673e655c58fe7638840a16125003e7809
Tested: Backend tests for codegen were added. A frontend test was added for the validation. A language test was added to confirm the behavior.
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/476020
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2026-04-27 09:42:38 -07:00
Nate Biggs d43d9df1a7 [dart2wasm] Remove dynamic modules support from the dart2wasm compiler.
Change-Id: If92f55296dfe83b64165a2bd07eaefb7d137198c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/497341
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2026-04-24 12:04:23 -07:00
Simon Binder 03fd5927a4 [dart2wasm, standalone]: Avoid importing js-string constants
dart2wasm imports strings as globals for which JavaScript engines would
provide the respective values. The standalone target needs to support
all WebAssembly runtimes, so it can't rely on this mechanism.

Instead, this imports functions to convert a WebAssembly arrays of char
codes or ASCII bytes into a string. For now, these functions have to
return JS strings since the rest of the SDK relies on that. In the
future, embedders would be able to return any string implementation as
an externref.

Because calling host functions is invalid in constant contexts, string
constants can't be regular globals. For now, this uses the default
non-eager constant implementation with one initialization function per
string constant. Eventually, we should probably initialize these
strings in a WASM start function instead.

TEST=pkg/dart2wasm/test/standalone_test.dart
Change-Id: I93b7c3846fbe99daa8ffa31e452f62672b61ce4b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/495020
Reviewed-by: Nate Biggs <natebiggs@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2026-04-24 10:49:11 -07:00