Commit Graph

109 Commits

Author SHA1 Message Date
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
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
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
Martin Kustermann 4ee6a66270 [dart2wasm] Format pkg/dart2wasm after language version was increased
The change in [0] increased the language version of pkg/dart2wasm. That
in return changes how the package is formatted by the autoformatter.

This CL runs now the formatter to re-format the code. Unfortunately this
makes blame lists worse. But not doing it will make us have to disable
auto-formatting before saving files which is very annoying.

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

Change-Id: I6953fe0d6a824b2b79a26bbadb0bb977cec70b7a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/490821
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2026-03-26 04:32:19 -07:00
Martin Kustermann 9e015ed1ed [dart2wasm] Simplify handling of noSuchMethod invocations
The code in `generateNoSuchMethodCall` is calling the `noSuchMethod`
instance method. Doing so required duplication of logic from
normal instance invocations.

Instead we call a static method in core libraries that will perform the
instance invocation. Compiling that static method will then use the
normal logic we have for instance invocations (instead of duplicating
that logic in the code generator).

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

Change-Id: Ic6a5e5bc7a87baa2039af551bd7a01b1557017a4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/488160
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2026-03-16 13:09:35 -07:00
Nate Biggs 3c0573bcd1 [dart2wasm] Update main invocation patching to avoid leaking types into the program.
Dart2wasm started using RTA to significantly speed up compilation.
However, a side effect of this is less exact type info during TFA.

In _invokeMain we conditionally use some js-interop logic when a
program's main takes arguments. On the web, most programs don't expect
any arguments so this is usually dead code. However, RTA sees
_invokeInternal is live and blindly treats all reachable classes as live
including the unused interop helper types in the other branches.

This code refactors the patching logic to make sure only the relevant
entry point is considered live.

This change uncovered a different bug that was suppressed by these
implicitly instantiated types. Dart2wasm was not marking some internally
instantiated classes as allocated in the function logic so entries in
the dispatch table were empty for those types. Instead any time we
generate the code for a constructor (which may not be reached via a
ConstructorInvocation like we were assuming before), we record the class
as allocated.

Change-Id: I556d3733c00b4a3e3455fa1ee2c90206d54c81f4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/484540
Commit-Queue: Nate Biggs <natebiggs@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2026-03-03 09:01:46 -08:00
Ömer Ağacan 590b656877 [dart2wasm] Update JS exception catching
This updates JS exception catching as discussed in #55481:

- Only catch JS exceptions when the exception type is `dynamic`,
  `Object`, or an extension of `JSValue`. (nullable or not)

  (Previously we also caught JS exceptions when the type is `Error`.)

- When the JS value caught in Wasm is `null` or `undefined`, box it as a
  non-interop class. For compatibility with dart2js, this class is
  copied from dart2js and has the same `toString` as the dart2js class.

- In other cases: box the JS values as `JSValue`. This means the value
  can be passed as any of the interop types, and can be passed back to
  JS without manual jsification.

Fixes #55481.

Issue: https://github.com/dart-lang/sdk/issues/55481
Change-Id: I23e73074729f740b90df2ca8b3c713fb39966556
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/479640
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
2026-02-24 01:43:10 -08:00
Nate Biggs a484bb3973 [dart2wasm] Use constants to represent dummy values.
Today "dummy values" are generated per-module to stand in for things
like default parameter sentinels (where a given selector has multiple
default values for an optional parameter).

However, these values can end up crossing between modules. The logic is
set up to use ref_eq to check if an argument is one of these dummy
values. However, if one of these dummy values crosses between modules,
the passed value vs the ref_eq checked value will be different. Since
each module has its own canonical dummy value per type.

This new layout simplifies our handling of these dummy values by
treating them as Constants so that our normal module canonicalization
logic applies to them. We already have plenty of logic to canonicalize
constants across modules. This avoids the need for custom handling of
these dummy value globals.

Change-Id: Ia9c79923c788d7712b16705193ffbf3142141b5d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/480320
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2026-02-19 12:48:49 -08:00
Nate Biggs 5a02a7a019 [dart2wasm] SIMD instructions template
SIMD proposal:
https://github.com/WebAssembly/spec/blob/main/proposals/simd/SIMD.md

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

Bug: https://github.com/dart-lang/sdk/issues/62516
Change-Id: Ib229feecf58714e92fcb1461f968919658cbce29
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/476540
Commit-Queue: Nate Biggs <natebiggs@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2026-02-03 10:01:43 -08:00
Martin Kustermann 6d5843815d [dart2wasm] Make type tests smaller in deferred loading case
This leads to a small -0.4% size reduction of essentials main
module.

The type check optimizations in the compiler relied on the fact that
if `--minify` is on, then parameters of functions aren't used and
`wasm-opt` will remove the parameters of the function (as they are not
used) and then shrink call sites by not passing them.

This works in the one module scenario, but when enabling deferred
loading, we have multiple modules and the call site of the function may
be in a different module then the callee, which means the main module
exports the function and the deferred module imports it. That means
`wasm-opt` won't change the signature.

So this CL:

* makes `as` checker functions call  `_throwErrorWithoutDetails` in `--minify`
  mode
  => This makes the call sites in as checkers smaller
  => This will no longer call the `_throw*AsChecker` helper functions
  across modules in `--minify` mode

* since the `_throw*AsChecker` functions won't be used across modules,
  `wasm-opt` can now optimize their signature away (for the uses in the
  core library)

Change-Id: I765b96c739401726c2c7bf8d9fb0996ee875aa89
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/473360
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2026-01-20 05:29:41 -08:00
Martin Kustermann 7022754207 [dart2wasm] Reduce RTT related wasm function
In essentials main module this removes 95k from code section
and adds 350k to data section. Uncompressed it increases size
by 3%. But because the data section addition compresses really
well, we see overall size reduction by -0.9%. We'll also see
faster startup, since we avoid the runtime to compile the 100kb
wasm function & execute it - instead just do a memcpy of the
data section.

In large apps the `_ModuleRtt.*` tables are too large to fit into
the global section, so they end up being lazily initialized.

For a large app we have a `_ModuleRtt.typeRowDisplacementSubstTable`
being 100 kb large wasm function.

The reason is that it initializes a `WasmArray<WasmArray<Type>>` which
is sparsely populated and nearby entries are often not the same,
requiring emitting 4 wasm instructions for every entry we fill.

Now instead of having two tables with the same layout where we use
the first to determine if two classes are related and the second
to give us the substitution, we store it in 3 tables:

* one that determins whether two classes are related
* one that stores the index of the substitution
* one that stores all unique substitutions

This shrinks the `WasmArray<WasmArray<_Type>>` table to be much
smaller (as it now holds only unique entries) and have a new
`WasmArray<WasmI16>` array which compresses really well and
who's data lives in the data segment.

Change-Id: If60af763b9739fd4e515e730a8e7be0db98beefd
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/473281
Reviewed-by: Slava Egorov <vegorov@google.com>
Reviewed-by: Nate Biggs <natebiggs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2026-01-16 03:48:22 -08:00
Simon Binder 7d8cd032dc [dart2wasm] Interfaces for accessing memories
This adds the `Memory` class to `dart:_wasm`, allowing Dart code to
load and store numeric types in linear memory.
Since `dart2wasm` doesn't generate a memory instance by default, there
is no singleton instance of `Memory`. Instead, memories are defined as
`external` top-level getters annotated with a pragma like
`@pragma('wasm:memory-tyype', MemoryType(limits: Limits(1, 10)))` to
declare their type.

Interop happens in a static way: Methods on `Memory` cannot be torn-off
and, since the target memory is encoded directly in the store/load
instruction, there's also no polymorphism for memories in Dart.
Attempting to call methods on a memory instance that isn't a direct
reference to its definition is a compile-time error.

Memories can also be imported and exported through the existing
`wasm:import` and `wasm:export` pragmas.

TEST=tests/web/wasm/memory_test.dart

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

    import 'dart:js_interop';

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

    @JS()
    external void throwFunction();

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

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

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

Output after:

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

Fixes #62218.

Issue: https://github.com/dart-lang/sdk/issues/62218
Change-Id: Ia9347e938af209b8b87752479d35b6236f721acf
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/469062
Reviewed-by: Martin Kustermann <kustermann@google.com>
2026-01-02 02:27:58 -08:00
Nate Biggs 740c0fd966 [dart2wasm] Allow missing checkLibraryIsLoadedFromLoadId function.
Applying the --enable-deferred-loading flag causes dart2wasm to look up this function. The lookup fails if there are no deferred imports in the program because the function gets tree-shaken.

Instead make the function lookup optional so this failed lookup doesn't throw.

Change-Id: I5076b171a074f566cef864d2030d0188dcf81c21
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/461380
Commit-Queue: Nate Biggs <natebiggs@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2025-11-12 09:17:37 -08:00
Martin Kustermann 59c8081950 [dart2wasm] Use shared closure argument dispatchers for dynamic call entries
This removes 4591 functions from ACX gallery main module
which translates to -43 KB / -1.6% (-3.5% code section)

We have already today a callsite guarantee that the closure call
(type, positional, named) arguments are valid arguments to the
target closure (they also have the right type).

