Skip to content

Instantly share code, notes, and snippets.

@TheWaWaR
Forked from josephg/README.md
Created September 2, 2022 08:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TheWaWaR/630ea0c137a52912fe42e97bb5b59356 to your computer and use it in GitHub Desktop.
Save TheWaWaR/630ea0c137a52912fe42e97bb5b59356 to your computer and use it in GitHub Desktop.
Getting Zig compiling to WASM

In case anyone else wants to play with Zig on webassembly, here's what you need to do to make it work on a mac today.

1. Get LLVM 7 compiled with webassembly support.

You'll need LLVM to output to the WASM target. This has just been added by default in trunk, so if LLVM >7 is available, you might be able to just brew install llvm.

If you have wasm support already you should see:

$ llc --version
LLVM (http://llvm.org/):
  LLVM version 7.0.0
  Optimized build.
  Default target: x86_64-apple-darwin17.7.0
  Host CPU: skylake

  Registered Targets:
...
    wasm32     - WebAssembly 32-bit
    wasm64     - WebAssembly 64-bit

If wasm32 / wasm64 isn't in the list, you need to enable wasm support explicitly in llvm by recompiling LLVM with the -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=WebAssembly flag.

With homebrew the easiest way to do this is to edit /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/llvm.rb and scroll down to this section:

    args = %W[
      -DLIBOMP_ARCH=x86_64
      -DLINK_POLLY_INTO_TOOLS=ON
      -DLLVM_BUILD_EXTERNAL_COMPILER_RT=ON
      -DLLVM_BUILD_LLVM_DYLIB=ON
      -DLLVM_ENABLE_EH=ON
      -DLLVM_ENABLE_FFI=ON
      -DLLVM_ENABLE_LIBCXX=ON
      -DLLVM_ENABLE_RTTI=ON
      -DLLVM_INCLUDE_DOCS=OFF
      -DLLVM_INSTALL_UTILS=ON
      -DLLVM_OPTIMIZED_TABLEGEN=ON
      -DLLVM_TARGETS_TO_BUILD=all
      -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=WebAssembly     # <--- Add this line!
      -DWITH_POLLY=ON
      -DFFI_INCLUDE_DIR=#{Formula["libffi"].opt_lib}/libffi-#{Formula["libffi"].version}/include
      -DFFI_LIBRARY_DIR=#{Formula["libffi"].opt_lib}
    ]

And add the -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=WebAssembly line in there somewhere like I've done above.

Then brew install -s llvm. This will rebuild llvm from source, and will probably take ~3-5 hours.

2. Build zig from source.

$ git clone https://github.com/ziglang/zig.git
$ cd zig
$ mkdir build
$ cd build
$ cmake .. -DCMAKE_PREFIX_PATH=/usr/local/opt/llvm@7/
$ make install

3. Write some zig code to test.

extern fn inc(a: i32) i32;

export fn addInc(a: i32, b: i32) i32 {
    return inc(a) + b;
}

Then build it and stuff:

For some reason zig's built-in wasm linker doesn't export symbols properly. You can work around that using wasm-ld which is included as part of llvm.

$ zig build-obj --release-small --target-arch wasm32 wasm.zig 
$ wasm-ld wasm.o -o xyz -O2 --no-entry --allow-undefined

If you want to see what the resulting wasm looks like, you can view it in text format using WABT (brew install wabt) then:

$ wasm2wat xyz

You should see some sweet (()()()()) code.

4. Run it through JS

Here's a dumb nodejs script to invoke your wasm code:

const fs = require('fs')
const m = new WebAssembly.Module(fs.readFileSync('xyz'))

const env = {
  inc(x) { return x+1 }
}

const i = new WebAssembly.Instance(m, {env})

console.log(i.exports.addInc(1,2))
$ node test.js
4

💃🕺🏾

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment