cc package icon
icedcoffee/cc
Public
wasmer run icedcoffee/cc

cc / gcc / clang — a C compiler that runs inside WASIX

cc, gcc and clang all run clang 23 as a WASIX module, with the WASIX sysroot and compiler-rt builtins alongside it. It compiles and links in a single process — no subprocesses, no fork, no asyncify.

cc hello.c -o hello.wasm        # compile and link
clang -c hello.c -o hello.o     # compile only
cc a.c b.c -o prog.wasm         # several inputs, one binary

--sysroot=/sysroot is supplied by the packaged driver configuration (/bin/clang.cfg), so no flags are needed for the normal case.

-c works

Every other wasm-hosted clang fails on compile-only invocations:

$ clang -c hello.c -o hello.o
error: unknown integrated tool '-cc1'. Valid tools include '-cc1' and '-cc1as'.

That is clang's integrated cc1 path mangling the tool name it passes to itself — clang/clang, syrusakbary/clang and the clang 16 this package replaces all do it. clang 23 built from the WASI portability patch does not:

$ clang -c hello.c -o hello.o
$ od -An -tx1 -N4 hello.o
 00 61 73 6d                    # \0asm — a real object file

No -fno-integrated-cc1 workaround is needed.

Why the linker runs in-process

A C compiler has to run a frontend and a linker. WASIX gives a module no usable way to spawn either:

  • fork() never returns without asyncify, and the runtime kills the process (Noexec, exit 45).
  • posix_spawn() returns ENOEXEC and, when given a real path, never returns at all.
  • wasm-opt --asyncify is the documented answer for fork, but it is not viable here: binaryen 133 aborts in its liveness fixpoint (liveness-traversal.h:244) on a module this size, and every link would pay for a copy of the compiler's address space.

So this build removes the subprocesses instead:

stephow it runs
cc1 (frontend)in-process, always — the driver's "more than one job ⇒ spawn cc1" rule is disabled
wasm-ld (link)lld::wasm::link() called directly by the driver through llvm::sys::ExecuteInProcessHook

Both are small, targeted patches (see patches/). The result is faster than the forking design would have been: a link costs no address-space copy, and cc hello.c -o hello.wasm finishes in about 1.5 s of compiler time.

What is in the package

pathwhat it is
/bin/clang.wasmclang 23.0.0git for wasm32-unknown-wasi, 68 MB, with lld's wasm driver linked in
/bin/clang.cfgdriver configuration: --sysroot=/sysroot
/sysrootthe WASIX sysroot: headers, libc, crt objects (33 MB)
/lib/clang/23clang's resource directory: builtin headers and libclang_rt.builtins.a (8.5 MB)

Three command names — cc, gcc, clang — all the same compiler.

Why gcc is clang

GCC has no wasm backend — upstream has never supported wasm32 as an output target, which is why emscripten ships clang for the same job. gcc --version prints clang's banner. It takes GCC's usual flags.

How it was built

Source: msorvig/llvm-project-wasi, branch wasi — the LLVM tree with the WASI portability patch that makes LLVM itself buildable for wasm32-wasip1. Upstream LLVM stops at #error GetMainExecutable is not implemented on this host.

cmake -G Ninja -S llvm -B build \
  -DCMAKE_TOOLCHAIN_FILE=Toolchain-WASIX-LLVM.cmake \
  -DLLVM_NATIVE_TOOL_DIR=<native>/bin \        # tblgen has to run on the host
  -DLLVM_TARGETS_TO_BUILD=WebAssembly \
  -DLLVM_ENABLE_PROJECTS="clang;lld" \
  -DLLVM_ENABLE_THREADS=OFF \
  -DLLVM_DEFAULT_TARGET_TRIPLE=wasm32-wasi \
  -DCMAKE_BUILD_TYPE=MinSizeRel -DLLVM_ENABLE_ASSERTIONS=ON
ninja bin/clang

The toolchain file targets wasm32-wasi against the wasix sysroot (not wasi-libc — that has no proc_exec at all), with -flto, -D_WASI_EMULATED_MMAN, and the flags the WASIX runtime requires: -matomics -mbulk-memory -Wl,--shared-memory -Wl,--import-memory -Wl,--max-memory=4294967296 -Wl,-z,stack-size=8388608,--stack-first. Without the shared-memory flags the module builds and then refuses to instantiate (incompatible import type ... shared: false vs true).

The final link is a full-LTO wasm-ld run: ~10 minutes, ~12 GB peak RSS. Everything before it is parallel across all cores.

Verified

$ cc --version                      → clang version 23.0.0git, Target: wasm32-unknown-wasi
$ clang -c hello.c -o hello.o       → 685-byte wasm object, no -fno-integrated-cc1
$ cc hello.c -o hello.wasm          → 64 067 bytes; runs: "hello from clang 23"
$ cc a.o b.o -o two.wasm            → 64 138 bytes; runs
$ cc -c a.c b.c                     → separate objects, then linked

Caveats

  • No llvm-ar/llvm-ranlib and no standalone wasm-ld binary: spawning a linker is what this build exists to avoid, and clang already contains one.
  • -fno-integrated-cc1 does not work: it asks the driver to spawn itself, which WASIX cannot do.

C compiler for WASIX - clang 23 with its sysroot, compiling and linking in one process as cc, gcc and clang


Github