That means the `closure.vtable.dynamicCall` entry's only purpose
is to unpack the (type, positional, named) argument arrays and call
the target.

Instead of calling the target directly (as we did so far) we now
unpack argument arrays and call the right vtable entry. This logic
can be shared amongst all closures of the same representation and
therefore leads to big reduction in wasm functions.

=> We do that in this CL.

There's two exceptions to this:

* In dynamic module scenario we don't have closed-world knowledge
  of closure definitions & closure call site. There's no specific
  vtable entries for positional+name combinations we could forward
  to.

* In closed world scenario where there's a usage of `Function.apply`
  with named arguments: We don't generate vtable entries for all
  possible name combinations a closure can be called with.

So we change the closure layouter algorithm to find out if there's
a usage of `Function.apply` with named arguments.

A few tangential changes:

* Fix a bug revealed by this change: The static tearoff
  instantiation constant's dynamic call entry must pass the generic
  closure object when calling the generic closure.
  => The shared dynamic call entry dispatchers will now verify
     (in assertion) mode the assumptions, which revealed this issue

* The closure layouter algorithm will now consider `obj.foo(a: ...)`
  as a potential dynamic call site (due to call-via-field) and
  therefore record the name combinations used there
  => Tested via `web/wasm/closures/dynamic_call_via_field_test`

We test the optimization by checking in 3 tests that show what
ends up in the vtables:

* `pkg/dart2wasm/test/ir_tests/dyn_closure.dart`
  => uses dynamic calls
  => dynamic call entries are "closure arguments dispatcher"

* `pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply.dart`
  => uses Function.apply without names
  => dynamic call entries are "closure arguments dispatcher"

* `pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply_named.dart`
  => uses Function.apply with named arguments
  => dynamic call entries are closure specific

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

Change-Id: I099984b542b05920b02596410a1bf6a08d2a0302
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/460080
Reviewed-by: Ömer Ağacan <omersa@google.com>
2025-11-07 11:17:57 -08:00
Martin Kustermann 72e3f7a0d4 [dart2wasm] Fix web/wasm/flute_stress_test in SDK mode
Currently `web/wasm/flute_stress_test` is failing on
`dart2wasm-linux-optimized-jsc` mode.

That mode runs with `--use-sdk` which makes it use `dart compile wasm`.

The reason the test is failing is because it passes both
`--enable-deferred-loading` and `--multi-module-stress-test` flags to
dart2wasm. This causes us to trigger the deferred module flow instead
of the specialized multi-module flow.

Those two flags should be independent.

Change-Id: I8cac976cd91d4e585f38632ada3062e41fce401f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/458800
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Nate Biggs <natebiggs@google.com>
2025-11-04 05:17:51 -08:00
Martin Kustermann 6184c447ab [dart2wasm] Introduce unresolved accesses & link phase
This reduces ACX Gallery's main module from 8.4 MB to 2.75 MB.

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

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

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

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

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

Part 1) Determination of global order

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

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

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

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

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

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

=> The result was the `ConstantInfo` object.

We now separate these things:

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

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

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

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

Part 3) Tracking constant uses

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

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

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

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

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

Part 4) Defining of constants

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

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

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

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

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

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

Misc

* We separate constant definition from importing / exporting them.

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

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

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

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

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

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

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

Change-Id: Idfefee25d5f84f8717808a0aee1b189d8f15d16d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/458000
Reviewed-by: Nate Biggs <natebiggs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-10-30 03:40:00 -07:00
Nate Biggs a20f1bcb40 [dart2wasm] Introduce "load ids" to dart2wasm deferred loading.
Load ids provide a way to reduce the overhead of deferred loading. By
default deferred loading requires mapping `loadLibrary` calls to a list
of modules. This requires including (1) library uris, (2) prefix names
and (3) module names directly in the main module. With this loading
modules is easier as the loading function gets the exact filename.

Load ids provide an alternative approach where the compiler emits a
separate file mapping a load ID to the module set required for that ID.
An app could store this mapping on the server allowing the frontend to
include only the load ID in its request and have the server figure out
which modules to send back.

Internal serving infra uses module sets like this so this change allows
easier integration into that tooling. Dart2js already supports emitting
this deferred mapping JSON and internal infra is using that today.

Other changes include:
- Run the deferred loading transformer after TFA. This will exclude unused libraries in the resulting deferred loading map. Mark deferred helpers as entry points so that they don't get tree-shaken.
- Some changes to naming conventions of JS helpers.
- Use filename as module name to simplify JS helpers

