fix(rate): bounded arena instead of a growing allocator — remove memory.grow - #327
Merged
Conversation
…ry.grow
The v1.130 fix made falcon-rate encode as a real component, but satisfied
`cabi_realloc` with `lol_alloc` — and a GROWING allocator emits `memory.grow`.
Disassembling the shipped falcon-rate:1.130.0 confirms it: the module's only
`memory.grow` sits inside lol_alloc. Per gale#89 and meld#299 that is precisely
what makes `meld fuse --memory shared --address-rebase` reject a component
("memory.grow not supported with address rebasing"), so the fix that unblocked
componentization partly re-blocked the MCU lowering it exists to serve.
Replace it with a bounded bump arena: fixed 8 KiB store, exhaustion TRAPS rather
than growing, frees are no-ops, cursor resets at the top of each `tick`.
Sizing is not arbitrary: the ABI only allocates here for the SPILLED PARAMETER
AREA — vehicle-state (14 f32) + rate-setpoint (4 f32) = 18 flat params exceeds
the canonical MAX_FLAT_PARAMS (16), so the host writes arguments into our memory
(verified: the core export is `(param i32) (result i32)`, a pointer, not 18
floats). A few hundred bytes per call; 8 KiB is ample.
That param spill also rules out the other candidate fix: wit-bindgen's
`disable_cabi_realloc` (#6) elides the export only for genuinely flat worlds,
and rate's params spill, so it needs realloc. The arena is the applicable
answer, not elision.
INTERIM, DELIBERATELY. The end state is NOT a per-component arena — it is
wit-bindgen's `cabi-realloc-extern` (pulseengine/wit-bindgen#4) delegating to
gale's `__cabi_arena_realloc`, i.e. ONE embedder-owned arena across the fused
image. A private arena per component multiplies by N once meld fuses the
cascade. This lands now because on the non-fork path `no_std` still requires a
`#[global_allocator]` (wit-bindgen-rt's cabi_realloc goes through alloc::alloc),
so the real choice today is WHICH allocator — and a non-growing one is strictly
better than lol_alloc. It is superseded, not made pointless, by wiring the fork.
Verified on the rebuilt component: memory.grow 1 -> 0, still a component
(0061736d0d000100), still ZERO wasi imports, cabi_realloc still exported, 3883
bytes (was 4674), and the through-wasm closed-loop proof unchanged over 3000
ticks — converges 0.193 s, |err| 0.0059 rad/s — which exercises the per-tick
reset under sustained use.
Refs: gale#89, meld#299, pulseengine/wit-bindgen#4.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG
avrabe
enabled auto-merge (squash)
August 5, 2026 06:07
avrabe
added a commit
that referenced
this pull request
Aug 5, 2026
…P06 (#331) * verify(oci): record the v1.131 no-grow + annotations evidence; defer P06 to v1.132 Code-free. Records three follow-on defects found and fixed since v1.130, each measured on the artifact rather than argued, against the requirement they strengthen (SWREQ-FALCON-OCI-P01, already verified): (v) NO-GROW (#327) — the v1.130 cabi_realloc fix satisfied the ABI with a GROWING allocator, so the shipped component carried `memory.grow`: precisely what makes `meld fuse --memory shared --address-rebase` reject a component (gale#89, meld#299). The fix partly re-blocked the MCU lowering it exists to serve. Replaced with a bounded static work-memory arena; memory.grow 1 -> 0. (vi) ROOT CAUSE — TARGET (#329) — `target_env = "p2"` is the wasip2 RUST TARGET, which links wasi-libc, whose cabi_realloc goes through malloc. A component does not need that target to be a valid P2 component. no_std components now build wasm32-unknown-unknown. (vii) LISTING METADATA (#328) — wasm.directory renders standard OCI MANIFEST ANNOTATIONS, not the embedded package-metadata. Our manifests carried none, so falcon-mixer:1.130.0 listed as "No description available" despite complete crate metadata. Also corrects an explicit claim in docs/OCI-DISTRIBUTION.md. Scope move, explicit: SWREQ-FALCON-MATHF32-P06 (Cody-Waite argument reduction) v1.131 -> v1.132. It is the hardest of the four proofs and is not ready; v1.131 is a fix release whose job is publishing the no-grow components and non-bare listings. Deferring is a decision, not a silent slip. rivet validate: PASS. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG * chore: re-trigger CI to pick up the Verify-Filter scope The gate reads `github.event.pull_request.body` from the EVENT PAYLOAD, not a live fetch, so editing the PR body does not change what a re-run sees — only a new pull_request event does. Empty commit to emit one. Scoping this PR with `Verify-Filter: (has-tag "oci")`: the unscoped ~370-step sweep timed out at 90 minutes TWICE on this two-file artifact change (263 PASS / 0 FAIL both times — out of clock, not out of correctness). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem my own v1.130 fix introduced
Making
falcon-rateencode as a real component required satisfyingcabi_realloc, and I did it withlol_alloc— a growing allocator, which emitsmemory.grow. Disassembling the shippedfalcon-rate:1.130.0confirms it: the module's onlymemory.growsits inside lol_alloc.Per gale#89 and meld#299, that is exactly what makes
meld fuse --memory shared --address-rebasereject a component:So the fix that unblocked componentization partly re-blocked the MCU lowering it exists to serve.
The fix
A bounded bump arena: fixed 8 KiB store, exhaustion traps rather than growing, frees are no-ops, cursor resets at the top of each
tick.Sizing isn't arbitrary. The ABI only allocates here for the spilled parameter area:
vehicle-state(14 f32) +rate-setpoint(4 f32) = 18 flat params exceeds the canonicalMAX_FLAT_PARAMS(16), so the host writes arguments into our memory. Verified in the binary — the core export is(param i32) (result i32), a pointer, not 18 floats. A few hundred bytes per call; 8 KiB is ample.That same spill rules out the alternative fix: wit-bindgen's
disable_cabi_realloc(#6) elides the export only for genuinely flat worlds. Rate's params spill, so it needs realloc. The arena is the applicable answer, not elision.Interim — deliberately
The end state is not a per-component arena. It's wit-bindgen's
cabi-realloc-extern(pulseengine/wit-bindgen#4) delegating to gale's__cabi_arena_realloc— one embedder-owned arena across the fused image. A private arena per component multiplies by N once meld fuses the cascade.This lands now because on the non-fork path
no_stdstill requires a#[global_allocator](wit-bindgen-rt'scabi_reallocgoes throughalloc::alloc), so today's real choice is which allocator — and a non-growing one is strictly better. Superseded, not made pointless, by wiring the fork.Verified
memory.grow0061736d0d000100wasi:*importsClosed-loop proof unchanged over 3000 ticks — converges 0.193 s, |err| 0.0059 rad/s — which also exercises the per-tick arena reset under sustained use.
Refs: gale#89, meld#299, pulseengine/wit-bindgen#4.
🤖 Generated with Claude Code