WASM-4 and Zig

Lately I've been into WebAssembly. I am confident that WebAssembly will resolutionize the software industry. The way I see, as a backend operator, is that it will be like how Docker changed the way backend software is delivered and operated, but at a larger scope. Instead, the effect of WebAssembly will span many more types of software, from data pipelines to mobile clients.

Docker operationalized the backend software delivery. Before Docker, I had to deal with tarballs, jar files, war files, and other different and incompatible formats. After Docker, it was just Docker image, and often times I did not have to go deeper than that. It is a solid baseline. What I imagine is, in the future, wasm binaries will be the standard unit of software. When someone hands you a piece of software, it would often be in the wasm format, because, unlike other existing types of libraries and executables, wasm is standardized with clear and portable semantics, and I'm not aware of anything that is like.

So, I wanted to try my hands on writing a wasm program. I picked up a book^1 and followed the tutorials to learn how it worked. The core spec is surprisingly minimal, almost bare. It was surprising because wasm binaries run at a near native speed, I did not know that can be achieved by such minimal architecture.

Concurrently, I started learning the Zig language, because I wanted to write wasm codes in Zig. Rust seemed like a natural choice as it was most well supported, but a few lines of Rust code are enough to bankrupt my brain capacity, so no Rust. Also, I do not like C, it is a work language, not a hobby language. So learning another new niche language it is then, and it was worth it. Zig is a very well designed language, and especially I was blown away by its Comptime feature. Even though it is still in development phase, using it was without much difficulty, at least for my purposes.

And this weekend the two came together and I made those wasm thingies that can be seen on this page, written in Zig. A wasm binary by itself is just functions and it needs a host platform to do I/O, but I was too lazy to hook up APIs together myself. So I used the WebAssembly-based fantasy console called WASM-4 to make it display prettily on web. The WASM-4 documentation is really easy to follow, thanks to @DaVyze.

The Zig code for the WASM-4 snake is here: https://github.com/jaese/wasm4-snake-zig

I wrote a set of rudimentary matrix routines in Zig to make the rotating cube. I adapted the style of https://pkg.go.dev/gonum.org/v1/gonum/mat and I think it works OK in Zig. Here is the part that rotates, using the matrix routines (note: I don't know how math):

// Params:
//  vs: (None, 3) Matrix - vertices
//  theta: radians - rotation
//  axis: (3) Vec - axis of rotation
fn rotate(dst: mat.Dense, vs: anytype, theta: f32, axis: anytype) void {
    assert(dst.rows() == vs.rows());
    assert(dst.cols() == 3);
    assert(vs.cols() == 3);

    // q = quaternion.rotation(theta, axis[0], axis[1], axis[2])
    const q = quaternion.rotation(theta, axis.atVec(0), axis.atVec(1), axis.atVec(2));

    // m = quaternion.mat_left(q)
    var m_arr = quaternion.matLeft(q);
    const m = mat.Dense.init(4, 4, &m_arr);

    // m_inv = quaternion.mat_right(quaternion.inverse(q))
    var m_inv_arr = quaternion.matRight(quaternion.inverse(q));
    const m_inv = mat.Dense.init(4, 4, &m_inv_arr);

    // x_p = jnp.vstack((jnp.zeros((1, x.shape[0])), x.T))
    var vs_p_arr = allocator.alloc(f32, 4 * vs.rows()) catch unreachable;
    defer allocator.free(vs_p_arr);
    const vs_p = mat.denseStackOf(vs_p_arr, mat.Zeros{ .r = 1, .c = vs.rows() }, vs.transpose());

    // x_p = (m_inv.T @ (m @ x_p))
    var scratch: [4]f32 = undefined;
    vs_p.mulLeft(&scratch, m);
    vs_p.mulLeft(&scratch, m_inv.transpose());

    // return x_p[1:, :].T
    dst.copy(vs_p.slice(1, 4, 0, vs_p.cols()).transpose());