feat(layout): --stack-layout=low — stack at the SRAM bottom, overflow BusFaults instead of corrupting linmem/globals (#687, VCR-MEM-003) - #694
Merged
Conversation
… BusFaults instead of corrupting linmem/globals (#687, VCR-MEM-003) Today's self-contained Cortex-M image puts the initial SP at the TOP of SRAM growing down toward the R9 globals table and linear memory — a stack overflow silently corrupts them (unicorn red evidence: deep recursion sweeps the linmem canaries and only faults once SP exits SRAM). --stack-layout=low (+ --stack-size, default 4096) reserves the stack at the SRAM BOTTOM: SP init = SRAM start + stack_size, and the ENTIRE RAM-anchored layout — startup R11 linmem base, R9 globals table, RAM auto-scale, AND the optimized path's absolute 0x2000_0100 base (new CompileConfig::linmem_base threaded to the OptimizerBridge) — shifts UP by stack_size as one. Overflow descends past 0x2000_0000 into reserved space and BusFaults on the FIRST errant push — every Cortex-M, no MPU. Applicability: self-contained images ONLY. --relocatable, import-forced ET_REL, and non-Cortex-M backends REFUSE the flag loudly (their linker script/harness owns the layout). Default high stays byte-identical by construction (reserve=0 degenerates every formula; startup MOVW/MOVT R11 encodes the historical fixed bytes) — frozen anchors 10/10 and whole-ELF cmp vs main on three self-contained fixtures confirm. Layout contract documented on build_multi_func_cortex_m_elf, cross- referenced from the #650/#669 R11 table contract (CallIndirectGuards). Oracle (scripts/repro/stack_layout_687_differential.py, CI-wired): RED high: recursion silently clobbers 4/8 linmem canaries BEFORE any fault (fault only at the SRAM floor, SP 0x1FFFFFF0) GREEN low: UC_ERR_WRITE_UNMAPPED below SRAM (SP 0x20000010) with 8/8 canaries intact — the BusFault precedes any linmem damage TRANSPARENT: in-budget calls match wasmtime under BOTH layouts; the existing #649 global-init differential passes unchanged under EXTRA_SYNTH_FLAGS="--stack-layout low" (R9 0x20010000→0x20011000) SHIFT PIN: low canary addresses = high addresses + stack_size exactly VCR-MEM-003 artifact added: the stack-guard ladder (low layout → MPU guard → v8-M PSPLIM), status implemented for the low-layout rung only. Relates #404/#406 (MPU-protectable region model). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
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.
Closes #687. Relates #404/#406 (MPU-protectable region model), VCR-MEM-001/002.
What
Today's self-contained Cortex-M image sets the initial SP at the top of SRAM growing down toward the R9 globals table and linear memory — a stack overflow silently corrupts them (red evidence below). This lands option 1 of the #687 graded menu:
--stack-layout=high|low(defaulthigh= today, byte-identical) +--stack-size <BYTES>(default 4096, low layout only; ≥256, 8-aligned; warned-and-ignored under high).low: SP init = SRAM start + stack_size, and the entire RAM-anchored layout shifts up by stack_size as one — startup R11 linmem base, R9 globals table (non-i32 global initializers silently zeroed — i64.const/f32.const/f64.const inits drop to 0 (init_i32 only decodes i32.const) #649/fix(globals): #649 i64.const global initializers reach the emitted image — decoder captures both words, startup materializes the R9 table #652 machinery), RAM auto-scale, and the optimized path's absolute0x2000_0100base (newCompileConfig::linmem_base→OptimizerBridge::set_linmem_base, so const-address user code follows the moved linmem, not just the startup).0x2000_0000into reserved/flash-alias space → precise BusFault on the first errant push, every Cortex-M, no MPU.Applicability (chosen: refuse loudly, documented): self-contained images only.
--relocatable, import-forced ET_REL, and non-Cortex-M backends error — their linker script/harness owns the layout, so warn-and-ignore would ship a flag that silently did nothing.Layout contract documented on
build_multi_func_cortex_m_elf(both layout tables + invariants, including the pre-existing0x100offset between the startup R11 base and the optimized path's absolute base — preserved verbatim in both layouts), cross-referenced from the #650/#669 R11 table contract onCallIndirectGuards.Oracles (the issue's acceptance — all run locally, new CI job
stack-layout-687-oracle)scripts/repro/stack_layout_687_differential.pyruns the REAL image (its own vector-table SP + reset path) under unicorn, both layouts:0x1FFFFFF0)UC_ERR_WRITE_UNMAPPEDbelow SRAM (SP0x20000010) with 8/8 canaries intact — the BusFault precedes any linmem damagestack_sizeexactly (0x2000F100→0x20010100)Existing fixture under the flag: the #649 i64-global-init differential (real startup, stateful wasmtime ground truth) passes unchanged under
EXTRA_SYNTH_FLAGS="--stack-layout low"(startup R90x20010000→0x20011000) — the layout shift is transparent when the stack doesn't overflow.Flag-off (untouched by construction — verified)
frozen_codegen_bytes).cmpvs a main-built binary: byte-identical self-contained images forstack_canary_687.wat,i64_global_init_649.wat,control_step.wasm.StackLayout::Highreserve = 0 degenerates every formula; the startup's parametrizedMOVW/MOVT R11encodes the historical fixed bytes (pinned bytest_minimal_startup_generation);CompileConfig::linmem_basedefaults to the historical0x2000_0100.resolve_stack_layoutrefusal contract.Rivet
VCR-MEM-003added toartifacts/verified-codegen-roadmap.yaml: the stack-guard ladder (low layout → MPU guard region → v8-M PSPLIM), statusimplementedfor the low-layout rung only; rungs 2/3 are future issues.rivet validate: 0 errors (one house-style WARN, same class as the file's other 98).Gates
cargo test --workspace2083 passed / 0 failed · clippy-D warningsclean · fmt clean · rebased onto6caca5a(#689) with all gates re-run.🤖 Generated with Claude Code