-
Notifications
You must be signed in to change notification settings - Fork 396
Add WebAssembly Linker Backend (with WasmGC and Wasm ExceptionHandling) #4928
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
Comments
This is an awesome writeup, thank you @tanishiking! Learning about WebAssembly was long overdue for me and this helped tremendously. I posted some questions / comments on the discourse thread. |
I've been wondering if compiling to WasmGC via Scala.js is really the best immediate solution (although it's hard to say what's "good" unless we define what our goals are in Wasm support) in the thread. |
I'm considering developing a Wasm IR (like JS IR in Scala.js) with a structure closely resembling Wasm (WAT S-expr format). This IR would compile to either WebAssembly binary or WAT, similar to what has been done in Kotlin/Wasm. Alternatively we could use Binaryen C API to emit WebAssembly binary or text formats (S-expr). We can generate the Binaryen IR from the JVM, and we Binaryen will generate Wasm binary/text, we don't need to implement those conversions. (this would limit the Scala.js wasm linker backend to only work on the JVM platform though). |
Why are you considering a new IR? Would it be built from SJSIR, or directly from Scala compiler ASTs? |
I think @tanishiking just means an AST data structure for Wasm. Then
@tanishiking It would be more in the spirit of this repo to define our own AST in Scala. As you mentioned, Wasm as such is not that complicated; the AST should be simpler than
|
Hi everyone 👋 long time no see! Stepping by just to let you know that there are at least a couple of Java projects that might spare you some cycles @tanishiking: |
@lrytz Ah yeah that's right, I meant in-memory data structure that serialize to binary/text, which is more like AST, thank you for the clarification @sjrd ! @andreaTP Thank you for your input. I'll definitely check out those projects!
That sounds great; I was personally looking for such a project 😃 However, for this specific project, I believe developing the functionality (to writing Wasm binary) in Scala would be a better fit. If we implement it in Scala, we can compile it's linker backend itself in Scala.js (which wouldn't be possible if it's in Java) as @sjrd mentioned. However, I'm willing to giving back insights to those projects in the future! |
While I'm still far from reaching a level where I can show it to others (the generated WAT code is invalid, and binary generation is not implemented yet), but I'm working implementing a prototype in this repository. https://github.com/tanishiking/scala-wasm I aim to create something that can at least perform operations with primitive types, function calls, virtual dispatch, and top-level method exports, ignoring complex data structures like String or Array and transforming something like java stdlib. Once I achieve that, I plan to tidy it up and share it as PR against scala-js for feedback. |
We had a meeting with @tanishiking and @gzm0 a few days ago about this, and we would like to share a summary. State of the current implementation of Scala-WasmIt is complete wrt. the semantics of Scala.js (i.e., the test suite passes), excluding:
Use casesThere are two big directions that a Scala-to-Wasm user can take: to use a JS host (typically for the Web) or another Wasm host. For a JS host, we definitely want the semantics of Scala.js. The benefits will certainly be reduced code size, and if we're lucky, performance improvements. For that use case, we need not change anything to the language semantics nor to the IR. For non-JS hosts, the benefit for users is to target an entirely new ecosystem. In that scenario, we will want to introduce interop features for at least the Component Model of Wasm, and instead remove the JS interop semantics. In that case, we definitely need IR changes. More critically, we will need a link-time way to reliably (i.e., from a reachability point of view) use one path or another. Either we do this with an entirely different ecosystem and IR (SWIR?), or we amend the Scala.js IR. If amending the Scala.js IR is not too disruptive, this would help adoption of Scala-to-Wasm, as the majority of the Scala.js ecosystem of libraries could be reused as is. We would avoid the slow bootstrap problem entirely. We discussed one possible link-time dispatch mechanism that would fit in the Scala.js IR: a "link-time if-else". Its conditions would be restricted to a few elementary operations, based on a config dictionary of Merging into Scala.js coreThe main driver at this stage for wanting to merge into Scala.js core is the ability to influence the behavior of the Optimizer. Currently Scala-Wasm cannot deal with the output of the Optimizer, because it produces IR that does not IR-check. We've known this for a long time, of course. The main culprit is that the optimizer internally refines the type of some values, but does not reify that information into the produced IR. That's fine for JS but for Wasm we need a type-preserving transformation. IMO what we need is to insert special " In terms of user experience, if we merge Wasm into Scala.js core, using Wasm would be one One possible disadvantage of merging is that it would introduce more friction for experiments, notably to target non-JS hosts. However, we discussed that if that proves problematic, experiments could happen in a fork of the Scala.js repo until they are ready to be merged, or separately shipped. Finally, we also mentioned possible de-merging in the future. We will need to clearly document that support for Wasm in the core is susceptible to be moved out at some point. If that happens, we should have learned what hooks we would need to effectively do that in the meantime. Considering all these things, we decided to merge Scala-Wasm into Scala.js core at the moment. We will need to clearly document that support for Wasm in the core is susceptible to be moved out at some point. Release cycle and versioningWith Wasm in the core, versioning will not change. We reserve the right to extract Wasm out of the core in a minor version. The release cycle should not be adversely impacted. If anything, it might provide new incentive to uphold our 2-month schedule between releases. BenchmarksWe decided that we do not need to have benchmarks before merging. Next stepsGiven all the above, the next steps are:
|
TY for the writeup. Comments from my side: Library Management We have also discussed the possibility of (selectively) using different artifacts for JS / wasm instead of a linking time mechanism and the difficulty / strain this would put on build / artifact resolution tooling. Config One thing that isn't entirely clear to me in terms of linker interface is how to handle extensions of output files. For JS, we currently have IIUC, for WASM we'd want |
Regarding |
FWIW: It seems the WASM spec is quite clear about the file extensions: https://webassembly.github.io/spec/core/binary/conventions.html IIRC, the reason we've introduced |
PR: #4988 |
I think we can call this done? @sjrd, @tanishiking ? |
Yes, indeed. |
I don't quite understand how The Can you take For |
We use scala-js/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala Line 745 in 4150da9
(a pattern match is compiled down to For interfaces it's more complicated because they do not have WASM types: scala-js/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/ClassEmitter.scala Line 493 in 4150da9
|
The first comment is the very rough sketch: you might be interested in the updated internal documentation here https://github.com/scala-js/scala-js/blob/4150da9e879e8b43511949977ec6c1cce6431b34/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/README.md |
Still WIP writing, thinking about adding a wasm (gc) support based on Scala.js
Overview
WebAssembly support in Scala.js was discussed in the presentation titled ["Scala.js and WebAssembly, a tale of the dangers of the sea" by Sébastien Doeraene, which can be found on YouTube here.
The presentation highlighted that in 2019, there were certain aspects lacking in the WebAssembly support for Scala: WasmGC was quite early stage like phase 1 or 0 at that time.
In late 2023, the WasmGC extension became the default in Chrome (V8)1 and Firefox2.
The Exception Handling proposal is now available on many WebAssembly (Wasm) engines, including those with JavaScript engines as embedders1. Given this development, it is an opportune moment to reconsider WebAssembly support for Scala in 2024. Notably, various garbage-collected languages such as OCaml, Kotlin, Java, and Dart support WebAssembly utilizing WasmGC.
This proposal suggests adding a new linker backend designed to compile linked sjsir modules into WebAssembly using
Why Wasm?
Wasm was initially designed for faster performance close to native code execution within web browsers. However, its usecases extend far beyond the browser, owing to its robust security features and portability. Also, the introduction of WASI further expands its range of use cases.
For more details: Exploring WebAssembly outside the browser - Atamel.Dev
Other ways to compile from Scala to Wasm?
Why do we propose compiling WebAssembly from Scala.js (SJSIR) when there are various methods to compile Scala to WebAssembly?
How?
Add a new implementation of org.scalajs.linker.standard.LinkerBackEnd that compiles to WasmGC.
This design is based on the observation that how Kotlin/Wasm and J2CL compile high-level constructs to WebAssembly. It's worth noting that the design might undergo changes during implementation.
A few notes on WasmGC and Kotlin/Wasm:
Class definition
The class definition will be represented as a
struct
type in WasmGC.vtable
anditable
.itable
, will explain more ininterface call
section.typeInfo
, andhashCode
fields, followingKotlin/Wasm
, but it might not be needed, let's see.The vtable contains the function references to the methods.
The constructor will setup vtalbe, itable, and initialize the fields.
Virtual call
The definition of
Derived
will be likeThe
bar
method (that contains virtual call tofoo
) will beWhy we don't use
call_indirect
as Rust does?table
is basically one big virtual table in a module (in Wasm 1.0), which is untyped alternative to typed function references.table
, the classes still need to the pointer (table index) to the methods. So, what's the point of usingcall_indirect
with WasmGC?Interface call
The
Cat
class will have an itableThe interface call site (
baz
method) will be looks like:The method to call will be searched for based on the signature at compile time, and we'll just access to the itables by index.
concurrency
I haven't yet delved into this area much, but it seems webassembly native threads feature is already at phase 4, and available at most of popular runtimes including wasmtime 4 thanks to wasi-threads
Image from WebAssembly Threads - HTTP 203 - YouTube
Focus on sindle-threaded at first, and eventually support multi-threading later on.
exception handling
Relies on wasm native exception-handling
Following the Kotlin/Wasm lowering strategy https://github.com/JetBrains/kotlin/blob/4786c945d933c82c9560a9923f33effc59a80093/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/TryCatchCanonicalization.kt#L24-L67
For try catch
We'll have only one
exception tag
in the module who's type isjava.lang.Throwable
. When we throw an exception, it's always compiled tothrow 0
with an operand of type (or subtype of)java.lang.Throwable
.Also, the catch clause in Wasm always catch the
java.lang.Throwable
withcatch 0
, and then validate the exception's type. If none of catch clauses (in Scala) caught an exception, rethrow the exception.try/catch
For example, (
ExceptionA extends Exception
andExceptionB extends Exception
)This will be compiled to
finally
try/catch
inside, is the normal try/catch handling described abovetry/catch
outside is for if the exception isn't caught by any catch clauses, do something in finally clause and rethrow the exception.JS interop
TBD
Q&A
Any advises or questions are welcome, especially from someone knows more about Scala.js / SJSIR internal.
Related
Footnotes
WebAssembly Garbage Collection (WasmGC) now enabled by default in Chrome | Blog | Chrome for Developers ↩
Firefox 120.0, See All New Features, Updates and Fixes. ↩
TeaVM is suffering from the problem No, WASM is slow. As a developer of TeaVM I can claim this. First of all, JS eng... | Hacker News and the author thinks that Wasm GC and EH will improve the situation Chrome is now released with Wasm GC enabled by default - TeaVM ↩ ↩2
Feature Extensions - WebAssembly ↩
The text was updated successfully, but these errors were encountered: