diff --git a/scripts/build-components.sh b/scripts/build-components.sh index f28de21..440fa8b 100755 --- a/scripts/build-components.sh +++ b/scripts/build-components.sh @@ -49,8 +49,21 @@ build_component() { local dir="$1" local wasm_dir="$HERE/wasm/cm/$dir" printf " building %-16s..." "$dir" >&2 - ( cd "$wasm_dir" && cargo component build --release --target wasm32-wasip2 --no-default-features 2>&1 ) \ - | grep -E "^error" | sed 's/^/ /' >&2 || true + # FAIL LOUD (v1.130): this used to end in `|| true`, so a build error was + # printed and then IGNORED — the script fell through to a stale artifact from + # a previous build and shipped it. That is exactly how falcon-rate:1.129.0 was + # published as a raw CORE MODULE: the no_std wasip2 build failed + # ("module does not export a function named `cabi_realloc`"), the failure was + # swallowed, and the fallback picked up a pre-componentization module. + # A build failure must stop the bundle, not degrade it silently. + if ! ( cd "$wasm_dir" && cargo component build --release --target wasm32-wasip2 --no-default-features 2>&1 ) \ + | tee /tmp/falcon-build-$dir.log | grep -E "^error" | sed 's/^/ /' >&2; then + : # grep found no "^error" lines — that is the success path + fi + if grep -qE "^error" /tmp/falcon-build-$dir.log 2>/dev/null; then + echo " ERROR: cargo component build failed for $dir (see above)" >&2 + exit 1 + fi # Prefer the wasip2 path; cargo-component <0.20 emits wasip1. local src src=$(ls "$wasm_dir"/target/wasm32-wasip2/release/*.wasm 2>/dev/null | head -1 || true) @@ -66,6 +79,18 @@ build_component() { local slug="${dir#falcon-}" local dest="$BUNDLE_DIR/falcon-$slug-v$MM.wasm" cp "$src" "$dest" + # ASSERT the payload really is a Component, not a core module (v1.130). + # jess found falcon-rate:1.129.0 shipped as a core module — `meld fuse` + # rejected it outright. The OCI config mediaType says "component" regardless + # of the bytes, so metadata cannot catch this; check the 8-byte header. + # core module : 00 61 73 6d 01 00 00 00 + # component : 00 61 73 6d 0d 00 01 00 + magic=$(od -An -tx1 -N8 "$dest" | tr -d ' \n') + if [ "$magic" != "0061736d0d000100" ]; then + echo " ERROR: $dest is NOT a wasm component (header $magic)" >&2 + echo " expected 0061736d0d000100; a core module is 0061736d01000000" >&2 + exit 1 + fi local bytes bytes=$(wc -c < "$dest" | tr -d ' ') printf " %s bytes\n" "$bytes" >&2 diff --git a/wasm/cm/rate/src/lib.rs b/wasm/cm/rate/src/lib.rs index 51862d1..9126144 100644 --- a/wasm/cm/rate/src/lib.rs +++ b/wasm/cm/rate/src/lib.rs @@ -23,6 +23,40 @@ static ALLOC: lol_alloc::AssumeSingleThreaded = unsafe { lol_alloc::AssumeSingleThreaded::new(lol_alloc::FreeListAllocator::new()) }; +// The Component-Model canonical ABI requires the guest to export +// `cabi_realloc`. wit-bindgen-rt provides it ONLY when it can lean on std — +// its implementation is `#[cfg(not(target_env = "p2"))]`, because on +// wasm32-wasip2 it expects the std toolchain to supply the symbol. This crate +// is `no_std` on the cargo-component path, so nothing exported it and +// `wasm-component-ld` failed with "module does not export a function named +// `cabi_realloc`" — leaving a raw CORE MODULE where a component was expected. +// Export it here, backed by the same single-threaded allocator. +#[cfg(not(feature = "bazel-bindings"))] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn cabi_realloc( + old_ptr: *mut u8, + old_len: usize, + align: usize, + new_len: usize, +) -> *mut u8 { + use core::alloc::{GlobalAlloc, Layout}; + unsafe { + if old_len == 0 { + // Canonical ABI: a zero-size allocation returns the alignment as a + // non-null, suitably-aligned dangling pointer. + if new_len == 0 { + return align as *mut u8; + } + return ALLOC.alloc(Layout::from_size_align_unchecked(new_len, align)); + } + ALLOC.realloc( + old_ptr, + Layout::from_size_align_unchecked(old_len, align), + new_len, + ) + } +} + // Bindings generated by cargo-component as src/bindings.rs. #[allow(warnings)] #[cfg(feature = "bazel-bindings")]