The tug-of-war over server-side WebAssembly
digest.browsertech.com
digest.browsertech.com
This plays out precisely as the blog post details with the split between WASI and Web Platform. Say you want to compile a Rust + C codebase to Wasm and run in the browser. You have three targets: wasm32-unknown-emscripten, wasm32-unknown-unknown, and wasm32-wasi. Emscripten is relatively old and not maintained. It'll work but you get some old JS that doesn't play well with newer stuff. wasm32-unknown-unknown has an ABI incompatibility which means you cannot interoperate between C compiled to Wasm and Rust compiled to Wasm. wasm32-wasi works, but now you have to have a WASI implementation in the browser and that's still very immature. Tools like wasm-bindgen or wasm-pack don't work with wasm32-wasi either.
Basically you have the Web Platform backend (wasm32-unknown-unknown) that plays well with the browser part, but does not play well with the interoperability across languages part. And you have the WASI backend (wasm32-wasi) that plays well with the interoperability across languages part but does not play well with browser part.
Since I was surprised to learn this, and to save everyone else the search, it's https://github.com/rust-lang/rust/issues/71871
* https://github.com/rust-lang/rust/issues/71871#issuecomment-...
* https://github.com/rust-lang/rust/pull/79998
Weirdly though, there's another issue (opened prior) that's ongoing and seems to indicate things aren't fixed after all:
No, if you look at the diff, it was only fixed for emscripten and wasi.
EDIT: I have to disagree though - emscripten is actively maintained. Not sure why you get the impression otherwise.
I am aware that emscripten is a product you can use to compile software. Frankly that's a little condescending to phrase it in such a way.
First let me provide some context: I'm Syrus, CEO of Wasmer (a WebAssembly-powered company). There has been actors in the industry (I'm not naming them purposefully) that are using their corporate power (that is, their seats of influence over the standards committee) to move things forward as specifications even though their usage is minimal and not strong enough to yet become a standard.
The issue about that strategy is that incentivizes creating new "standards" to gain control over them, even though there are already viable solutions for things that will solve people current needs. Let me put some examples of this:
1. Creating Wasi-NN instead of pushing for WebNN standard API [1]
2. Creating WebAssembly Interfaces instead of pushing for WebIDL (specially important for the browser, if we ever want to get DOM access from Wasm, this is probably the fastest way atm)
3. Creating WASI-fs instead of pushing towards the Filesystem spec [1]
4. And so on
The issue is not on the technical advances per se, which I personally think they are great.
But on the fact that those advancements start creating a breach that is different and competitive with the browser instead of being cohesive and complementary, and by doing that they are putting their governance power interests over the user needs.[1] https://www.w3.org/TR/webnn/
[2] https://developer.mozilla.org/en-US/docs/Web/API/FileSystem
Are these companies doing this to control the new standards or are they doing this because adhering to the web standard (both that you linked, I think?) puts them in a position where they'll be captive to an even larger committee (any web standards body), making it so any changes will take longer to push through, and implementation costs will be higher?
One of the upsides to using the big committee standards is that the big companies often have a reference implementation you can use, which is a time-saver. But then you can hit sharp edges or limitations.
In this case, you just need to analyze how fast they have been moving to see if iteration speed was the reason for pushing for own-governance outside of the web-standards (note: WASI, coined by those entities, was launched 4 years ago)
Quite comprehensive, but I think there's a pretty informative question to ask. If I write a Go wasm and you write a Rust wasm module, how do they work together? How can I call your thing? I could be mistaken on these but I think I one really does this now, and it's the macro-focus of component-model, to build a stable inter-module binary interface.
Today... on the web there's not really a good solution other than hand writing more glue code in js to relay activity. On the server side wasi defines syscall like things you can call out & do, but I think it's just limited pre-baked operations, not a generic way to consumer a variety of external resources. Component-model is here to let us define exports & imports at a higher level & to let us stitch modules together via these interfaces.
The article paints a very good very real story of how modules & runtimes interact, but that's a somewhat insufficient question, for the real goal of of interoperable modules working together. Asking how modules work with each other (not just the upper system) highlights how the systems question here is broader.
CORBA, Beans etc predate the modern web but its telling that no one has really bothered to try to recreate them...what is the demand? server-side wasm just doesn't seem to be anything that is strongly needed and looks a lot like completists just scratching an itch
two languages that only communicate at the wasm level...isn't this just a variation on "DLL Hell"?
as to a Go program accessing a Rust module...on a practical level you would almost always be better off taking the thing in Rust that you want to use in Go and just doing a best-effort source translation or find some library out there that provides substitute functionality
I'd rather have source translation tech that just gets functionality into the language I am using
The semantics of golang and rust are so different that any translation runs the risk of producing wildly not-idiomatic code.
Of course, you lose out on all the runtime benefits that various platforms might provide (I don't see anyone who is invested in the JVM giving it up for wasm) but why not have rust and golang binaries compiled to a common format and let the communicate directly?
Personally, I don't do much polyglot programming these days so I don't really have a dog in the fight, so to speak, but my experience with source-to-source translation is that it tends to be pretty awful short of simple translations that keep the same runtime and underlying data primitives.
hypothetically...but it would also move part of your integration testing into the realm of wasm
I can't think of why this is better than just translating functionality into the language you are using
Because this can be a huge amount of effort.
The hard part about bridging languages isn't solved by WASM, it's about working with complex types across the call boundary. If you want to pass a Python dict to a C++ function that accepts std::map then something will have to do that translation, handle GC concerns, etc. Likewise for function types, object instances, files, strings etc. And then that interop overhead will hurt performance so you may need to optimize that.
That's why if you look at Truffle, which is SOTA for this problem, it works in a very different way to WASM. There's no attempt to do a unified bytecode or a COM-like component system. It works with whatever the language's natural representation is and does interop by optimizing and JIT compiling merged partially evaluated ASTs or unrolled bytecode interpreters.
Today FaaS (ex: aws lambda) are most frequently run by having a process for each function, but with wasm the nice sandbox system/object-capabulities style, everyone can share a runtime & have less barrier crossing & much less overhead. There's a ton of near magic properties here that could help radically change how we write & maintain systems, shifting us from big blocky monoliths to something more like an Erlang universe, where there's lots of small interoperating pieces, sharing a common fabric that stitches them together.
I think the CloudFlare Workers world probably comes the closest to showing off these promises, and it's a pretty slick elegant low fluff take, that also comes with very big advantages about being architected from the start to have many many deployment points, with the ability for individual workers to come & go on demand at very high speed at any given deployment site.
We can also easily spot incipient demand for wasm by just looking at all the places folks embed scripting languages. Every time you see a Lua scripting engine (neovim, nginx, a million other places), you could potentially have any langauge you please. And given the lightweight sandbox, how quick it is to spawn instances, we can make our software pipelines much more user configurable & dynamic.
A good parallel is the kernel, which has become much more capable of programmed behavior via the addition of ebpf to instrument not just networking but all kinds of subsystems. This has lead to huge wins, huge gains in all kinds of very fast very secure networking interlinks, with record low overhead. It's being used to help remap weird input devices behaviors, and dozens of other interesting little things you only get by having some flexibility built in. Rather than hard-coded subsystems everywhere. Wasm is lined up to be the ebpf of user land systems, it is the thing that allows apps to bake in flexibility & programmability, in a fast, safe, interoperable way.
The discussion here is about how the library of wasm modules can arise & reinforce each other. The cynicism against interoperability, suggesting source translation, is blind to the use cases above. It doesn't see the Erlang view of the world, it's rooted in the boring legacy view that computing is and always will be giant monolithic apps doing three million different things within the confines of the colossus-sized process. Having other alternatives to computing where we can have a sea of smaller units that message each other doesn't have a clear & obvious triumph, it's not inherently obviously better than the megaprocess view of the universe, but it should be possible & we should find out what we can do. And even if the femto-service / Nano-function model ends up being bad, we'll still have apps that have a well established means & well supported common tool chains for adding programmability in. That requires a mature interoperable modular system to get anywhere, and thankfully folks aren't scared off from trying, not dissuaded because someone mentions the CORBA boogeyman under the bed & tries to gloom us away.
This is incredibly promising technology & I recommend everyone come evaluate it with a positive hopeful light. I hope I've helped share good outlooks that can help get folk excited (as they should be!)
but people like code reuse, so its almost always better to communicate, collaborate and share at the source code level than at the level of a compiler target
as far as I can tell, the only real target audience here are pathological language snobs ("I insist on writing neovim extensions in Rust!") who will validate their inflexibility by pointing at the lowest common denominator of wasm
How many of these have embedded scripting systems? How different will they be? How many offer lightweight nano-processes? How many are secure sandboxes? How many support arm, RISC-V, or the next architecture?
Having a common platform here makes so much sense to me. Enabling a variety of things, safely, quickly, & with well defined patterns, seems intuitively advantageous, frees us from having to tangle with a variety of minor subdivisions that fix software onto narrower paths.
no, pick one that is a decent compromise and move on
no one is going to dismiss the entirety of a platform like nginx because they can't script it in their favorite language
> but people like code reuse, so its almost always better to communicate, collaborate and share at the source code level than at the level of a compiler target
And now you argue we should all only ever have Lua to collaborate on.
Cause that's the only big player in embedded scripting atm. Js alas didn't take off but it's not that bad to embed. Few other languages have much at all. There are some projects to make ebpf embeddable in apps.
You seem to really have it in for what seems like a perfectly good attempt that seemingly makes a lot of sense & has hugely obvious benefits. I don't get the opposition to trying.
The idea is to simply run normal Linux binaries on macOS and Windows. How? You create a virtual machine using the Mac/Windows APIs without any OS inside, in fact without even any virtual hardware. It's literally just a new address space and some trivial min-viable VM configuration. Then you map the ELF binary and a ld.so into the VM with a minimal ELF interpreter, kick off execution and anytime there's a syscall you trap it and translate to the host OS syscalls. It can work quite well on macOS because the syscall interface is so similar.
Note that this sort of VM is not:
• A sandbox
• A hardware abstraction
Apps run this way hold all their data in the filing system of the host OS, they use the network stack of the host OS, etc. The VM is only being used to allow trapping and emulation of the syscall interface. The app isn't aware that it's being run in a special CPU mode on top of an emulated kernel.
Advantages: lightweight, simple, apps can use all CPU features, can run at native speed, the Linux syscall interface is highly stable, based on POSIX specifications and you can easily pick a subset of it to standardize.
Disadvantages: requires the emulator, apps exposed to missing features or quirks of the host OS e.g. Windows file system performance is much lower than Linux.
WSL1 sort of worked that way, albeit without the VM aspect that lets userspace apps do it. They abandoned it partly for performance reasons and users expected all existing Linux apps to just work. But WASM doesn't target existing apps. It expects developers to bend and do things the WASM way, and accepts that not all apps are compatible with it, so that's not necessarily a problem.
An example of how to implement this is NOAH:
Do we really want to surrender to the dominance of two proprietary ISAs? To me, the insistence on something like wasm bytecode is a kind of optimism, a hope that the current duopoly won't be permanent.
So bytecode is useful for lots of other reasons when paired with good runtimes, but it doesn't seem to be necessary to let you switch CPU ISAs or vendors.
Meanwhile the costs are super high. WASM doesn't and isn't going to expose the real capabilities of the hardware any time soon, so it'll always lag behind in performance. The web has an unfortunate design goal of killing all the incentives OS and hardware makers have to add new features, because browser makers are these days pretty averse to allowing web pages to have special capabilities on specific underlying platforms, so features only get exposed to the web if every (major) OS and CPU combination supports them. But why add new features if apps will only start to use them if all your competitors also ship the same feature - there's no competitive advantage to be had there, which in turn means there's no point in investing in R&D.
In turn that means that there's actually no point in designing or deploying a new ISA if everything is WASM. Why bother if it can't add any unique capabilities or nothing would use them? RISC-V says licensing is a good enough reason, but not many competitive design houses seem to care: making a high performance CPU is so expensive that ARM licensing costs just aren't a big deal in the grand scheme of things and in practice silicon OEMs seem fine with outsourcing ISA development to them.
Leaning in to native code fixes the incentive problem, and software is constantly being updated these days anyway. If CPU portability was all that mattered it'd be cheaper and quicker to invest in better cross-compilation toolchains, like a standard file format for expressing all the URLs to all the different OS/CPU specific binaries for a program and build servers that could incrementally build and upload binaries for less popular ISAs in the background.
I outline a proposal for an alternative web-like thing that works this way here (whilst also supporting wasm and other bytecodes):
https://docs.google.com/document/d/1oDBw4fWyRNug3_f5mXWdlgDI...
This really understates how significant it is that Apple customized their CPUs in order to give their emulator good performance. Ordinary emulator developers can't do this.
https://qemu-project.gitlab.io/qemu/user/index.html
This is, by the way, how Hangover is supposed to work.
https://www.phoronix.com/news/Wine-Hangover-2023-Open-Source
https://www.phoronix.com/news/Hangover-0.8.3
They were working on ARM and a bit of POWER9 for a while, before it had to be put on hold due to all the PE conversion work and resulting churn in WINE. Now it seems to have been restarted/rewritten.
> CloudABI is no longer being maintained. It was an awesome experiment, but it never got enough traction to be sustainable. If you like the idea behind CloudABI, please consider looking into the WebAssembly System Interface (WASI). WASI's design has been inspired by CloudABI.
https://github.com/NuxiNL/cloudlibc
edit: https://github.com/bytecodealliance/wasmtime/blob/main/docs/... is a good reference and also mentions the Capsicum framework (https://www.cl.cam.ac.uk/research/security/capsicum/). Which is present in FreeBSD for several versions btw.
I got bogged down in the wasi/wasm debate and terminology and different available runtimes (or whatever they should be called) - wasmtime, wasmer, wasmedge, fermyon, bytecode alliance... I probably would have spent more time and figured it out if I didn't want to learn rust right now. Seems rust is the best supported language for wasm as far as I can tell.
Otherwise admittedly I would have used a large token context ChatGPT to convert the library to rust for me, and then also have it compile it to wasm.
WASI has the advantage that many compilers support it directly, e.g. in Zig:
> cat hello.zig
const print = @import("std").debug.print;
pub fn main() void {
print("Hello World!\n", .{});
}
> zig build-exe hello.zig -target wasm32-wasi
> wasmtime hello.wasm
Hello World!
Or if you prefer C: > cat hello.c
#include <stdio.h>
int main() {
printf("Hello World!\n");
}
> zig cc hello.c -o hello.wasm -target wasm32-wasi
> wasmtime hello.wasm
Hello World!
(I guess Rust can do the same, or for a purely clang-based solution see: https://github.com/WebAssembly/wasi-sdk)....while for web compatibility, you'll always need some sort of shim solution to talk to the Javascript APIs.
I think the system interface is probably extremely important and significant part of webassembly and history. It's history making. It's the definition of a foundational interface that all users of webassembly shall inherit and need to target if it takes off. Which I hope it does. A standardised bytecode format is awesome!
Why do I say this? The binary interface for C programming such as the amd64 SysV calling convention is established and is important for C FFI interoperability. Likewise, the syscall interface for Linux is established and so is Win32 API or POSIX.
If people are trying to compile existing code so it doesn't need to be changed to support webassembly, then that's one constraint. Another constraint is performant Javascript interop. Another constraint which I personally find most interesting is the capability of defining a new API that is fit for purpose for application development. So that incorporates threading, garbage collection and sockets.
raw sockets, dynamic linking, 32bit vs 64, linear memory (gc), memory protection, real threads, TLS - the list is seemingly endless.
If I were building something in the wasm community (I'm not) I'd be less focused in on building one off bindings for fuctions-as-a-service, which seems to be the gtm right now and more focused on building the underlying foundations that are necessary.
https://github.com/WebAssembly/WASI/blob/main/Proposals.md https://github.com/WebAssembly/wasi-threads
If WASM gets sucked into the vortex of pragmatism and fails at its original mission of discarding all of the technical debt and obsolete paradigms, then we'll miss out on a golden opportunity to reinvent the modern technology stack.
Those who want to deliver something that works right now can fork the project and call it WARN (WebAssemblyish Right Now).
Also, how this ecosystem replicates the era of Java in both browser and server.
On the other hand relying on JS shims means a lot of guaranteed copying to/from the JS heap, which is a very annoying speed tax compared to… not.
I wonder why browsers don’t allow direct WebIDL-based APIs direct from WASM to browser APIs
- WASI once had an official polyfill https://wasi.dev/polyfill/, now apparently succeeded by https://github.com/bjorn3/browser_wasi_shim
- wasmer-js provides a JS polyfill for WASI https://docs.wasmer.io/integrations/js/wasi
- Cloudflare has a WIP polyfill https://github.com/cloudflare/workers-wasi
I'm generally leery of non-temporary polyfills, so I'm not sure that any of these feel like a long-term viable option for me.
Isn’t that what the compiler generated shim is that something like Emscripten emits? The Emscripten wrapper code is… frustrating.
Since we optimize for size on the web, it's hard to justify bringing the entire jemalloc into every wasm module.
I think it's telling that Unix started with sbrk and ended up with mmap, here.
Modern general purpose allocators are a bit more clever then just grabbing the next available address from a single free-list, usually they group allocations by size into buckets.
Then there's specialized allocators (e.g. arena, bump, stack ...) which not only prevent fragmentation but also increase memory management performance.
In general, memory fragmentation in a limited address space is only a problem for programs that don't have a proper memory management strategy.
Even with everything you mentioned, the point is that the allocator (doing all those things) cannot return free memory to the OS, because the APIs simply don't exist for it: that's what mmap() provides over sbrk().
In the worst case, even for an allocator doing all those things, this can result in an allocator being unable to return any memory: if exactly the last allocation remains alive, sbrk() cannot be called (as you'd free a still-live allocation); but in the case of mmap(), we can free most, or in the best case, all of the memory not in actual use.
It seems like the vendors are recognizing this though, with firefox now having a discard function aparently!
This is exactly how your OS works. There is no malloc syscall. You call sbrk [1] (the modern mechanism to do that on Linux eludes me, maybe it's through mmap), get a chunk of memory and do your thing. malloc is implemented in glibc, and you can swap it out with any other allocator, but your kernel just gives you a chunk of memory and it is your job (as userland process) to suballocate it, deal with fragmentation, etc.
Also take a look at "Some applications need address space, not memory" in that link, same stuff.
Long story short, running inside WASM is like running without virtual memory. Virtual memory was invented to solve a bunch of issues, so now WASM has all those issues unsolved.
The benefit is that you can actually understand what your code is capable of doing to its environment (instead of working with the “one size fits all” type of interface that has produced so many security issues on existing platforms).
Webassembly is targeting a virtual cpu, so it is plattform independent.
s/Java/WebAssembly/g
There! All up-to-date!
wasm is much lower level bytecode that e.g. C can be compiled to.
Java isn't the first language base on bytecode, and WebAssembly will not be the last.
The lack of memory protections that wasm currently has such as the lack of read-only memory or memory randomizations or anything else you really can't run any code with any level of assurance that it won't be taken advantage of. Typically a software engineer doesn't have to think about someone being able to rewrite a function during run-time or a statically defined variable because those memory protections are in place. It simply isn't allowed. However in wasm land, if you have a function that is say 'func isAdmin()' for instance you'd expect to return true if the logic is correct but without memory protections an attacker can force it to return true every time. This is one of the pretty serious features missing in wasm currently.