-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Scala.js: Support js.async and js.await, including JSPI on Wasm. #23846
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
5174016
to
42404e1
Compare
Looks like Node.js is too old. @hamzaremmal Could we upgrade to the latest Node.js (24.7.0) on the runners? |
Previously, if an `@JSName` annotation had an argument that was not a literal, but a reference to a constant expression (such as a `final val`), it would not be constant-folded in the generated Scala.js IR. This produced worse code than necessary. For Wasm, it was particularly bad, as the names must then be evaluated on the Wasm side instead of being pushed to the custom JS helpers.
42404e1
to
61946f8
Compare
Otherwise looks good to me! |
This is forward port of the Scala.js commit scala-js/scala-js@0d16b42 The body of `Closure` nodes always has a simple shape that calls a helper method. We previously generated that call in the body of the `js.Closure`, and marked the target method `@inline` so that the optimizer would always inline it. Instead, we now directly "inline" it from the codegen, by generating the `js.MethodDef` right inside the `js.Closure` scope. As is, this does not change the generated code. However, it may speed up (cold) linker runs, since it will have less work to do. Notably, it performs two fewer knowledge queries to find and inline the target method. It also reduces the total amount of methods to manipulate in the incremental analysis. More importantly, this will be necessary later if we want to add support for `async/await` or `function*/yield`. Indeed, for those, we will need `await`/`yield` expressions to be lexically scoped in the body of their enclosing closure. That won't work if they are in the body of a separate helper method.
This is forward port of the compiler changes in the two commits of the Scala.js PR scala-js/scala-js#5130 --- We add support for a new pair of primitive methods, `js.async` and `js.await`. They correspond to JavaScript `async` functions and `await` expressions. `js.await(p)` awaits a `Promise`, but it must be directly scoped within a `js.async { ... }` block. At the IR level, `js.await(p)` directly translates to a dedicated IR node `JSAwait(arg)`. `js.async` blocks don't have a direct representation. Instead, the IR models `async function`s and `async =>`functions, as `Closure`s with an additionnal `async` flag. This corresponds to the JavaScript model for `async/await`. A `js.async { body }` block therefore corresponds to an immediately-applied `async Closure`, which in JavaScript would be written as `(async () => body)()`. --- We then optionally allow orphan `js.await(p)` on WebAssembly. With the JavaScript Promise Integration (JSPI), there can be as many frames as we want between a `WebAssembly.promising` function and the corresponding `WebAssembly.Suspending` calls. The former is introduced by our `js.async` blocks, and the latter by calls to `js.await`. Normally, `js.await` must be directly enclosed within a `js.async` block. This ensures that it can be compiled to a JavaScript `async` function and `await` expression. We introduce a sort of "language import" to allow "orphan awaits". This way, we can decouple the `js.await` calls from their `js.async` blocks. The generated IR will then only link when targeting WebAssembly. Technically, `js.await` requires an implicit `js.AwaitPermit`. The default one only allows `js.await` directly inside `js.async`, and we can import a stronger one that allows orphan awaits. There must still be a `js.async` block *dynamically* enclosing any `js.await` call (on the call stack), without intervening JS frame. If that dynamic property is not satisfied, a `WebAssembly.SuspendError` gets thrown. This last point is not yet implemented in Node.js at the time of this commit; instead such a situation traps. The corresponding test is therefore currently ignored. --- Since these features fundamentally depend on a) the target ES version and b) the WebAssembly backend, we add more configurations of Scala.js to the Scala 3 CI.
61946f8
to
0f8368f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, just waiting for the updated Node.js I assume
I already told @sjrd but the Node.js might not happen as I'm thinking of getting rid of the self-hosted runners. |
This PR forwards two PRs from Scala.js for Scala 2: scala-js/scala-js#5081 and scala-js/scala-js#5130.