Compile to LLVM or WASM

I’m curious if anyone can point me to information on the process and progress being made toward compiled Scala 3 and whether there is a Scala-specific intermediary which branches to LLVM and WASM or if those are two entirely independent projects.

I’d also like to know how much progress has been made on those.

1 Like

Yes indeed! Check out the Scala Native project. It targets LLVM, which then makes it possible to target a variety of architectures, including WASM.

The stable Scala Native 0.4.x series is already supported by several major libraries: it is fully possible to deploy an HTTP microservice or CLI application compiled to native. Meanwhile we are actively working towards 0.5.x which brings the necessary changes to support multi-threading and WASM.

Further reading:

10 Likes

“The Future of WebAssembly for Scala” talk was just posted :slight_smile:

9 Likes

Great, Awesome! We want this. Keep doing the good work.

2 Likes

Excellent development. I would be interested to write a sound synthesis / DSP engine in Scala native, targetting both native sound API on the desktop and web audio in the browser. I could probably do without threads and exceptions, perhaps even without garbage collection. Any news on the current progress?

1 Like

Yes, there is a proposal here:

That’s a really nice writeup!

I’m pretty unfamiliar with WebAssembly, so I’m wondering where does a WASM backend fit given the existing JVM, JS and Native backends.

The ticket has a great " Why Wasm?" section which I have a few comments about. I’m very interested to hear more thoughts (ping @WojciechMazur, for example).

“Faster code execution in browser” (20-30% is mentioned) is nice, but not a game changer ([1]). “Plugins” is also nice to have, but probably won’t be a driver for Scala adoption.

The cloud / edge / IoT use cases seem a lot more important. That’s where all the excitement seems to be and it looks like an opportunity for Scala.

In these scenarios outside the browser, is JS interop relevant at all? If not, is the approach of compiling to WASM through sjsir still the best option, vs via Scala native / LLVM?

Again, the writeup has a great “Other ways to compile from Scala to Wasm” section, explaining that other approaches would be slower and / or not integrated with WASM GC.

I think the goal should be for Scala to be as good as a first-class citizen on WASM as possible (for a GC’d language). So once WASM GC (and probably exception handling) is available in all the runtimes (including the new browser-independent ones like wasmtime), that is probably the best option. Though I wonder how long will it take for the runtimes to get there? I found a feature table.

The “Interop with other languages” part seems to be important. Is WebAssembly also an ecosystem of libraries / SDKs, or will it become one? What will it mean for Scala to integrate with WASI?


[1] On the other hand, since wasm has proper primitive types and (in the future?) exception handling, it might actually be a good backend for Scala.js in the long term (@sjrd)? Though JS interop might be slower? Either way, in the big picture it doesn’t look like a game changer for Scala.

4 Likes

The problem with traditional non-GC WASM is that it’s basically infeasible to have GC. You can’t scan thread-stacks for roots. So your only option is a shadow stack
where all pointers have to be stored. I fear this would make Scala much slower than competing languages like Rust.

1 Like

vs via Scala native / LLVM?

As @odersky explained, the problem with compiling to Wasm via LLVM is, there’s no way to express WasmGC primitives with LLVM.

Can WasmGC adopt a similar toolchain model as WasmMVP, and in particular use LLVM? Unfortunately, no, since LLVM does not support WasmGC (some amount of support has been explored, but it is hard to see how full support could even work). Also, many GC languages do not use LLVM–there is a wide variety of compiler toolchains in that space. And so we need something else for WasmGC.
(quote from A new way to bring garbage collected programming languages efficiently to WebAssembly · V8)

As far as I know, TeaVM and AssemblyScript ships with their own GC code instead of WasmGC. However, to make GC work, we need to ship with our own GC and allocation code, and also need to manage our own shadow stack on linear memory to maintain GC roots. That will make Scala on Wasm slower. (see the comment from TeaVM author).

Fortunately, WasmGC is already at phase 4, and pure Wasm runtimes are actively working on it.

Opting for our own GC might be an option if we wanna compile Scala to a WASI as soon as possible, at the expense of execution performance. However, in 2024, waiting for WasmGC would lead to better Wasm support for Scala in a long run.

