w2c2: Compiling WebAssembly Modules to Portable C89
Translates WebAssembly modules to portable C
At a glance
- What is it?
- w2c2 is an MIT licensed C translator that turns WebAssembly modules into portable C89 source, with extensions for threads, bulk memory and sign extension, plus a WASI implementation. It suits people who want one binary artefact to run on many operating systems and CPU architectures.
- Who is it for?
- w2c2 fits teams that want to ship one WebAssembly artefact and have it run on far more operating systems and CPU architectures than a native toolchain would cover, and it fits embedded and legacy targets where C89 is the only common denominator. It is the wrong choice if you need WebAssembly sockets, or if you want a maintained release stream, since the repository publishes no GitHub releases and the last push was on 2026-09-21.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 31 days ago.
- What is it written in?
- Mainly C, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One WebAssembly module, many operating systems
w2c2 takes a compiled WebAssembly module and emits C source that any C compiler can build. The project is inspired by wabt's wasm2c and pushes toward the idea of WebAssembly as a universal binary: source code in C++, Rust or another language compiles to WebAssembly, w2c2 converts that to C89, and the resulting program targets operating systems and CPUs directly. That sidesteps the usual bottleneck, where a native binary is locked to one architecture and one operating system combination. The generated code is written in mostly C89, with some common extensions used and not all limits followed. The stated platform reach is wide: Mac OS X, Mac OS 9, Haiku, Rhapsody, OPENSTEP, NeXTSTEP, DOS and Windows XP are all named, along with x86, ARM, PowerPC, SPARC and PA-RISC, and big-endian systems are supported explicitly. That combination of old and new targets is the whole point of the project.
Core spec coverage plus five post-MVP extensions
The translator implements the WebAssembly Core Specification 1.0 and layers several extensions on top. Threads and atomics are supported, as are bulk memory operations, conditional data segment initialization, sign-extension operators and non-trapping float-to-int conversions. The last of those changes observable behaviour rather than just performance, since a trapping conversion becomes a defined result instead of an error. The project reports passing 99.9% of the WebAssembly core semantics test suite, which is a precise way of saying that something is missing rather than a blanket claim of conformance. It is worth finding out which cases fail before you rely on the translator for a module whose edge cases matter. The repository also includes separate example directories for language toolchains such as clang, Go, Rust, Swift, Python and SDL, which gives a sense of the range of inputs the translator is expected to handle.
Building the translator with CMake, or with Make
If your system is supported by CMake 2.8.12 or newer, the documentation recommends CMake because it detects features for you. The build itself is two commands in the w2c2 directory, and the WASI implementation builds the same way from the wasi directory.
cd w2c2
cmake -B build
cmake --build buildcd wasi
cmake -B build
cmake --build buildOn systems without CMake you can use Make instead. The two paths are not equivalent, though. The Make build takes its options through variables rather than CMake cache entries, so a debug build is requested with `BUILD=debug` passed to `make`, and sanitizers are enabled by listing them in a `SANITIZERS` variable, for example `make BUILD=debug SANITIZERS="base clang address thread"`. The named sanitizers are the Undefined Behavior Sanitizer, the Clang-specific set and the Thread Sanitizer. One wrinkle applies to both build systems: run `make clean` before changing build features or switching between static and shared library builds.
Separate files and worker threads for large modules
Compilation is streaming and single-pass with low memory usage, which is the property that matters for a very large module. On top of that, w2c2 can split the output into several C files, which the documentation recommends for large modules and for hosts with limited resources. The `-f` flag sets how many functions go into each file.
./w2c2 -f 100 module.wasm module.cCompilation can also run in parallel when w2c2 was built with threading support. By default the translator spawns as many worker threads as there are CPU cores, and the `-t` flag sets the number explicitly.
./w2c2 -t 2 module.wasm module.cBeyond that, the project describes support for separate compilation into multiple files, support for multiple modules and instances, and a mode that assists with incremental compilation by separating static and dynamic functions through comparison with another module. The basic invocation compiles a module to a C file and a header.
./w2c2 module.wasm module.cDebug information depends on libdwarf and drops extreme line numbers
Generating debug information is optional and has conditions. Function names appear in the output if the module carries them in the `names` custom section, and source line mapping works if DWARF line information is present in the `.debug_line` custom section. The second path has a dependency: libdwarf has to be installed, and the Make build needs `-ldwarf` added when the library was compiled with libdwarf support. There is also a portability compromise worth knowing about, since line numbers outside C89's portable range of 1 through 32767 are omitted from the generated `#line` directives. For a codebase with very long files or generated sources, that means the emitted line mapping stops matching the original in places. If you plan to step through the translated program in a debugger, check that your module actually ships the DWARF line section rather than assuming it.
Embedding the translator as a library
w2c2 builds a static translator library by default, and a shared library can be requested on POSIX systems with `-DSHARED_LIB=ON` for CMake or `SHARED_LIB=1` for Make. Windows supports static libraries only, which is a constraint to plan for if you ship tooling across platforms. The documented CMake install path configures an install prefix, builds, runs the tests and installs.
cmake -S w2c2 -B build -DCMAKE_INSTALL_PREFIX=/your/prefix
cmake --build build
ctest --test-dir build --output-on-failure
cmake --install buildThat install places the library, the public headers, the CLI and a relocatable package. Consumers locate it with the `find_package(w2c2 CONFIG REQUIRED)` pattern and link against `w2c2::w2c2_lib`, and the consumer's own configure step needs `-DCMAKE_PREFIX_PATH=/your/prefix`. The package supplies the library's thread and libdwarf dependencies when those are enabled. The Make route mirrors this.
make -C w2c2
make -C w2c2 test
make -C w2c2 install PREFIX=/your/prefix
cc -I/your/prefix/include/w2c2 examples/embed/main.c /your/prefix/lib/libw2c2.a -lm -pthread -o embedThe WASI implementation, and the socket calls it does not have
The WASI side is a substantial part of the project and is described as able to run clang and Python. It covers many operating systems and architectures and supports big-endian systems, and it implements the threads proposal. The status list is long and specific. File descriptor calls such as `fd_read`, `fd_write`, `fd_seek`, `fd_pread` and `fd_pwrite` are checked, along with `path_open`, `path_readlink`, `path_rename`, `path_symlink` and `path_unlink_file`, plus `poll_oneoff`, `proc_exit`, `random_get` and `thread-spawn`. The gaps are as informative as the entries. The socket family is absent entirely: `sock_recv`, `sock_send` and `sock_shutdown` are unchecked, as are `sched_yield`, `fd_advise`, `fd_allocate`, `fd_renumber` and the file descriptor flag and rights setters. A WASI program that expects sockets is out of reach here, and that is the clearest line between this implementation and a complete one.
A project without releases, and one performance number
Two facts frame any adoption decision. The first is measurement: the project reports Coremark 1.0 running about 7% slower than native. That is a single benchmark on a single workload, so read it as an indication that the generated code is in the same region as native performance rather than as a general guarantee for your workload. The second is release hygiene. The repository publishes no GitHub releases, so there is no tagged version to pin, and the last push was on 2026-09-21. The MIT licence is permissive for commercial use and modification, though the project itself notes that the generated code inherits whatever licence the original WebAssembly module carried. The testing story is decent but manual: the test suite needs Python 3 and wabt for `wast2json`, and it is run from the tests directory.
cd tests
make gen
make run-testsFor an alternative, wabt's own wasm2c does a similar translation with a smaller feature set. w2c2's difference is the extension coverage, the WASI implementation and the emphasis on very old targets and big-endian hardware, which is where its streaming design and low memory use pay off.
Editorial conclusion
w2c2 fits teams that want to ship one WebAssembly artefact and have it run on far more operating systems and CPU architectures than a native toolchain would cover, and it fits embedded and legacy targets where C89 is the only common denominator. It is the wrong choice if you need WebAssembly sockets, or if you want a maintained release stream, since the repository publishes no GitHub releases and the last push was on 2026-09-21. Before committing, run the test suite with make gen and make run-tests in the tests directory to confirm your host builds a passing translator.
Frequently asked questions
How do I compile a WebAssembly module to C with w2c2?
Build the translator with cmake -B build and cmake --build build in the w2c2 directory, then run ./w2c2 module.wasm module.c to produce the C source and header.
Which WebAssembly extensions does w2c2 support?
Beyond the WebAssembly Core Specification 1.0, the listed extensions are threads and atomics, bulk memory operations, conditional data segment initialization, sign-extension operators and non-trapping float-to-int conversions.
Does w2c2 support WASI sockets?
No. The WASI status list leaves sock_recv, sock_send and sock_shutdown unchecked, along with items such as sched_yield and fd_advise. A WASI program that needs sockets is outside what this implementation covers.
What is needed to produce debug information with w2c2?
Function names come from the names custom section and source line mapping from DWARF line information in the .debug_line custom section. The line mapping path requires libdwarf to be installed, and line numbers outside the C89 portable range of 1 through 32767 are omitted from the generated directives.
Can w2c2 compile a large module without running out of memory?
Compilation is streaming and single-pass with low memory usage, and separate compilation splits the output into multiple C files with a set number of functions each, which is recommended for large modules and for hosts with limited resources.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/turbolent-w2c2)Was this useful?