flak rss random

www which wasm works

We’ve gotten libjxl built for wasm. It was a struggle, but we got it done, and we’re ready to run it. WASM is a straightforward standard designed for ease of implementation, so this should be a walk in the park.

wazero

Let’s start with wazero: the zero dependency WebAssembly runtime for Go developers. “Import wazero and extend your Go application with code written in any language!”

The initial download, install, whatever you call it goes smoothly enough. Start with one of their tiny examples, verify it works, then switch out the wasm file.

import[17] memory[a.a]: invalid byte for limits: 0x3 != 0x00 or 0x01

I don’t know what this means. Searching for “invalid byte” finds one line.

err = fmt.Errorf("%w: invalid byte for importdesc: %#x", ErrInvalidByte, b)

Except that’s not an exact match. The real error line is cleverly obfuscated and required searching for “for limits”. There’s a comment.

// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#limits%E2%91%A6

Sure enough, the standard says there is a flag of 0 or 1. Not 3. Or 0x3. Or 0x03.

wasmer

Let’s try wasmer-go, a complete and mature WebAssembly runtime for Go based on Wasmer.

We have a new error!

Validation error: threads must be enabled for shared memories (at offset 513)

Let’s browse the detailed API documentation full of examples. Well, I can’t see any means to enable threads. The word thread doesn’t even appear on the page.

I feel like I’ve just been told to draw the rest of the owl.

wasmtime

We’ve got one more wish, wasmtime-go. This Go library uses CGO to consume the C API of the Wasmtime project which is written in Rust. No bragging about completeness. Good or bad sign?

The first sign of danger appears before we even run our code. Upon linking, we get this warning.

/usr/bin/ld: warning: x86_64.o: missing .note.GNU-stack section implies executable stack

Good grief, what demons is wasmtime summoning that it needs an executable stack in 2023? I guess it’s good that people only ever use wasmtime in their programs, and never mix in any other unsafe code.

A new error emerges when we run this demon spawn.

Invalid input WebAssembly code at offset 513: threads must be enabled for shared memories

But wasmtime has the long sought answer!

config.SetWasmThreads(true)

And now we’re getting closer to running our wasm library. It’s parsed and processed and locked and loaded. But not actually runnable.

expected 18 imports, found 0

Fair enough, I need to provide a little runtime support. I think it would have saved some time had these requirements been documented, but once we see what’s missing it should be pretty easy to get back on track.

    for _, imp := range mod.Imports() {
        fmt.Printf("missing import: %s\n", *imp.Name())
    }

Let’s run this again.

missing import: b
missing import: c
missing import: d
missing import: e
missing import: f
missing import: g
missing import: h
missing import: i
missing import: j
missing import: k
missing import: l
missing import: m
missing import: n
missing import: o
missing import: p
missing import: q
missing import: r
missing import: a

Ah, I see now why they didn’t feel the need to document these imports. They’re so simple and obvious, it probably never occurred to anybody that this would benefit from documentation. I like that the list starts with “b” and ends with “a”.

web

At this point I figured it would be a good idea to verify that the wasm we’re using works somewhere, anywhere. Back to the browser.

The libjxl wasm demo includes a little site builder, although it turns out to be pretty complicated. There’s python scripts and node scripts and packers and bundlers and uglifiers galore. It seems less like a demo of libjxl and more like a demo of every webdev tech around that just happens to use libjxl.

Ironically, for all that, there’s no web server included, so you need to bring your own static file http server. That part is easy. Then you discover the uglifier mispacked the bundle, or whatever, and the scripts don’t parse, but it was possible to salvage them. And then it still tries to load resources from netlify instead of localhost, because apparently they paid for the script, and they really want you to use their server. A few more ninja edits and we get an authentic error from the browser, that SharedArrayBuffer is not available. We need to edit the web server to add the necessary danger zone headers. (Wouldn’t it have been helpful to include a web server that does this, if we’re already requiring every other tool in the box?)

Eventually, though, this does run, and I was able to see a jxl file appear on my screen.

standards

At this point I’ve confirmed that all the puzzle pieces are working, even if they don’t yet fit together as I’d hoped. People say the wonderful thing about standards is there’s so many to choose from, but I think that overlooks the true superpower of standards. They let people on the internet say, “It’s a standard. It just works.”

the owl

Alright, let’s draw the rest of this fucking owl.

func b() {
}
func c() float64 {
    return 0
}   
func d() {
}
func e(int32, int32, int32, int32) int32 {
    return 0
}   
func f(int32) {
}   
func g(int32, int32, int32) {
}   
func h(int32, int32, int32, int32) float64 {
    return 0
}   
func i(int32) {
}   
func j(int32) {
}
func k() { 
}
func l(int32) {
}   
func m(int32) {
}   
func n(int32, int32, int32, int32, int32) int32 {
    return 0
}   
func o(int32) int32 {
    return 0
}   
func p(int32, int32, int32) {
}   
func q(int32) int32 {
    return 0
}   
func r(int32, int32, int32, int32) int32 {
    return 0
}   
func a(int32) int32 {
    return 0
}   

Just finish coloring in the lines, and... hahaha, no. Even if this were a good idea, it doesn’t work because “a” is a shared memory, and although wasmtime has let us enable threads for shared memory, it will not let us create a shared memory. We can create memory but only of the boring unshared variety.

rewind

At this point, I backed up and started over with a smaller C file. Like the little wasm examples that can add two 32-bit numbers in a mere thousand cycles. This does work. And we can incrementally add in assorted artifacts we find in the libjxl build directory until it works or breaks.

Cobble some code together from some other examples, write some glorious error oblivious C code and eventually we have a wasm file that can decode a jxl file and return to us the first four bytes of the resulting image. That’s not quite the whole image, but it’s something.

zero

What if we try wazero again, now that we’ve left the cruel world of shared memory behind?

invalid function[7]: invalid instruction 0xfe

Never mind. We’re just gonna have to rock that exec stack for fun and profit.

epilogue

I did eventually get a prototype working, that can decode jxl files and retrieve their data, sufficient for go to encode it as a jpeg which looks about the same. It’s pretty horrifying, but there’s hope that one day I will consider using a distant descendant of it.

Posted 22 Sep 2023 17:38 by tedu Updated: 22 Sep 2023 17:38
Tagged: go programming