A minimal, CMake-installable C and C++ runtime shim for the
wasm32-unknown-unknown Rust/Clang target.
wasm32-unknown-unknown ships with no libc, no libc++, no libc++abi —
which makes it impossible to link C++ code against it out of the box. WASI
SDK and Emscripten both fill the gap but bind you to their respective
platforms (WASI imports, JS shims). This project provides the smaller,
narrower thing you need to compile a self-contained C++ kernel and link
it against wasm-bindgen-style wasm without dragging in either ecosystem.
Status: All three components implemented; CI green on
{Ubuntu, macOS} × {LLVM 20, 21}; the headline wasm32-unknown-unknown
property (zero unexpected imports) is asserted on every wasm we ship.
End-to-end validation: a slice of manifold's GoogleTest test suite
(boolean / SDF / cross-section) runs on the shim under Node. The
shim ships a wasm_cxx_shim_add_manifold() CMake helper to
streamline the integration; manifold-csg (Rust bindings) builds +
runs on wasm32-unknown-unknown against the shim. See
CHANGELOG.md and
releases for
per-version detail.
See docs/context.md for the design background and
docs/plan.md for the phased implementation roadmap +
decision log.
The first concrete target is unblocking
manifold-csg (Rust bindings to
the manifold3d CSG kernel) on
wasm32-unknown-unknown. Today, manifold-csg supports
wasm32-unknown-emscripten; this project aims to make
wasm32-unknown-unknown viable so consumers using wasm-bindgen (Bevy,
Leptos, Yew, etc.) can use it directly.
The intent is to be useful beyond manifold-csg over time — any cmake-based C++ library facing the same gap (rust-skia, embedded-CSG/physics libraries, etc.) should be able to consume this. But the scope grows by demand: a symbol gets added when a real consumer reports it missing, not proactively for completeness.
The library is split into three buildable units that map onto the underlying C/C++ runtime layers, so consumers can mix and match — pull in only what they need, and replace any one with a better standalone if it appears.
| Subdirectory | Provides | Replaces / equivalent |
|---|---|---|
libc/ |
malloc/free, memcpy/memmove/memset | musl libc subset, walloc for the allocator side |
libm/ |
sin, cos, pow, hypot, fma, etc. | musl libm, Rust's libm crate |
libcxx/ |
__cxa_*, operator new/delete, __libcpp_verbose_abort, virtual destructor stubs |
LLVM's libc++ + libc++abi subset |
Each subdirectory has its own CMakeLists.txt and is independently
buildable. A consumer can do:
find_package(wasm-cxx-shim REQUIRED COMPONENTS libc libm libcxx)
target_link_libraries(myproject PRIVATE
wasm-cxx-shim::libc
wasm-cxx-shim::libm
wasm-cxx-shim::libcxx
)…or pick individual components if they already have a better source for
some of them (e.g., satisfying libm from Rust's libm crate via FFI
instead of pulling ours).
For the common case — building manifold
- Clipper2 against the shim — the package config ships a helper:
find_package(wasm-cxx-shim REQUIRED COMPONENTS libc libm libcxx)
wasm_cxx_shim_add_manifold() # uses the tested-pin defaults
target_link_libraries(my-wasm PRIVATE
wasm-cxx-shim::libc wasm-cxx-shim::libm wasm-cxx-shim::libcxx
manifoldc manifold Clipper2
my-libcxx-extras) # consumer-provided; see test/manifold-link/The helper does FetchContent for both projects, applies the three
carry-patches (MANIFOLD_NO_IOSTREAM / MANIFOLD_NO_FILESYSTEM /
CLIPPER2_NO_IOSTREAM), and flips manifold + Clipper2's CMake options
to disable the bits the shim doesn't support (Python/JS bindings,
threading, googletest pulls, etc.). The tested-pin combination for
each shim release is documented in CHANGELOG.md;
overrides for arbitrary refs + custom patch sets are supported via
the helper's named arguments — see
cmake/WasmCxxShimManifold.cmake
for the full API.
The shim's test/manifold-link/ is a worked
example that consumes this helper.
If someone publishes a better libm package tomorrow, you should be able to drop ours and pick theirs without touching the libc/libcxx halves. Same for the allocator. Keeping the units small and disjoint makes that swap cheap.
The boundaries follow the conventional C/C++ runtime layering — libc underneath libcxx underneath user code — but each layer here is a subset sized to actual demand, not a complete reimplementation.
- A complete libc / libc++ — only what real consumers need. WASI SDK exists for the complete-platform case.
- WASI compatibility — this targets the no-WASI
wasm32-unknown-unknowntriple specifically. There are no__wasi_*imports. - Threading — no pthreads. Emscripten with
-pthreadexists for that. - File I/O, sockets, time — out of scope. If your code needs them, you want WASI SDK.
- Exceptions — release builds use
-fno-exceptions. Implicit STL throws (bad_alloc,regex_error) abort.
MIT (see LICENSE). Where source is derived from third-party projects (musl, dlmalloc, libc++/libc++abi reference shims), original license + attribution is preserved alongside the code.