I have a few questions.
First of all: What do you hope to achieve with compiling Scala to WASM?
I see people here talking about performance. But the reality is likely that Scala on WASM would run slower than all other platforms (at least for many years because WASM isn’t very well optimized yet, especially not for VM languages). I would expect currently a hit up to one order of magnitude. Depending how library code would be called even worse…
WASM has some very interesting properties, no question. In the long run we should support this platform for sure. But it’s not about performance.
What one should know about WASM performance:
Money quote:
On the one hand, I was happy to see that Liftoff’s output was faster than what Ignition or Sparkplug could squeeze out of JavaScript. At the same time, it didn’t sit well with me that the optimized WebAssembly module takes about 3 times as long as JavaScript.
But please read the rest. The situation is very nuanced. WASM can be fast! But only when used for the right thing in the right way.
While WebAssembly can run faster than JavaScript, it is likely that you will have to hand-optimize your code to achieve that.
But here we go: Optimizing a whole language VM is a very hard task. See how Microsoft stands currently:
https://krausest.github.io/js-framework-benchmark/2024/table_chrome_121.0.6167.86_win.html
[ STRG-F: blazor-wasm ]
(Note: “blazor-wasm-aot” is kind of like Scala Native; the other is the VM version; this clearly shows for which kind of runtime model WASM is actually better suited)
These performance considerations don’t mean WASM makes no sense at all, of course! WASM is good performance wise when you do kind of low-level “number crunching” stuff. The abstractions it offers are good at simulating a kind of very bare bones computer. (Hence the reference to ASM, a.k.a. assembly language).
Running a full VM on this abstract computer is quite wasteful. You want to run the “pure” computations.
This has also another reason: Calling into and out of your “virtual computer” is not free. You need to marshal and unmarshal all data on its way between the two worlds for “normal” calls. You could use shared memory instead, and do some zero-copy thing, but this requires again hand-optimized code.
The very next question I would rise is about library ecosystem: Scala.js links with JS libraries, and offers JS semantics where it matters, but JS libs don’t run on the WASM VM, right? So you would need to constantly call in and out when using any not Scala.js native lib. I’m not sure this is a good idea.
What I’m trying to say, I think Scala.js is not the right language variant to be compiled to WASM. It’s a VM language, it binds to JS libs, it runs most likely much faster on it’s native platform.
WASM is made for “C-like” languages. The ones that compile fine to WASM are things like Rust, C, C++, Zig. And we have also something in this space: Scala Native!
But Scala Native would for sure need a WASM GC back-end. Otherwise it will most likely also be quite underwhelming when it comes to performance.
Scala Native talks natively to languages like Rust, C, C++, Zig, thought C “ABI”. Exactly the languages which also compile to WASM… So Scala Native could use its native lib ecosystem without expensive VM to VM calls! (Besides pure Scala libs, of course).
Still this doesn’t mean that Scala.js is out of the picture. As WASM is mostly good to do some “heavy lifting” but not really optimized to do “the usual UX browser things” one needs actually both. So some interop between Scala.js and Scala-Native-compiled-to-WASM would be very welcome. It could hide the otherwise needed JS incantations to bind WASM module functions to the outside world.