Commit Graph

219 Commits

Author SHA1 Message Date
Martin Kustermann 94a36d59ff [dart2wasm] Mark Closure.context as final
We also use the same variable to represent the type of the
context slot instead of repeating that type in several places.

Change-Id: Ia4d10db4781dfd78617d40279d37335eedeb7408
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/430760
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-05-23 06:02:14 -07:00
Martin Kustermann 26da655ddf [dart2wasm] Remove duplicate code to call references
Change-Id: I304729f6ed19a75de16ddced15b3f47f894e9c49
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/430740
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-05-23 05:38:08 -07:00
Martin Kustermann 3c10662004 [dart2wasm] Remove special casing of noSuchMethod in dispatch table builder
So far we force-included the `noSuchMethod` selector in the dispatch
table, making us have an entry for each class id (i.e. a dense row).

This is wasteful for most cases, because there may be few (if any)
overrides of `noSuchMethod`.

So instead we use the same policy on how to call `noSuchMethod` as
with all the other selectors, namely if there's only a single target
we call it directly, if there's a few we may use a static polymorphic
dispatcher function and otherwise use dispatch table.

Change-Id: I5067fb54908d905396c3b93b824bf251dc73ff50
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/428521
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-05-15 04:01:39 -07:00
Martin Kustermann f7250266df [dart2wasm] Make dynamic call forwarders use normal CallTarget infrastructure
So far running the compiler with `--print-wasm` wouldn't print
the code for dynamic call forwarders as it doesn't use the same
compiler infrastructure as normal function compilations.

This CL makes the dynamic call forwarders be `CallTarget`s that
can be called and if so will enqueue a `CompilationTask` in the
compilation queue.

This will ensure we treat those forwarders as any other target
we may call, which will also make e.g. `--print-wasm` work.

Change-Id: Ieb5673befa1456e941276d538e2212ff8d4077fc
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/428700
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-05-15 03:41:20 -07:00
Mayank Patke c56a8be4e5 [dart2wasm] Replace "dynamic module" with "submodule" where appropriate.
Change-Id: I4e51e10928ccc26c18f2a63f6cb6b23cd618d853
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/426003
Commit-Queue: Mayank Patke <fishythefish@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
Reviewed-by: Nate Biggs <natebiggs@google.com>
2025-05-02 10:15:40 -07:00
Nate Biggs 0e040b80fd [dart2wasm] Use dynamic invocations to call closures when dynamic modules are enabled.
Running with dynamic modules means that closure invocation shapes cannot be statically known. A closure can flow between any modules and can then be invoked with a shape that's unknown to the module that defines the closure.

Given this, our 2 options are to generate code for every invocation shape or invoke the closure as if we don't know it's shape (dynamic invocation).

The former would scale exponentially relative to the number of named parameters so is not practical.

The latter generates slower, bigger code but is the more practical of the 2.

Change-Id: I5e3613d23662e5b2213fdaa725642678fe26e43a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/421920
Reviewed-by: Martin Kustermann <kustermann@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
2025-04-28 17:51:59 -07:00
Martin Kustermann 302885c467 [dart2wasm] Smaller as T type checks due to less inlining
Currently we are quite agressive when inlining `as T` type checks. This
is due to a number of `@pragma('wasm:prefer-inline')` annotations
combined with `@pragma('wasm:static-dispatch')` annotations causing us
to generate polymorphic dispatcher functions for calls to
`_Type._checkInstance`, combined with the polymorphic call target force
inlining targets with <= 2 specializations.

These combination of factors lead to a `x as T` to become something like

    <... code for checking x & T's nullability ...>
    classId = x.classId;
    if classId = ClassId.getClassId(_InterfaceClass)
      ...
    else
      ...

This lead to binaryen sometimes infer the `x.classId` value to be a
constant which prunes the branches which then calls the faster path for
interface type checks.

Though this is quite a lot of code size. So instead of inlining all
these things, but still taking advantage of the binaryen global
optimizations that may infer `x.classId` we load the class id (which
binaryen may sometimes turn into a constant) and then pass it to the
polymorphic dispatcher (which we no longer inline to safe code size).

This way if the class id is a constant, either binaryen or V8 will see
that it can inline the polymorphic dispatcher as most of its body
disappears if the class id is known.

Since we no longer inline the polyhmorphic dispatcher, we can now also
mark other common types via `@pragma('wasm:static-dispatch')` - such as
`_RecordType._checkInstance`. This in return will speed up any code that
uses records in collections (e.g. in maps / sets / lists) as the
covariance checks now involve loading class id and branching on it to a
devirtualized `_RecordType._checkInstance` instead of an indirect call
that also involves a function type check).

We also remove the `@pragma('wasm:prefer-inline')` on the
`_checkSubclassRelationshipViaTable` function: The idea was that if
binaryen infers the load of class id most of the code that follows can
be optimized away at compile time. Unfortunately the tables can get
large, which made us not use `ImmutableWasmArray` but instead normal
`WasmArray`. That in return makes binaryen unable to optimize loads from
it (at constant index) away, as the contents of the array may change
(they never do, but binaryen doesn't know that). So there's little
benefit in inlining it.

Change-Id: I416fbdd35c6425a626378f2e9ea2009e50bf600b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/420320
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-04-10 06:01:48 -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 Ağacan 93df675c5e [dart2wasm] Implement a workaround for front-end bug #60375
Issue: https://github.com/dart-lang/sdk/issues/60375
Change-Id: I6441c8b0c3cf94c9b33facc73ccf7bf8bba111d7
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/419160
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
2025-03-31 05:48:31 -07:00
Martin Kustermann b8d25b791a [dart2wasm] Make slow path call StackTrace.current on null errors
For throwing normal exceptions we already outlined obtaining
the current stack. We can do this for null check errors as well.

Change-Id: Ie0505bfd99ff9c6738040c47b07e542439ee74a5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/419161
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-03-31 04:10:39 -07:00
Nate Biggs bd7f26f54d [dart2wasm] Allow dynamic modules to run without re-compiling or running TFA on main module.
Accomplishes this by serializing more metadata in the main module metadata:
1) Most of this new metadata is to have access to the main module's dispatch table from the dynamic modules, including ProcedureMetadataAttributes and the DispatchTable itself.
2) Indices to create correct calling names from the dynamic module into the main module (or into the global updateable functions "table").
3) Basic tree-shaking information about classes (e.g. did TFA fully delete a member or just delete its body).

Some metadata from TFA is still expected throughout the compiler. For dynamic modules, we create pessimistic versions of this information and attach it to the new Component.

All the same dynamic module tests that were passing (or failing) before are still in the same state. This significantly speeds up compilation of dynamic modules though as only necessary code is compiled and TFA is not run.

Change-Id: I109f53cf5dcbe6579c0f78e71ce7779d593455e9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/415500
Reviewed-by: Martin Kustermann <kustermann@google.com>
2025-03-21 20:53:53 -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
Martin Kustermann 0b6df4d3f1 [dart2wasm] Utilize true/false singleton property in identical() implementation
We maintain two singleton boxed bool instances for representing `true`
and `false`. So the code for `identical()` no longer has to treat them
as value classes where it has to unbox and compare the WasmI32.

Filed [0] for CFE to improve precision of `ConstantExpression.getStaticType()`.

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

Change-Id: I8c02191e974aae9e168b4a826db9a87362949516
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/416941
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
2025-03-20 07:46:59 -07:00
Martin Kustermann d66befd347 [dart2wasm] Add support for @pragma('wasm:weak-export', '<name>')
We explicitly & unconditionally export functions to JS via the
`@pragma('wasm:export', '<name>')` annotation.

This is mainly useful for external APIs that the JavaScript side can
invoke - for example `$invokeMain()`.

Though we currently use the same mechanism also in other places where a
Dart function `A` (if used) calls to JS which calls back into Dart via
calling exported Dart function `A*`.

The issue is that this mechanism doesn't work very well with tree
shaking: If the function `A` is not used it will be tree shaken. We will
then also not emit the JS code, but we still compile the exported
function `A*` as it's a root due to `@pragma('wasm:export', '<name')`. We then also have to compile everything reachable from `A*`.

This is the case for a few functions in the core libraries but even more
pronounced in code that the modular JS interop transformer generates for
callbacks: It generates `|_<num>` functions that call out to JS which
call back into a dart-exported `_<num>` function. The former may be
unused & tree shaken (as well as their JS code) but the ladder are
force-exported and therefore treated as entrypoints.

This CL solves problem by

  * Mark function `A*` as weakly exported via
    `@pragma('wasm:weak-export', '<name>')`

    => TFA will not consider such functions as entrypoints
    => TFA will only retain such functions if they are referenced by
       other functions that aren't tree shaken.
    => The backend will export such functions as `<name>` if they are
       referenced by any other code that's compiled.

  * Making the code that calls function `A` also reference (but
    not use) `A*`.

    => This will make TFA retain function `A*` if it retains `A`.
    => In core libraries we manually reference `A*` in code that uses
    `A` and mark `A*` as weakly exported
    => In JS interop transformer we emit similar code for callbacks
    => We refer `A*` by using `exportWasmFunction()` which is an opaque
       external function that prevents TFA and backend from optimizing
       it away - so the function will be generated & exported.

Overall this CL ensures we only keep the exported functions if we
actually need them (i.e. we call to JS and JS calls those exported
functions).

This shrinks stripped dart2wasm hello world file in -O4 from
28 KB to 12 KB.

It also enables tree shaking of callback using JS interop code.

Change-Id: Ie81eac49cbcb574d569ea95a90538e8f417e2a12
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/415220
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-03-14 01:31:10 -07:00
Martin Kustermann f4b4e5e297 [dart2wasm] Fix incorrect setup of checked entrypoints
A method may be overriden multiple times, the overriden methods may add
optional parameters. Dart2wasm generates a signature for such selectors
that includes all optional parameters from all overriden methods.

That means
* callers may pass more arguments than the signature of the target
  => it passes dummy values
* implementations get more parameters passed than their Dart function
  => it ignores them

The checked/unchecked entry functions need to also support this
mechanism.

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

Change-Id: Ifb60c44d3c5e5b6e374a15731723e16d22bfc094
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/414720
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-03-11 03:57:09 -07:00
Martin Kustermann 20fc102287 [dart2wasm] Switch to only using JS strings
Currently we have 3 different string types (JS Strings, OneByteString
and TwoByteString)s. There's some advantages to this, mainly that
if strings are used purely inside Dart we have more control over
optimizing them. But it does come with some issues

* Operations on mixture of strings are slow
* We get JS strings from outside (in DevTools e.g. websocket messages)
* Any kind of DOM interaction requires copying strings
* Regular expression matches can result in O(N*N) instead of O(N)
* Encoding of string literals/constants is terrible, high size overhead
* ...

Now that there's a standardized way to access JS strings (via
the `js-string` builtin spec) and this standard is finalized and
enabled in Chrome & Firefox it makes sense for us to switch to it.

It reduces app size:

* Smaller size: hello world -25%, flute -5.5%
* Faster startup

The performance changes are nuanced, some workloads will improve
significantly, some workloads will regress.

Improvements will come especially in cases where
strings are concatenated (due to JS not actually allocating new
strings in this case). That impacts e.g. string interpolations,
string buffer, json-to-string encoding, ...

Regressions will come especially for cases where we have to
construct strings from bytes (e.g. in utf8 decoder, utf8+json
decoder) - mainly due to having to go through an intermediary
`WasmArray<WasmI16>` to allocate strings. Also in cases where we
access individual char codes from the strings.

There's some follow-up improvements we can do, but it's better to
not iterate on this CL even longer but get it landed.

This CL will make the benchmarking system use
`--require-js-string-builtin` as well as most of test CI
(in `pkg/dart2wasm/tool/compile_benchmark`)

Though we run some configurations via overriding with
`--no-require-js-string-builtin`
(in `tools/bots/test_matrix.json`)

Issue https://github.com/flutter/flutter/issues/159400#issuecomment-2538593980
Issue https://github.com/dart-lang/sdk/issues/59699

TEST=ci

Change-Id: I238ac65efe092de569da870f23134f889ac929f9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/392903
Reviewed-by: Slava Egorov <vegorov@google.com>
Reviewed-by: Lasse Nielsen <lrn@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-02-27 00:33:34 -08:00
Martin Kustermann 997066e2cd [dart2wasm] Avoid lazy initialization checks for certain performance critical globals
The Dart language semantics requires global / static fields to be lazily
initialized on first access.

Though if the initializer expression does not have any side-effects, an
implementation may take the liberty to initialize a global earlier. The
downside of that is that if the initializer (despite being side-effect
free) is costly, the startup cost may suffer.

We introduce a `@pragma('wasm:initialize-at-startup')` that allows us to
explicitly opt-into running a global field's initializer at starutp.

This will mean we don't have to pay the lazy-initialization cost at
access time anymore. So when we before did this:
```
  block X
    global.get GX
    br_on_non_null
    call initializer
  end
```
we now do this:
```
  global.get GX
```
we also get rid of the initializer function.

We start to make use of this pragma in 3 places:

  * hash map code that checks for deleted marker
  * double to string cache
  * string interning cache

Change-Id: I172ecda33fad8fab1a02b48b16784f8a9c89d205
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/410340
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-02-18 13:06:43 -08:00
Ömer Sinan Ağacan 5da354f46e [dart2wasm] Don't compile catch blocks multiple times for JS exceptions
Currently when a Dart `catch` block can catch both Dart and JS
exceptions, we compile the `catch` body once in a Wasm `catch` block,
once in a Wasm `catch_all` block.

This is because a Dart exception needs to be caught in a `catch` with
the right tag, to be able to get the exception and stack trace values,
and JS exceptions need to be caught in `catch_all` and they come without
error values and stack traces.

With this CL we generate one Wasm block per Dart `catch` block. Wasm
`catch` and `catch_all` blocks only do type tests and jump to the right
Wasm `block` when a type test passes.

This allows using the same block for multiple Wasm `catch` and
`catch_all` blocks.

When jumping to the block for a Dart `catch` we pass the error value and
stack trace to the block. As before, when the caught exception is a JS
exception, we pass an empty `JavaScriptError` as the error value and the
call stack of the Dart `catch` as the stack trace.

We also replace Wasm `rethrow` instruction with `throw` when compiling
Dart `rethrow` statements. This change is necessary as the blocks for
Dart `catch` blocks are no longer enclosed by a Wasm `try`, and it also
makes it easier to switch to the new exception handling proposal, which
doesn't have a `rethrow` instruction.

This changes Wasm exceptions reported to the console in uncaught
exceptions, but when we switch to the new exception handling
instructions we will recover the stack traces, as `throw_ref` doesn't
update the stack trace of the error value.

Change-Id: I732c0192af158611d5f0a584182a48b0e13ff83a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/410321
Commit-Queue: Ömer Ağacan <omersa@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2025-02-18 02:40:17 -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
Martin Kustermann c833c1227c [dart2wasm] Add support for dispatch table calls to unchecked entries
Now that we have support for generating checked & unchecked entrypoints,
we can make dispatch tables also target unchecked entry points.

This is beneficial especially in cases where there's dispatches on
`this` that require covariant checks but we don't know the target method (i.e. we cannot devirtualize it because the method we
dispatch to may beoverriden).

We keep the existing selectors that we have, but a selector will now
have

  * one row if none of the implementations of the selector need to
    perform type checks
    => `SelectorInfo` has a `SelectorTargets _normal`

  * two rows if any of the implementations of the selector need t
    perform
    a type check
    => `SelectorInfo` has a `SelectorTargets _checked`
    => `SelectorInfo` has a `SelectorTargets _unchecked`

Once an unchecked entrypoint is also used in the dispatch table (only
if there's any unchecked calls to that selector) then binaryen can no
longer optimize the signature of the function. It means we may have
perform e.g. downcasts / boxing in the unchecked entry where we
wouldn't do before (because we only had static calls to unchecked entry
before this PR).

So we're going to force-inline calls to unchecked entrypoints. This
avoids sometimes down casts and boxing. It also seems to actually
shrink the binary size.

Change-Id: I3ba4980c42886cc883fb610533f5fac9cce39b65
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/407740
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-02-05 04:57:00 -08:00
Martin Kustermann f05ae1c3af [dart2wasm] Introduce unchecked entrypoints to instance members
If an instance member is a normal method or a setter, it has arguments
that may need to be type-checked. In some situations we know that we
don't have to perform them at all (e.g. if we know there's only
dispatches on `this`). In other situations we have guarantees on
individual call sites that we can skip the checks.

Up until now we have only taken advantage of the call site guarantees
when we decided to inline the target (then we avoided doing the type
checks).

In this CL we will make this also work if the target isn't inlined, but
called: Whenever a member has any parameters that we need to type check,
then we will generate checked & unchecked entrypoints. Both of them
do the optional parameter handling, but only the checked entrypoint will
perform the type checks, the unchecked entrypoint skips them. Both
unchecked & checked will call to a body function that has the body of
the member.

=> We will skip the type checks whether we inline the target or not.

The dart2wasm compiler currently represents targets it can call via
via `Reference`s: A member may be used in different ways: as a tear-off,
as a setter, type checker, etc.

=> We introduce now 3 more `Reference` kinds, namely checked, unchecked
and body.
=> The rest of the compiler is adjusted to also handle those new
`Reference` types.

All calls in the code generator that may target members that could have
checked and unchecked entrypoints now use
```
  Reference getFunctionEntry(Reference target, {required bool uncheckedEntry})
```

We maintain an invariant throughout the code base that a function

* **either** has only one "normal" entry if no arguments need type checks
* **or** has "unchecked" and "checked" entries (which both call a "body")

The dispatch table currently has only "normal" or "checked" entries in
it. So the "unchecked" entries are (if used) always called directly.

=> We only generate "unchecked" if there's direct unchecked calls.
=> We only generate "checked" if there's direct checked calls or
   dispatch table calls.
=> If only one entrypoint ends up being generated, binaryen can inline
   the body into the entrypoint function.
=> If both entrypoints are present, binaryen may often inline the
   unchecked one into call sites that then directly call the body.

In a future CL we may allow calling unchecked entries also via the
dispatch table.

Overall this approach leads to minimal changes to code size changes, but
brings -O2 performance closer to -O4.

Change-Id: Ic3082cc397335b969fd413f72652cc5af753adf7
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/406980
Reviewed-by: Ömer Ağacan <omersa@google.com>
2025-02-03 00:08:26 -08:00
Martin Kustermann 74aa17dd3e [dart2wasm] Emit unreachable instructions after calls that never return
This allows wasm optimizers as well as wasm runtimes to optimize code
better as they know calls to slow paths that throw will never return.