Change-Id: I5e2f5e374de77c87095d08bfff6534506cb10652
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/454240
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2025-10-13 13:42:47 -07:00
Nate Biggs aa8f15fe48 [dart2wasm] Reland br_table change.
Dedupes all the logic that was being copied between the int and enum case by adding helpers into the BrTableInfo class.

Reverts: https://dart-review.googlesource.com/c/sdk/+/443082?tab=comments
Fixes: https://github.com/dart-lang/sdk/issues/61223

Change-Id: I861d4b878059d9c633789ce1a09e0e69a2ad925f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/443220
Commit-Queue: Nate Biggs <natebiggs@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2025-08-01 10:10:05 -07:00
Martin Kustermann 21f7ffc699 [dart2wasm] Revert switch-case optimizations that introduced a bug.
This reverts commit b65435017d
  This reverts commit 2254755d65

The switch-case optimizations introduced a bug that is surfaced when
running the dart2wasm self-compilation test in [0]. With that test
running

```
% python3 tools/test.py -n unittest-mac pkg/dart2wasm/test/self_compile_test
```
will fail with
```
...
Exception in StaticInvocation at file:///FakeSdkRoot/sdk/lib/_internal/wasm/lib/boxed_double.dart:346:29
Bad state: Unhandled WasmArray intrinsic: StaticIntrinsic.wasmArrayIndex
    at module0.Error._throwWithCurrentStackTrace (wasm://wasm/module0-0121906a:wasm-function[160]:0xd0d76)
    at module0.AstCodeGenerator.visitStaticInvocation (checked entry) (wasm://wasm/module0-0121906a:wasm-function[6304]:0x1668e5)
    at module0._TreeVisitor1Default&Object&TreeVisitor1DefaultMixin&ExpressionVisitor1DefaultMixin.visitStaticInvocation (checked entry) (wasm://wasm/module0-0121906a:wasm-function[6306]:0x167b61)
    at module0.StaticInvocation.accept1 (wasm://wasm/module0-0121906a:wasm-function[6297]:0x165f5f)
    at module0.AstCodeGenerator.translateExpression (wasm://wasm/module0-0121906a:wasm-function[1742]:0xfcd1b)
    at module0.AstCodeGenerator.visitEqualsCall (checked entry) (wasm://wasm/module0-0121906a:wasm-function[6960]:0x1777e4)
    at module0.EqualsCall.accept1 (wasm://wasm/module0-0121906a:wasm-function[6944]:0x1773b4)
    at module0.AstCodeGenerator.translateExpression (wasm://wasm/module0-0121906a:wasm-function[1742]:0xfcd1b)
/Users/kustermann/src/dart-sdk/sdk/pkg/dart2wasm/bin/run_wasm.js:346: [object WebAssembly.Exception]
```

The switch-case in the intrinsifier seems to be miscompiled.

