afi

Wasm for Bare-Metalheads

I’ve often heard the remark that the web (and its related technologies) are the single biggest example of cross platform software in the world. I was recently introduced to it through talking about writing a small compiler in a work Slack. Admittedly, my first thought was to avoid it - I usually don’t touch the web, mostly because I find the oldschool native software approach easier to reason about.

One thing struck me about this conversation though was that Wasm was introduced as “an analog to the JVM”. I never quite got into Java as a language, but the JVM has a hard to refute reputation for being a reliable tool for “industrial strength” programming. I think (for the web folks) Wasm can be thought of as a successor to Java applets. I was also very pleased to hear that you can use the runtime wasmtime to run binaries locally. Since I don’t like using my web browser much for things other than reading HTML and watching videos, I was sold.

Wasm is a portable binary format that evolved from wanting to have a lingua franca for the browser that was easily optimizable and also could be generated from multiple languages. I think this is welcome because not everyone likes writing Javascript (I personally have never quite vibed with it). One criticism I’ve heard is that it’s harder to inspect Wasm than it is to read Javascript on the web. I would tend to agree on principle, but practically most JS seems to be obfuscated and minified to the point where no one has any idea what any of it actually means. Also, the indie web (and other creative ventures) are a minority on the internet. Most people are just “doing stuff”.

I should really be clear here - I do like the idea of having an open scripting language that you can embed into your webpage. I don’t wish for the death of using Javascript on the web, but more choices are nice.

I’m going to poke through this Wasmtime tutorial.

If you want to follow along:

On MacOS, it’s simple enough to ozzzzun brew install wabt and brew install wasmtime.

The canonical example seems to be using Rust to generate wasm.

fn main() {
  println!("Hello, World!");
}

We would normally use cargo-expand, but here is suffices to run rustc -Zunpretty=expanded main.rs.

In order to use this you need to rustup default nightly for a quick second.

#![feature(prelude_import)]
#![no_std]
#[prelude_import]
use ::std::prelude::rust_2015::*;
#[macro_use]
extern crate std;
fn main() { { ::std::io::_print(format_args!("Hello, world!\n")); }; }

hyperfine "wasmtime main.wasm" --warmup 10

wasm2wat main.wasm > main.wast

hyperfine "wasmtime main.wast" --warmup 10

Weirdly the mean of running the wasm version was slightly higher than the mean of running the wast version. (11.0 ms ± 0.3 vs 10.1 ms ± 0.2)

wasmtime compile --target aarch64-apple-darwin main.wasm

This results in main.cwasm, which can be run like this:

wasmtime --allow-precompiled main.cwasm

I imagine that wasmtime normally compiles on the fly to bytecode (like how CPython works), but can allow you to compile it beforehand.

Interestingly, file main.cwasm tells me that this is an ELF file.

main.cwasm: ELF 64-bit LSB relocatable, ARM aarch64, version 1, not stripped

What I find interesting about this is that I asked for “darwin” (MacOS’s kernel’s name), but I got an ELF file, which is more of a GNU/Linux thing. I naturally cannot run main.cwasm as ./main.cwasm.

I found a few neat functions when I used objdump.

array_to_wasm_trampoline

https://docs.wasmtime.dev/api/wasmtime/struct.CompiledModule.html#method.wasm_to_array_trampoline

I don’t really know what a WASM trampoline is admittedly.

Okay, googled it.

Looks like it’s a feature to let us pass data (from Javascript) to Webassembly.