openpencil/crates/wasm-libc-shim/build.rs
Kayshen-X 8ac685ff84 feat(shell): add wasm-libc-shim crate for wasm32-unknown-unknown skia bundle
Provides the libc / libcxx / libm symbols that the wasm32-unknown-
unknown skia static archive (built via vendor/skia-safe-op) imports
at link time but wasm-bindgen does not synthesize. With this crate
linked in, the post-bindgen bundle has 0 env.* imports and is
runtime-loadable as a vanilla browser ES module.

Categories implemented (~83 symbols, dedup against the actual
import list):
  - allocator: malloc / free / calloc / realloc / malloc_usable_size
    via dlmalloc-rs + a 16-byte size header per allocation so the
    GlobalAlloc::dealloc layout contract is preserved on free and
    realloc copies min(old_size, new_size) on grow
  - libm: asinh / acosh / atanh / nextafterf / remainder via libm
  - libc string: memchr / wmemchr / strcmp / strcpy / strtoull
    hand-rolled byte-wise
  - libc stdio: snprintf / vsnprintf / vfprintf as C-side variadic
    stubs (stdio_stub.c, compiled by cc) that route into a Rust
    extern wasm_libc_shim_stdio_panic before returning, so any
    actual invocation surfaces a named panic via console_error_
    panic_hook instead of a silent empty success
  - libc misc: abort (panics with diagnostic) + __errno_location
    (single-mut-static — single-threaded wasm only)
  - C++ ABI: __cxa_atexit (no-op), __cxa_guard_acquire / release,
    __cxa_pure_virtual (panics)
  - operator new / delete: _Znwm / _Znam / _ZdlPv* / _ZdaPv*
    forwarding to malloc / free; _Znwm(0) routes through
    malloc(1) per C++ standard (operator new must return a
    non-null pointer)
  - threads: sem_init / sem_destroy / sem_post / sem_wait no-op
  - libcxx string / locale / iostream / shared_weak_count /
    to_string: ~23 panic stubs via the libcxx_stub! macro that
    panic with the symbol name; these are linker-pulled by
    templated code that the skia raster + custom_empty fontmgr
    pipeline does not exercise at runtime, so a panic = regression
    signal

Build-time gating:
  - active only on wasm32-unknown-unknown via cfg(all(target_arch
    = "wasm32", target_os = "unknown")); native builds link an
    empty crate so the symbols do not collide with the host libc
  - compile_error! on target_feature = "atomics" because the
    static-mut errno + non-atomic __cxa guard impls would race
    under wasm threads — the path forward is real TLS errno +
    atomic guard variables in a follow-up sub-phase

Step 1b §3.2 P0.5B Run path, sub-phase C-hard.2.
2026-05-09 21:07:00 +08:00

38 lines
1.3 KiB
Rust

// Compile the C-variadic stdio stubs for wasm32-unknown-unknown.
// On any other target this is a no-op so the crate links empty.
fn main() {
let target = std::env::var("TARGET").unwrap_or_default();
if target != "wasm32-unknown-unknown" {
return;
}
let emsdk = match std::env::var("EMSDK") {
Ok(v) => v,
Err(_) => panic!(
"wasm-libc-shim requires the EMSDK env var pointing at an emsdk install \
(used only for libcxx headers + emsdk's bundled wasm32-aware clang)"
),
};
let llvm_bin = format!("{emsdk}/upstream/emscripten/llvm/bin");
let sysroot = format!("{emsdk}/upstream/emscripten/cache/sysroot");
let mut build = cc::Build::new();
build
.compiler(format!("{llvm_bin}/clang"))
.archiver(format!("{llvm_bin}/llvm-ar"))
.file("src/stdio_stub.c")
.flag(format!("--sysroot={sysroot}"))
.flag(format!("-isystem{sysroot}/include"))
.flag(format!("-isystem{sysroot}/include/compat"))
.flag("--target=wasm32-unknown-unknown")
.flag("-fvisibility=default")
.flag("-fno-exceptions")
.flag("-O2")
.opt_level(2)
.warnings(false);
build.compile("wasm_libc_shim_stdio");
println!("cargo:rerun-if-changed=src/stdio_stub.c");
println!("cargo:rerun-if-env-changed=EMSDK");
}