Skip to content

Stack promotion: loop exits, direct closure calls, try under gc/none - #606

Merged
ASDAlexander77 merged 1 commit into
mainfrom
stack-promotion-refinements
Oct 18, 2026
Merged

ASDAlexander77 merged 1 commit into
mainfrom
stack-promotion-refinements

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

These are the reviewer's deferred findings on stack promotion version 1 (#605).

  1. Loop exits. A use outside the loop now counts as loop-carried only when the site can run again after it. That is CFG reachability from the use's block, and unwind edges count too. A return p.s or a found = p.s; break in a search loop now promotes. A slot declared outside the loop (loop_carried.ts) still stays on the heap. Test: loop_exit_use.
  2. Direct closure call. const g = () => ...; g() lowers to ts.SymbolCallInternal with the box as operand 0. decideBox now classifies that use through the callee's parameter fact. Test: closure_direct_call (gc/none → stack).
  3. ReleaseContents. emitReleaseContents now nulls each field it releases, so a later release finds nothing. At a return those stores are dead and LLVM drops them. Test: test-stack-release-contents-nulls-{rc,own}, which greps the lowered IR for the null store after the release call.
  4. Caches and recursion. The pass caches are cleared at the start of each run. Recursion now comes from one iterative Tarjan SCC pass over the same edges as before. A function that calls itself counts as recursive. Guard test: recursive_mutual. It passed before this change too; the change keeps the same answers.
  5. Nested-closure invariant. There is now a comment plus checks in decideBox: a box field must not hold a capture box, and every closure made from the box must keep its this, checked through the parameter fact. No source program can produce the violating shape, so no test can fail first. closure_nested covers the case where the invariant holds.
  6. Try under gc/none. A function with a try region is refused only under rc and own now. Tests: try_region (gc/none → stack, rc/own → heap: try-region), plus try_catch_reads, which reads a stack object in a catch (a funclet on Windows) and continues a loop from it.

The decision tests for items 1, 2, 3 and 6 failed on the old binary before the fix. All new files are also run tests under every model, JIT and AOT. The box itself is now also checked by the loop rule, which keeps the decision on the safe side. The user docs (docs/memory-models.md) and spec section 5.5 are updated.

Full ctest: 4652/4652 passed. With -DTSLANG_TEST_NO_STACK_PROMOTION=ON: 4652/4652 passed.

🤖 Generated with Claude Code

- Loop rule: a use outside the loop is loop-carried only when the site
  can run again after it (CFG reachability, unwind edges included), so a
  `return p.s` or `found = p.s; break` in a search loop promotes.
- A closure called by name (ts.SymbolCallInternal with the box as its
  `this`) is classified through the callee's parameter fact.
- The nested-closure invariant is checked: a box field never holds a
  capture box, and every closure made from the box keeps its `this`.
- Functions with a try region promote under gc and none.
- ts.ReleaseContents nulls each field it gives back.
- Pass caches cleared per run; recursion from one SCC pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit d63a7ea into main Oct 18, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the stack-promotion-refinements branch October 18, 2026 19:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant