Stream: show and tell

Topic: wasm without a host


view this post on Zulip Richard Feldman (May 03 2023 at 01:12):

I implemented a wasm example (running on Node.js, but could just as easily be in the browser) that doesn't have a host! https://github.com/roc-lang/roc/blob/4cb02f1d04e5f458389e44a78c542e497f589266/examples/nodejs-interop/wasm/hello.js#L59

view this post on Zulip Richard Feldman (May 03 2023 at 01:13):

so there's no dependency on Zig or clang or Rust or anything - all you need to build and run the example is roc and node

view this post on Zulip Richard Feldman (May 03 2023 at 01:13):

(or roc and a browser)

view this post on Zulip Richard Feldman (May 03 2023 at 01:14):

it works by implementing roc_alloc and such directly in JS, working with the WebAssembly's raw byte array representation of memory using JS commands instead of C commands

view this post on Zulip Richard Feldman (May 03 2023 at 01:14):

(thanks to @Brian Carroll for explaining to me how all that works in wasm!)

view this post on Zulip Brendan Hansknecht (May 03 2023 at 01:15):

Awesome!!!

view this post on Zulip Brendan Hansknecht (May 03 2023 at 01:15):

That is actually really exciting. Gets rid of a whole layer of wrapping and dependencies.

view this post on Zulip Richard Feldman (May 03 2023 at 01:17):

yeah! I'm not sure what the runtime performance implications are; presumably writing JS code instead of wasm code is slower in the general case, but in this case is it noticeable? I very much doubt it, but I don't really know :big_smile:

view this post on Zulip Brendan Hansknecht (May 03 2023 at 01:24):

I guess it depends how much you delegate to roc vs run in js. Given most stuff we are talking about here is just thin wrapper, I don't think it should make a tangible difference, but I haven't dug into anything here.

view this post on Zulip Brendan Hansknecht (May 03 2023 at 01:24):

I could try porting my old wasm cpu example to this to see the perf diff.

view this post on Zulip Agus Zubiaga (May 03 2023 at 01:28):

These thin functions around Uint8Array / DataView probably optimize well

view this post on Zulip Luke Boswell (May 03 2023 at 02:01):

When I saw that last commit I thought it was super cool. I'm very interested to play with this some more and see what I can build with it. Are you thinking of going this direction for the Node TS integration?

view this post on Zulip Richard Feldman (May 03 2023 at 02:19):

yep, exactly!

view this post on Zulip Richard Feldman (May 03 2023 at 02:21):

at Vendr we're planning to introduce Roc in multiple places in the code base using calls to small Roc functions (at first, then more and more Roc over time), which means it will be common to rebuild the platform

view this post on Zulip Richard Feldman (May 03 2023 at 02:22):

I want to make that rebuild as fast as possible, so taking out the step of building and linking the host (even if it's zig doing the build and wasm-ld instead of ld) will get multiplied by the number of different entrypoints we have

view this post on Zulip Richard Feldman (May 03 2023 at 02:22):

also, it means I don't have to introduce a zig dependency for everyone (nontrivial; we aren't currently using Nix, but rather Homebrew on Mac and Docker on Linux)

view this post on Zulip Richard Feldman (May 03 2023 at 02:23):

(well I guess introducing it is fairly trivial, but people have to run a manual command to get it, and then the versions have to be kept in sync, which requires re-running that command, etc. etc.)

view this post on Zulip Brendan Hansknecht (May 03 2023 at 02:27):

I could try porting my old wasm cpu example to this to see the perf diff.

Actually that won't be useful. It currently never allocates, that said, it is using effects that directly call into JS just like your JS host is doing. And it was way faster than the JS only version, so at least that is a good sign. It has to call into JS for every memory read and write that the cpu does. So essentially they are super thin functions that just wrap array access. So you would think the overhead of function calls could matter a lot in that case, but it doesn't seem to.