(Also, If the goal is to compile Scala to WASI without relying on WasmGC, alternatives such as using Scala.js with QuickJS or TeaVM can be explored).


Regarding from ScalaNative (NIR) to Wasm, I’m not 100% sure which one (SJSIR to Wasm or NIR to Wasm) is better choice TBH. Especially if we don’t care JS interop.

Technically, both NIR and SJSIR can be lowered to WasmGC I guess.

With SJSIR we can benefit the Scala.js’s JS interop API for Wasm on JS environment. On the other hand, I couldn’t find the benefits of compiling from NIR.

It could be interesting to integrate with libc using wasi-libc. However, it’s impossible to link with native library with wasm (It works only if the native library compiles to wasm component + WIT). Wasm backend is basically the new different backend, most of the scala-native ecosystem won’t work with wasm out of the box.

I’d like to hear some more opinions which compilation path would be desirable @WojciechMazur @sjrd


The “Interop with other languages” part seems to be important. Is WebAssembly also an ecosystem of libraries / SDKs, or will it become one?

Yes, Wasm Component Model proposal is the thing. Although still in the early stages, there are high expectations and a number of related implementations and ecosystems are already being built, such as wit-bindgen and jco. They generates a glue code for WIT.

Please check out the UseCase for more details.

3 Likes

Another huge benefit of sjsir → wasm is that the Scala.js linker should work out of the box, so the code would be minimized and optimized. I don’t know how this looks on the Scala native side, probably they rely on LLVM for optimizations / tree shaking?

ScalaNative also has an NIR-level optimizer scala-native/docs/blog/interflow.md at main · scala-native/scala-native · GitHub
While ScalaNative relies on LLVM optimizers, WasmGC is also an quite optimizable language and binaryen provides more optimization passes for WasmGC :+1:

SJSIR versus NIR is the wrong question. The correct question is: do you want Scala.js language semantics or Scala Native language semantics?

If you want Scala.js language semantics, compile Scala.js IR to WasmGC.

If you want Scala Native language semantics, compile NIR to Wasm with linear memory (or maybe WasmGC anyway, but there will be some semantics like direct access to the object layout that will be lost).

Oh and, if you want Scala/JVM language semantics, then perhaps TeaVM is the right solution.

IMO, the avenue that is the most promising is as the proposal was written: take Scala.js language semantics, and compile SJSIR to WasmGC. The Scala.js IR is at a very good abstraction level to be compiled to WasmGC. Even its control flow structures have direct equivalences in wasm (fun fact: Scala.js IR and Wasm independently and concurrently invented the same “labelled block expression” control structure).

The linker front-end (to put it simply: dce + IR-level optimizations) can be reused out of the box, indeed. Only the LinkerBackend needs rewriting.

7 Likes

Are you talking about https://www.scala-js.org/doc/semantics.html and https://scala-native.org/en/stable/user/lang.html, or something beyond that?

Yes, that’s what I’m talking about.

SJSIR versus NIR is the wrong question. The correct question is: do you want Scala.js language semantics or Scala Native language semantics?
If you want Scala.js language semantics, compile Scala.js IR to WasmGC

That’s all true.

I’m still on the fence wether should we go from Scala.js or ScalaNative.
As @lrytz mentioned,

In these scenarios outside the browser, is JS interop relevant at all? If not, is the approach of compiling to WASM through sjsir still the best option, vs via Scala native / LLVM?

I believe that Wasm support in Scala could have a significant impact, especially for non-browser embeddings like WASI.

