Using Zig to compile C++ to Wasm/WASI

I’m working on a project which will target the browser via Wasm, and, unfortunately, a key part of this project is a library written in C++. Given Zig’s great support for compiling Zig and even C to Wasm (using WASI via wasi-libc), and given this library doesn’t do anything like graphics or audio where Emscripten would be needed, I figured it might “just work”, but unfortunately C++ exceptions are giving me some issues.

I’ll document what I’ve tried so far below, but just to preface this with a question: has anyone successfully used Zig to compile C++ (using the C++ standard library) to Wasm/WASI without resorting to Emscripten?


Here’s my very simple example program:

#include <iostream>

int main() {
    std::cout << "Hello, world!" << std::endl;
    return 0;
}

If I try to compile this using zig build-exe -target wasm32-wasi -lc++ main.cpp, I get the following error:

error: wasm-ld: /home/ian/.cache/zig/o/d0437116c48db9b675834f561e3055cf/libc++abi.a(/home/ian/.cache/zig/o/b79e1fcbfe3ba7adfe32cdcfe055da93/cxa_exception.o): undefined symbol: _Unwind_RaiseException

This makes sense: the default Wasm “CPU” used in Zig is the lime1 feature set, which includes no support for exception handling. But libunwind does have Wasm support via the widely supported (in browsers) exception handling proposal, so my first thought was to try that: zig build-exe -target wasm32-wasi -mcpu lime1+exception_handling -lc++ main.cpp

Now I run into a Clang bug (which I will need to figure out how to properly report upstream edit: reported upstream: Clang crash when compiling libc++ for Wasm with `-mexception-handling` · Issue #148550 · llvm/llvm-project · GitHub):

Compiler output
error: sub-compilation of libcxxabi failed
    :1:1: note: clang frontend command failed with exit code 139 (use -v to see invocation)
    :1:1: note: diagnostic msg: 
                ********************
                
                PLEASE ATTACH THE FOLLOWING FILES TO THE BUG REPORT:
                Preprocessed source(s) and associated run script(s) are located at:
    :1:1: note: diagnostic msg: /tmp/stdlib_new_delete-898c18.cpp
    :1:1: note: diagnostic msg: /tmp/stdlib_new_delete-898c18.sh
    :1:1: note: diagnostic msg: 
                
                ********************
    :1:1: note: clang frontend command failed with exit code 139 (use -v to see invocation)
    :1:1: note: diagnostic msg: 
                ********************
                
                PLEASE ATTACH THE FOLLOWING FILES TO THE BUG REPORT:
                Preprocessed source(s) and associated run script(s) are located at:
    :1:1: note: diagnostic msg: /tmp/cxa_personality-9c5fcf.cpp
    :1:1: note: diagnostic msg: /tmp/cxa_personality-9c5fcf.sh
    :1:1: note: diagnostic msg: 
                
                ********************
error: sub-compilation of libcxx failed
    :1:1: note: clang frontend command failed with exit code 139 (use -v to see invocation)
    :1:1: note: diagnostic msg: 
                ********************
                
                PLEASE ATTACH THE FOLLOWING FILES TO THE BUG REPORT:
                Preprocessed source(s) and associated run script(s) are located at:
    :1:1: note: diagnostic msg: /tmp/new-ec2e48.cpp
    :1:1: note: diagnostic msg: /tmp/new-ec2e48.sh
    :1:1: note: diagnostic msg: 
                
                ********************
    :1:1: note: clang frontend command failed with exit code 139 (use -v to see invocation)
    :1:1: note: diagnostic msg: 
                ********************
                
                PLEASE ATTACH THE FOLLOWING FILES TO THE BUG REPORT:
                Preprocessed source(s) and associated run script(s) are located at:
    :1:1: note: diagnostic msg: /tmp/string-881880.cpp
    :1:1: note: diagnostic msg: /tmp/string-881880.sh
    :1:1: note: diagnostic msg: 
                
                ********************
    :1:1: note: clang frontend command failed with exit code 139 (use -v to see invocation)
    :1:1: note: diagnostic msg: 
                ********************
                
                PLEASE ATTACH THE FOLLOWING FILES TO THE BUG REPORT:
                Preprocessed source(s) and associated run script(s) are located at:
    :1:1: note: diagnostic msg: /tmp/ios-85bd41.cpp
    :1:1: note: diagnostic msg: /tmp/ios-85bd41.sh
    :1:1: note: diagnostic msg: 
                
                ********************

So compiling with exception handling doesn’t seem to be possible, but the library I’m using doesn’t use exceptions anyway, so I figured I could just disable them entirely using -fno-exceptions. However, it seems that Zig currently doesn’t support disabling exceptions for libc++ (Add ability for zig c++ to compile libcxx and libcxxab with -fno-exceptions · Issue #17470 · ziglang/zig · GitHub), so even if I use -fno-exceptions, I still get a linker error related to _Unwind_RaiseException.

The last resort would be to just rewrite the library in Zig and avoid this entirely, which I may do at some point, but that would be a pretty big yak shave at this point :smile:

1 Like

You probably want -lunwind, although I can’t vouch for how well it works on WASM.

1 Like

Thank you! I actually didn’t realize that linking it explicitly would be required, but I see now that it would be for Wasm: zig/src/target.zig at e7b18a7ce69f30c85f21ec8ad6a70211abf5f24b · ziglang/zig · GitHub Either way, unfortunately, with the exception_handling feature disabled, libunwind fails to compile (as expected), and with it enabled, I get the same bug with Clang.

While I was looking a bit more into this, I realized that this actually did work as-is in Zig 0.14.1 (zig build-exe -target wasm32-wasi -lc++ main.cpp runs successfully and produces a usable output), but I was using Zig master for my testing. I did some digging, and I found that Zig used to pass -fno-exceptions when compiling libc++ for WASI, but this was removed in the LLVM 20 upgrade (see the changes to libcxx.zig in libcxx: Update to Clang 20. · ziglang/zig@156ab87 · GitHub, specifically lines 470-476 in the “before” code). I didn’t see any explicit mention of this in the commit message or the associated PR description; was there a change in libc++ 20 that motivated the removal of this logic? That (passing -fno-exceptions to libc++ for WASI) seems like the best option to me for normal use, or perhaps conditional on the availability of the exception_handling feature.

I most likely just aligned the flags with upstream libc++. But it’s not unthinkable that I made a mistake.

Anyhow, can you open an issue for this? I’ll try to investigate before 0.15.0 is tagged.

1 Like

Done: libc++ compiled for WASI enables exceptions even though WASI does not support them · Issue #24437 · ziglang/zig · GitHub