WebAssembly: A promising technology that is quietly being enshitified
kerkour.comComponents are basically COM / CORBA for WASM. Just like COM enabled multiple languages to exchange structs and function calls with each other, WASM components do the same.
The C ABI approach works fine but becomes complicated when you want to pass things that aren't fixed-length, because unlike the C ABI usage when linking different libraries into the same address space, the host and the module run in different address spaces. Eg if the host wants to pass a string to a module fn, it first needs to call a different "malloc" module fn to allocate some space in the module address space and return that address, then the host writes the string to that address, then calls the original fn it wanted to call with that address, then calls a third "free" function at the end to release that allocation. Does that work? Of course it does. But the component ABI abstracts over all that for you and lets you call the function with the host's string type, and the runtime on the host side and bindings on the module side handle all the thunking so that it appears on the module side as the module's string type.
TFA doesn't seem to have any arguments other than "It's too many new words for me to learn so it's unnecessary I decided." Eg:
>And don't get me started on WIT's kebab-case identifiers (function names, interface names...). Why??? How can a specification about cross-language interoperability can come with a convention that basically no programming language use?
The point of the kebab case is that it does *not* show up in the generated bindings. It's processed by the bindings generator into the appropriate casing for the language. Eg a function name `foo-bar` becomes `foo_bar` in Rust, and a type named `foo-bar` becomes `FooBar`.
I think the argument here is that you don’t need a full N:1:M mapping of data types to make this work. Transparency is a myth. If you try it (as in Sun RPC, DCE, CORBA, COM, etc.) you end up just writing wrappers anyway as you hit some edge case that the universal type system doesn’t support. E.g., look at the clunky IDL interfaces COM had to invent to get interop with JavaScript objects because IDL had baked-in assumptions about how records work.
Since the point is to enable component-level interop, not fine-grained function interop, you can instead define a simple interchange format (more JSONish than IDLish) and write the library interfaces accordingly. More of a microservice approach than a linker approach.
Counter-point: I happen to be working at $dayjob on things related to Components (both on the host side and the module side), including using some "WIP" things like the "resource" feature (opaque handles with methods), and haven't hit any edge cases.
Sure, it’s expected that you won’t hit problems if you’re using the paradigms that existed when the IDL was defined. On the other hand, trying to apply Sun RPC after everything became object-oriented…well, that’s when we blew it up and invented CORBA. So I’m just saying the cycle is likely to continue with this approach. In other words, come back in ten years and see how it aged.
The alternative is to use something so simple it can’t become outdated. Dumb down the interface rather than making the interop omniscient. (Kind of like the dumb network principle.)
I just realized: The very fact that WASI isn’t using any of the previous N attempts at a supposedly universal IDL, but instead inventing yet another one, itself demonstrates the problem with this approach.
It's entirely reasonable for WASM to use its own IDL. Afaik Chrome already uses an IDL to describe the interaction between native C++ classes and their Javascript bindings.
Integrating WASM modules with each other, WASM with JS, and WASM with native functionality seamlessly is a huge and unsolved challenge.
Or it just points towards NIH syndrome, combined with hype and anti-hype driven mentality of programmers, and general forgetting of previous technologies. Or even when the differences are close to 0, doing a new not exactly same implementation for ... whatever reason.
Or COM's evolution, WinRT, where the .NET metadata also doesn't really map to JavaScript or C++, without some additional kludges with metadata as workaround.
Don't forget the extra fun that happens when you call free() from a different allocator than the one that allocated the memory object, though I suspect WASM's module approach might make it difficult to happen...
Yes, that won't happen here, because the host is just calling some global malloc and free exported by the module. Well, obviously the implementations of those globals can be buggy in some way, but that's the module's bad.
OT but this...
> I strongly believe that async is the new billion dollar mistake of the 2020's: a design aberration that wasted so much developers time that it had cost billions of dollars to companies.
Yes. Nicely put
Completely disagree.
Async as a paradigm has probably created billions of dollars in that it's allowed many many many developers to reasonably scale beyond what they could've otherwise.
Agree wholesale. I've got a complex data reader built on a single server that receives tens of thousands of requests a day, and most tend to happen in a few hour window. Async was super important to scale to user requests by allowing non-blocking calls to not get captured behind blocking ones.
I think GP is arguing that green threads e.g. in Go is a better way to structure the same thing.
No. I had a nasty experience with Typescript's async/await and I am annoyed
I find async/await very confusing. It as one thing pretending to be another
I prefer my paradigms simple and straight
Async await is deceptively simple but very wiggly. Not straight at all. Loads of magic fairy dust obscuring what's really happening
It is helpful until it isn't and then it's very unhelpful
And I disagree with your disagreement :)
Just some background first: one of the billion dollar mistakes is the null pointer which is a special value to be assigned to any type. It was inherent in the first high (today considered low) level languages. Those languages (C most importantly, then Java and nowadays even Go) enabled many more programmers to create programs and they likely would not have been able to, had they been forced to write assembly. So, it's a billion dollar mistake within a trillion dollar revenue.
The async now solves a similar practical problem but in the space of prallelism (or concurrency, in some cases). A different approach might have given us the same result, but a different implementation that might be just as approachable.
Optionals and Results have given us a better way than to have nulls, and maybe golang style channels could have given us a better way to handle async?
What does this even mean, how can you do any form of modern computation without async?
async / await as they exist in JavaScript, Python, and Rust aren’t the only way to execute tasks concurrently.
That’s what I’m asking - did the comment mean that the async/await pattern is bad, or that asynchronous programming in general is bad?
asynchronous programming is the bee's knees, and my preferred mode
Asyc/await pretends to be synchronous and gets all tied in horrible knots when the sunk costs mount
Writing IO without blocking has always been second nature to me
But they are a darn convenient mental model if you’re just trying to do some basic async. The alternatives are harder to wrap your head around. Unless I’m missing some other cool pattern.
> Unless I’m missing some other cool pattern.
Explicit state machines
Wait they promised to not adopt the cancerous function coloring approach in wasm. Please tell me they didn't follow the dark path of JS
The plan is to support async without function colouring.
I recommend this talk by Luke Wagner on async plans in WASI 0.3 (https://youtu.be/y3x4-nQeXxc) for background on how it will work.
To summarise, from an API perspective, the caller and callee won't know if the other is async or not. When calling async from non-async, an event loop is inserted at link-time and it blocks.* When calling non-async from async, the callee will get suspended if it tries to do blocking IO. That still leaves the problem of long-running compute without IO in a non-async context, but at least you're not wasting CPU time in that scenario.
* If the host runtime already has an event loop, I assume the callee will get added to that.
> You have been blocked! If you are not a malicious actor, please update your web browser to the latest version to access this website. If the problem persists, please contact support.
Unfortunate.
I think that these days the scammers have won. They can do a better job than I can at being authentic.
FWIW based on the comments I actually prefer a richer runtime that allows some of the language level features that have been hard to get support for. I think having mainstream support for effects would be sort of magical.
Actual isolated modules? Talk about living in the future.
Wasm is a lot like the jvm and flash, which makes some people hate it. But for me the problem with those platforms was the execution, not the idea.
I'm cautiously optimistic that wasm will finally give us a good high performance cross platform secure execution target for the web.
You have been blocked, apparently Android Chrome is a malicious browser.
Disabling Ublock Origin for that site worked for me.
Just had it too, refreshing worked.
Not on my case.
Probably they are blocking access from 'suspicious places' e.g. anywhere that's not the US or Western Europe.
The irony of talking about enshittification...
Germany...
On a version of Chrome from two days ago, and still blocked from visiting the site. What an aggressive, false positive, worthless check.
It's hard because, a lot of this stuff (async WASM, effect-driven WASM, GC in WASM, etc) is all driven by the desire to push these concerns out of wasm bytecodes, because it explodes the binary's size.
I have a pretty trivial webapp written in Yew and I stopped working on it once I saw that the wasm artifacts were weighing at 4MB (uncompressed) for relatively little functionality. THIS is what is driving the next round of "work" on WASM: to wring more functionality out of a system (WASI 0.1) designed as a drop-in replacement for emscripten output. As an aside on this point, the author is waaaay in front of their skies with basically all of their critiques around ByteCode Alliance; A lot of innuendo to basically serve a rhetorical point that isn't really true (WASI 0.1 is "good enough"). It has drawbacks! That's what the iterations on the protocol are exploring!
And this tech is cool and all, but right now a reasonably-vanilla typescript react app w/ a modern bundler (ie including stuff like router, redux, oidc, etc) is beating the breaks off of a similar app in Rust or C#/Blazor in terms of bin size. And there's no perf or API-surface argument that overcomes this. And it has chilling effect on developers when they reach for this tech.
Then maybe "this stuff" should not have been tacked onto the WASM standard in the first place? There's no free lunch. Sure, it sucks that your favorite garbage-collected interpreter/runtime bloats your module size. But the alternative option is to bloat every WASM engine by shoehorning your use case into the standard.
As much as binary size should be a problem for individual developers to deal with, I've seen how those individual developers treat Electron. There's no doubt in my mind that someone will ship 50 little JavaScript programs inside wasm modules, each with its own garbage collector. Then it becomes my problem.
Promise you won't tell the GC'd language enthusiasts this, but wasm engines supporting the wasm-gc proposal don't need to ever run a garbage collector. A simple bump allocator is 100% compliant, as long as the module's memory gets cleaned up once it terminates. Wasm engines for embedded systems will probably do exactly that, and leak memory rather than collect garbage. The web already has a garbage collector for JS, so not a big deal there either. Bloat has its cost, but at least it's only paid by cloud/edge/desktop and other use-cases that can afford to put it in their engine.
I think wasm-gc is worth it for WebAssembly. The dream is "run any language, anywhere" but it's always been easier for low level languages like C, C++ and Rust, because their binaries are smaller. Maybe with things like wasm-gc, WebAssembly can be great for C, C++, Rust, but also JavaScript and Python, Java and Go, OCaml, Perl, and whatever language gets sprung on us next.
It really depends on what you're using. If you use Rust with `wasm32-unknown-unknown`, you'll likely get small binaries (<200 kB). If you use C++ and Emscripten with all features enabled, then yeah you'll have multiple megabytes with all the libcxx and musl stuff.
My direct experience with wasm32-unknown-uknown contradicts your own. I am building a yew on nightly, moving between release or debug affects nothing.
> moving between release or debug affects nothing
I'd start with that, it's an obvious red flag. Switching between both should create huge differences. Also look into `wasm-opt` from the Binaryen project for post-link optimizations, `wasm-ld` from LLVM isn't that great at DCE.
Thanks for the feedback.
I was wrong about debug vs release; It's the difference between 4.0MB & 893KB on a ~500 LOC rust codebase (per cloc).
I want to observe that your suggestions don't undermine or refute my point about problems that WASM adoption faces. This serves to underline that the developer experience needs work. Opinions may differ on how much. Also that "developer experience" runs on multiple axes of concern (bin size, perf, accessibility, utility of language, etc).
I agree, my personal take: if you don't want to get your hands dirty, WebAssembly is not ready for you yet. It'll take at least 5 more years before the tooling gets into a state where things should just work (especially DWARF support). I mean, we still cannot free memory! (actually there is a crazy way by recreating a new WebAssembly instance with a shrunk'd `ArrayBuffer`, but it requires you writing your own memory allocator)
My point is: if you're comfortable working with a slightly obscure microcontroller, then you won't have much problems. LLVM supports WebAssembly out of the box, so it mostly feels like programming for one of those.
Anecdotally: we run a large Rust app in under 1 MB of WebAssembly at Zscaler.
> 4.0MB & 893KB on a ~500 LOC rust codebase (per cloc).
My guess is 50,000 LOC would not be 100 times bigger err
Methinks you are counting overhead
More accurately... meguess
It's refreshing to see that we are not alone in our thoughts on how the community is not being stewarded towards its own interests. I applaud the author on how clear he made the argument.
For those that aim to continue working on top of WASIp1, WASIX (https://wasix.org) might be a great way to get your programs with sockets and threads fully running on Wasm.
Note: I work at Wasmer (https://wasmer.io), a WebAssembly runtime.
But WebAssembly is one of the most promising opportunities in a long time to escape the C ABI problem. Why should we squander that? Do we want to be stuck writing C forever?
Why not just come up with a new calling convention and implement it for a handful of popular languages?
Some of us already escaped that, with other bytecode formats, or stuff like COM, AIDL, XPC, D-BUS.
The WIT types are a great proposal: we need wasm to be able to pass around strings and lists! Maybe should have been part of the MVP to make wasm a success.
This whole component model mess on the other hand though: Don't get me started with all these kebab case worlds
I wish people would stop using the term "enshittify" to mean other things than what Doctorow coined it to mean. This has nothing to do with "enshittification" and there were plenty of other words to pick from.
I see the article says "sabotaged", so perhaps that was fixed? Or the titles never matched?
The promise of being able to use any library from any language is really quite compelling, and that's what the WASM component model is about for me. It's pretty sad to say this amount of hate TBH
Already fulfilled by other bytecode runtimes in the past since UNCOL (1958), eventually people settled on a couple of key languages instead.
Many of us (haters), are old timers seating on the saloon bench seeing yet another slew of gold diggers arrive full of enthusiasm into town.
This time is going to be different, and it will take over the world (TM).
> The promise of being able to use any library from any language is really quite compelling, and that's what the WASM component model is about for me.
I'm excited for having extremely lightweight sandboxed. WASI enables having a runtime with a bunch of loaded libraries. You can start a very small script & link in the already loaded modules on the fly, in a very secure fashion.
It's be like having an isolate-oer-requesf model. Super secure, but with fantastically low overhead. Ideally instead of having a huge app server, a front end router would be picking which specific actions to run.
The ability to spin up a cast number of very lightweight secure processes and have them communicating with each other is fascinating. App servers as we build them are ghastly complicated swiss army knives, and being able to have something like a "serverless"/lambda architecture where we can narrow the scope down & really think about what has to be in a given request handling's process could be a big operational boon, if we're willing to once more venture away from the comforting warmth of the monolith that folks love huddling up next to.
As usual though hope & possibility is speculative & nuanced & diverse, and disbelief & disgust is blanket & unifying. I have no clue how I'm still so shocked to see negativity upvoted, positivity out down upon, after it happening so many times but I keep being surprised how strongly negativity reigns. And how fiercely & widely it downvotes! There's just something about the disbelievers & skeptics that they have to smash the downvote, can't abide possibility or excitement; there's never any wait and see, never any maybe about it. Just doom & gloom on and on.
You have been blocked! If you are not a malicious actor, please update your web browser to the latest version to access this website. If the problem persists, please contact support.
To be clear, this is (mainly) about the enshittification of WASI, not WASM. If you're writing code to run in a web browser you will never interact with WASI. It is unfortunate to see WASI fall victim to the software componentry / IDL meme, but I doubt we're going to see that be an issue in browser WASM.
The part that actually pertains to WASM - async/await - exists because everything in a browser lives in Someone Else's Single Threaded Event Loop. Writing code that lives in an event loop is pure pain, and async/await exists solely to fix this problem. The billion dollar mistake is not adding async/await to programming languages, it's single-threaded event loops. Anyone talking about C10K or non-blocking I/O in regards to async/await is probably missing the point, because aside from one stubbornly single-threaded programming language[0] you can launch threads to handle reading or writing data and sockets.
[0] JavaScript. It's always JavaScript.
If you were thinking Python, you're wrong. Even with the GIL, Python supports threads, and concurrent I/O is one of the few reasons why they're useful.
You definitely will, because this is the approach that is also being taken by runtimes like Dart, Kotlin among others.
> Writing code that lives in an event loop is pure pain,
Do you think?
I find explicit event loops pleasurable.
Does that make me a freak?
They're pleasurable when you're the only author. They become a minefield unless every contributor is diligent, which happens very often.
Yup. Somebody is going to write something that hogs cpu without yielding to the loop or queues up an absurd number of tasks waiting to be executed which effectively has a similar effect. All of a sudden latencies are high. Depending on how tracing is done it can appear like certain io operations are the culprit if you just go off of traces.
P99 latency for every route on your web server will be fixed at the max time any individual unit takes to execute before yielding plus loop overhead and its own time. This can drastically increase it for many routes.
Meanwhile some genius insists that our app is “io bound” so the single threaded async runtime must be a perfect fit.
Apart from just being generally faster at least with go I know that when somebody screws up there should be n other threads still executing tasks.
> If you are using WebAssembly in a web browser then you are good, WASI does not concern you.
In case you absolutely do not care for WASI but love WebAssembly for the web.
I don't think there's any guarantee that browser vendors will continue to support WASI 0.1 indefinitely; Once multiple versions of the standard are in play, codebase supporting them simultaneously explode in size. None of this gets into the likelihood that, once WASI hits 1.0, the prior versions very well could be "retired".
And keep in mind that the perspective of the author is that things will only get worse from hereon, with regard to WASM.
I didn't know browser vendors supported WASI! I said that as some of us do not use WASI at all, just WebAssembly itself, so they can save some time by not reading this if they're not interested in WASI and/or its limitations.
Browsers don't have any built-in support for WASI. It's up to the web page to define those as exports for WASI-expecting WASM modules to import.
Actually, looking at the https://component-model.bytecodealliance.org/design/wit.html is the best way of understanding why we have what we have.
We have the basic primitives like signed and unsigned integers of various sizes, floating point numbers, bools and chars.
Remember that wasm components are like shared libraries (dll/so objects). Shared libraries themselves have dependencies (eg: vlc needs qt, which needs mesa, which needs x11/wayland etc..). Each component/shared library has a set of imports from other shared libraries and exports items to other shared libraries or apps.
For example, lets say that I want give a vector/array as an argument or receive it as the return type. On native libs, we just give a pointer + len as arguments, and the function can simply read/write using that pointer.
Except, wasm components are isolated (shared-nothing model). So, I can't just allocate the bytes and give a "pointer" to the jpeg decoder. Because both of us don't share the memory. This has a few reasons:
1. security: separate memories make sure that a component can't read/write another component's memory. 2. safety: If we don't have higher level types, then people will just pass around blobs and cast those bytes into types. Imagine one component thinks of rect as `Rect { x, y, w, h: f32 }` and another component thinks `Rect { x1, y1, x2, y2: f32}`. Without "record" types, we can't find that error. 3. flexibility: Lets say we want to pass around a list of strings between components. how would you do it between rust and js? To let each language feel natural, we need to "copy" these higher level types across the boundary (and wasm needs to understand these higher level types) into the respective suitable types.
This is why we have records (structs), strings, list<T>, variants (tagged unions), Option<T> and such types to allow for a feature-rich API. These are all passed by copy at the boundary with proper validation by the runtime in advance, so you get both performance, safety and ergonomics.
Finally, we also need to talk about "ownership", because some objects (like files via file descriptors) need to be passed across component boundary and we need to wasm let somehow know that we are passing on ownership of the file. We do this with "resource" (object with an ownership like a file descriptor or host native object or socket etc..). And wasm must also ensure that the object will be alive for the duration of the borrow.
The rest of the WIT is simple.
Interface is literally just a group of type signatures or objects. just like a module in python, rust. In C world, we usually just prefix the library/type name for all the function that belong to a certain library/type. In WASI, we just place the related fns inside an interface.
Similarly, a world is just a group of imports and exports that represent a component. world = component. world can import/export types/interfaces/fns/data. You can have multiple 'worlds' and interfaces within a wit file.
And a package is a group of wit files. similar to a java or go package that a file belongs to.
Its not really hard to understand. Most of the terminology directly translates to what we all see in any modern language like js, py, java, rust, go etc..
And the docs are not accessible at the moment, because its still a WIP and unstable. They are experimenting with rust and js to see how well this model works in practice.
> We're sorry but this website doesn't work properly without JavaScript enabled. Please enable it to continue.
> It means that the scope of WASI has shifted from falling language B from language A to solving something that nobody has asked for.
Uhh really? You just got done pointing out
> But, WASI 0.1 was severely limited about the type of data that it could exchange: basically integers and pointers to buffers.
We obviously couldn't stop there.
The author has all sorts of shit to throw about the interface definitions being way more complex than they want.
It turns out calling other languages isn't just about calling their stuff. You also need to be able to import and then latter link the things you want to use. Being able to say what it is you want brought in, what you want to link to, that's stuff the runtime needs to be able to do.
Look at c and c++. They've been around for decades and there's still nearly no mainstream package/library management. Because it looks like what Kerkour asked for, because everything is too simple in that world.
You need this stuff. You need to be able to create high level semantics to interoperate across. You need a rich enough ABI to let languages negotiate for & get the things they're going to call.
The qualms against async are even less well defined & even more unsupported, which is compensated for being ever more foaming at the mouth & wild gesticulating. Rather than acknowledge that yeah, some people like & use async in languages, there's utterly untargeted shade of the broadest degree:
> Most importantly, after all these years, nobody knows when to use [rust's async] or not! Should this library be async or not?
Io-less libraries that can put off being async are in fact excellent. But there still, in most systems, are things happening over time, and async happening in Python and Node and others radically upped the game of what was possible. And in the past decade have even evolved into something pretty nice & great & usable.
But Rust having some difficulty figuring out how to manage their ecosystem is, to Kerkour, apparently enough to damn the whole enterprise, "The new billion dollar mistake." Having been around for cgi-bin and mod-perl, I don't think I'm so massively massively confidently assured.
Things aren't always exactly as perfect as we might want. And that just really drives some people wildly mad, is inexcusable. 'We should never have tried, ruin to those trying to improve things, & drop it all & go back!,' seems to be the message. Actually the message here is even worse, more, 'these people are vultures trying to syphon money with the express intent to create waste & milk the anarchy,' which is some contagious shit to be spewing.
This seems so overblown. WIT's are not that complex. Theres reasons stuff is like this (permitting wasm engines form a registry to give people what they're asking to use, without needing internals that would have to DIY this all themselves). And maybe such a broad objective as a universal computing runtime might be possible with less. But honestly the risk of underbidding & failing to connect different systems well seems far worse than the risk of, I dunno, what the author seems to be apoplectic about, perhaps maybe enjoying using a package manager to help satisfy dependencies.
One thing the author is right about, that makes me extremely sad:
> I hardly see browser vendors implementing this
I can excuse this today, because it is unknown. But wow it's scary as hell seeing how uninvolved & interest the browsers look. The browsers seem full speed ahead building their own take on platform, doing their own file system APIs and what not. There's great efforts by many to bridge the two worlds, to create WASI runtimes for the browser, but some day I really hope the promised universal machine of WASI is something the web can enjoy and use. But I'm also not in a hurry; I think there's a lot of figuring how best to make this wasi 0.2 world nice & letting languages figure out their own tooling to come play. Patience & improving & iterating is good; trying is good. We improve & progress through time and effort.