What Happened To WebAssembly
110 points by EmNudge
110 points by EmNudge
It seems to have been advertised as a world-changing advancement. Was it just oversold?
Yes, like every single technology that gets vc money injected into it. It's an ok technology but on one hand it requires a low level programming language to be leveraged well, and on the other it was pushed to solve software distribution issues that are actually better solved by using a better compiler toolchain.
That being said, some software distribution issues are indeed nicely solved by VMs and I'm a very happy user of WASM: all the language server implementations (eg) that I'm working on are written in Zig, compiled to WASM and bundled directly in their relative VSCode extension, so that users don't have to do any manual step (nor I have to write javascript code to fetch and manage the right version of a native executable).
We use wasm at work to expose some Rust code to node instead of using a native library because it means we can build something in CI that works on Linux x86 (prod) and macOS arm (developer machines) without having to worry about cross compilation or getting macOS CI runners.
That feels like an example of the parent's "issues that are better solved by using a better compiler toolchain". Cross compilation should be pretty easy with a decent compiler toolchain, no? I don't mean to poke holes in your use case; it always seems cool when people find uses for WASM.
We use WASM at work to build Rust code is run both in a browser and on a server, from a node.js service. In this case a WASM target is strictly necessary; while I suppose we could cross-compile it to both WASM and native code, being able to run something that's essentially identical between these two contexts is great for a lot of reasons. Cross-compilation can be annoying, we'd have to write both node native bindings and WASM bindings since there's no uniform representation.... a single universal bytecode has really helped us in this context.
Cross-compiling to macOS specifically is painful for licensing reasons; no amount of compiler support will help you there.
Yes and no. Cross-compiling to macOS and linking against Apple's frameworks is painful for licensing reasons. WebAssembly doesn't help there, because you either create a shim that exposes those APIs (which needs to be built on a Mac) or you just don't use them and stick to WASI, which is a subset of what you can do with toolchains built from the open-source bits (Zig ships one of these).
Cross compilation should be pretty easy with a decent compiler toolchain, no?
No. Cross-compilation is a mess. It can be simple for very simple programs but operating system have different ways to handle input/outputs, networking, threads, etc. and the sibling already mentioned the licensing issues. You need a macOS machine to compile to macOS.
Yet golang manages to cross compile just fine
...by ignoring the hosts libc, right? I haven't tried, but I'd be surprised if cross-compilation to macOS works just as simple with go once you want to integrate with macOS SDKs etc?
They now dynamically link libSystem and forward system calls to that, because one macOS update changed the system call ABI for the system call that implements the gettimeofday POSIX function, which every Go program uses in startup and so broke every single Go program.
Which, to be fair, they are wont to do. Linux is pretty singular in its requirement that the syscall ABI never breaking backwards compatibility.
Linux and FreeBSD both maintain system call compatibility. But FreeBSD introduces new versions of system calls with new syscall numbers and uses symbol versioning in libc (or, in newer FreeBSD, in a separate library that just provides the syscalls) to make sure that programs dynamically linked against old libc call the old versions, whereas Linux doesn't have a C public interface (the system-call wrappers are from glibc / musl, which are third-party projects).
OpenBSD and NetBSD don't guarantee syscall backwards compatibility.
Linux's syscall stability is also not quite 100%. If you look in the Linux syscall table, you'll see a bunch of things that are marked as removed (though mostly for niche use cases such as AFS).
Cross-compilation feels different than writing portable code. Making the cross-compilation work doesn't require that the resultant code correctly handles all of the idiosyncratic differences between platforms. A good toolchain will cross-compile open("/foo") on all platforms even though that's not portable on Windows.
You can absolutely cross-compile easily, but whether those programs are portable is another question (additionally whether the libraries you use are platform specific is an ecosystem problem--plenty of platforms don't have this problem). Also WASM doesn't solve portability issues--the underlying WASM runtime has to make decisions about what platform APIs it will support and how it will handle them. A naive WASM runtime is not necessarily going to translate open("/foo") to open("C:\foo") on Windows, for example, and many WASM runtimes will not support open() at all.
You need a macOS machine to compile to macOS
I just tested this. I compiled a binary for darwin/arm64 from a linux/arm64 machine, copied it to my macOS machine, and it ran just fine. Is the idea that, by doing this, I violated some licensing issue?
Is the idea that, by doing this, I violated some licensing issue?
No. You didn't violate any licensing terms. You only need macOS SDK/linking if you use macOS-related functionality (think equivalent of libc in linux).
I do agree that wasm doesn't really solve this problem; since wasm doesn't offer networking, threading or file support. What wasm does, however, is force you to think about these decisions ahead of time. If you are developing in a specific platform and targeting cross-platforms, you need to continually compile for all the targets that you are targeting to make sure you didn't use something that will break somewhere.
Cross compilation should be pretty easy with a decent compiler toolchain, no?
I've broken my teeth too many times trying to get anything[^1] in Rust to cross-compile. Given I'm pretty basic at this, but it seems that the issues are too fundamental for the experts to create a setup that is easy to use.
[^1]: Mostly this is the cross-compilation pain between arm/x64 when I want to build something on my Mac to run on a server. From what I gather it's fairly straight-forward to compile something on your machine to run on an embedded device.
Yes, like every single technology that gets vc money injected into it.
I don't think WASM got a lot of VC money injected into it. WASM is fine. It's not amazing, but it's useful and it's used all the time.
I actually think it's the opposite? There is little money (almost nothing?) that got injected into WASM when compared to Crypto or AI.
it requires a low level programming language to be leveraged well
It's getting possible to use high-level, GC'ed languages like Java: https://graalvm.github.io/graalvm-demos/native-image/wasm-javac/
Considering the genesis of Java, there is a cruel irony that it can once again run in the browser.
I was promised that WebAssembly would bring many other and better languages to the world of front-end development other than just Javascript. This is the only reason I cared about it, and it has obviously not happened in the way that was being promised at the time.
"Replace JavaScript" was always a fixation of the (vague) JavaScript Hater Community™, not something WebAssembly developers and proponents were ever focused on.
Yeah, until DOM APIs are available I have little reason to touch WASM in my personal projects.
Why? A JavaScript glue layer as exists in every WASM web framework adds neither considerable latency nor code size.
I don't think this was necessarily the exact "sales pitch" promised by wasm, but many people interpreted the sales pitch as "you can write rich web apps in your language of choice without any JS" which afaict isn't true today. In addition all the learning materials I've been able to find assume you're familiar with both Javascript as a language and are willing to use JS ecosystem tools. Hopefully we'll get to the point where that's not true at some point! But I haven't seen it yet.
"WASM accomplished what they set out to do" and "WASM doesn't do what I was hoping for" aren't mutually exclusive.
You can do that: https://yew.rs/docs/getting-started/build-a-sample-app
It just usually is more work for a worse user experience, so whether you'd want to is a different question.
Well I'm not worried about latency or code size, I just want something that's simple and built-in
Wasm is never going to have any I/O built into it by default, DOM or otherwise. You're always going to have to import some API that gives your module the things it needs.
Wasm already has standardized APIs for other things, e.g. WASI and the wasm:js-strings API. My guess is OP is asking for something similar, but for DOM. I think it's reasonable, you don't want every Wasm app to ship its own JS glue to access DOM.
WASI isn't built in to browsers, and the js-strings API (and proposed followup) is specifically only "pure computation" APIs.
It is conceivable that someone could define a WASI-equivalent for web features (maybe on top of the component model?), with the host side implementation defined in JS, but so far people mostly use glue code specific to their application (often with this being generated by other tools like wasm-bindgen).
WASI isn't applicable to browsers and it's an abstraction layer above Wasm, anyway. The JS string built-ins proposal is a set of imports that toolchains need to opt into using if they can deal with u16 codepoints instead of u8. A well-specified set of Wasm imports for DOM primitives would be nice, but I don't know if there's a one-size-fits-all solution. An API that takes full advantage of Wasm GC would be what I'd want, but that probably will make life difficult for toolchains that target linear memory like emscripten. This is all to say that I don't understand why not having some formally specified set of DOM inputs is a blocker as it's quite easy to import whatever you need right now.
But today I can't import an API that allows me access to the DOM. I can only write my own API that provides access to the DOM then import and use it or try some hobbyist's project which will probably stop being supported soon.
it has obviously not happened in the way that was being promised at the time
This is not at all obvious to me. WASM is widely supported in deployed browsers and you have many choices of languages and frameworks to use. You can use them if you want. Not sure what else you were hoping for?
I dunno, Blazor standalone web assembly projects are pretty cool. Can utiltise static web hosting and things like browser local storage, without having to write any javascript of my own, or run my own web server or such.
Agreed, been using blazor webasm for a lot of stuff. It's pretty awesome. Shame multithreading keeps getting delayed.
But honestly it's by far the nicest "SPA webdev" experience going imo.
I mean this earnestly: What does success on this look like to you? Does it look like "I should be able to write react-style code in Python"? Like in your mind's eye is there an ideal that you would like?
I think some people really don't want to write frontend code more or less at all, which is fine in some sense, but WASM won't solve that for you.
Some people don't like React and friends. I have, on multiple ocassions, tried to use, like, native frontend toolkits like GTK and friends and it's hard for me to not conclude that React is pretty nice to use in comparison.
react-style Python exists to some extent and you can use it today if you wanted to! Hell, even before WASM, we had clojurescript (amongst other transpiled languages) letting you just opt out of JS (though maybe clojurescript isn't the thing for you). Though it's still not the ideal, I think
One thing I would like to want is for all of this WASM-style stuff to be easily integrateable. For tools like esbuild to have some FFI or calling convention system so that I could just make a someTrickyCode.hs and then do import {veryComplicatedAlgo} from './someTrickyCode.hs' in my JS, and have it all work together nicely.
This is the only reason I cared about it, and it has obviously not happened in the way that was being promised at the time.
IDK, I'm shipping Qt webassembly apps nowadays.. there's rough edges but a whole lot of cases where it just works, and is so much more efficient as a developer experience than webdev
There is a somewhat credible Rust frontend development story out there now. I think that's already a big win.
Who promised that?
I did a random search for “web assembly javascript replace” and found this as an example. Obviously, this sort of thing was misinformed, in retrospect, but it was also rampant.
why do people think nothing has happened?
Because the sensationalist news cycle can only perceive things like "Epic World-Changing Future Magic", "Horrendous Evil Torment Nexus", and "Stupid Garbage Bullshit What the Fuck Were They Thinking". No room for just, like, normal things happening, moderate improvements and additions, etc.
Security and what it enables
Also Firefox famously uses WASM-to-C compilation to sandbox various legacy libraries like format parsers.
It is such that you can ensure process-like isolation within a single process. Cloudflare takes advantage of this aspect within V8 to run untrusted code very efficiently using V8 isolates. This means significant efficiency gains without significant security trade-offs.
I wonder if the author is deliberately avoiding describing this in terms of capabilities? To me the most intriguing thing about WASM is that it brings capability systems "to the masses" and we might finally start being able to see some of the benefits in programs I might actually use.
Hopefully! The capability model really shines with Wasm GC but it's still misunderstood as "bloat" or whatever.
What about the GC specifically (as opposed to just "WASM in general") causes the capability model to shine wrt it?
Object capability security requires reference passing as opposed to pointers into linear memory. Wasm GC added heap allocated reference types, so the composition of Wasm's import/export interface + GC references is an object capability system. Notably WASI takes a "shared nothing" approach instead, which is going to make collaboration amongst modules needlessly difficult.
What examples do you have of “ the philosophy of purposely obfuscating teaching material around Wasm”. From what I’ve seen around the community, this “feels” right but I’m struggling to find specifics.
There are no official guides on WAT. There are public specifications and guides on using certain tools, but the philosophy of the org(s) has been that teaching materials should be very high level (i.e. teach tool use, not how wasm works). I talked to org members and that’s been the response privately and publicly. IMO this hurts people’s ability to self solve.
For a couple years the tools were broken in some pretty annoying ways and no one but a select group of people knew enough to fix them. The only resources I found were physical books you had to buy. That’s how I learned enough to build watlings
There are no official guides on WAT. There are public specifications and guides on using certain tools, but the philosophy of the org(s) has been that teaching materials should be very high level (i.e. teach tool use, not how wasm works). I talked to org members and that’s been the response privately and publicly.
That seems pretty logical given wasm is intended as a compilation target, that's literally the intro blurb on https://webassembly.org:
WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable compilation target for programming languages
For the vast majority of users it is an irrelevant implementation detail, in the same way other intermediate languages or assemblies are e.g. as far as I can tell LLVM only has the language reference and https://llvm.org/docs/tutorial/MyFirstLanguageFrontend/LangImpl03.html on the LLVM IR, not that people don't complain about that
Unfortunately, learning material on LLVM is usually aimed at compiler engineers, not generalist working programmers.
So I don't think it's surprising, or "purposeful obfuscation", that efforts have been put more or less everywhere but there.
I think an important difference from LLVM IR is that LLVM IR is only intended to be used by LLVM, but WASM is supposed to be a target for other compilers. Meaning that if I'm targeting LLVM IR then there's a good chance I would use LLVM to create the IR in the first place.
A better comparison would be to JVM bytecode or various machine codes, and those all have thorough documentation.
I think an important difference from LLVM IR is that LLVM IR is only intended to be used by LLVM, but WASM is supposed to be a target for other compilers. Meaning that if I'm targeting LLVM IR then there's a good chance I would use LLVM to create the IR in the first place.
That is completely untrue. Tons (most?) LLVM frontends are not part of LLVM and don't use it to generate the IR they end up sending to it. Rust, Zig (formerly, not sure it still uses llvm at all), mlton, rapid (a compiler for idris), GHC's llvm backend, ... Using LLVM to generate IR requires writing your compiler (or at least large parts of it) in C++, which is... often undesirable to compiler authors.
I think you misunderstood their point: LLVM IR is not consumed by anything other than LLVM, while JVM bytecode & WASM are meant to be generated and consumed by many implementations (and are thus more stable than LLVM IR).
Using LLVM to generate IR requires writing your compiler (or at least large parts of it) in C++
LLVM has a C API with many bindings available, including OCaml, Python, and Java, meaning you can write a compiler in whatever language you want (as long as it can call C). This is how Rust and Zig use LLVM (unlike Rapid which generates the textual IR themselves). You would prefer this over emitting text/bitcode because LLVM would need to deserialize that output, whereas calling the C API builds the object graph directly.
Yeah, that is a problem, people can’t fix things if they don’t know whom they work! Thanks for writing watlings, I love diving into low level details, so I’ll check it out.
physical books
What are the best WASM books? I don't know anything, so I am looking for a comprehensive resource.
"Web Assembly from the ground up"! I think the authors even hang out here from time to time
The standard is advancing, so most books will be out of date on the newer features. I learned from The Art of WebAssembly (2021). Selfishly I think https://github.com/EmNudge/watlings/ is another good starting place (I am the author).
If you want something interactive and paid, Dominic Elm's course is probably good as well (https://learn-wasm.dev/ )although I've yet to try it.
Thanks. I'm curious, is it viable to use watlings without npm? I'd like to learn more about wasm, but I'm not sure I want it badly enough to use npm.
Watlings has no npm runtime dependencies. What you might be seeing is node and a package.json NPM is not required
I think web assembly jumped the shark. I had a project that needed to run plugins in a sandboxed runtime. I chose RISC-V instead of web assembly. I realized that all I need is for the plugins to be compiled to some kind of bytecode that a cross-platform emulator could safely execute. RISC-V fits that purpose better than web assembly, because it has a much simpler spec and has none of the high level stuff that web assembly gained through scope creep (threading, gc, etc).
I'd love to hear more about this. Assuming you're just not using threading, GC, etc., what specific advantages are you seeing with RISC-V vs. WebAssembly?
I haven't actually done it, but I've been contemplating using WebAssembly for extensions in an aspirational program. Just having a standardized way to provide functions (or variables) from the host (program) seems helpful--and WebAssembly provides this.
I'm very intrigued by your comment but am having trouble picturing it :)
The main advantage of RISC-V is simplicity. It has far fewer instructions than web assembly, and since it's an actual CPU ISA, there is no pressure to add high level features that they continue to shove into web assembly. This is important to me because I was determined to write my own runtime, and writing a RISC-V emulator is much easier than a web assembly runtime. If you're using an off-the-shelf web assembly runtime, you may not care about this.
Thanks! I see the value in having a fixed ISA instead of one that evolves—at web tech pace, no less.
My WebAssembly plan was to just not support extensions, but that might be a mistake. Someone could easily come along and expect SIMD or GC extensions. Still worth it for having a spec for calling host functions, IMO.
I've been thinking about that as well, even x86_32 should be enough for many of my projects and can be handled by a decent VM interpreter.
wasm + webgl + webaudio was always intended as the replacement for flash, which it is and does very well at.
It seems to me that Wasm largely succeeded and meets most/all of the goals for when it was created. The article backs this up by listing the many niches in which its found support, and I personally have deployed dozens of projects (both personal and professional) that use Wasm as a core component.
I''m personally a big fan of Wasm; it has been one of my favorite technologies ever since the first time I called malloc from the JS console when experimenting with an early version of Emscripten. Modern JS engines can be almost miraculously fast, but Wasm still offers the best performance and much higher levels of control over what's actually running on the CPU. I've written about this in the past.
The only way it really fell short is in the way that a lot of people were predicting that it would become a sort of total replacement for JS+HTML+CSS for building web apps. In this regard, I'd have to agree. It could be the continued lack of DOM bindings that have been considered a key missing piece for several years now, or maybe something else or more fundamental.
I've tried out some of the Wasm-powered web frameworks like Yew and not found them to provide an improvement for me at all. It just feels like an awkwardly bolted-on layer on top of JS and CSS without adding any new patterns or capabilities. Like you still have to keep all of the underlying semantics of the way JS events work, you still have to keep the whole DOM and HTML element system, and you also have to deal with all the new stuff the framework introduces on top of that.
Things may be different with other frameworks like Blazor which I've not tried, but I just find myself wanting to write JS instead. I openly admit that it might just be my deep experience and comfort building web apps using React or Svelte though.
Anyway, I strongly feel that Wasm is a successful technology. It's probably in a lot more places than you think, silently doing its job behind the scenes. That, to me, is a hallmark of success for something like Wasm.
WebAssembly is the dark horse of AI right now. It’s used all over the place to sandbox agentic AI interactions. It’s not sexy tech but it’s very useful.
I think it's in a good place. Compression tech like basis_universal, Google's draco3D and stacks with ZSTD have all profited from great performance uplifts and proven WASM delivery. Toolchains with EMSCRIPTEN just work more often than not with both pthreads like and browser worker thread like APIs for multi-threading.
It is such that you can ensure process-like isolation within a single process. Cloudflare takes advantage of this aspect within V8 to run untrusted code very efficiently using V8 isolates. This means significant efficiency gains without significant security trade-offs.
Wasm programs can start 100x faster if you can avoid spinning up a separate process. Fermyon, a company in the Wasm hosting space, advertises sub-millisecond spinup times.
I thought that after Spectre and similar speculative execution attacks, the consensus was that in-process security was very broken and that you should always run untrusted code in separate processes (because speculative execution potentially gives code running in an in-process VM a way to read and even write to other memory owned by that process)? Has this changed? Or is this more of a, "when efficiency is paramount and your threat model is forgiving, go ahead and use in-process isolation but if the software you're writing is a high-value target, your threat model is nation-state attackers, and security is really paramount (say you're writing a critical component for the military, or Coinbase), then you should still run untrusted code in a separate process"?
We don’t yet see major websites entirely built with webassembly-based frameworks. We’re not building our applications directly to WebAssembly for maximum portability. But why not?
Because WebAssembly can't do DOM things directly.
That means in practice your two options for building a Web UI are:
Wasm also can't directly create or manipulate the in-memory JS objects that all DOM APIs require, and converting between the two causes unavoidable performance overhead to using Wasm compared to JS.
Putting these together, using Wasm to build UIs in the browser requires starting from a position of both worse complexity and worse performance, and then it's up to the particular Wasm offering to deliver enough upsides to compensate for both of those unfixable downsides. That's a high bar to clear.
Actual benchmarks do not really corroborate DOM manipulation being a real performance problem for WebAssembly. If you care about code size a lot, that can be an issue though.
Whether it's more complex than just using JavaScript really depends on what you're doing and the rest of your stack.
There is still no standard way to express "your web assembly module should export these globals and functions with these types." The Component model, as far as I can tell from attempting to use it, does not solve this problem; there's no concept of a third party being able to provide a set of types and desired exports for a resulting module. There's no "world stencil," only concrete "world". Without this, implementers of an interface are seemingly supposed to copy-paste a wit definition into their implementation, or one is stuck pasting a snippet of WAT and hoping the reader can figure out how to make their chosen toolchain emit something that looks like that, or describe the interface in C and try to explain that it's not necessary to use the "C ABI" variant of WASM.
To draw an analogy, it'd be like gRPC using protobuf to describe types but needing every project to construct its own IDL file for the actual RPCs.
WASM is overengineered for web uses. You can get better results more quickly by targetting something like freestanding riscv64, creating runtime and then sandboxing that. Riscv is pretty trivial to emulate and it can run on real hardware too.
About a year and a half ago I wanted to use webassembly to provide sandboxing for code written in some language (I wasn't very picky which language) and expected to be invoked from code running on the JVM and perform some pure computation and return a result. I couldn't find a viable way to pass strings in and out of it, so I gave up on webassembly and used a different approach.
Same way you'd do it in C: allocate the string in linear memory, pass around a pointer and length.
If you're using a language without pointers then it will depend on how your compiler targets wasm, of course. The component model proposal is intended to provide a language-agnostic way to define higher level interfaces between different languages, including strings, but it will lower to the same thing in the end.
Somebody should make a table comparing the current WASM/WASI/... roadmap with Gary Bernhardt's "The Birth and Death of Jávascript": https://www.destroyallsoftware.com/talks/the-birth-and-death-of-javascript
I don't think he was too much off.
I dunno, Emscripten's pretty neat for putting legacy C code into web things, f'rinstance DECtalk