Summary
ruby-rbs-sys 0.3.0's build.rs has no wasm32-wasi* target awareness — there's no way to point cc at a wasi-targeting compiler or propagate the same flags to bindgen. I drafted a build refactor mirroring ruby-prism-sys's pattern (split build.rs into build/main.rs + build/vendored.rs, detect the wasm target, route everything through wasi-sdk). The C compile of vendored librbs to wasm32-wasip1 then succeeds cleanly. However, the bindgen step fails differently across three different libclang implementations, and the failure modes suggest a deeper bindgen-on-wasm issue that may also affect ruby-prism-sys — but isn't surfacing there because its wasm CI doesn't actually run.
This issue documents what I found in case maintainers (or anyone with deeper bindgen-on-wasm experience) can spot the underlying cause. I'm happy to send a PR for the build-refactor portion regardless — that part is mechanical and parallels prism cleanly. The bindgen question is what I'd like guidance on before proceeding.
Goal
Build ruby-rbs (and consumer crates) for wasm32-wasip1 so it can run in browser environments via wasi-libc shims. Used in a downstream project that ingests RBS signatures alongside Ruby source for a transpile-time type-resolution pipeline.
What works after the build refactor
- Vendored librbs C compiles to
wasm32-wasip1 cleanly with WASI_SDK_PATH set, using -D_WASI_EMULATED_MMAN=1 (defensive <sys/mman.h> includes in rbs_allocator.c; no actual mmap calls, so no link against libwasi-emulated-mman needed).
- Native build path is unaffected; the wasm code is gated on
target.contains("wasm32") && target.contains("wasi").
- Bindgen is invoked with the same
-I, -D, and --sysroot flags as cc via BINDGEN_EXTRA_CLANG_ARGS_<target> propagation — same convention ruby-prism-sys uses.
Three libclangs, three failure modes
| Platform / libclang |
Failure |
| macOS, Apple CommandLineTools libclang (default) |
All 18 FFI function declarations dropped from generated bindings. bindings.rs contains struct/typedef/enum definitions but no extern "C" blocks for functions. Downstream ruby-rbs then fails with 90 "cannot find function" errors. |
macOS, brew llvm 22 libclang (LIBCLANG_PATH=/opt/homebrew/opt/llvm/lib) |
Functions emit. But 5 typedef-after-forward-declared structs (rbs_ast_symbol, rbs_namespace, rbs_type_name, rbs_types_block, rbs_ast_declarations_class_super) emit as opaque (pub _address: u8) despite the full typedef struct foo { ... } foo_t; definition appearing later in vendor/rbs/include/rbs/ast.h. Bindgen's own layout-test assertions then fail at rustc time (size_of::<rbs_ast_symbol>() - 24usize overflows because actual size is 1, expected size is 24). |
Ubuntu 24.04, distribution libclang (apt install clang libclang-dev) |
Different missing types — at minimum rbs_parser_t is absent, breaking ruby-rbs's codegen-from-config.yml output (pub struct AnnotationNode<'a> { parser: NonNull<rbs_parser_t>, ... }). |
The pattern looks like bindgen handles forward-decl-then-typedef structs differently across libclang versions when running with --target=wasm32-wasip1 --sysroot=<wasi-sysroot>. The native build (no wasm target, no sysroot override) handles them correctly across all three libclangs.
Why prism's pattern alone may not be a sufficient precedent
I initially assumed ruby-prism-sys had wasm support figured out and copying its build pattern would suffice. The build-script pattern transferred cleanly, but the bindgen issues surfaced anyway. On checking: ruby-prism's wasm CI doesn't appear to actually run. The repo has rust/ruby-prism/examples/wasm/{main.rs,run.sh} and the rake cargo:examples task that invokes them, but I don't see a GHA workflow job that exercises the wasm path end-to-end — rust-bindings.yml adds targets: wasm32-wasip1 to the rustup-action but I couldn't find a job that actually runs cargo build --target=wasm32-wasip1 on prism. Maintainers can correct me if I'm reading this wrong.
If that's right, prism may share a similar issue; it just hasn't been forced to surface. If it's wrong, the prism-specific differences (e.g., PRISM_EXPORT_SYMBOLS macro, .size_t_is_usize(true), .layout_tests(true) configuration) are clues for what RBS may need.
What might help
I'm not sure what the right fix is. Plausible directions:
- Disable
layout_tests for the affected struct names. Workaround for the brew-libclang failure mode; doesn't address the Apple/Ubuntu modes.
- Investigate the typedef-after-forward-decl handling — possibly an ordering or
__attribute__((visibility)) interaction with wasm targets that bindgen's libclang sees differently across versions. May require a minimal repro filed against rust-bindgen.
- Generate bindings on the host platform, vendor them into the crate, skip bindgen entirely for wasm builds. Ugly but unblocks downstream.
If maintainers have a preferred direction, I'd be glad to send a PR. The build-script refactor (the wasm-detection block alone, mirroring prism) is independently useful and could land first.
Summary
ruby-rbs-sys0.3.0'sbuild.rshas nowasm32-wasi*target awareness — there's no way to pointccat a wasi-targeting compiler or propagate the same flags to bindgen. I drafted a build refactor mirroringruby-prism-sys's pattern (splitbuild.rsintobuild/main.rs+build/vendored.rs, detect the wasm target, route everything through wasi-sdk). The C compile of vendored librbs towasm32-wasip1then succeeds cleanly. However, the bindgen step fails differently across three different libclang implementations, and the failure modes suggest a deeper bindgen-on-wasm issue that may also affectruby-prism-sys— but isn't surfacing there because its wasm CI doesn't actually run.This issue documents what I found in case maintainers (or anyone with deeper bindgen-on-wasm experience) can spot the underlying cause. I'm happy to send a PR for the build-refactor portion regardless — that part is mechanical and parallels prism cleanly. The bindgen question is what I'd like guidance on before proceeding.
Goal
Build
ruby-rbs(and consumer crates) forwasm32-wasip1so it can run in browser environments viawasi-libcshims. Used in a downstream project that ingests RBS signatures alongside Ruby source for a transpile-time type-resolution pipeline.What works after the build refactor
wasm32-wasip1cleanly withWASI_SDK_PATHset, using-D_WASI_EMULATED_MMAN=1(defensive<sys/mman.h>includes inrbs_allocator.c; no actual mmap calls, so no link againstlibwasi-emulated-mmanneeded).target.contains("wasm32") && target.contains("wasi").-I,-D, and--sysrootflags asccviaBINDGEN_EXTRA_CLANG_ARGS_<target>propagation — same conventionruby-prism-sysuses.Three libclangs, three failure modes
bindings.rscontains struct/typedef/enum definitions but noextern "C"blocks for functions. Downstreamruby-rbsthen fails with 90 "cannot find function" errors.LIBCLANG_PATH=/opt/homebrew/opt/llvm/lib)rbs_ast_symbol,rbs_namespace,rbs_type_name,rbs_types_block,rbs_ast_declarations_class_super) emit as opaque (pub _address: u8) despite the fulltypedef struct foo { ... } foo_t;definition appearing later invendor/rbs/include/rbs/ast.h. Bindgen's own layout-test assertions then fail at rustc time (size_of::<rbs_ast_symbol>() - 24usizeoverflows because actual size is 1, expected size is 24).apt install clang libclang-dev)rbs_parser_tis absent, breakingruby-rbs's codegen-from-config.ymloutput (pub struct AnnotationNode<'a> { parser: NonNull<rbs_parser_t>, ... }).The pattern looks like bindgen handles forward-decl-then-typedef structs differently across libclang versions when running with
--target=wasm32-wasip1 --sysroot=<wasi-sysroot>. The native build (no wasm target, no sysroot override) handles them correctly across all three libclangs.Why prism's pattern alone may not be a sufficient precedent
I initially assumed
ruby-prism-syshad wasm support figured out and copying its build pattern would suffice. The build-script pattern transferred cleanly, but the bindgen issues surfaced anyway. On checking:ruby-prism's wasm CI doesn't appear to actually run. The repo hasrust/ruby-prism/examples/wasm/{main.rs,run.sh}and the rakecargo:examplestask that invokes them, but I don't see a GHA workflow job that exercises the wasm path end-to-end —rust-bindings.ymladdstargets: wasm32-wasip1to the rustup-action but I couldn't find a job that actually runscargo build --target=wasm32-wasip1on prism. Maintainers can correct me if I'm reading this wrong.If that's right, prism may share a similar issue; it just hasn't been forced to surface. If it's wrong, the prism-specific differences (e.g.,
PRISM_EXPORT_SYMBOLSmacro,.size_t_is_usize(true),.layout_tests(true)configuration) are clues for what RBS may need.What might help
I'm not sure what the right fix is. Plausible directions:
layout_testsfor the affected struct names. Workaround for the brew-libclang failure mode; doesn't address the Apple/Ubuntu modes.__attribute__((visibility))interaction with wasm targets that bindgen's libclang sees differently across versions. May require a minimal repro filed against rust-bindgen.If maintainers have a preferred direction, I'd be glad to send a PR. The build-script refactor (the wasm-detection block alone, mirroring prism) is independently useful and could land first.