Change-Id: Iace1827062dbe00ce24c737b6369bf588e748ee9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/404582
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-01-21 06:02:22 -08:00
Nate Biggs d80fab4a8a [dart2wasm] Fix dynamic switch casts.
If the switch's expression has type 'dynamic' and all the case expressions have the same type, we compare them using '=='. This requires a cast to ensure all the types match for the dispatch to the '==' function. However, we don't check that the type of the switch expression matches the type of the case expressions. So the cast fails if they don't match.

This adds a guard to ensure the types match before running through the case expressions. If the guard fails, we either jump to the default case or if there isn't one, we skip the switch entirely.

Fixes: https://github.com/dart-lang/sdk/issues/59782
Change-Id: I12e81f98d1c2046ee47e8ca4371642fd40620636
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/402460
Commit-Queue: Nate Biggs <natebiggs@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2025-01-07 11:44:30 -08:00
Nate Biggs fb5fbf1dec [dart2wasm] Fix null checks being added on non-nullable value types.
Bug: https://github.com/dart-lang/sdk/issues/59840
Change-Id: Ifaac6430eabfee21e4971b3dd864e22f65c80adf
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/402980
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2025-01-06 10:32:33 -08:00
Nate Biggs d82e84ca2f [dart2wasm] Fix tearoff codegen on boxed types.
The struct type for closures requires the context value be a struct. If the tearoff is on an unboxed value, it first has to be boxed before being used to create the closure struct.

The new test currently fails at runtime (or via assertions at compile time) with this error:
"BoxedInt.abs tear-off" failed: struct.new[2] expected type (ref struct), found local.get of type i64

Change-Id: Ie861bc12a34b21f8b3415edadf55ce3d59f97580
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/402560
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2025-01-06 10:23:43 -08:00
Nate Biggs 7bea7d185d [dart2wasm] Add indirection for struct initialization.
For dynamic modules we will "adjust" the class ID at runtime to ensure each module gets independent class ID spaces.

This initial change simply provides the point where we will eventually add that logic.

Change-Id: Iad9c38d9e3e842be2e77c48b1755ebe57d02d023
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/400923
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2024-12-18 08:19:42 -08:00
Nate Biggs 96c4e4c81f [dart2wasm] Use field type instead of global type for static field type.
The calls to 'getGlobalForStaticField' eagerly generate the initializer constant for the associated field. In both these cases we don't need that just to get the type of the field. Instead we can use 'translateTypeOfField' directly (same as 'getGlobalForStaticField').

This also removes unnecessary nullness from the type when the field is lazily instantiated. The global type may be nullable even if the field type isn't.

Change-Id: Id369e07335fc5350524a1b8b6c04f19dd94f8b8a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/401280
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2024-12-17 15:59:28 -08:00
Nate Biggs 96fbc264ca [dart2wasm] Add indirection for dispatch table calls.
In a future change we will add logic to `callDispatchTable` that performs a modified lookup if the provided selector is marked as "overrideable" by the dynamic_interface.yaml passed by a user.

Change-Id: Ie8991ca3ebf5d3ff1287cbfc4af58e1b1509826d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/401202
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2024-12-17 11:55:35 -08:00
Ömer Sinan Ağacan dbee089237 [dart2wasm] Devirtualize closure calls based on TFA direct-call metadata
Use the TFA direct-call metadata to directly call a closure in function
invocations.

Closes #55231.

Tested: existing tests cover the new code paths, but I also added a new
test.
Change-Id: Ib5f26b10efd77570e256196b4bfb07e6bef800c0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/397260
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
2024-12-11 10:46:20 +00:00
Ömer Sinan Ağacan f53c758b11 [dart2wasm] Remove redundant arguments list function
Remove `visitArgumentsLists`, use `_visitArguments`.

Also added some comments in `_visitArguments`.

Change-Id: Ia2ab08fd54f3b2b834cc423d76e894ff6ccc955c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/399140
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
2024-12-05 12:34:29 +00:00
Ömer Sinan Ağacan d746c5f941 [dart2wasm] Some refactoring around context and capture generation
This mainly refactors code that use the `Closures` type to make it a bit
more clear what it does and how it does it, and make it more difficult
to misuse.

Changes:

- Make `CaptureFinder`, `ContextCollector`, `Closures.findCapures`,
  `Closures.collectContexts`, `Closures.buildContexts` private.

  It doesn't make sense to use these types outside, and the `Closures`
  members need to be called together and in the right order. Make them
  private and call them in the constructor.

  Reduces API surface of `closures.dart` and makes it easier to use.

- Document all public members of `Closures`.

- Remove unused `ClosureRepresentation.exportSuffix`.

- In `TearOffCodeGenerator`, inline the single-use function
  `generateTearOffGetter`. Makes it clear that the code is not reused
  elsewhere.

- Make the type of `Types.nonNullableTypeType` more precise. Use it in
  `closures.dart` instead of having a separate copy of the same thing.

- In a few places where we had function body entirely guarded with an
  `if`, add an early return.

  For example:

  ```
  void f() {
      if (x) { ... lots of code ... }
  }
  ```

  Becomes:

  ```
  void f() {
      if (!x) return;
      ... lots of code ...
  }
  ```

  Similarly do it in loop bodies.

Change-Id: Ia2a74b89ae311b8f32b9f1a4e72c51d4ea3861e8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/392942
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
2024-11-01 13:30:40 +00:00
Martin Kustermann a96ad1b176 [dart2wasm] Add ImmutableWasmArray to dart:_wasm
Dart does not incorporate immutability into it's type system.
Though the WasmGC type system does have such a concept:

  * mutable wasm arrays: they are invariant in the element type
  * immutable wasm arrays: they are covariant in the element type

Currently we use the mutable wasm array types for e.g.

  const foo = const WasmArray<WasmI32>.literal([0]);

which will be compiled to

  (type $Array<WasmI32> () (array (field (mut i32))))
  (global $globalFoo
         (ref $Array<WasmI32>)
         (i32.const 0)
         (array.new_fixed $Array<WasmI32> 1
  )

=> Notice the **mut** in `array (field (mut i32))`

This CL introduces a `ImmutableWasmArray` which is analogous to
`WasmArray`. The array supports only reading methods since the
contents of the array cannot be modified.

Future CLs will make use of this new array type in various places.

=> This will allow binaryen to optimize things better as it knows the
array contents cannot change.
=> So loads from a (final) global with immutable contents can be
folded away at compile time if the index is known at compile-time.

NOTE: The `WasmArray`& `ImmutableArray` classes are not related to
each other. The reason is that in WasmGC a mutable array isn't a
subtype of a immutable array.

Change-Id: I3a764b5aaa3ac47827332f3064035133ab01f1b2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/392900
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2024-10-31 19:04:27 +00:00
Ömer Sinan Ağacan 9046485cda [dart2wasm] Add function names to source maps
Annotate code with the enclosing Dart function names.

Fixes #56718.

Change-Id: I1d53fc3752580514aa92b3d62ec717c93ad58d66
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/386780
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
2024-10-18 07:43:41 +00:00
Nate Biggs b79a06d99b [dart2wasm] Add last few fixes to translator to allow module test mode.
- Declare dummy value globals as needed per module. We cannot share these across modules as they can be used in a const context which limits  how we can reference them. The other option is to declare the dummy values for all heap types in the main module. However, declaring as needed per-module is more in line with our approach elsewhere and will work better for dynamic modules.

Change-Id: Ib2cd0a9300610ff8aa86d904902815d4fe9042d7
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/385401
Reviewed-by: Martin Kustermann <kustermann@google.com>
2024-09-23 18:56:24 +00:00
Nate Biggs 1d3142d34b [dart2wasm] Fix closure representation in multimodule scenarios.
This pushes the module branching to the specific functions used for closures. Rather than have separate ClosureRepresentation objects for each module, we have the representation create the functions it needs within each module on demand.

This duplicates some of the closure logic but it means each module (including main) only needs the representations relevant to it and we don't have to worry about module import ordering.

If there are no deferred imports (i.e. the program is 1 module) then this has no effect on the generated program. If there are deferred imports (i.e. multiple modules) they may contain duplicate closure representations which will increase total code size.

Change-Id: Ib9506e1f94866a4ae6de4472362bd5cd4260b1e5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/385182
Reviewed-by: Martin Kustermann <kustermann@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
2024-09-23 18:56:24 +00:00
Ömer Sinan Ağacan 6d1f17133a [dart2wasm] Rename some code generator methods
Per suggestions in https://dart-review.googlesource.com/c/sdk/+/370500,
rename:

- CodeGenerator.wrap -> translateExpression
- CodeGenerator.visitStatement -> translateStatement

Motivations:

- visitStatement is confusingly named: it's not a part of the visitor
  interface.

- wrap doesn't always wrap, it also downcasts and unwraps.

- These methods are the entry points for compiling expressions and
  statements, so it makes sense for them to be named consistently.

  Alternative namings could be: compileExpression/Statement,
  generateExpression/Statement.

Change-Id: I4e4ef53a7d9a04e3686b0ed3a80f4b7f0a26ee5f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/374080
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
2024-09-17 12:02:59 +00:00
Nate Biggs 048b8f6037 [dart2wasm] Update remaining .call() to use callFunction or callReference.
The ones in 'constant.dart' and 'types.dart' were actually incorrect, the others based on their context are known to be in the same module. However, it's better for the codebase if we always go through 'callFunction'.

Note: .call is the default behavior in the single module case so nothing was broken because these weren't updated.
Change-Id: I31be143c3095f206dc20b76c30d306341528af13
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/385180
Reviewed-by: Martin Kustermann <kustermann@google.com>
2024-09-13 15:58:12 +00:00
Nate Biggs b8ff81ea0d [dart2wasm] Fix while scoping in async generator.
The new test was resulting in an uninitialized late read of localContext here:
https://github.com/dart-lang/sdk/blob/main/pkg/dart2wasm/lib/code_generator.dart#L830

The `++i` results in a let expression declaring a new variable within the condition of the while loop.

The code linked above tries to look up the variable within the current context. The `ContextCollector` defines the while scope as including the while's condition:
https://github.com/dart-lang/sdk/blob/main/pkg/dart2wasm/lib/closures.dart#L1486

However, the AstCodeGenerator doesn't register the while's scope until after processing the condition:
https://github.com/dart-lang/sdk/blob/main/pkg/dart2wasm/lib/code_generator.dart#L1248

So when processing the condition, the loopkup happens on the scope above the while even though the variable is captured on the while's scope.

A synthetic let statement is the only way to get a variable declaration within a while's condition. And it wouldn't otherwise be captured (a closure can't reference it) but async function bodies mark all variables as captured.

Change-Id: I5f8fdd69d4875c099a21505cd588b08cacdd86c1
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/384863
Reviewed-by: Martin Kustermann <kustermann@google.com>
2024-09-12 17:59:36 +00:00
Martin Kustermann d7cff6c809 [dart2wasm] Make --omit-implicit-checks also omit AsExpression checks inserted by CFE
There are several different kinds of implicit type checks. The most
common ones are the checks on covariant parameters of methods. Those are
not represented in the kernel AST via `AsExpression`s but backend
compilers have to insert them themselves.

Then there's other implicit type checks that users haven't written but
the CFE inserts as synthetic `AsExpression`s. There's several different
kinds.

=> We make --omit-implicit-checks now also not perform those anymore
   (which one would expect given the name of the flag)

Change-Id: Ie61536c0a7269e5c62cd98c62e77d1b6a34e3f1f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/384740
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
2024-09-11 10:04:28 +00:00
Nate Biggs c936c0fdd2 [dart2wasm] Add deferred loading support to dart2wasm (11/X).
This is the final CL for deferred loading. It wires up the library-module analysis logic to the compiler. With all the Translator module predicates implemented, code should now be generated in separate modules (assuming the flag is enabled).

This also handles the module naming scheme. For an invocation of dart2wasm like `dart2wasm main.dart out.wasm` this will produce files like `out.mjs, out.wasm, out_module1.wasm, out_module2.wasm, ...`. `out.wasm` is the main module that gets loaded on initialization. When the flag is disabled this will always be the only output.

If the flag is disabled then the `_importMapping` in `deferred.dart` will be empty and we will default to the same behavior as today which will be to just return an empty `Future`. When enabled, `loadLibrary` will fetch and instantiate the new module(s) before proceeding.

Change-Id: I0dd136c0af61b916be2a24b3d79052ff1b786b52
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/380440
Reviewed-by: Martin Kustermann <kustermann@google.com>
2024-09-04 21:58:12 +00:00
Nate Biggs 56536825ed [dart2wasm] Add deferred loading support to dart2wasm (10/X).
Remove all references to a global `ModuleBuilder` (code like `ModuleBuilder get m => ...`). References to module builders should be more specific now, whether that be to the `mainModule` on Translator or some other module.

Technically this could remain and always refer to the mainModule but making the name more specific makes it clear there is no single ModuleBuilder anymore and code that needs to access a ModuleBuilder will need to consider which one it needs.

Change-Id: I140c4e80a131a1786fa66c93be07a622dd0756c3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/381443
Reviewed-by: Martin Kustermann <kustermann@google.com>
2024-09-04 21:58:12 +00:00
Nate Biggs f0153c1711 [dart2wasm] Add deferred loading support to dart2wasm (8/X).
Update dynamic forwarders to be defined in the main module but be callable from any module. Each forwarder gets a Reference that can be used to call it indirectly via "callReference".

Change-Id: I8c343f30e873058aea2a28db5b4f2c9bb4679523
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/381441
Reviewed-by: Martin Kustermann <kustermann@google.com>
2024-09-04 21:58:12 +00:00
Nate Biggs 1841b183b4 [dart2wasm] Add deferred loading support to dart2wasm (7/X).
Updates various CallTarget implementations to track the "callingModule" so that they can generate functions in the same module and call them directly.

Though it may produce some duplicate code across modules, it keeps things like is/as checks faster since they don't require indirect calls.

Change-Id: Ia35ccbc3e74ed57b1fd70dc9a24813369780bdfa
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/381440
Reviewed-by: Martin Kustermann <kustermann@google.com>
2024-09-04 21:58:12 +00:00
Nate Biggs c5bd4d05db [dart2wasm] Add deferred loading support to dart2wasm (6/X).
Wasm globals serve a few purposes in the compiler such as storing static fields and closure vtables. Sometimes the access of these globals will be from a different module than the ones they're defined in. We need some indirection to be able to access them in these cross-module situations.

This change adds getter and setter functions that can be called via the StaticTable when a global needs to be accessed from a different module.

We use References to track the owning module for each global to determine if we can access it directly or not.

Change-Id: I93191c83dee1b7a47171c5808e64b071479cdeea
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/381324
Reviewed-by: Martin Kustermann <kustermann@google.com>
2024-09-04 21:58:12 +00:00
Nate Biggs a7f5845a4e [dart2wasm] Add deferred loading support to dart2wasm (5/X).
Add support for a StaticTable which holds references to known functions that need to be called across modules. For calls that target the DispatchTable we will still go through there if possible. But for any functions not referenced in that table (including any compiler generated functions) we add a separate static table.

Also adds import/export support to both the DispatchTable and the StaticTable. The table will always be defined in the main module and imported into subsequent modules.

Change-Id: Iedc683d1ecfe721393900913826010cdd9b2c3c4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/381323
Reviewed-by: Martin Kustermann <kustermann@google.com>
2024-09-04 21:58:12 +00:00
Nate Biggs b5b335fb75 [dart2wasm] Add deferred loading support to dart2wasm (4/X).
Adds `Translator.callReference` which will be the main indirection point for calls between modules.

Any call that might need to be made across modules should go through `callReference` and this will handle checking if the call is local to the same module. If it is then it will use a normal "call" instruction, otherwise it will re-route the call through a table and "call_indirect".
`Reference` is the primary module assignment mechanism, we will generate synthetic References for anything that doesn't have one from the Kernel.

For now just maintains the current behavior of generating a `call` instruction.

Change-Id: I9a300d100bc7c27ec2aba42af367e91201dcadc3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/381322
Reviewed-by: Martin Kustermann <kustermann@google.com>
2024-09-04 21:58:12 +00:00
Nate Biggs 3b4ab02425 [dart2wasm] Add deferred loading support to dart2wasm (3/X).
Adds some simple module predicates and helpers. Uses them to add support for multimodule exception tags. If we have multiple modules we need to share the same exception tag between them so that error handling works. We do this by defining and exporting the tag in the main module and importing it into subsequent modules.

Change-Id: Id458453033db4c11914943231104d2d004abf719
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/381321
Reviewed-by: Martin Kustermann <kustermann@google.com>
2024-09-04 21:58:12 +00:00
Martin Kustermann d769a9105b [dart2wasm] Adjustments to inliner
* Inline small `get iterator` & `get current` iterator methods
* Inline bodies that are small compared to arguments
* Make AST node counter more precise
* Manually mark ListIterator methods as prefer inline

CoreLibraryReviewExempt: Only adds annotation to existing functions
Change-Id: Ib6379e73713cd47a88e5cc67cecd4b5c8344adcb
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/382882
Reviewed-by: Slava Egorov <vegorov@google.com>
2024-08-30 13:14:56 +00:00
Martin Kustermann 8c07aa5117 [dart2wasm] Handle is/as checks that only require nullability check in backend.
Up to Patchset 4 reverts commit 36fb02deca

Remaining Patchsets implement specialized support in is/as check
implementation to handle cases where only a null check is required. The
most common case in iterators:

  class <...>Iterator<T> {
    T? _current;

    T get current => _current as T;
  }

This ensures we're never emitting the general subtype checking code as
this only requres checking whether the object is non-`null` or whether
the destination type is declared nullable.

The asymmetry between `is` and `as` checks comes due to the fact that
the `as` checks have to (for the slow case) materialize a type object
for a nice error message.


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

Change-Id: Ie4a845816ec4eda37a5a2e78cac0aeda0e411abd
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/381482
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2024-08-22 11:05:46 +00:00
Martin Kustermann 58d07a3ae8 [dart2wasm] Support skipping covariance type checks in inlining
We can have call-site guarantees that covariance type checks will
succeed. In that case, we can avoid performing the checks iff we inline
the callee.

The call site guarantees can be

  * TFA has prooven they will succeed
  * we dispatch on `this`

Change-Id: I54411de970cc3af854ebcb9e73cf03a862f0b55c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/381020
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2024-08-20 08:36:50 +00:00