45 comments

[ 3.4 ms ] story [ 73.1 ms ] thread
Law of question marks on headlines holds here: no / never seems to be the answer.

Article l also discussed ref types, which do exist and do provide... Something. Some ability to at least refer to host objects. It's not clear what that enables or what it's limitstions are.

Definitely some feeling of being rug-pulled in the shift here. It felt like there was a plan for good integration, but fast forward half a decade+ and there's been so so much progress and integration but it's still so unclear how WebAssembly is going to alloy the web, seems like we have reams of generated glue code doing so much work to bridge systems.

Very happy that Dan at least checked in here, with a state of the wasm for web people type post. It's been years of waiting and wondering, and I've been keeping my own tabs somewhat through twists and turns but having some historical artifact, some point in time recap to go look at like this: it's really crucial for the health of a community to have some check-ins with the world, to let people know what to expect. Particularly for the web, wasm has really needed an update State of the Web WebAssmebly.

I wish I felt a little better though! Jco is amazing but running a js engine in wasm to be able to use wasm-components is gnarly as hell. Maybe by 2030 wasm & wasm-components will be doing well enough that browsers will finally rejoin the party & start implementing new.

We use WASM quite a bit for embedding a ton of Rust code with very company specific domain code into our web frontend. Pretty cool, because now your backend and frontend can share all kinds of logic without endless network calls.

But it’s safe to say that the interaction layer between the two is extremely painful. We have nicely modeled type-safe code in both the Rust and TypeScript world and an extremely janky layer in between. You need a lot of inherently slow and unsafe glue code to make anything work. Part is WASM related, part of it wasm-bindgen. What were they thinking?

I’ve read that WASM isn’t designed with this purpose in mind to go back and forth over the boundary often. That it fits the purpose more of heaving longer running compute in the background and bring over some chunk of data in the end. Why create a generic bytecode execution platform and limit the use case so much? Not everyone is building an in-browser crypto miner.

The whole WASM story is confusing to me.

>The whole WASM story is confusing to me.

Think of it as a backend and not as library and it clicks.

Yes, but that’s exactly what I’m trying to avoid.
The confusion is perhaps due to your usage focus and the security constraints browser compiler makers face to make something secure.

First off, remember that initially all we had was JS, then Asm.JS was forced down Apple throats by being "just" a JS compatible performance hack (remember that Google had tried to introduce NaCl beforehand but never got traction). You can still see the Asm.JS lineage in how Wasm branching opcodes work (you can always easily decompose them into while loops together with break and continue instructions).

The target market for NaCl, Asm.JS and Wasm seems to have been focused on enabling porting C/C++ games even if other usages was always of interest, so while interop times can be painful it's usually not a major factor.

Secondly, As a compiler maker (and to look at performance profiles), I usually place languages into 3 categories.

Category 1: Plain-memory-accessors, objects are usually a pointer number + offsets for members, more or less manually managed memory. Cache friendlyness is your own worry, CPU instructions are always simple.

C, C++, Rust, Zig, Wasm/Asm.JS, etc goes here.

Category 2: GC'd offset-languagses, while we still have pointers(now called references) they're usually restricted from being directly mutated, instead going through specialized access instructions, however as with category 1 the actual value can often be accessed with the pointer+offset and object layouts are _fixed_ so less freedom vs JS but higher perf.

Also there can often be GC-specific instructions like read/write-barriers associated with object accesses. Performance for actual instructions is still usually good but GC's can affect access patterns to increase costs and some GC collection unpredictability.

Java, C#, Lisps, high perf functional languages,etc usually belong here (with exceptions).

Category 3: GC'd free-prop languages, objects are no longer of fixed size (you can add properties after creation), runtimes like V8 tries their best to optimize this away to approach Category 2 languages but abuse things enough and you'll run out a performance cliff. Every runtime optimization requires _very careful_ design of fallbacks that can affect practically almost any other part of the runtime (these manifest as type-confusion vulnerabilities if you look at bug-reports) as well as how native-bindings are handled.

JS, Python, Lua, Ruby, etc goes here.

Naturally some languages/runtimes can straddle these lines (.NET/CIL has always been able to run C as well as later JS, Ruby and Python in addition to C# and today C# itself is gaining many category 1 features), I'm mostly putting the languages into the categories where the majority of user created code runs.

To get back to the "troubles" of Wasm<->JS, as you noticed they are of category 1 and 3, since Wasm is "wrapped" by JS you can usually reach into Wasm memory from JS since it's "just an buffer", the end-user security implications are fairly low since the JS has well defined bounds checking (outside of performance costs).

The other direction is a pure clusterf from a compiler writers point of view, remember that most of these optimizations of Cat 3 languages have security implications? Allowing access would require every precondition check to be replicated on the Wasm side as well as in the main JS runtime (or build a unified runtime but optimization strategies are often different).

The new Wasm-GC (finally usable with Safari since late last year) allows GC'd Catgory 2 languages to be built directly to Wasm (and not ship their own GC via Cat 1 emulation like C#/Blazor) or be compiled to JS, and even here they punted any access to category 3 (JS) objects, basically marking them as opaque objects that can be referred and passed back to JS (improvement over previous WASM since there is no extra GC synching as one GC handles it all but still no direct access standardized iirc).

So, security has so far taken a center...

WASM is just hotfixing javascript to use any language people want.

It's all about javascript being popular and being the standard language, js is not a great language, but it's standard across every computer, which dwarfs anything that can be said about js.

Adjusting browsers so they can use WASM was easy to do, but telling browser vendors to make the DOM work was obviously more difficult, because they might handle the DOM in various ways.

Not to mention js engines are very complicated.

The entire DOM API is very coupled to JS, it's all designed with JS in mind, any new and future proposed changes are thought about solely through the lens of JS.

If they introduced a WASM API it would perpetually be a few months/years behind the JS one, any new features would have to be implemented in both etc.

I can see why it's not happened

(edit) And yes, I think the intention of WASM was either heavy processing, or UI elements more along the lines of what used to be done with Java applets etc. potentially using canvas and bypassing the DOM entirely, not as an alternative to JS for doing `document.createElement`

>> The entire DOM API is very coupled to JS,

Is it though? I thought it was all specified in WebIDL and all the browser vendors generate C++ headers from it too.

Has anybody written a nice DOM wrapper for C++/Rust? So you can do everything from the comforts of the C++/Rust application? The API whould match JavaScript APIs as much as possible.
I want DOM access from WASM, but I don't want WASM to have to rely on UTF-16 to do it (DOMString is a 16-bit encoding). We already have the js-string-builtins proposal which ties WASM a little closer to 16-bit string encodings and I'd rather not see any more moves in that direction. So I'd prefer to see an additional DOM interface of DOMString8 (8-bit encoding) before providing WASM access to DOM apis. But I suspect the interest in that development is low.
I've been reading about this for a while. Will it. Won't it.

Does anybody know why is it such a big problem to add dom access to wasm?

In worst case, we should have a second option to Js (which is not typescript - typescript is just a lipstick on a pig). If wasm is not it, why not something different? Having dart in would be great.

I'm worried that wide use of WASM is going to reduce the amount of abilities extensions have. Currently a lot of websites are basically source-available by default due to JS.
> Wasm includes various JavaScript APIs that allow compiler-generated glue code

One of the reasons I’m interested in wasm is to get away from the haphazardly evolved JS ecosystem…

You get more of that with wasm actually. You get the rust zoo, elixir zoo, gleam, three toolchains for c or what not. And for each of them you need a bunch of fine autogenerated javascript glue to sniff, because those people can't agree on what the string looks like in memory.
Disclaimer: I work on Jco, one of the user-facing Bytecode Alliance WASM JS ecosystem projects

Just a note, but there is burgeoning support for this in "modern" WebAssembly:

https://github.com/bytecodealliance/jco/tree/main/examples/c...

If raw WebIDL binding generation support isn't interesting enough:

https://github.com/bytecodealliance/jco/blob/main/packages/j...

https://github.com/bytecodealliance/jco/blob/main/packages/j...

https://github.com/bytecodealliance/jco/blob/main/packages/j...

Support is far from perfect, but we're moving towards a much more extensible and generic way to support interacting with the DOM from WebAssembly -- and we're doing it via the Component Model and WebAssembly Interface Types (WIT) (the "modern" in "modern" WebAssembly).

What's stopping us the most from being very effective in browsers is the still-experimental browser shim for components in Jco specifically. This honestly shouldn't be blocking us at this point but... It's just that no one has gotten around to improving and refactoring the bindings.

That said, the support for DOM stuff is ready now (you could use those WIT interfaces and build DOM manipulating programs in Rust or TinyGo or C/C++, for example).

P.S. If you're confused about what a "component" is or what "modern" WebAssembly means, start here:

https://component-model.bytecodealliance.org/design/why-comp...

If you want to dive deeper:

https://github.com/WebAssembly/component-model

You should write an article about this stuff and post it here since this is the first time I’m hearing about all of this
I have used Jco quite a bit (and contributed a few times) to build out simple utilities binding Rust code to JS [1][2]. I think it is great and the Component Model is the most exciting step towards real useful polyglot libraries I have seen in years. I wish it were better publicized, but I understand keeping things lowkey until it is more fleshed out (the async and stream support coming in Preview 3 are the real missing pieces for my usecases).

[1] https://github.com/awslabs/aws-wasm-checksums [2] https://github.com/landonxjames/aws-sdk-wasi-example

I think it's a huge exaggeration to say "the support for DOM stuff is ready now."

When writing most non-web software, you can usually write it quickly in a high-level language (with a rich standard library and garbage collection), but you can get better performance (with more developer effort) by writing your code in a lower-level language like C or Rust.

What developers are looking for is a way to take UI-focused DOM-heavy web apps, RIIR, and get a performance improvement in browsers.

That is not ready now. It's not even close. It might literally never happen.

What is ready now is a demo project where you can write WASM code against a DOM-like API running in Node.js.

What you have is an interesting demo, but that's not what we mean when we ask when WASM will get "DOM support."

Interop with JS APIs is a requirement. Therefore, glue code is a requirement. If we’re not going to get direct access to the DOM, can we at least get the ability to just list the JS functions that our wasm will call? Of course the list will be bundled with (compiled into) the wasm blob, and whether it’s literally a text list or something like a registration call naming those JS functions, I am agnostic about. Everyone having to write all their own glue[*] is just nuts at this point.

[*]Yeah, the toolchains help solve this a bit, but it still makes me ship JS and wasm side-by-side.

I am adding a WASI runtime (0.1 and 0.2) in https://exaequos.com, an OS running in the web browser. What would be a good DOM api ?
I don't think I want WebAssembly to have DOM support.

Would it be nice? Yes. But.

Every added feature is a trade-off between need -vs- outlay, overhead, complexity & other drawbacks. In order to justify the latter things, that "need" must be significant enough. I'd like to have DOM, but I don't feel the need is significant.

Some thoughts on use-cases:

1. "Inactive" or "in-instance" DOM APIs for string parsing, document creation, in-memory node manipulation, serialisation: this is all possible today in WASM with libraries. Having it native might be cool but it's not going to be a significantly different experience. The benefits are marginal here.

2. "Live / active" or "in-main-thread" direct access APIs to manipulate rendered web documents from a WASM instance - this is where the implementation details get extremely complex & the security surface area starts to really widen. While the use-cases here might be a bit more magical than in (1), the trade-offs are much more severe. Even outside of security, the prospect of WASM code "accidently" triggering paints, or slow / blocking main thread code hooked on DOMMutation events is a potential nightmare. Trade-offs definitely not worth it here.

Besides, if you really want to achieve (2), writing an abstraction to link main-thread DOM APIs to WASM postMessage calls isn't a big lift & serves every reasonable use-case I can think of.

i miss the times where computers where used to solve problem. Nowerdays people MAKE there problems themself by layering thousands of apis over each other and calling back and forth milions of times. no wonder computer are not getting faster.
The web is a platform that has so much unrealized potential that is absolutely wasted.

Wasm is the perfect example of this - it has the potential to revolutionize web (and desktop GUI) development but it hasn't progressed beyond niche single threaded use cases in basically 10 years.

I have zero experience with anything wasm, just regular old DOM with typescript, but I wonder if this is the kind of problem that could be addressed the same way that phoenix liveview addesses frontend updates, by message passing only with diff changes, and delegate the Dom manipulation to what works, effective modelling the wasm runtime as an actor.
It's one of the ways one can write the glue between WASM and JS. The actual problem is, like everything in javascript, there is no standard glue, no ABI, no expectation of what memory representation of a list or even a string looks like.

You can even compile elixir into wasm-fx and run actor model, it's super fun and mad, but what you can't do is not deal with the technicalities.

So either you buy into one of the frameworks that are (not) built on top of wasm and lock into their paradigm or roll your own, because wasm proper doesn't even have any abstractions above numbers on a stack.

This is one of those self-taught versus college educated subjects. See this for some background https://news.ycombinator.com/item?id=44599228

This subject is interesting because your typical college educated developer HATES the DOM with extreme passion, because it’s entirely outside their area of comfort. The typical college educated developer is typically educated to program in something like Java, C#, or C++ and is how the world is supposed to work. The DOM doesn’t work like that. It’s a graph of nodes in the form of a tree model, and many developers find that to be scary shit. That’s why we have things like jQuery, Angular, and React.

These college educated developers also hate JavaScript for the same reasons. It doesn’t behave like Java. So for many developers the only value of WASM is as JavaScript replacement. WASM was never intended or positioned to be a JavaScript replacement so it doesn’t get used very often.

Think about how bloated and slow the web could become if WASM were a JavaScript replacement. Users would have to wait on all the run time and dependencies to download into the WASM sandbox and then open like a desktop application, but then all that would get wrapped in something like Angular or React because the DOM is still scary.

Why would college educated developers hate node or tree structures? That’s half of our degrees!
> Java, C#, or C++ and is how the world is supposed to work. The DOM doesn’t work like that. It’s a graph of nodes in the form of a tree model

This makes no sense. A graph of nodes is a very common data structure in all these languages. Not only that, but DOM was originally designed with those languages in mind (hence why it's defined via IDL). Indeed, Java actually used to have the complete DOM API in its standard library: https://docs.oracle.com/en/java/javase/17/docs/api/java.xml/...

DOM is so dynamic, any integration with DOM will look no different than the way we do it now through JS. JS is built to work naturally with DOM.

Maybe we should stop overdesigning things and keep it simple. WASM needs more tooling around primitive types, threading, and possibly a more flexible memory layout than what we have now.

It's not just the DOM, it's also all other APIs like WebGL2.

I ended up having to rewrite the entire interfacing layer of my mobile application (which used to be WebAssembly running in WebKit/Safari on iOS) because I was getting horrible performance losses each time I crossed that barrier. For graphics applications where you have to allocate and pass buffers or in general piping commands, you take a horrible hit. Firefox and Chrome on Windows/macOS/Linux did quite well, but Safari...

Everything has to pass the JavaScript barrier before it hits the browser. It's so annoying!

I am confused by this. If WASM is a VM then why would it understand the DOM? To me it akin to asking "When will Arm get DOM support?" Seems like the answer is "When someone writes the code that runs on WASM that interacts with the DOM." Am I missing something? (not a web dev.)
At this point, why not implement DOM inside WebAssembly? The end of DOM compatibility issues ...
Emscripten has a handy tool called "Embind" for binding JavaScript/TypeScript and C/C++/whatever code. It's underappreciated and not well documented all in one place, but here is a soup-to-nuts summary.

Emscripten + Embind allow you to subclass and implement C++ interfaces in TypeScript, and easily call back and forth, even pass typed function pointers back and forth, using them to call C++ from TypeScript and TypeScript from C++!

Embind: https://emscripten.org/docs/porting/connecting_cpp_and_javas...

Interacting with Code: https://emscripten.org/docs/porting/connecting_cpp_and_javas...

Embind's bind.cpp plumbing: https://github.com/emscripten-core/emscripten/blob/main/syst...

C Emscripten macros (like EM_ASM_): https://livebook.manning.com/book/webassembly-in-action/c-em...

Emscripten’s embind: https://web.dev/articles/embind

I'm using it for the WASM version of Micropolis (open source SimCity). The idea is to be able to cleanly separate the C++ simulator from the JS/HTML/WebGL user interface, and also make plugin zones and robots (like the monster or tornado or train) by subclassing C++ interface and classes in type safe TypeScript!

emscripten.cpp binds the C++ classes and interfaces and structs to JavaScript using the magic plumbing in "#include <emscripten/bind.h>".

There is an art to coming up with an elegant interface at the right level of granularity that passes parameters efficiently (using zero-copy shared memory when possible, i.e. C++ SimCity Tiles <=> JS WebGL Buffers for the shader that draws the tiles) -- see the comments in the file about that):

emscripten.cpp: https://github.com/SimHacker/MicropolisCore/blob/main/Microp...

  /** 
   * @file emscripten.cpp
   * @brief Emscripten bindings for Micropolis game engine.
   *
   * This file contains Emscripten bindings that allow the Micropolis
   * (open-source version of SimCity) game engine to be used in a web
   * environment. It utilizes Emscripten's Embind feature to expose C++
   * classes, functions, enums, and data structures to JavaScript,
   * enabling the Micropolis game engine to be controlled and interacted
   * with through a web interface. This includes key functionalities
   * such as simulation control, game state management, map
   * manipulation, and event handling. The binding includes only
   * essential elements for gameplay, omitting low-level rendering and
   * platform-specific code.
   */
  [...]
  ////////////////////////////////////////////////////////////////////////
  // This file uses emscripten's embind to bind C++ classes,
  // C structures, functions, enums, and contents ...
Is there any data on the performance cost of JS/WASM context switches? The way the architecture is described, it sounds as if the costs could be substantial, but the approaches described in the article basically hand them out like candy.

This would sort of defeat the point that WASM is supposed to be for the "performance critical" parts of the application only. It doesn't seem very useful if your business logic runs fast, but requires so many switching steps that all performance benefits are undone again.

The fact that this needs to be explained with mentions of pointers and data alignment and garbage collection tells me that the decisions that the web standards committees make continue to be just completely disconnected from anything sane.

maybe my read is wrong, but everything i look at today just screams to me that the web is extremely poorly designed; everything about it is simply wrong.

Actually my journey was quite similar. I started to build a bindings and web components framework in Pure Go so that I can build user interfaces with webview/webview.

My apps just go:embed all their assets and spawn a local webview as their UI, which is quite nice because client and server use the same schemas and same validations for e.g. web forms and the fetch/REST APIs.

Server-side-rendered components are implemented using a web components graph whose components can be String()ified into HTML.

It's a bit Experimental though, and the API in the components graph might change in the future:

https://github.com/cookiengineer/gooey