Fix #549: GC-aware tail-call optimization for allocating functions#671
Conversation
Pre-fix, WASM return_call reverted to plain call whenever a function had needs_alloc=True, because return_call discards the current frame and would skip the GC epilogue, leaking shadow-stack slots per iteration. This forced agents to restructure allocating tail- recursive code into array_fold/array_map shapes or to hoist allocations outside the recursion. The fix preserves TCO AND the shadow-stack invariant: instead of reverting, the post-process now PREPENDS a two-instruction $gc_sp restore (local.get $gc_sp_save; global.set $gc_sp) immediately before each return_call in an allocating function. Args are already on the WASM operand stack at the return_call site; the restore only touches the $gc_sp global, so args transfer atomically to the callee. The callee's prologue saves a clean new $gc_sp baseline, so per-iteration shadow-stack usage stays bounded at caller's entry + n_arg_roots regardless of iteration count. Postcondition-bearing functions still revert to plain call — the runtime postcondition check needs to run after each call, and return_call would skip it. Dispatch precedence: postcondition- revert > GC-aware-TCO-patch > untouched. Tests: - Updated TestTailCallOptimization517 — renamed the falls-back test to test_allocating_function_uses_gc_aware_tco_549 and inverted assertions; added test_allocating_function_with_ postcondition_still_reverts for precedence. - Updated TestGCShadowStackOverflow::test_shadow_stack_overflow_ traps to use a non-tail-recursive shape that still genuinely overflows post-#549. - tests/test_stress.py — switched the existing 1K-iter deep-tail test from string-pool literals (didn't actually set needs_alloc) to genuine array allocation; added a 1M-iter companion parametrised over default/eager-GC. Closes #549. Co-Authored-By: Claude <noreply@anthropic.invalid>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: ⛔ Files ignored due to path filters (6)
📒 Files selected for processing (14)
💤 Files with no reviewable changes (1)
📝 WalkthroughWalkthroughThis PR implements issue ChangesGC-aware allocating tail-call optimization
Sequence Diagram(s)sequenceDiagram
participant _compile_fn as _compile_fn
participant GCPrologue as GC_Prologue
participant body_instrs as body_instrs
participant ReturnPatcher as ReturnPatcher
_compile_fn->>GCPrologue: ensure gc_sp_save local allocated
_compile_fn->>body_instrs: emit function body (may contain return_call)
body_instrs->>ReturnPatcher: locate each return_call site
ReturnPatcher->>body_instrs: inject "local.get gc_sp_save"
ReturnPatcher->>body_instrs: inject "global.set $gc_sp"
ReturnPatcher->>body_instrs: or rewrite to "call" when post_instrs present
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #671 +/- ##
=======================================
Coverage 91.02% 91.02%
=======================================
Files 60 60
Lines 23398 23409 +11
Branches 259 259
=======================================
+ Hits 21297 21308 +11
Misses 2094 2094
Partials 7 7
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
The #549 post-process inserts `local.get $gc_sp_save; global.set $gc_sp` immediately before each `return_call` site in an allocating function. Pre-fix the two inserted lines were appended as raw strings with no leading whitespace, so the rendered WAT looked misaligned when the `return_call` was nested inside an `if/else` (operators.py prepends 2 spaces to every instruction inside a then/else arm; functions.py later prepends 4 more at the body-level stringify step — so a nested return_call carries 6 leading spaces while my inserted lines only got 4). Functionally inert — WAT parses by tokens, not whitespace — but visually misleading for anyone reading the WAT for debugging. Fix: copy the leading whitespace from the original return_call line and apply it to both inserted lines so the restore sequence visually nests at the same depth as the return_call it precedes. Co-Authored-By: Claude <noreply@anthropic.invalid>
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
tests/test_codegen.py (1)
15089-15136:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winDon't let
_run_trap()hide the wrong failure mode.Line 15136 now proves only that something trapped. If this shape regressed into a plain WASM stack overflow or a different allocator trap, the test would still pass without ever exercising the shadow-stack overflow guard it documents. Please pair the runtime trap with a structural assertion against the emitted overflow-check sequence in
overflow's WAT.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_codegen.py` around lines 15089 - 15136, The test currently only calls _run_trap(src) which only verifies that "something" trapped; update the test to also inspect the emitted WAT for the function overflow and assert the specific overflow-check instruction sequence is present so we know the shadow-stack overflow guard was generated. Concretely, after generating/compiling src (the same code used by _run_trap), extract the WAT or disassembly for the function named "overflow" and assert it contains the expected overflow-check pattern (e.g. the shadow-stack load/compare/branch sequence your backend emits), then keep the runtime _run_trap() call to ensure the trap actually fires. Reference the test helper _run_trap and the function symbol overflow when locating where to add the WAT/disassembly assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@HISTORY.md`:
- Line 301: The HISTORY.md footer still reads "153 tagged releases" after adding
the v0.0.154 entry; update that roll-up from 153 to 154 and then update the
matching release count in README.md (the README release figure that displays the
total tagged releases) so both files remain synchronized with the new v0.0.154
entry.
In `@tests/test_codegen.py`:
- Around line 2169-2175: The test only checks that "return_call $build" is not
present but doesn't ensure the GC-restore preamble (e.g. injected local.get ...;
global.set $gc_sp) was rejected, so tighten the assertion by verifying the
preamble isn't present; update the test around the build function/body to also
assert that sequences like "local.get" followed by "global.set $gc_sp" (the
GC-restore preamble) do not appear in build_body, and keep the existing checks
for "call $build" and absence of "return_call $build" so the test ensures
postcondition-bearing functions cannot contain the GC-restore preamble or a
return_call.
- Around line 2097-2122: The test currently only checks that two lines before
each "return_call $build" is a "local.get ..." but doesn't verify it matches the
local saved by the prologue; update the assertion to capture the exact saved
local from the prologue (e.g. parse the prologue to find the "local.set" or
local index used to store the GC baseline, call it the $gc_sp_save local) and
require that the line two lines before each "return_call $build" equals that
exact "local.get <N>" string, while still asserting the immediate previous line
is "global.set $gc_sp"; use the existing variables build_body, prologue,
return_call $build, global.set $gc_sp and local.get to locate and compare the
exact saved local.
---
Outside diff comments:
In `@tests/test_codegen.py`:
- Around line 15089-15136: The test currently only calls _run_trap(src) which
only verifies that "something" trapped; update the test to also inspect the
emitted WAT for the function overflow and assert the specific overflow-check
instruction sequence is present so we know the shadow-stack overflow guard was
generated. Concretely, after generating/compiling src (the same code used by
_run_trap), extract the WAT or disassembly for the function named "overflow" and
assert it contains the expected overflow-check pattern (e.g. the shadow-stack
load/compare/branch sequence your backend emits), then keep the runtime
_run_trap() call to ensure the trap actually fires. Reference the test helper
_run_trap and the function symbol overflow when locating where to add the
WAT/disassembly assertion.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 2f2e1fea-10db-427b-8b62-5e4d08d1a865
⛔ Files ignored due to path filters (6)
docs/SKILL.mdis excluded by!docs/**docs/index.htmlis excluded by!docs/**docs/index.mdis excluded by!docs/**docs/llms-full.txtis excluded by!docs/**docs/llms.txtis excluded by!docs/**uv.lockis excluded by!**/*.lock,!uv.lock
📒 Files selected for processing (14)
CHANGELOG.mdHISTORY.mdKNOWN_ISSUES.mdREADME.mdROADMAP.mdSKILL.mdTESTING.mdpyproject.tomltests/test_codegen.pytests/test_stress.pyvera/__init__.pyvera/codegen/api.pyvera/codegen/functions.pyvera/wasm/calls.py
💤 Files with no reviewable changes (1)
- KNOWN_ISSUES.md
Surfaced during #549 review when the user pointed out that the compiler currently stamps WAT indentation onto instruction strings inline at ~14 emission sites scattered across vera/wasm/ and vera/codegen/. This is implicit-by-convention: any new emission site that doesn't know about it produces visually-misaligned WAT (as #549's GC-aware TCO post-process did initially. Vera source has vera fmt because consistent presentation matters for a debuggable surface. Same principle applies to compiler- emitted WAT — the primary surface agents read when debugging codegen bugs. Logged as #672 for a single-pass formatter that walks s-expression and control-flow depth and applies canonical indentation. Co-Authored-By: Claude <noreply@anthropic.invalid> EOF )
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@vera/codegen/functions.py`:
- Line 406: The code currently uses an `assert gc_sp_save is not None` guard
which can be stripped under `-O`; replace this assertion with an explicit
invariant failure by checking `if gc_sp_save is None:` and calling the
module/class `_error()` method with a descriptive message (e.g. "internal
compiler error: gc_sp_save is None") so the failure is always raised; update the
code paths around the `gc_sp_save` reference (the `gc_sp_save` variable usage in
functions.py) to use this explicit check instead of `assert`.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 68cd9def-74d0-42d9-8968-146833da7393
📒 Files selected for processing (2)
ROADMAP.mdvera/codegen/functions.py
1. **Ruff S101** (CI failure) — two `assert gc_sp_save is not None` in `vera/codegen/functions.py` flagged by the `Security lint (ruff S rules)` job. Both are pure mypy type-narrowing asserts (the variable is `int | None` and we're inside an `if ctx.needs_alloc:` block where it's guaranteed non-None). Added `# noqa: S101` to both sites, matching the existing project precedent in `vera/codegen/api.py` (five similar sites). 2. **Finding 1: HISTORY.md footer release count** (valid) — the table-footer total still read "153 tagged releases" after the v0.0.154 row landed. Bumped to 154 to match the README figure. 3. **Finding 2: postcondition test tightened** (valid) — the test only asserted `return_call $build` is absent; a future regression that mistakenly took BOTH the postcondition-revert and the GC-aware patch paths would slip through. Added a structural assertion that no `local.get N; global.set $gc_sp` pair precedes any plain `call $build` site, pinning the dispatch precedence (post_instrs revert and GC-aware patch are mutually exclusive, not additive). Cannot forbid the sequence outright because the GC epilogue at function-end legitimately contains it. 4. **Finding 3: GC-aware TCO test now matches exact prologue local** (valid) — previously the test only required `local.get <any>` two lines before `return_call`. Now we parse the GC prologue (`global.get $gc_sp; local.set <N>`) at function entry to capture the exact `$gc_sp_save` local index, then require the preamble at each return_call site uses *that same* local. A typo or off-by-one in the patch code that picked up an unrelated local would now fail. 5. **Finding 4: shadow_stack_overflow_traps WAT structural check** (partially valid, added) — `_run_trap` alone proves "something trapped" but not "the shadow-stack-overflow guard fired specifically." Added a structural assertion that the distinctive `global.get $gc_stack_limit` token appears in the `$overflow` function body, catching a hypothetical regression where the guard is silently dropped but `_run_trap` still passes via an unrelated trap class. TESTING.md test_codegen.py line count updated to reflect the new test additions (107 lines). Co-Authored-By: Claude <noreply@anthropic.invalid>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@TESTING.md`:
- Line 69: The TESTING.md stress-test counts are inconsistent: the header for
`test_stress.py` says "9 logical tests / 16 instances" but the stress section
still reads "8 initial test programs" and "14 parametrised instances"; update
the stress section text so all three mentions align with the header (i.e.,
change "8 initial test programs" to "9 initial test programs" and "14
parametrised instances" to "16 parametrised instances") and ensure the
descriptive phrase matches the header (e.g., "9 logical tests × eager-GC lane
parametrisation = 16 test instances" referencing `test_stress.py`).
In `@tests/test_codegen.py`:
- Around line 2086-2131: The WAT substring checks can false-match
similarly-prefixed symbols; replace plain string searches in the build-body
extraction and in return/call-site checks with boundary-safe regex searches (use
re.search with word-boundary patterns like r'\b\(func \$build\b',
r'\breturn_call \$build\b', and r'\bcall \$build\b') where
build_start/build_end, build_body, and return_call_indices are computed, and
apply the same replacement to the analogous checks later in the file (the
call-site assertions around the other return_call/call checks).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: fd8f8adc-bf2d-4044-acb2-e5ea5c482a8f
📒 Files selected for processing (4)
HISTORY.mdTESTING.mdtests/test_codegen.pyvera/codegen/functions.py
**Finding 1: TESTING.md stress count inconsistency** (valid) — header table said `test_stress.py` has 16 instances but the detailed section still read "8 initial test programs" and "14 parametrised instances". Fixed cohesively: - `### The 8 initial test programs` → `### The 9 initial test programs` so the section claim matches the header. - Added entry **9. `test_tco_with_allocation_1m_iterations`** so the section actually enumerates what it claims to. - Updated test #3's stale narrative — this PR switched its body from `let @string = "stress"` (which doesn't trigger needs_alloc because string-pool literals don't allocate) to `let @array<Int> = [_, _]` (genuine heap alloc). Old text asserted == 6,000 from `string_length("stress")`; new text asserts == 2,000 from `array_length([_, _])`. Without this update the doc described a test that no longer exists. - "Six of the eight" → "Seven of the nine" in the eager-GC lane paragraph (math now adds up to 16 with the 9th test). - CLI examples and Budget line updated from 14/8 to 16/9. **Finding 2: boundary-safe regex in WAT searches** (valid) — the three #549 tests used plain `.find("(func $build")` and `"return_call $build" in body` substring matches. Today's test source defines only `$build` and `$f` so there's no false-match risk, but a future symbol like `$build_helper` would match falsely. Switched all three tests to `re.search` with boundary-safe patterns: - `re.search(r"\(func \$build\b", ...)` for function-start. - `re.search(r"return_call \$build\b", ...)` for return_call sites (trailing `\b` excludes `$build_anything`). - `re.search(r"\bcall \$build\b", ...)` for plain call sites (leading `\b` rules out `return_call $build`; trailing `\b` rules out `$build_anything`). - Same boundary-safe extraction applied to `$overflow` in `test_shadow_stack_overflow_traps`. Note on CR's literal suggested pattern `r'\b\(func \$build\b'`: the leading `\b\(` is incorrect — `\b` requires a word-char on one side, but `(` is preceded by whitespace or newline (both non-word) at WAT indentation depths, so that anchor wouldn't match. Dropped the leading anchor; kept the substantive trailing `\b`. Scope decline: did NOT update the pre-existing tests in the file that use the same substring-search pattern (the #517 tests on lines 1899/1934/1949/1968/2016/2027 etc.). Those existed before this PR and changing them is out of scope for #549. CR's relayed wording said "apply the same replacement to the analogous checks later in the file" — interpreted as the analogous checks in MY new/modified tests, not the entire pre- existing test suite. Validation: pytest passes (3862/3862), mypy clean, ruff S rules clean, doc-counts consistent (test_codegen.py grew by +39 lines from the regex switchover; TESTING.md updated to match). Co-Authored-By: Claude <noreply@anthropic.invalid>
Comprehensive PR review (code-reviewer + pr-test-analyzer + comment-analyzer) surfaced 5 actionable items. All fixed here: **Inaccurate comments** (comment-analyzer) 1. `tests/test_stress.py` — `test_tco_with_allocation_1m_iterations` docstring claimed "~12 bytes per leaked frame ≈ 1300 iters". `gc_shadow_push` writes one i32 = 4 bytes, not 12 (the 12-byte figure came from a different fn elsewhere in the file with 2 pointer params + 1 tmp root). The `loop` fn here only pushes 1 pointer per iteration. Corrected to "4 bytes per leaked pointer root ≈ 4,096 iterations" and added the inline note that the @int params aren't pointer-typed. Also dropped the "~200ms on a 2026 MacBook Pro" claim (will rot). 2. `vera/codegen/functions.py` — comment cited "operators.py" unqualified, but `vera/codegen/` and `vera/wasm/` are peer directories — should be `vera/wasm/operators.py`. This matters because #672 explicitly audits the inline-indentation surface; a mis-pathed reference would cost search time. Added a #672 cross-reference too. 3. `vera/codegen/functions.py` dispatch comment said "Two sources of post-body work" but the actual code is a three-branch dispatch (revert / patch / untouched). Reframed as "Three outcomes (precedence: 1 > 2 > 3)" with the third explicitly named — the common non-allocating tail-recursion case from SKILL.md's "Iteration" section. Also trimmed the `alloc_local` implementation-detail digression that didn't add value. 4. `tests/test_codegen.py` — `test_allocating_function_uses_gc_ aware_tco_549` docstring said "sacrificing TCO for shadow- stack correctness". Inverted framing — pre-#549 the GC epilogue ran fine, what was sacrificed was WASM call-stack depth (1M iters trapped with `call stack exhausted` because the function paid a WASM frame per recursion). Reframed. **Test coverage gap** (pr-test-analyzer rating 8) 5. Added `test_allocating_function_gc_aware_tco_patches_both_ branches`. The patch loop iterates every body instruction and patches every `return_call` site individually; a buggy implementation that bails after the first match (e.g. an accidental `break`) would still pass the single-site test above. New test uses a `match` with two ADT arms each ending in `return_call $build`, asserts BOTH sites have the `local.get N; global.set $gc_sp` preamble, and that N matches the same `$gc_sp_save` local captured by the prologue. **Suggestion** (pr-test-analyzer rating 6) 6. `test_shadow_stack_overflow_traps` — added inline calibration for the 2000-iteration count: "16K shadow stack / 12 bytes per frame (2 Array<Bool> params + 1 tmp root) ≈ 1,365 frames; 2000 chosen with ~1.5× safety margin". Previously the threshold was undocumented; a future per-frame size change could silently weaken the test without anyone noticing. **Not addressed**: - pr-test-analyzer's mutual-recursion test suggestion (rating 8) — the dispatch is per-function and each function's correctness is independently pinned. Cross-function GC composition is a deeper concern that doesn't sit in this PR's scope and would benefit from a separate issue. - pr-test-analyzer's zero-`return_call`-site test (rating 6) — edge case is fine; allocating leaf fns just have an unused `$gc_sp_save` local, no harm. - api.py inner-helper workaround mention in `stack_exhausted` message — too tangential for an end-user-facing trap message. **code-reviewer verdict**: no issues found at confidence ≥ 80. The fix is structurally correct, the dispatch precedence is explicitly pinned by tests, and CodeRabbit's prior concerns (S101 noqa, indent alignment, boundary-safe regex) are all addressed. Validation: pytest 3863/3863 ✓, mypy clean ✓, ruff S rules clean ✓, doc-counts consistent (test counts updated to 3,893). Co-Authored-By: Claude <noreply@anthropic.invalid>
PR self-review summaryRan the code-reviewer — clean
Verified independently:
pr-test-analyzer — one important gap, one suggestionImportant — no multi-branch Fixed in Suggestion — undocumented 2000-iteration figure in Declined:
comment-analyzer — four factual fixesAll four flagged in the comment-analyzer report and fixed in
Declined:
ValidationAfter
Commit trail on this PR
|
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
Summary
return_call→call" fallback with a GC-aware patch. The post-process invera/codegen/functions.py::_compile_fnnow PREPENDSlocal.get $gc_sp_save; global.set $gc_spimmediately before eachreturn_callsite in an allocating fn, restoring the shadow-stack pointer to the caller's entry baseline so the callee saves a clean new baseline. Per-iteration shadow-stack usage stays bounded atcaller's entry + n_arg_rootsregardless of iteration count.return_callsite the args are already on the WASM operand stack; the two-instruction restore touches only the$gc_spglobal, not the operand stack, so the patch is non-interfering —return_callstill consumes args and atomically transfers control.return_call→ plaincall(the post-check needs to run after each call;return_callwould skip it). Dispatch precedence:post_instrsrevert >needs_allocGC-aware patch > untouched.KNOWN_ISSUES.mdbug-tracker section apart from Conservative GC scan can spuriously retain heap objects via wrapper handle field (#573 latent) #578 — the bug list will be empty once Conservative GC scan can spuriously retain heap objects via wrapper handle field (#573 latent) #578 lands.Test plan
pytest tests/— 3,862 passed, 14 skipped, 16 deselectedmypy vera/— cleanpython scripts/check_conformance.py— 86/86python scripts/check_examples.py— 34/34python scripts/check_doc_counts.py— consistentpython scripts/check_version_sync.py— 0.0.154 across 6 filespython scripts/check_site_assets.py— up-to-datepython scripts/check_limitations_sync.py— consistentpytest tests/test_stress.py::test_tco_with_allocation_1m_iterations -m stress) — 1M iterations under both default and eager GC in ~190ms eachBehavioural impact
Pre-#549, an allocating tail-recursive function like:
trapped with
stack_exhaustedat ~30K iterations (the WASM call-stack limit) because the post-process revertedreturn_call→ plaincall. Post-#549 the same function runs in constant stack space at 1M+ iterations — the per-iteration$gc_sprestore keeps the shadow stack flat across the entire run.Test changes
tests/test_codegen.py::TestTailCallOptimization517— renamedtest_allocating_function_falls_back_to_plain_calltotest_allocating_function_uses_gc_aware_tco_549and inverted its assertions: it now verifiesreturn_call $foois emitted AND every such site is preceded bylocal.get <N>; global.set $gc_sp. Added a sibling testtest_allocating_function_with_postcondition_still_revertsto pin the postcondition-revert precedence.tests/test_codegen.py::TestGCShadowStackOverflow::test_shadow_stack_overflow_traps— rewritten to use a non-tail-recursive shape (recursive call wrapped inarray_append) so the overflow guard still genuinely trips post-GC-aware tail-call optimization for allocating functions #549.tests/test_stress.py::test_deep_tail_recursion_with_allocating_arg— body switched from string-pool literals (didn't actually setneeds_alloc) to genuine array allocation, so the test now actually exercises GC-aware tail-call optimization for allocating functions #549's path.tests/test_stress.py::test_tco_with_allocation_1m_iterations— new 1M-iteration companion parametrised over default-GC and eager-GC modes (~190ms wall-clock in both).Doc sweep
KNOWN_ISSUES.md— removed the GC-aware tail-call optimization for allocating functions #549 bug row.SKILL.md(+ regenerateddocs/SKILL.md) — removed the "tail-call optimization disabled for allocating functions" row from the bug table; updated the narrative paragraph below it to describe both Tail-call optimization missing: deep recursion blows the WASM call stack #517 and GC-aware tail-call optimization for allocating functions #549 as live optimisations.vera/wasm/calls.py— comment near_translate_callupdated to describe both branches of the post-process (GC-aware patch vs postcondition revert).vera/codegen/api.py— thestack_exhaustedruntime trap-fix message updated: drops the "allocating functions are an exception" paragraph, adds a brief mention of the postcondition-revert as the remaining exception.pyproject.toml,vera/__init__.py,uv.lock,docs/index.html,README.md.CHANGELOG.mdv0.0.154 entry added with the design rationale, plusHISTORY.mdrow.TESTING.md,ROADMAP.md,README.md— test counts updated to 3,892 (was 3,889).🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Tests
Documentation