Skip to content

Instantly share code, notes, and snippets.

@josephg
Last active March 7, 2024 06:58
Show Gist options
  • Star 35 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save josephg/873a21d4558fd69aeccea19c3df96672 to your computer and use it in GitHub Desktop.
Save josephg/873a21d4558fd69aeccea19c3df96672 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

💃🕺🏾

@cd155
Copy link

cd155 commented Apr 10, 2021

I got error: unrecognized parameter: '--release-small'
When I run zig build-obj --release-small --target-arch wasm32 wasm.zig. I use MacOS.

@josephg
Copy link
Author

josephg commented Apr 10, 2021

Yeah this is super out of date now. I wrote this a year or two ago and that’s an awfully long time for zig.

@cd155
Copy link

cd155 commented Apr 11, 2021

Do you know the current way to compile zig file to wasm file?

@josephg
Copy link
Author

josephg commented Apr 11, 2021

Looks like there's some instructions here. Not sure if thats up to date.

If not you could also try taking out the --release-small flag if thats causing problems. That just tells the optimizer to optimize for output size. Its optional.

@pims
Copy link

pims commented Aug 24, 2021

$ zig version
0.8.0

zig build-lib src/main.zig -target wasm32-freestanding -dynamic -OReleaseSmall

# produces main.wasm in the current directory

@jackdbd
Copy link

jackdbd commented Apr 15, 2022

I had the same issue with zig 0.9.1 on Ubuntu, then I found out that you can force symbols to be exported if you use --export.

Example:

zig build-lib your_lib.zig -target wasm32-freestanding-musl -dynamic -O ReleaseSmall --export=some_zig_function --export=another_zig_function

@IcyTv
Copy link

IcyTv commented Sep 15, 2022

For anyone wondering, with ^0.9 this is not necessary anymore. You can just use a shared library with target wasm32 (or as the triplet: wasm32-freestanding-none)
(Resolved in ziglang/zig#1570)

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