Repository navigation
Install the stack-exhaustion budget (EH-12..EH-15) - #153
Merged
Merged
Conversation
The budget converts imminent native stack exhaustion into the compiler's psrs:stack-exhaustion exception before the engine's trap, under the watermark contract that keeps the depth counter honest across EH throws. - mir/budget.rs: the weight formula W_f = FRAME_WEIGHT_FIXED + FRAME_WEIGHT_PER_LOCAL x locals with the design's provisional constants (32 / 16) and STACK_BUDGET = 262144; recorded on every MIR function as Function::frame_weight at P9, from the *lowered* function's parameter and value count (P9 adds fresh values beyond the CC source's). - wasm/lower/budget.rs + function/mod.rs + structure/region/mod.rs: every lowered function gains the prologue (depth += W_f; if depth > STACK_BUDGET then throw, emitted before the first structured block — the overflow unwinds out of the function because no try region is armed yet), the epilogue decrement on every return path, and the tail decrement at every return_call* site; trapping blocks carry none. - try_unit.rs: every try_table is preceded by the watermark save into the try region's fresh i32 local, and its catch path begins with the restore, settling every frame the throw unwound at once. - wasm/lower/entry.rs: the synthesized run entry wraps its call to main in a try_table (catch_all); the handler restores depth to the value saved at entry and leaves through exit-with-code 1 (Entry gains declared locals, carried through verify and encode). - The two CFG analyses that still viewed only normal edges are now truthful about unwind edges, per the design's invariant: value availability (mir/verify/function.rs) consumes dominators_with_unwind_edges so a handler can read enclosing values, and the structurer's predecessor map and SCC DFS (wasm/lower/structure/cfg) count unwind edges, so a handler joining a loop's body is no longer an irreducibility false positive. - Coordination (EH-15): the execution helpers pin the engine's stack limit (-W max-wasm-stack=524288, measured equal to wasmtime 48.0.1's default); the coordination fixture (checkThrows discharge over a genuinely non-tail self-recursive function) yields the compiler's exception at depth ~1,490 while the engine's trap sits between 6,000 and 7,000 frames, and the depth-100 returning recursion consumes ~7% of the budget. The leak fixture (21,848 throw/catch iterations then deep recursion) proves no counter leak; deep tail recursion at 1,000,000 still exits 0. - The disabled profile emits none of the budget; no profile default change.
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.
What lands
Slice 4 of the exception-handling adoption (rows EH-12..EH-15), per the design from PR #149:
mir/budget.rs:W_f = 32 + 16 × locals(lowered count),STACK_BUDGET = 262144, recorded asFunction::frame_weightat P9 from the lowered function's parameter+value count (the measured firing depth matches the recorded weight exactly: 262144/176 = 1490.0).depth += W_f; if depth > STACK_BUDGET then throw :stack-exhaustion, out-of-function unwinding since no region is armed yet), epilogue at everyReturn, tail decrement atreturn_call*; trapping blocks emit none. Mutualeven/oddtail recursion at depth 1,000,000 exits 0 — no false throw.try_tablepreceded by a watermark save into a freshi32local per region; catch path's first action restores. Leak fixture: 21,848 throw/catch iterations followed by shallow guardedrec(100)then deep guardedrec(100,000)— the deep recursion still reaches the budget point (no leak).runentry wrapsmainintry_table (catch_all)restoring depth thenexit-with-code 1; coordination fixture (non-tailrecunder the checkThrows discharge) yields the compiler's exception under the pinned engine, never the engine trap.Measured calibration (first measured numbers of the adoption; recorded in the checklist + capability-profile notes)
-W max-wasm-stack; pinned limit 524288 (measured equal to this engine's default — the pin is inert for the current corpus but makes the condition explicit). Driver run helpers now pin it.STACK_BUDGETpast the engine limit reproduces the recorded failure mode (enginecall stack exhaustedtrap, exit 134).Related invariant fixes (per the design's own authority)
Validation
cargo fmt --all --check✅ ·cargo clippy --workspace --all-targets -- -D warnings✅ · full workspace suite ✅ 2189 passed, 0 failed (coordinator re-verified fmt/clippy + psrs-backend 587/0). Checklist rows EH-12..15 → Verified with measured evidence; D-04 untouched; profile default unchanged (the flip is EH-17).