Skip to content

Zaneham/Wasabi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Wasabi

A WebAssembly to x86-64 AOT compiler. Zero dependencies. No LLVM. No runtime. Just wasm in, Linux ELF out.

Named after the condiment after I was sitting eating some sushi at St Pierres(if you know you know). Because it's small, it burns through things fast, and too much will make you cry. Please don't ask.

What It Is

Wasabi takes a .wasm binary and produces a native Linux x86-64 executable. No interpreter, no JIT, no intermediate representation. The wasm stack machine maps directly to registers, functions compile to native calls, and the output is a static ELF binary that runs without any runtime support.

This has been sitting in my folder for a while. It started as a "how hard can it be" experiment and now it passes the WebAssembly spec test suite, so apparently the answer was "not that hard if you don't mind hand-encoding x86 instructions."

Spec Compliance

Tested against the official WebAssembly spec test suite:

Test Pass Total
i32 374 374 100%
i64 384 384 100%
f32 2500 2500 100%
f64 2500 2500 100%

5,758 tests. Zero failures.

I had to wait like 2 hours just for the tests to run so boy was I happy when it worked.

Building

make

Yep, that's it. GCC and a pulse.

Usage

wasabi input.wasm -o output

The output is a Linux ELF binary. Run it directly or under WSL.

wasabi program.wasm -o program
./program

There's also an invoke mode for calling individual exports (used by the spec test runner):

wasabi --invoke "add" i32:1 i32:2 module.wasm -o test
./test | xxd

Other flags:

wasabi --dump input.wasm      # dump module structure
wasabi --disasm input.wasm    # disassemble wasm opcodes

Running the Spec Tests

Requires WSL with wast2json installed (from wabt).

python tests/run_spec.py --all --filter i32
python tests/run_spec.py --all

What's Supported

  • Full integer arithmetic (i32, i64) including all edge cases
  • Full floating point (f32, f64) with correct NaN propagation and signed-zero semantics
  • Locals, globals, memory loads/stores (all widths and sign extensions)
  • Control flow: block, loop, if/else, br, br_if, br_table, call, call_indirect, return
  • WASI: fd_write, fd_read, proc_exit, args, environ, clock_time_get, path_open, and more
  • Data segments, element segments, function tables
  • Saturating truncation (0xFC prefix)
  • memory.copy, memory.fill, memory.init
  • memory.grow / memory.size (runtime page counter, 16MB growable address space)

What's Not (Yet)

  • SIMD
  • Threads
  • Multi-value returns
  • Tail calls
  • RISC-V and ARM (Still needing to do my ARM assembler for dummies course)
  • Any kind of optimisation (it's fast enough, and correctness comes first)

Architecture

wasm_decode.c    decode .wasm binary into module structs
wasm_compile.c   translate wasm opcodes to x86-64 machine code
x86_emit.c       x86-64 instruction encoder (hand-rolled, no assembler)
elf_emit.c       ELF binary writer
wasi.c           WASI syscall implementations
main.c           CLI and ELF patching

The wasm virtual stack maps directly to x86 registers (RAX, RCX, RDX, RSI, RDI, R8-R11) with spill to the native stack. RBX holds the linear memory base. R12 holds the data segment address. R14 holds the text base for indirect calls.

Why

Same reason as BarraCUDA. I wanted to understand the thing, so I built the thing. Compilers are the most fun you can have with a keyboard, and wasm is a surprisingly clean target to compile from — it's basically a well-typed stack machine with no ambiguity, which is more than I can say for most things I've had to parse.

License

Apache 2.0. See LICENSE.

About

WebAssembly to x86-64 AOT compiler.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors