LLVM 8 has recently been released, with quite a few significant changes and improvements. One of them being that the WebAssembly target is no longer considered experimental, and is built by default.
Around the same time, Mozilla’s WASI was announced. WASI includes a modified musl C library designed to work on top of a minimal set of external functions (/imports /hostcalls /whatever you call functions called from WebAssembly that are not defined in the module itself).
This set of external functions has already been implemented, with different levels of completion, in several WebAssembly runtimes: wasmtime, wasmer and lucet-wasi.
Compiling C code to WebAssembly using standard tools, and running the resulting module should now be way easier than it used to.
Unfortunately, documentation is still lacking, so here’s a quick guide to achieve this.
Install clang/LLVM 8
If you are using macOS, brew install llvm
will immediately get you the latest stable version of LLVM, with (almost) everything you need to build WebAssembly modules.
Linux distributions with up-to-date packages should also have LLVM 8 packages ready to install. On Arch Linux pacman -S llvm clang
will do the job.
Install WASI
Clone wasi-libc.
On macOS, the Homebrew version of clang
should be used instead of the Xcode one. This can be achieved with:
export PATH=/usr/local/opt/llvm/bin:$PATH
Compile and install WASI with:
make install INSTALL_DIR=/tmp/wasi-libc
Obviously, this can be installed to a more permanent location. The default is /usr/local
, which may mess with files installed through proper packages, so you may want to always supply a custom INSTALL_DIR
.
Try compiling to WebAssembly
Start with something simple, saved as example.c
:
#include <stdio.h>
int main(void)
{
puts("Hello");
return 0;
}
Make sure that you are still using the correct clang/llvm versions.
As we previously did in order to compile WASI, on macOS, put the Homebrew path before the system path in order to do so:
export PATH=/usr/local/opt/llvm/bin:$PATH
Compile the previous example with:
clang --target=wasm32-unknown-wasi --sysroot /tmp/wasi-libc \
-O2 -s -o example.wasm example.c
Which may fail with:
/usr/lib/clang/8.0.0/lib/wasi/libclang_rt.builtins-wasm32.a:
No such file or directory
Damn. We were so close.
wasi/libclang_rt.builtins-wasm32.a
The wasi/libclang_rt.builtins-wasm32.a
file is part of the clang_rt
package distributed with the WASI SDK.
Since compiling it is quite time consuming, you can directly get the precompiled file here: libclang_rt.builtins-wasm32.a.
The same file works everywhere, since this archive contains WebAssembly code.
Copy it where the previous error message suggests. On Linux, it’s likely to be something like the above.
On macOS, it should be in /usr/local/Cellar/llvm/8.0.0/lib/clang/8.0.0/lib/wasi/
.
You probably have to create the wasi
folder first.
Note that this file is not required to build WebAssembly objects. But it definitely is if you want to build a module.
Really compile to WebAssembly
Your compilation environment is finally complete.
clang --target=wasm32-unknown-wasi --sysroot /tmp/wasi-libc \
-Os -s -o example.wasm example.c
should succeed and produce a WebAssembly module called example.wasm
. Yay!
Run it
This is a WebAssembly module. Which has to be interpreted and/or translated to native code in order to be executed.
Since it uses WASI, we need something that natively supports the WASI low-level API.
To compile and/or install them, see these project’s respective documentation.
With wasmtime
wasmtime /tmp/example.wasm
Hello
With wasmer
wasmer run /tmp/example.wasm
Hello
With lucet
Move example.wasm
to the directory of the Lucet source code first, as Docker cannot access parent directories.
The runtime (lucet-wasi
) loads native code that has to be compiled first, using lucetc-wasi
:
source ./devenv_setenv.sh
lucetc-wasi -o example example.wasm
lucet-wasi example
Hello
Compiling more complex projects
In order to compile some projects, you may need to use llvm-ar
instead of ar
, llvm-nm
instead of llvm
, llvm-strip
instead
of strip
, and llvm-ranlib
instead of ranlib
.
Failure to do so with result in symbols not being found at link time, or archives containing objects with paths that don’t make any sense.
Using autotools
The wasm32-unknown-wasi
target is not recognized by autotools yet.
Namely, the config.sub
needs to be patched.
Here is a simple way to do it:
grep -q -F -- '-wasi' config.sub || \
sed -i -e 's/-nacl\*)/-nacl*|-wasi)/' config.sub
CFLAGS and LDFLAGS
On a variety of tests, -Os
and -O2
appear to be the optimization
flags producing the fastest code.
The LLVM linker (lld
) currently has a bug that can make builds
randomly stall. A workaround is to add -Wl,--no-threads
to LDFLAGS
.