Change-Id: I3bfe8887fa133573379c32d52e15769a4e6db43e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/443082
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
2025-07-31 06:13:38 -07:00
Nate Biggs 2254755d65 [dart2wasm] Generate br_table for some switch statements.
Often switches over enums are exhaustive or nearly exhaustive. The current generator uses identity for the enum values which requires iterating over each value to compare it to the test expression. So each time the switch body is entered is an O(n) operation (where is n is the # of case expressions).

This now generates a br_table using the index of the enum and jumps directly to the correct clause, effectively an O(1) operation.

A similar approach is taken for switches over an integer range. If the range of case expresison values is close in size to the # of values, it is advantageous to normalize the range around 0 and treat the values themselves as table indices.

This approach will also save code size when the index range is dense as the br_table is more compact than the identity/br_if checks. For sparse ranges this may produce a bit more code though only on the order of a few bytes per value in the range.

I've added a denseness heuristic to decide when to revert to the current strategy to avoid the code size penalty.

Golem benchmark: https://golem.corp.goog/Comparison?repository=dart#targetA%3Ddart2wasm-O2-d8%3BmachineTypeA%3Dlinux-x64%3BrevisionA%3D117402%3BpatchA%3Dnatebiggs-dart2wasm-switch-tables3%3BtargetB%3Ddart2wasm-O2-d8%3BmachineTypeB%3Dlinux-x64%3BrevisionB%3D117401%3BpatchB%3DNone

See 100% improvement in SwitchFSM.int and 90% improvement in SwitchFSM.enum.

Change-Id: Ie29e8fd59ef6235044ba5b4a4af04023d702ce57
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/441760
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2025-07-24 11:07:58 -07:00
Martin Kustermann 7e4145d141 [dart2wasm] Assign names to globals/initializer functions for constants
* Make globals for constants have names which allows reading code
  easier as the name of the global for a constant will have
  the type/kind in it

* Make lazy initializer functions for globals have explicit names in
  most cases, which avoids arbitrary length names for list constants

* Both globals and lazy initializer functions will get names
  that include C<number> (for ease grep'ping navigating in wat files)
  and a descriptive name that includes the type and/or value of
  the constant.

* Make list constant implementation lower to wasm array constants,
  just as we do for map constants. This avoids code duplication
  and allows canonicalization of the wasm arrays.

Closes https://github.com/dart-lang/sdk/issues/55409

Change-Id: I7ccbdd3d44cf916bf147fdd1053ed52ddd1c0dc5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/435420
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-06-18 02:26:54 -07:00
Ömer Ağacan 9439795445 [dart2wasm] Complete async fun futures directly, instead of via completer
In `async` functions, instead of completing the function's `Future` via
`Completer`, do it directly.

This should be slightly more efficient as we eliminate a layer of
indirection when completing.

Move reading the `_future` field of `_AsyncSuspendState` to the
completion functions, to avoid adding a `struct.get`s at each call site.

In ACX demo, makes the final binary 0.1% smaller (13,689 bytes).

Fixes #60719.

Issue: https://github.com/dart-lang/sdk/issues/60719
CoreLibraryReviewExempt: Wasm-specific change.
Change-Id: I27b376eb2fb9c3705ee930fb33b06d9accfd14b8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/429000
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
2025-05-19 05:04:32 -07:00
Nate Biggs b3e7e2fbbd [dart2wasm] Serialize dill before and after TFA for dynamic module enabled main modules.
Modifies the serialization strategy used for dynamic modules. Emits 2 dill files, main.dill and main.opt.dill.

main.dill serialized before TFA runs and is used by the CFE to compile the dynamic module faster (it doesn't have to recompile the main module libraries from source).

main.opt.dill is serialized at the end of the compilation of the main module and contains a copy of all the main module libraries after TFA. This dill is used by dart2wasm to produce consistent references into the main module.

Also emit new record classes into a distinct library so that all downstream code will appropriately emit the new code without special logic.

Change-Id: Idf7a87d2014dad513c753330743f753312aa472f
Tested: Just updating signature, no testing needed.
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/422342
Reviewed-by: Martin Kustermann <kustermann@google.com>
2025-04-28 17:51:59 -07:00
Ömer Ağacan 6952a80978 [dart2wasm] JS interop: pass small ints as i31ref
In V8, the only way to pass a Wasm integer or float to JS without
allocation is by passing it as a 31-bit integer.

This can be done by:

1. Passing as `i32`. If the integer fits into 31 bits it's passed
   without allocation.

2. Passing as externalized `i31ref`.

(1) requires importing the JS function with different signatures: for
each `int` argument we would need a signature with the `i32` as the Wasm
argument type, and another with `externref` (or `f64` if we want to pass
large integers as `f64`).

This is not feasible as with a JS function with N `int` arguments we
would need `2^N` imports. So we implement (2): we import each interop
function with one signature, passing `externref` as the argument, as
before. When the number fits into 31 bits we convert it to an `i31ref`
and externalize it. Otherwise we convert the number to `externref` as
before, by calling the JS function `(o) => o` imported with type `[f64]
-> [externref]`.

New benchmark checks `int` passing for small (31 bit) and large (larger
than 31 bit) integers. Results before:

    WasmJSInterop.call.void.1ArgsSmi(RunTimeRaw): 0.020 ns.
    WasmJSInterop.call.void.1ArgsInt(RunTimeRaw): 0.018 ns.

After:

    WasmJSInterop.call.void.1ArgsSmi(RunTimeRaw): 0.014 ns.
    WasmJSInterop.call.void.1ArgsInt(RunTimeRaw): 0.018 ns.

Issue: https://github.com/dart-lang/sdk/issues/60357
Change-Id: I749001e0e7e9784114415439298c2f3e0fb974b3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/419880
Commit-Queue: Ömer Ağacan <omersa@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2025-04-11 04:14:04 -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
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
Nate Biggs f057e13e31 [dart2wasm] Add BoxedInt immutable array cache to support dart:convert.
https://github.com/dart-lang/sdk/blob/main/sdk/lib/_internal/wasm/lib/convert_patch.dart#L1110

Change-Id: I00fb812af9818c566e72360b5744699a50e3eb67
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/412860
Commit-Queue: Nate Biggs <natebiggs@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2025-03-04 08:50:40 -08: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
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
Nate Biggs e05a423e04 [dart2wasm] Avoid extra global definitions for int constants that are wasmI32.
In a few places we create IntConstants to represent constant fields that are typed as WasmI32. If these are nested within other constants we end up hitting ConstantCreator.visitIntConstant for them. This always generates a global for a BoxedInt constant, whether it's used or not.

The global we create goes unused because in constant intiailizer for the outer constant we use the wasmI32 value directly.

At -O0 this reduced the wasm binary size by ~24k for a simple Flutter app. Binaryen mostly treeshakes these unused globals but I still see a slight improvement even with binaryen running.

Change-Id: I89b8392138269d7322196fa415e56e3062a46088
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/408121
Commit-Queue: Nate Biggs <natebiggs@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2025-02-05 19:01:45 -08: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
Nate Biggs 25706745bc [dart2wasm] Fix handling of user-defined _typeArguments members.
The dart2wasm transformer injects a `_typeArguments` into each class.
https://github.com/dart-lang/sdk/blob/main/pkg/dart2wasm/lib/transformers.dart#L201

This is a synthetic private member that gets handled specially. However, users can have their own private members with the same name so we have to ensure the intrinsic handler is only operating on synthesized members by looking at the library attached to the private name.

Bug: https://github.com/flutter/flutter/issues/154383
Change-Id: I04062bec1f2bcf9ce6420e749c417a65f676505d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/386040
Auto-Submit: Nate Biggs <natebiggs@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2024-09-23 21:15:06 +00:00
Ömer Sinan Ağacan c8a7922160 Reland "Reapply "[dart2wasm] Allocate boxed bools once" and "[dart2wasm] Box Pointer values obtained from FFI calls""
This is a reland of commit fca5417d49

- Patchset 1 is the reland.
- Patchset 2 adds a regression test and fixes the bug.

Also tested engine chrome-dart2wasm-html-engine,chrome-dart2wasm-html-html,chrome-dart2wasm-html-ui,chrome-dart2wasm-canvaskit-canvaskit,chrome-dart2wasm-canvaskit-ui,chrome-dart2wasm-skwasm-ui tests manually.

Original change's description:
> Reapply "[dart2wasm] Allocate boxed bools once" and "[dart2wasm] Box `Pointer` values obtained from FFI calls"
>
> This reverts commit a4e9775a99.
>
> - Patchset 1 reverts the revert.
>
> - Patchset 2 adds regression tests. These tests fail.
>
> - Patchset 3 and the rest fixes the bug in `_loadPointer` and
>   `_storePointer` intrinsics. Also adds some inline annotations to
>   reduce the noise in unoptimized builds.
>
> Tested: updated test web/wasm/ffi/ffi_native_test
> Change-Id: I748a8aa8ff7cc663b1980d2bfab0d0da9f369e3b
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/384760
> Reviewed-by: Martin Kustermann <kustermann@google.com>
> Commit-Queue: Ömer Ağacan <omersa@google.com>

Tested: web/wasm/ffi/ffi_native_test updated. Manual run of engine chrome-dart2wasm-html-engine,chrome-dart2wasm-html-html,chrome-dart2wasm-html-ui,chrome-dart2wasm-canvaskit-canvaskit,chrome-dart2wasm-canvaskit-ui,chrome-dart2wasm-skwasm-ui tests.
Change-Id: Ifcd5156b8a03228119b2146edcfc56db8ac6273b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/384843
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
2024-09-12 09:55:11 +00:00
Ömer Ağacan d146083865 Revert "Reapply "[dart2wasm] Allocate boxed bools once" and "[dart2wasm] Box Pointer values obtained from FFI calls""
This reverts commit fca5417d49.

Reason for revert: https://ci.chromium.org/ui/p/flutter/builders/try/Linux%20Engine%20Drone/2677631/overview

Original change's description:
> Reapply "[dart2wasm] Allocate boxed bools once" and "[dart2wasm] Box `Pointer` values obtained from FFI calls"
>
> This reverts commit a4e9775a99.
>
> - Patchset 1 reverts the revert.
>
> - Patchset 2 adds regression tests. These tests fail.
>
> - Patchset 3 and the rest fixes the bug in `_loadPointer` and
>   `_storePointer` intrinsics. Also adds some inline annotations to
>   reduce the noise in unoptimized builds.
>
> Tested: updated test web/wasm/ffi/ffi_native_test
> Change-Id: I748a8aa8ff7cc663b1980d2bfab0d0da9f369e3b
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/384760
> Reviewed-by: Martin Kustermann <kustermann@google.com>
> Commit-Queue: Ömer Ağacan <omersa@google.com>

Change-Id: I2cb55b6d2fdf429c5a0634329079e9df7e9e5871
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/384841
Reviewed-by: Martin Kustermann <kustermann@google.com>
Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
2024-09-11 17:06:58 +00:00
Ömer Sinan Ağacan fca5417d49 Reapply "[dart2wasm] Allocate boxed bools once" and "[dart2wasm] Box Pointer values obtained from FFI calls"
This reverts commit a4e9775a99.

- Patchset 1 reverts the revert.

- Patchset 2 adds regression tests. These tests fail.

- Patchset 3 and the rest fixes the bug in `_loadPointer` and
  `_storePointer` intrinsics. Also adds some inline annotations to
  reduce the noise in unoptimized builds.

Tested: updated test web/wasm/ffi/ffi_native_test
Change-Id: I748a8aa8ff7cc663b1980d2bfab0d0da9f369e3b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/384760
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
2024-09-11 11:09:39 +00:00
Jason Simmons a4e9775a99 Revert "[dart2wasm] Allocate boxed bools once" and "[dart2wasm] Box Pointer values obtained from FFI calls"
This reverts commit d7a283de26 and commit d7a39788d4

The "[dart2wasm] Box `Pointer` values obtained from FFI calls" change causes a Flutter Web test to hang (https://github.com/flutter/engine/blob/main/lib/web_ui/test/fallbacks/fallbacks_test.dart)

Change-Id: Ia44bcf68cd6edc72697a0c2b05e05b4f8e1408fd
TEST=reproduced the hang locally and confirmed that it does not happen with this revert
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/384321
Reviewed-by: Siva Annamalai <asiva@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Jason Simmons <jsimmons@google.com>
2024-09-09 20:10:06 +00:00
Ömer Sinan Ağacan 71327ba9de [dart2wasm] Move BoxedBool to its own lib
For consistency with rest of the standard library types (numbers types,
strings, collections like maps, sets, lists, ...) move boxed bool type
to its own library.

Change-Id: I30ea7dbb0957115a0614609ec4433f6119164939
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/382360
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
2024-09-05 12:09:01 +00:00
Ömer Sinan Ağacan d7a39788d4 [dart2wasm] Box Pointer values obtained from FFI calls
Currently `Pointer` argument and return types of `FfiNative` functions
are translated to Wasm as `i32`.

This causes problems when we need to box those `i32` values as we don't
track which Dart types Wasm types come from when converting a Dart type
like `Pointer` to a Wasm type like `i32`.

Currently this works somewhat accidentally. All `i32`s are boxed as
`BoxedBool`, including `i32`s that represent `Pointer`s.

This breaks when we need to get the type parameter of a `Pointer` (e.g.
in a `is` or `as` check), but more importantly it means that we can't
cache boxed `true` and `false` values and return those cached values
when converting an `i32` to a boxed type as we don't know whether the
`i32` represents a bool or pointer.

Ideally we would have some kind of intermediate layer between
`wasm_builder` and dart2wasm that allows types like "a Wasm i32
representing an unboxed T" (for some T).

Alternatively we could attach extra information to `ValueType`s, for
example using expandos or maybe by adding a `dynamic` field to the base
class.

However it's unclear whether it's worth doing a major refactoring, when
a simpler alternative exist: we box `Pointer` values obtained from an
import in the Dart wrapper for the imported function.

VM already boxes `Pointer`s, so the performance should be acceptable.

This CL implements this simpler alternative of boxing `Pointer` values.

Tested: web/wasm/ffi/ffi_native_test updated.
Change-Id: I02e5c07fdb021a7b51ed5db2a44123b1608c9aad
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/383325
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
2024-09-05 11:24:10 +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
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
Vyacheslav Egorov 196f6c90c2 [vm] Create dart:_compact_hash library.
This opens possibility for other core libraries to access implementation
details of compact hash maps/sets and call special "core-library-only"
methods.

TEST=ci

CoreLibraryReviewExempt: VM only library change
Change-Id: I1d7524932c34e6fbe2428853dd547d58bef2d061
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/379840
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Slava Egorov <vegorov@google.com>
Reviewed-by: Lasse Nielsen <lrn@google.com>
2024-08-13 09:20:05 +00:00
Ömer Sinan Ağacan 67ccbea650 [dart2wasm] Move hash map/set classes to their own library
Similar to all the other libraries (list, string, typed data), this
moves hash map and set implementations to an internal library.

This will allow importing implementation clases in `dart:convert` and
accessing internals, in the dart2wasm port of
https://dart-review.googlesource.com/c/sdk/+/374564.

Change-Id: Ifcbad3b9546cb66d9f94dfe4bc891b2c2613aad9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/379760
Commit-Queue: Ömer Ağacan <omersa@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2024-08-09 12:58:07 +00:00
Ömer Sinan Ağacan 273594fe8a [dart2wasm] Fix switch compilation when switched expr is dynamic
When the `switch` expression's type is dynamic, call the equality method
of the `case` expressions.

Fixes #56321.

Change-Id: I25338ffebaf9130c39ce3aa66e7b4a7eb20aac71
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/377921
Commit-Queue: Ömer Ağacan <omersa@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2024-07-30 16:13:18 +00:00
Martin Kustermann 2cfa1726ad [dart2wasm] Extend specialized is/as helper functions for non-instantiated types
The specialized is/as checker functions are currently only used
for cases where the type to check against doesn't require checking
argument types.

This CL will extend that for cases where we do have to check
argument types, but we know that there's no need to substitute
type arguments. This saves the RTT system from searching for
the type argument substitution value.

Cases where this applies is for example
```
class Base<T> {}
class Sub<T> extends Base<T> {}
class Sub2<T> extends Base<T> {}
class Sub3<T> extends Base<T> {}

final l = <Base<Object>>[Sub<int>(), Sub2<String>(), Sub3<double>()];
foo<T>() {
  final Base<Object> b = l[1];
  if (b is Base<T>) { ... }
  if (b is Base<String>) { ... }
}
```

Here we know that all classes that directly or indirectly implement
`Base<T>` just pass their type parameter up the hierarchy.
=> There's no need to translate from type parameter array of subclass
to that of the super class.

We'll generate optimized is/as helpers for this case now, e.g.
```
func foo {
      ...
      local.get $var0
      global.get $global40
      call $<obj> is Base<T0>
      ...
}

func $<obj> is Base<T0> {
      // Check whether obj is in class-id range of Base subtypes
      i32.const 0
      local.get $var0
      struct.get $Base $field0
      i32.const 107
      i32.sub
      i32.const 3
      i32.ge_u
      br_if $label0
      drop
      // Call Object._getTypeArguments()
      i32.const 0
      local.get $var0
      local.get $var0
      struct.get $Base $field0
      i32.const 338
      i32.add
      call_indirect (param (ref $#Top)) (result (ref $Array<_Type>))
      // Check whether first type argument is subtype of T0
      i32.const 0
      array.get $Array<_Type>
      ref.null none
      local.get $var1
      ref.null none
      call $_TypeUniverse.isSubtype
      i32.const 1
      i32.ne
      br_if $label0
      drop
      i32.const 1
}
```

It has overall negligible code size impact (~ 0.1% increase)

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

Change-Id: Ic151269ad1b4a1456782b387beb4d646786ac493
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/374681
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2024-07-08 09:52:24 +00:00
Ömer Sinan Ağacan cc44c910eb [dart2wasm] Move boxed double and int types to their own libs
This allows injecting internally public members int and double members
to the libraries and it's consistent with how we've been restructuring
the dart2wasm standard libraries.

Tested: existing tests
Change-Id: I5261f3ab78667de93b4d7583d99bb9ae151a6cb8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/373241
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
2024-07-05 11:34:34 +00:00
Ömer Sinan Ağacan 844ced8016 [dart2wasm] Move list implementations to their own library
This WasmArray-backed list classes to `dart:_list`, similar to
`dart:_string` and `dart:_typed_data`

This will allow having internally-public list members like unchecked
getters and setters that we will use in the rest of the standard
library.

This CL doesn't add anything new, just renames things.

- `dart.core._ListBase` -> `dart._list.WasmListBase`

  This is to avoid confusion with `dart.core.ListBase` (defined by Dart
  standard library), and also to be consistent with `WasmTypedDataBase`
  and `WasmStringBase`.

- `dart.core._List` -> `dart._list.ModifiableFixedLengthList`

  Similar to the above, to avoid confusion with `dart.core.List`.

- `GrowableList.ofOther` -> `GrowableList.fromIterable`

  `ofOther` sounds like it should make a growable list from another
  growable list, but the type is more general than that. To reflect what
  it actually does we call it `fromIterable`.

Tested: Existing tests.
Change-Id: I24398765e1b0d549fc70b03ba94161479c5fc54c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/372622
Commit-Queue: Ömer Ağacan <omersa@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2024-06-21 14:46:58 +00:00
Ömer Sinan Ağacan a954aa0200 [dart2wasm] Move JSStringImpl to dart:_string
This moves `JSStringImpl` class from `dart:_js_types` to `dart:_string`.

This allows implementing a common base class with unchecked operations
to all string classes as internal methods (so users won't be able to
call them via `dyanmic`), and extension methods to call these unchecked
methods in libraries like `dart:convert`.

Uses of these methods are introduced in
https://dart-review.googlesource.com/c/sdk/+/372443.

Change-Id: Ie4cfe778654c42d62bc4a90391fe349fa783a42c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/372442
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
2024-06-20 13:08:03 +00:00