Compiling C/C++ to WebAssembly using LLVM, Binaryen and WABT

Guy Bedford
InstructorGuy Bedford
Share this video with your friends

Social Share Links

Send Tweet
Published 8 years ago
Updated 6 years ago

We go through the command-line steps to compile C/C++ via LLVM into an intermediate S file, then use Binaryen to compile that S file into a WAST file and finally WABT to get the WASM binary. We then show an approach to clone and configure a custom std library headers for Web Assembly to build includes to the standard library.

[00:00] I've got a file located in nativedemo.c which we're going compile into WebAssembly. I've got my WebAssembly build of LLVM located at llvm/wasm. I'm going to use the bin/cling driver to be able to do the C or C++ compilation. When we run cling, we can't convert directly into WebAssembly yet. For now, what we have to do is output an intermediate S format.

[00:26] This is the [indecipherable] LLVM flag for cling or we can just use the short hand capital S. I'm then going to apply my C optimizations, and I'm going to choose the target to be wasm32. By default, wasm is a 32-bit system. That may well change in future and then using the C flag to compile the file at nativedemo.c. Then, finally, output that into a temporary file demo.s.

[00:54] Ideally, this would be on a temporary folder or something like that. When we run that compilation process, we'll see if there's any warnings or errors. Then we can see the final demo.s file contains this intermediate LLVM format. To convert this LLVM intermediate format into WebAssembly, we use the binaryen project which comes with a tool called s2wasm.

[01:16] I'm going to call this against the demo.s file. I'm going to pass an S flag to set the stack signs. I strongly recommend remembering this flag because otherwise code will run and will break. I'm going to set a stack size of half a megabyte. It's a size in bytes. Then we're going to output into a demo.wast file.

[01:39] If I wanted to compile my WebAssembly module to import its memory, instead of exporting it by default here, I could pass an import memory flag. For now, I'm going to compile with the export memory option. If we look at demo.wast, we can now see that this is a WebAssembly module. We have our function exports, our memory being set up, etc.

[01:58] The last step is to convert this wast file into WebAssembly binary that we can run in the browser. This is where we use the third tool from the WebAssembly binary toolkit called wast2wasm. I'm going to run that against demo.wast outputting a demo.wasm file. If we look inside that demo.wasm file, you can see it starts with an asm header and it's all binary format, ready to run in a browser.

[02:25] One of the first problems you run into with this kind of a compilation process is if I try and compile a different demo that uses the standard library, we'll see that we immediately get an issue trying to compile with a standard library and [indecipherable] to find it. Because WebAssembly is so new, we need to configure the standard library ourselves.

[02:43] This is where it can be useful to use an all encompassing tool like emscripten that can handle all of these details for us. What I found works quite well here is linking against emscripten version of the standard libraries. Because it's a large download, clone emscripten from GitHub. I'll actually just hack together a repo that contains just these include folders.

[03:04] If we clone this, we can then use this as our input target to get some basic standard Lisp support. When running our clang build command, we add the -Include option, capital I, and then we can load this include folder, either using LISP/c, or LISP/cxx, or both, to get standard library headers that can then fix the build.