view this post on Zulip Brendan Hansknecht (May 03 2023 at 02:33):

Also, I wonder what the perf cost of having JS do memory management would be. As in alloc would essentially be a wrapper around new Uint8Array(requestedBytes). dealloc would be delete mem or a noop.

view this post on Zulip Richard Feldman (May 03 2023 at 02:49):

whoa, I didn't even think of that! Although I don't know if wasm can "see" those bytes :thinking:

view this post on Zulip Richard Feldman (May 03 2023 at 02:49):

like I don't know if it can address into them

view this post on Zulip Richard Feldman (May 03 2023 at 02:49):

but if so, that would be really cool!

view this post on Zulip Brendan Hansknecht (May 03 2023 at 03:10):

Ah, sounds like no

view this post on Zulip Brendan Hansknecht (May 03 2023 at 03:11):

Wasm has a buffer that js can read and write to, but wasm can't access js memory

view this post on Zulip Brendan Hansknecht (May 03 2023 at 03:11):

I guess this is where the wasm GC proposal should help in the future, iirc.

view this post on Zulip Brendan Hansknecht (May 03 2023 at 05:18):

Yeah, looked into this again.the new wasm GC stuff should resolve this. Discussed some in this talk: https://youtu.be/Nkjc9r0WDNo?t=855s

view this post on Zulip Brendan Hansknecht (May 03 2023 at 05:19):

Specifically for this, it adds reference types to wasm that can reference host data.

view this post on Zulip Brian Carroll (May 03 2023 at 21:02):

Wow this is a really interesting idea!
I bet JS is plenty fast enough. These functions will be called a lot so v8 will optimise the heck out of them. Especially since they're doing low-level things with numbers and byte arrays.

view this post on Zulip Brian Carroll (May 03 2023 at 21:06):

new Uint8Array won't work for memory management though. The Wasm module has a single Uint8Array and "allocation" means dividing it up into chunks on demand, and keeping track of each chunk so that you can free it and then hand it out to someone else again. In other words you are writing an allocator in JS. You cannot build on top of the JS allocator.

view this post on Zulip Brian Carroll (May 03 2023 at 21:07):

In other words you are sub-dividing the piece of memory that JS has already allocated to Wasm.

view this post on Zulip Brian Carroll (May 03 2023 at 21:07):

And growing it and so on

view this post on Zulip Brian Carroll (May 03 2023 at 21:07):

you can't grab more memory and shove it in there

view this post on Zulip Brendan Hansknecht (May 03 2023 at 22:15):

Yeah, makes sense. Though i guess you could implement the malloc algorithm that sub divides in js if wanted.

view this post on Zulip Brendan Hansknecht (May 03 2023 at 22:17):

Otherwise, more convenient apis should be possible once the reference types proposal is implemented (apparently it was split out of the wasm GC proposal and is already implemented in wasmtime)

view this post on Zulip Brendan Hansknecht (May 03 2023 at 22:20):

Oh, actually it looks like the host references will be opaque to wasm, so this still doesn't help here. Wasm can't twiddle the bytes.

view this post on Zulip Brendan Hansknecht (May 03 2023 at 22:23):

Also, found an interesting related read: https://fitzgeraldnick.com/2020/08/27/reference-types-in-wasmtime.html

view this post on Zulip Brian Carroll (May 04 2023 at 17:32):

That's right. When you hear that some GC feature is coming to Wasm, you naturally think it sounds related to this and really useful. But in fact it has zero relevance to implementing roc_alloc. What is needed is a malloc implementation written in JS that manipulates a UInt8Array.

view this post on Zulip Brian Carroll (May 04 2023 at 17:39):

So one approach would be to try to find a simple open source allocator in C or Zig and translate it line by line to JS. I know the Zig folks have a small-code-size allocator that they use for Wasm. Can't remember the name of it.


Last updated: Jul 06 2025 at 12:14 UTC