openpencil/crates/wasm-libc-shim/src/stdio_stub.c

59 lines
2 KiB
C
Raw Normal View History

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 13:07:00 +00:00
/*
* C-hard.2 stdio stubs for wasm32-unknown-unknown skia bundles.
*
* Skia uses snprintf/vsnprintf/vfprintf for SkString::printf style
* formatting (e.g. shader debug logs, font path debug). On the raster
* + custom_empty fontmgr pipeline none of these are exercised in
* normal rendering — verified by running the post-link bundle and
* confirming 0 env.* imports. If a path IS actually hit at runtime
* we want to FAIL FAST with the symbol name in the panic message
* rather than silently returning a successful empty string (codex
* Round 1 C4: silent empty masked unexpected printf reachability).
*
* Variadic in stable Rust requires the `c_variadic` nightly feature.
* Doing this in C keeps the wasm-libc-shim crate stable-compatible;
* the Rust side (`imp.rs::wasm_libc_shim_stdio_panic`) does the
* actual panic with `console_error_panic_hook` integration.
*/
#include <stddef.h>
#include <stdarg.h>
extern void wasm_libc_shim_stdio_panic(const char *name) __attribute__((noreturn));
int snprintf(char *buf, size_t size, const char *fmt, ...) {
(void)fmt;
if (buf && size > 0) {
buf[0] = 0;
}
wasm_libc_shim_stdio_panic("snprintf");
}
int vsnprintf(char *buf, size_t size, const char *fmt, va_list ap) {
(void)fmt;
(void)ap;
if (buf && size > 0) {
buf[0] = 0;
}
wasm_libc_shim_stdio_panic("vsnprintf");
}
int vfprintf(void *stream, const char *fmt, va_list ap) {
(void)stream;
(void)fmt;
(void)ap;
wasm_libc_shim_stdio_panic("vfprintf");
}
fix(shell-web): Step 3 stop-hook — actually render canvas text Codex stop-hook flagged: "web Step 3 cannot render the claimed canvas text". Root cause: WebBackend::draw_text was a Phase A no-op stub. CanvasViewport calls draw_text for "Hello OpenPencil" / "Click me" / Layer panel labels / etc — none of those text strings actually rendered in the browser. # Fix `crates/openpencil-shell-web/src/backend/mod.rs::WebBackend`: - Embeds `assets/Roboto-Regular.ttf` (Apache 2.0, 35 KB, copied from rust-skia test resources) via `include_bytes!`. The C-hard wasm32-unknown-unknown skia build uses `skia_enable_fontmgr_custom_empty=yes` (see `vendor/skia-safe-op/skia-bindings/build_support/platform/ wasm_unknown.rs`), so there are no system fonts and we have to bake the bytes in. - New `typeface: Option<Typeface>` field, lazy-init on first draw_text via `FontMgr::custom_empty().and_then(|m| m.new_from_data(ROBOTO_TTF, None))`. Build failure → None silently no-ops subsequent draws (no panic — text just doesn't render). - `draw_text` now iterates `layout.runs()` and calls `Canvas::draw_str` per run with a `Font::new(typeface, font_size)` + `Paint` from the run color. `crates/openpencil-shell-web/Cargo.toml`: - Drops `textlayout` skia-safe feature. We use raw `draw_str` not paragraph builder; textlayout pulled ICU + Harfbuzz + ~400 KB gzip + a swarm of font-lookup imports we don't exercise. # 24 new env.* imports — wasm-libc-shim expansion Even without textlayout, Skia's font path imports 24 libc symbols our prior C-hard.2 shim didn't cover. All resolved: `crates/wasm-libc-shim/src/imp.rs` — Rust extern "C" shims: - string ops (real impls): strncmp, strncpy, strstr, strrchr, strcat, strtol, tolower, qsort (insertion sort, fits Skia's small-array call sites; debug_assert on element size > 256) - file I/O (sentinel error returns, no filesystem on wasm): fopen → null, fread → 0, fclose → 0, fputc → c, fileno → -1, fstat → -1, pread → -1, ftell → -1, fseek → -1 - env: getenv → null - mmap: returns MAP_FAILED ((void*)-1); munmap → -1 - setjmp/longjmp: setjmp returns 0 (treat as initial call); longjmp panics — happy text path through in-memory TTF parse should never trigger it - C++ nothrow new: `_ZnwmRKSt9nothrow_t` forwards to malloc, returns nullptr on OOM (the nothrow contract) `crates/wasm-libc-shim/src/stdio_stub.c`: - fprintf C-side variadic stub (Skia diagnostic path) that routes into the same panic helper as snprintf / vsnprintf / vfprintf — same fail-fast policy. # Verification - `cargo build -p openpencil-shell-web --target wasm32-unknown-unknown --features skia --release` — green - `wasm-bindgen --target web` produces ../pkg/openpencil_shell _web.{js,_bg.wasm} - `bash tools/check-wasm-bundle.sh` — PASS: - 0 env.* imports preserved (24 new ones absorbed by shim) - 906 935 bytes gzip = 86% of 1 MiB ceiling (+286 KB vs pre-text — Skia font/freetype subsystem is substantial. Headroom: 14% of ceiling) - `cargo test -p openpencil-shell-core --lib` — 39 tests - `cargo build -p openpencil-shell-native --example inspector_window` — green (desktop demo unchanged — uses jian-skia textlayout via NativeBackend, not the web font path) - `cargo check -p openpencil-shell-native --target aarch64-apple-ios` — green - `cargo check -p openpencil-shell-native --target aarch64-linux-android` — green # Re-run the demo ``` EMSDK="$HOME/.emsdk" cargo build -p openpencil-shell-web \ --target wasm32-unknown-unknown --features skia --release wasm-bindgen --target web --out-dir crates/openpencil-shell-web/pkg \ target/wasm32-unknown-unknown/release/openpencil_shell_web.wasm cd crates/openpencil-shell-web/smoke python3 -m http.server 8000 # Browser: http://localhost:8000/step-1b.html ``` Now the canvas viewport renders "Hello OpenPencil" + "Click me" text in addition to the rect/stroke geometry.
2026-05-10 04:38:33 +00:00
/* Step 3 codex stop-hook addition: fprintf is variadic so it
* needs the C-side stub treatment. Skia uses it for diagnostic
* messages on freetype error paths; happy text-rendering path
* doesn't reach it. Same fail-fast policy as the snprintf
* family — easier to find a real call than to debug an empty
* silent return. */
int fprintf(void *stream, const char *fmt, ...) {
(void)stream;
(void)fmt;
wasm_libc_shim_stdio_panic("fprintf");
}