Commit Graph

849 Commits

Author SHA1 Message Date
Nate Biggs e3e82c95cb [dart2wasm] Skip constructor entrypoints of dynamic callable anonymous mixins constructors.
These mixin constructors are now marked callable by the dynamic interface annotator: https://dart-review.googlesource.com/c/sdk/+/430002

However, they are not directly invokeable and trying to compile the constructor entrypoints causes dart2wasm to crash. So we explicitly skip them when considering dynamic callable constructors.

Note: This is blocking internal rolls as code patterns like this show up in our internal codebase.

Change-Id: I76383aec07bc53a0dbd1581c0577cba370d956f9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/430801
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
Auto-Submit: Nate Biggs <natebiggs@google.com>
2025-05-23 15:37:33 -07:00
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
Ömer Ağacan 91374ffb12 [dart2wasm] Minify more errors
Transform front-end generated `throw` expressions (as indicated by the
kernel node's `forErrorHandling` flag) to error throwing functions that,
in minify mode, throw without details.

ACX demo sizes: (`-O2 --minify`)

- Before: 12,841,863 bytes
- After: 12,396,399 bytes
- Diff: -445,464 bytes, -3.46%

Issue: https://github.com/dart-lang/sdk/issues/60432
Change-Id: I3c0bfebd5a9cb460af312dafb63b5a0c9aeda22e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/430380
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
2025-05-23 03:31:36 -07:00
Martin Kustermann 226a667ef9 [dart2wasm] Add const bool.fromEnvironment('dart.tool.dart2wasm.minify')
We add the `dart.tool.dart2wasm.minify` environment define (analogous to
how dart2js exposes `dart.tool.dart2js.minify`).

We make `package:expect/variations.dart` then expose to tests what
guarantees they can assume, namely

   * `readableTypeStrings`
   * `preciseErrorsWithDetails`

Change-Id: I43da09ed924ca9137721f4d70b7a494e01bf36e4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/430680
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-05-23 01:26:38 -07:00
Nate Biggs 7d199f2171 [dart2wasm] Enable compiler asserts for dynamic module tests.
Fixes several asserts that were throwing when running with dynamic modules enabled.

Bug: https://github.com/dart-lang/sdk/issues/60743
Change-Id: I6bcd3723ecdee784b3d1603e1c87a47e3703a7f3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/423261
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2025-05-22 16:48:22 -07:00
Nate Biggs 63ee73a2ed [dart2wasm] Fix dynamic modules bugs.
- For direct calls in selector branch, ensure 'if' branches have correct inputs.

- For overrideable selectors, the receiver type cannot be known in the main module so use 'top' type. Technically we could do better if selectors tracked interfaces they targeted. Then we could take the LUB of all those classes. But this would be a significant refactor for a small benefit.

- Type checks on classes defined in the main module should use the class ID ranges from the main module rather than those from the dynamic submodule. This only applies to non-dynamic module extendable types (otherwise we'd use the RTT checks). So we know the class can only exist in one of the range sets, not both.

I discovered (1) from running Flutter which led me to create this test which uncovered (2) and (3).

Change-Id: I80f39835f66aa7cf0cff527341e3f4a948a7a0cb
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/430360
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2025-05-22 16:15:58 -07:00
Martin Kustermann 59a5a36230 [dart2wasm] Add missing polyfill for wasm:js-string.test and remove unused (and not polyfilled) wasm:js-string.fromCodePoint
This is a follow up to [0] that started to make use of
`wasm:js-string.test`.

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

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

Change-Id: Iac4b6ad1b845a486e4b8583e18d5f0941f55add6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/429943
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
2025-05-21 05:25:50 -07:00
Ömer Ağacan 273ae87679 [dart2wasm] JS interop: use dartify functions specific to types being converted
`dartifyRaw` converts based on the runtime type and it's slow. Use
dartify functions specific to the expected Dart types when converting JS
values to Dart.

Benchmarks before:

    WasmJSInterop.call.bool.0Args(RunTimeRaw): 0.023022675490940652 ns.
    WasmJSInterop.call.nullableBool.0Args(RunTimeRaw): 0.02189111747851003 ns.
    WasmJSInterop.call.num.0Args(RunTimeRaw): 0.028148974752406197 ns.
    WasmJSInterop.call.nullableNum.0Args(RunTimeRaw): 0.028181565023670287 ns.
    WasmJSInterop.call.double.0Args(RunTimeRaw): 0.022986874778290176 ns.
    WasmJSInterop.call.nullableDouble.0Args(RunTimeRaw): 0.02907384054925038 ns.
    WasmJSInterop.call.int.0Args(RunTimeRaw): 0.029822888283378746 ns.
    WasmJSInterop.call.nullableInt.0Args(RunTimeRaw): 0.029088963619688168 ns.
    WasmJSInterop.call.string.0Args(RunTimeRaw): 0.020517746991606835 ns.
    WasmJSInterop.call.nullableString.0Args(RunTimeRaw): 0.02004299314157027 ns.
    WasmJSInterop.call.JSArray.0Args(RunTimeRaw): 0.020724174653887113 ns.
    WasmJSInterop.call.nullableJSArray.0Args(RunTimeRaw): 0.020034212115113702 ns.
    WasmJSInterop.call.JSUint8Array.0Args(RunTimeRaw): 0.030711090544501003 ns.
    WasmJSInterop.call.nullableJSUint8Array.0Args(RunTimeRaw): 0.030679086538461537 ns.

Benchmarks after:

    WasmJSInterop.call.bool.0Args(RunTimeRaw): 0.019944312674022892 ns.
    WasmJSInterop.call.nullableBool.0Args(RunTimeRaw): 0.0206124732933157 ns.
    WasmJSInterop.call.num.0Args(RunTimeRaw): 0.024475179056557175 ns.
    WasmJSInterop.call.nullableNum.0Args(RunTimeRaw): 0.02481147236988503 ns.
    WasmJSInterop.call.double.0Args(RunTimeRaw): 0.01829122645842903 ns.
    WasmJSInterop.call.nullableDouble.0Args(RunTimeRaw): 0.024581756014165346 ns.
    WasmJSInterop.call.int.0Args(RunTimeRaw): 0.01788861473387381 ns.
    WasmJSInterop.call.nullableInt.0Args(RunTimeRaw): 0.02333933933933934 ns.
    WasmJSInterop.call.string.0Args(RunTimeRaw): 0.018238532110091743 ns.
    WasmJSInterop.call.nullableString.0Args(RunTimeRaw): 0.019454117647058823 ns.
    WasmJSInterop.call.JSArray.0Args(RunTimeRaw): 0.020055434567412395 ns.
    WasmJSInterop.call.nullableJSArray.0Args(RunTimeRaw): 0.019759825327510916 ns.
    WasmJSInterop.call.JSUint8Array.0Args(RunTimeRaw): 0.030023959269242288 ns.
    WasmJSInterop.call.nullableJSUint8Array.0Args(RunTimeRaw): 0.03057233704292528 ns.

JSValue wrapper type performance (`JSUint8Array`, `JSArray`) does not
change as we had a special case (before this CL) for those types and
directly boxed them.

Issue: https://github.com/dart-lang/sdk/issues/60357
Change-Id: I0ee8e49cf3ed801634e4c6e13b1c597b2e730d59
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/424021
Commit-Queue: Ömer Ağacan <omersa@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2025-05-21 04:39:30 -07:00
Martin Kustermann 7882084ab5 [dart2wasm] Correctly deal with abstract members in selector building
When we create a `Selector` we compute various information
based on all members belonging to that selector. Before this
CL this was split up: Some things like `ParameterInfo` was
calculated&updated while discovering members, others was
calculated after all members were discovered.

We now make all fields whose value depends on knowing all
members `late final` and initialize those fields after
having found all members of a selector.

When we compute the `ParameterInfo` of the selector we take
special care:

If there's non-abstract implementations and the selector is
not overridable by dynamic modules then we create the
`ParameterInfo` by looking only at the non-abstract members

=> This increases precision as we ignore the types and
   default values of optional parameters for abstract members

If there's no implementations of the selector it may still be
referred to by instance invocation AST nodes (which are
unreachable). The current code generator still needs a
`Selector.signature` in order to evaluate arguments for such
nodes.

=> We calculate `ParameterInfo` based on the abstract member
   and use `defaultSentinel` for optional parameters.

If a selector can have implementations in dynamic modules
then we cannot determine whether all implementations have
the same default value of optional parameters.

=> We calculate `ParameterInfo` using `defaultSentinel`
   value for all optional parameters. Ensuring the caller
   doesn't pass them but the callee will default to it's
   default value if it wasn't passed by caller.

See added test case for when we (before this CL) may
use unreachable members in the param info / signature
calculation and how that could lead to a compiler bug
(before this CL) due to a missing
 `VariableDeclaration.initializer`

TEST=web/wasm/unreachable_selector_implementation_test

Change-Id: I3628177f8e93c5d24ff0eb0ff8fee6121a4a08d1
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/428541
Reviewed-by: Nate Biggs <natebiggs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-05-19 06:46:18 -07:00
Martin Kustermann 5ed14b257f [dart2wasm] Remove dynamic module hacks in dispatch table
The dispatch table was so far getting rows for selectors even though
we don't need to call those via dispatch table:

* if there's only 0 targets in the module then it's unreachable
* if there's only 1 targets in the module then we can call it directly

Only if we have >1 targets in the module do we need a dispatch
table entry for the selector.

So we update the dynamic module updatable function mechanism to handle
the

   * unreachable
   * direct call
   * dispatch table call

cases just like we do in the normal code generator.

Change-Id: I7144771d61fefa09c215e28dd498a4f888c73d22
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/428981
Reviewed-by: Nate Biggs <natebiggs@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-05-19 06:02:44 -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
Martin Kustermann 4cd2c1da95 [dart2wasm] Make pkg/dart2wasm/tool/compile_benchmark auto-detect correct architecture on Mac
Running `pkg/dart2wasm/tool/compile_benchmark` on

  * MacOS on X64 should use xcodebuild/ReleaseX64
  * MacOS on ARM64 should use xcodebuild/ReleaseARM64

Change-Id: Id4cceb70eea1a3d0723e277325b21eff0fa36ef8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/428780
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-05-15 04:51:03 -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
Nate Biggs e9cd695d6b [dart2wasm] Disallow inlining code from main module into dynamic module.
This inlining would require increasing the exported surface area of the main module and would increase the complexity of the contract between modules.

Change-Id: I6d0bc50600b93ea86842cdf136fd7da6e30109bb
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/428202
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2025-05-13 09:13:47 -07:00
Nate Biggs 769853512d [dart2wasm] Fix static tear-off vtable instantiation.
When dynamic modules are enabled vtables should only include the dynamic call entry point.

Change-Id: Ifdf935a00039fe478c290d4bef585659cfdb9661
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/427860
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2025-05-13 08:39:41 -07:00
Martin Kustermann eb41d43c0d [dart2wasm] Make JS setters not return a value, fix incorrect variable name, use === instead of ==
Change-Id: I129f2b28535c8676b620e78389f6903d1448d904
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/426802
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-05-06 04:09:12 -07:00
Martin Kustermann df9e0f9729 [dart2wasm] Use polyfill for string constants if builtin isn't available
This makes us always emit strings into the `<app>.wasm` module only.
If the runtime doesn't support `js-string` builtin (and we
don't have `--require-js-string` builtin flag on) then we use a
JS Proxy object to resolve the string imports.

Now we only emit string constants in the `<app>.mjs` file iff those
cannot be encoded in the `<app>.wasm` file due to being invalid
utf-8 (such as unpaired surrogates, ...)

Change-Id: I7f4a0d61238e847c0c7dccadfa9e473f76512dc1
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/426462
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-05-05 03:19: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
Mayank Patke 9ece92ed0f [dart2wasm] Enable dynamic module validation.
This change requires some modifications to the dart2wasm flags for
dynamic module support. Previously, we determined that dynamic module
support was needed if `--dynamic-main-module` was passed. We determined
that the main module was being compiled if `--dynamic-interface-uri` was
also passed, and a submodule (AKA "dynamic module") otherwise. This
design prevented us from passing the interface specification to the
dynamic module validator when submodules were being compiled.

Instead, we now build with dynamic support when the
`--dynamic-module-type` flag is passed. Allowable values are `main` and
`submodule`. Both the main module URI and interface URI are currently
required for both dynamic module types. (The main module URI could be
made optional if we generated a default filename like we do for dynamic
module metadata.)

Dynamic interface validation is enabled by default and is controlled by
the `--validate-dynamic-modules` flag. This flag can be negated via
`--validate-dynamic-modules=false` or `--no-validate-dynamic-modules`.

Change-Id: I3165c3a8255205a61c3ccfe546f5d436472ed0d9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/425154
Reviewed-by: Nate Biggs <natebiggs@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
2025-05-02 10:15:40 -07:00
Nate Biggs 61952d4a1f [dart2wasm] Fix dynamic modules not generating code for dynamic module callable members if they are inherited by a concrete class.
Prior to this we were using the dispatch table to get the class IDs in order to register the callable references via `recordClassTargetUse`. However, the dispatch table only contains concrete classes.

A callable member may exist on an abstract class with no concrete subclasses. In this case the dispatch table would not contain any classes and the code for the member would never get generated. This is problematic when the abstract class (or a subclass) is dynamic module extendable. Then the callable member could be inherited onto a subclass in the dynamic module.

Also makes sure to mark members that are callable AND overrideable as invoked so that the updateable dispatch mechanism registers the member.

Change-Id: Icc079f76ab300aa761a47c8fbe24b8d9583d0da7
Fixes: https://github.com/dart-lang/sdk/issues/60602
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/423260
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2025-04-28 17:51:59 -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
Nate Biggs d8e046cac8 [dart2wasm] Add wrapper functions to settle type differences between main module and dynamic module dispatch tables.
Due to new overrides being introduced, the expected type of a function in the dispatch table can change between the main module and dynamic module. For functions imported from the main module into the dynamic module and included in the dynamic module's dispatch table, this can lead to type errors.

This change introduces a wrapper that casts any differing types to the type expected by the dyanmic module and includes the wrapper in the dispatch table instead.

Only creates the wrapper when the function types differ. The wrapper will only contains casts for the parameters (or return type) that differ, the others will be passed through without a cast.

Change-Id: I7265693adcd5d8f831b36ed1c8960fbf363b908c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/422345
Reviewed-by: Ömer Ağacan <omersa@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2025-04-28 17:51:59 -07:00
Nate Biggs 6750538b88 [dart2wasm] Move uncheckedEntry handling in updateable function wrappers to be branched at runtime.
For selectors that can be overridden in a dynamic module we produce a special dispatch wrapper that each module implements to do dispatch based on the details of its own dispatch table. Then at runtime dispatches of these selectors get sent to the wrapper for the module that owns the class of the receiver object.

uncheckedEntry references occupy a different space in the dispatch table and so require their own wrapper logic.

Currently we create separate wrappers for the unchecked and checked references. Now we use the same wrapper and have it take an extra argument that indicates if the call is checked or not. We then delegate to the appropriate offsets in the table based on this new flag.

Change-Id: I2004575d06c76c0a80570347747e179e8561463a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/422344
Reviewed-by: Martin Kustermann <kustermann@google.com>
2025-04-28 17:51:59 -07:00
Nate Biggs 597671800f [dart2wasm] Dynamic module shared constants.
Updates the main module to export constants accessible from dynamic modules. This includes the backing global store as well as the initialization function if the constant is 'lazy'. The dynamic module then imports these directly from the main module and does not need to duplicate any of the code.

Change-Id: I2154572688b1e913b720e400286e7fc305694c22
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/422343
Reviewed-by: Martin Kustermann <kustermann@google.com>
2025-04-28 17:51:59 -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 8c89088f4d [dart2wasm] Return null in void typed interop functions
Instead of boxing the return values just return `null`.

Also drop the result type from the imports when the return type is
`void`.

Change-Id: I871717480e516ac4cec260b2f9f4abe3dd5df535
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/424742
Commit-Queue: Ömer Ağacan <omersa@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2025-04-25 04:47:39 -07:00
Ömer Ağacan db448306dc [dart2wasm] Pass Dart double as f64 to JS interop
Don't convert Dart `double`s to `externref`s when calling JS interop
functions, pass them as `double`.

We pass other unboxed values (`int`s and `bool`s) as `externref`s, as
before:

- `int`s are most efficiently passed as `externref`s, as small integers
  can be converted to `i31ref` and externalized without allocation.

- `bool`s passed as `i32` mostly work because JS treats 0 as false and
  everything else as true, but the values can still be observed as
  numbers rather than bools, which causes some test failures.

Changes:

- Make raw interop procedures take `double` as argument, when the Dart
  type for the interop function argument is non-nullable `double`.

- Pass static type of the value and expected type (by the interop
  function) to `jsifyValue`.

- `jsifyValue` then takes the static type and expected type into account
  to avoid conversions when both are `double`s.

- `jsifyValue` is refactored to avoid the type conversion mapping
  allocation on every call.

New benchmark result before the changes:

    WasmJSInterop.call.void.1ArgsDouble(RunTimeRaw): 0.018275229357798167 ns.

After:

    WasmJSInterop.call.void.1ArgsDouble(RunTimeRaw): 0.014034965034965034 ns.

Issue: https://github.com/dart-lang/sdk/issues/60357
Change-Id: Ia70671f9a8e14f359f1119beda123e94aacdd2cd
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/422480
Commit-Queue: Ömer Ağacan <omersa@google.com>
Reviewed-by: Srujan Gaddam <srujzs@google.com>
2025-04-24 01:58:20 -07:00
Nate Biggs 2b4cce1377 [dart2wasm] Update dynamic module callable logic to more correctly exclude non-subtypes of wasm object struct.
Fixes: https://github.com/dart-lang/sdk/issues/60589
Change-Id: I5d74fcc7ee2f52c5efacc26c275ef1cf45264abf
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/423880
Commit-Queue: Nate Biggs <natebiggs@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
2025-04-23 09:05:00 -07:00
Alexander Markov 6156dd6e59 [dynamic modules] Respect dyn-module:language-impl:* pragmas during dynamic module validation
Dynamic module validation now accepts reference to classes and members
from 'dart:*' libraries if they are annotated with
dyn-module:language-impl:* pragmas. This matches the behavior of
dynamic interface annotator.

This is a follow-up to
https://dart-review.googlesource.com/c/sdk/+/418663.

Also, when checking for possible dynamic overrides in TFA,
treat _enumToString from dart:core as a public name because
front-end desugars enums into classes which override
private _enumToString method in a different library.

TEST=pkg/dynamic_modules/test/data/enum
Fixes b/395992622

Change-Id: I45ed9855f806224a510fa4e56962607de3371eb0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/423820
Reviewed-by: Slava Egorov <vegorov@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Nate Biggs <natebiggs@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
2025-04-23 07:43:02 -07:00
Ömer Ağacan 6c5d3fb71f [dart2wasm] Pass arg counts from JS to Dart as i32
The JS code we generate for converting a Dart function to a function
callable from JS currently looks like this:

      _194: f => finalizeWrapper(f, function(x0,x1) {
          return dartInstance.exports._194(f,arguments.length,x0,x1)
      }),

`_194` called by this JS code is an export:

    (func $_194 (;296;) (export "_194")
        (param $callback (;0;) anyref)
        (param $argumentsLength (;1;) f64)
        (param $x1 (;2;) externref)
        (param $x2 (;3;) externref)
        (result externref)
        ...)

Here the `double` parameter current causing redundant boxing and slow
`BoxedDouble` operations, because it's compared against an `int` values
when checking whether the right number of arguments is passed to the
Dart function: (in the body of `$_194`, unoptimized)

    local.get $argumentsLength
    local.set $var5
    i32.const 75
    local.get $var5
    struct.new $BoxedDouble
    global.get $global664
    call $BoxedDouble.>=
    ...

We could compare it against double values instead of int to improve
this, but a better way is to make `argumentsLength` an `i32`, as the
argument will always be a small integer (smi), which can be passed
without allocation, so that's what we do in this CL.

The same code with this CL: (unoptimized)

    (func $_194 (;294;) (export "_194")
        (param $callback (;0;) anyref)
        (param $argumentsLengthWasmI32 (;1;) i32)
        (param $x1 (;2;) externref)
        (param $x2 (;3;) externref)
        (result externref)
      ...
      local.get $argumentsLengthWasmI32
      i64.extend_i32_s
      local.set $argumentsLength
      local.get $argumentsLength
      i64.const 2
      i64.ge_s
      ...)

Change-Id: Ib9d4d625bea896e8b680e7e5bffc02c19db0d3cf
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/423740
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
2025-04-23 01:00:20 -07:00
Ömer Ağacan 86e31f9ad9 [dart2wasm] Document InteropSpecializerFactory._jsObjectLiteralMethods
Change-Id: Ib29757f95c2ed5964d796c7f481e27aaba8848ea
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/422500
Reviewed-by: Nate Biggs <natebiggs@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
2025-04-16 02:09:29 -07:00
Nate Biggs 4f818d498b [dart2wasm] Generate additional mjs module with JS support values.
This updates the dynamic module compile to emit an extra mjs file containing JS-defined functions and values (i.e. Strings). The runtime then loads this JS module along with the wasm module and links the two.

Change-Id: Iedddf378822f3abd957f0ca9016a9f7883854973
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/422341
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2025-04-15 13:06:11 -07:00
Nate Biggs 62e7377dfa [dart2wasm] Update closure types to be not final when dynamic modules are enabled.
Dynamic modules can introduce new closure shapes that result in new subtypes of certain closure helper structs (#Vtable, #Closure, etc.). Because we need the struct definitions to match across modules, we need to assume that there may always be subtypes of these structs and not mark any of them as final.

Change-Id: If578dc463d0f80352319d0bd44e1e4cecba28bee
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/422340
Reviewed-by: Ömer Ağacan <omersa@google.com>
2025-04-15 13:06:11 -07:00
Ömer Ağacan e4676d5b56 [dart2wasm] Convert simple async function bodies to sync Future.value calls
This transforms functions like

    async foo() => const ...;

to

    foo() => Future.value(const ...);

The transformation is done when the async function body is a `const`
expression or a basic literal (string, int, double, bool).

These expressions don't have side effects and they cannot throw, so it's
safe to convert them to `Future.value`s.

This makes the generated code in the ACX demo 2.5% smaller: (`-O4` with
symbol names removed)

- Before: 9,040,020 bytes
- After: 8,805,471 bytes
- Diff: -234,549 bytes, -2.59%

With this we also remove the same special case in the backend to avoid
generating a state machine for these functions, as the special case
handled before the backend now and backend never sees this kind of
functions.

(Technically with inlining or other backend optimizations it could still
see these cases, but the pattern it matches is too strict, and currently
the special case in the backend doesn't do anything on the ACX demo.)

Note: I tried implementing the same in dart2js's await lowering pass in
https://dart-review.googlesource.com/c/sdk/+/422061 and reusing that
pass in https://dart-review.googlesource.com/c/sdk/+/420140.

However while that transformed simple programs as expected, in ACX demo
it still introduced a lot of `Future.sync` calls and made the overall
binary larger. I think we never want to introduce `Future.sync` calls
(at least until we improve code size for closures, see relevant issue
https://github.com/dart-lang/sdk/issues/60458), so for now we don't
reuse dart2js's pass.

Issue: https://github.com/dart-lang/sdk/issues/60433
Change-Id: I206ac8c6081201041f67e7fc91776077e52180a0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/422120
Reviewed-by: Nate Biggs <natebiggs@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
2025-04-15 02:41:05 -07:00
Ömer Ağacan a3e243154c [dart2wasm] Simplify void JS return value boxing
Change-Id: I875a8e0bc83f2338b74060e871f22a02ef808755
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/422301
Reviewed-by: Nate Biggs <natebiggs@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
2025-04-14 09:48:40 -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 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 ddba36c234 [dart2wasm] Avoid redundant type checks in awaits
When the `runtimeCheckType` field of an `AwaitExpression` is null we
don't have to check the type of the object being awaited.

This should improve `await` performance in cases when the awaited object
is already a `Future` with the right type parameter.

Change-Id: Ib456b6d6d11bc08c4ba461736c9819c2d3a74fe7
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/421302
Commit-Queue: Ömer Ağacan <omersa@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2025-04-10 04:36:52 -07:00
Nate Biggs 0c17d7fc89 [dart2wasm] Cleanup member and reference serialization.
The return value is unused and previous changes ensure members always exist.

Change-Id: I476c11261048b5fe48d0603b899e1dde9235a705
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/419500
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2025-04-08 15:16:03 -07:00
Ömer Ağacan b372b54ecb Reland "[dart2wasm] Don't dartify JS values when returning as void"
This is a reland of commit 4bb05dc562

Initial commit didn't properly box the `void` return value, so when we
cast the return value to `ref #Top` to pass it to a Dart function (as
`Object?` or `dynamic`) it caused "illegal cast" traps.

In this commit we properly box the `externref` as `JSValue` (a proper
Dart class). A new test added passing the return value of a `void` JS
return to `print`.

Original change's description:
> [dart2wasm] Don't dartify JS values when returning as void
>
> When calling a JS function that returns `void`, avoid dartifying the
> result.
>
> Technically the return value of `void` functions can still be used, by
> casting the return type to `Object?` or `dynamic`. However this
> shouldn't be done, and `dartifyRaw` overhead just to support this case
> which should be extremely rare is too much.
>
> Any hacky code that uses return values of `void`-returning JS interop
> functions can manually dartify the retrun values with `toDart` or
> similar.
>
> Issue: https://github.com/dart-lang/sdk/issues/60357
> Change-Id: Ic370c7cf6eb6982f61f8a07c91e3bb93c5345ac6
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/417240
> Reviewed-by: Srujan Gaddam <srujzs@google.com>
> Reviewed-by: Martin Kustermann <kustermann@google.com>
> Commit-Queue: Ömer Ağacan <omersa@google.com>

Change-Id: I264211cca4f6b87e63d68909647975ab96f0b5bd
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/421080
Commit-Queue: Ömer Ağacan <omersa@google.com>
Reviewed-by: Srujan Gaddam <srujzs@google.com>
2025-04-08 09:29:33 -07:00
Derek Xu 1dccb827aa Revert "[dart2wasm] Don't dartify JS values when returning as void"
This reverts commit 4bb05dc562.

Reason for revert: suspected to be blocking rolls into Flutter, e.g. https://github.com/flutter/flutter/pull/166618

Original change's description:
> [dart2wasm] Don't dartify JS values when returning as void
>
> When calling a JS function that returns `void`, avoid dartifying the
> result.
>
> Technically the return value of `void` functions can still be used, by
> casting the return type to `Object?` or `dynamic`. However this
> shouldn't be done, and `dartifyRaw` overhead just to support this case
> which should be extremely rare is too much.
>
> Any hacky code that uses return values of `void`-returning JS interop
> functions can manually dartify the retrun values with `toDart` or
> similar.
>
> Issue: https://github.com/dart-lang/sdk/issues/60357
> Change-Id: Ic370c7cf6eb6982f61f8a07c91e3bb93c5345ac6
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/417240
> Reviewed-by: Srujan Gaddam <srujzs@google.com>
> Reviewed-by: Martin Kustermann <kustermann@google.com>
> Commit-Queue: Ömer Ağacan <omersa@google.com>

Issue: https://github.com/dart-lang/sdk/issues/60357
Change-Id: I27e0a7355eeee94d1dc02fba0c9569b53954d1c5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/420780
Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Derek Xu <derekx@google.com>
2025-04-07 10:01:41 -07:00
Ömer Ağacan 4bb05dc562 [dart2wasm] Don't dartify JS values when returning as void
When calling a JS function that returns `void`, avoid dartifying the
result.

Technically the return value of `void` functions can still be used, by
casting the return type to `Object?` or `dynamic`. However this
shouldn't be done, and `dartifyRaw` overhead just to support this case
which should be extremely rare is too much.

Any hacky code that uses return values of `void`-returning JS interop
functions can manually dartify the retrun values with `toDart` or
similar.

Issue: https://github.com/dart-lang/sdk/issues/60357
Change-Id: Ic370c7cf6eb6982f61f8a07c91e3bb93c5345ac6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/417240
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
2025-04-04 04:04:24 -07:00
Ömer Ağacan 26a065c60d [wasm_builder] Update names of instructions extern.internalize/externalize
These instructions were renamed to any.convert_extern and extern.convert_any in
Wasm GC spec commit:
https://github.com/WebAssembly/gc/commit/5bca3f7de6ad117a0d4349c138b5e32981c88d2f

Change-Id: Ie696541f75974c69828fb317754e3b613691eb4d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/420141
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
2025-04-03 14:24:31 -07:00
Ömer Ağacan 68fd76fa68 [dart2wasm] Special case async functions with simple bodies
When an async function directly returns a `const` value, a basic
literal, or implicit `null` (empty body), omit the state machine loop
and exception handling.

See the new test for the cases this simplifies.

Issue: https://github.com/dart-lang/sdk/issues/60433
Change-Id: I25c25cb42c02cc3a9155e01ce205f63b567a1008
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/419840
Reviewed-by: Martin Kustermann <kustermann@google.com>
2025-04-02 05:40:44 -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
Nate Biggs 52da6a0037 [dart2wasm] Add support for overrideable function dispatch to handle signature changing param count.
We compare the signatures from the main module and the dynamic module. If the dynamic module has more parameters these must be from an override with extra optional paramters so we can provide the default value.

Change-Id: I818187fde3e38812dcdae0a14bbeefec4ee91e51
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/416460
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
2025-04-01 21:55:15 -07:00
Ömer Ağacan dad797e04a [dart2wasm] Remove unused default states in br_tables in state machines
When generating the `br_table` for the state machines in `sync*` and
`async` functions, we currently generate a default state (required by
`br_table`) with an `unreachable` instruction.

Instead, remove the default state and make the last state in the
function the default state.

This saves a few instructions per `sync*` and `async` functions.

Issue: https://github.com/dart-lang/sdk/issues/60433
Change-Id: Ie89c3581304a97082628b150cc5604f9e4c2849e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/419540
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
2025-04-01 05:01:13 -07:00
Martin Kustermann bc76743c0f [dart2wasm] Move canonicalization of symbols to one place
Only in `Function.apply` we have the problem of determining
the order of named parameters for calling the closure. In all
other cases, the compiler can ensure the caller and callee
agree on the order.

There's one place where we determine this order. That place
can also be used to swap out user-provided `new Symbol()`s that
were passed to `Function.apply` with the canonicalized
`const Symbol()` instances the compiler uses for the
closure functions.

Doing so allows us to use `identical()` for symbol comparisons
everywhere else in the system.

Change-Id: Ib9833e708ba197c8b9ef18ef4bf9e7a64bdf9a40
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/419201
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2025-04-01 04:28:30 -07:00