When compiling from Scala.js, to support WASI, a substantial portion of scalalib and javalib in Scala.js, which relies on JS interop, would need to be reimplemented based on WASI where we can’t leverage the JS interop of Wasm.
(Maybe we should check how kowasm supports WASI based on Kotlin/Wasm (that targets browser WASM).

On the other hand, if we compile from ScalaNative (NIR), the wasi-libc (or wasix-libc) would assist in supporting scalalib/javalib in WASI. To execute this WASI on a browser, we can employ browser_wasi_shim. (I’m not 100% sure it’s feasible though).

Compiling from Scala.js or ScalaNative represents a choice between prioritizing Wasm for browser or focusing on WASI.
If the emphasis is on cloud/edge and IoT applications, compiling from ScalaNative with wasi-libc may be more aligned with those interests.

1 Like

I believe market size is one thing, but technical feasibility is the first concern. What do we do with GC for WASI? if we don’t have a good answer for that the whole thing is doomed from the start.

The WASM compatibility table indicates that WASM-GC is now supported in Chrome, Firefox and, under a flag, also in Node.js. And no doubt others will follow. That seems solid enough for me.

2 Likes

While wasmer and wasmedge haven’t supported WasmGC yet, wasmtime is actively working on it

That seems solid enough for me.

Do you mean, you think other pure wasm runtime (for WASI) such as wasmtime and wasmer will follow implementing WasmGC, am I correct?

Anyhow, here’s my brain dump:

If we wanna compile to WasmGC via Scala.js, we’d have to re-implement scalalib and javalib based on WASI (when we wanna support WASI) and JS ecosystem won’t be available there.

With ScalaNative (via NIR), we could use wasi-libc that is a libc implementation based on WASI. (Regarding posixlib, we’ll remove them for a while and wait for WASI preview 2. WASIX might be interesting, but only wasmer supports it).

One comment from @tgodzik is that, maybe we can implement 2 wasm backend: SJSIR to Wasm on browser, and NIR to WASI. While it takes some more implementation effort, that makes sense to me (while I’m not sure there’s a demand for wasm on browser for Scala)

2 Likes

Yes, I think others will follow. But anyway, with Node.js we already have one of the largest ecosystems outside the browser supporting it.

1 Like

There is :slight_smile:

  • Protosearch is a cross-platform library for search. The idea is to build a search index on the JVM/Native (e.g. as part of a build pipeline) and run queries against that index in client-side JavaScript. If we can compile the client code to WASM, hopefully we can get a nice performance boost (and the Scala answer to Rust’s Stork Search).

  • The wonderful folks at Gemini Observatory are currently using Scala.js and JavaScript Web Workers to do numerical calculations client-side in their browser applications (for example to calculate guide stars). Ideally compiling these routines to WASM would also yield a performance benefit.


Anyway, the reason I came here is to bring up a practical concern for me. I help maintain the Typelevel ecosystem on a growing number of platforms:

  • Scala JVM
    • pre-Loom
    • post-Loom
  • Scala.js
    • browsers
    • Node.js
  • Scala Native
    • Linux x86_64
    • Linux ARM64
    • macOS

I listed it specifically like this for a reason: ultimately we ship three artifacts (per Scala compiler version) but each of those single artifacts must itself support multiple deploy targets. I’ve written alternative implementations of various functionalities for all of the deploy targets enumerated above. We currently rely on the following mechanisms to select the appropriate implementation:

  • on Scala JVM we use runtime reflection. This is a JVM feature.
  • on Scala.js we use runtime feature-tests. This is a JavaScript feature.
  • on Scala Native we use linktime-resolved conditional branches. This is a Scala Native feature.

As far as I understand, we are not proposing WASM as a separate Scala platform with its own binary suffix and artifacts.[1] So it’s going to end up under Scala.js or Scala Native or both. Thus we will need a mechanism to embed the new WASM-specific implementations within our existing artifacts and select those implementations when compiling to WASM.

Currently Scala Native is best equipped to handle this, since you can select implementations at linktime without relying on some dynamic capability of the runtime. We even recently landed support for linktime-resolved Service Providers which opens the door for alternative implementations of JDKs to exist and be maintained outside the core repository.


From what I’ve read above, compiling SJSIR to WasmGC sounds compelling. Overall Scala.js is superbly stable and widely supported across the ecosystem which makes the idea very attractive to me. Starting from those foundations seems like a fantastic jump start.


  1. Well, there are trade-offs, but I suppose we could do this. Unlike the Scala.js or Scala Native repos, Scala WASM would not need its own compiler plugin, just e.g. a LinkerBackend. But it would ship its own Java and Scala libraries and its own sbt plugin that configures the compiler plugins, libraries, and binary suffixes appropriately. ↩︎

5 Likes