Skip to content

Fix generators losing state across manual .next() calls#242

Merged
ASDAlexander77 merged 1 commit into
mainfrom
adv_tests2
Jul 17, 2026
Merged

Fix generators losing state across manual .next() calls#242
ASDAlexander77 merged 1 commit into
mainfrom
adv_tests2

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Summary

  • const g = gen(); g.next() (manual iteration on a real function* generator, as opposed to for...of) never advanced generator state — g is a storage-less const binding, so each g.next() property access re-materialized a temp alloca seeded from the pristine, unmutated original on every call site.
  • Fix: a ScopedHashTable<mlir::Value, mlir::Value> cache on MLIRGenImpl, keyed by the accessed object's SSA identity, lets repeated bound-ref accesses reuse the ref materialized on the first access. Scoped at function entry (mirroring symbolTable's own RAII scoping) since nested closures re-enter function-body codegen recursively.
  • Two correctness gates were needed after the initial fix surfaced real regressions on the full suite:
    • Only cache during real codegen passes (not the discovery/type-inference dummy-run pass, whose ops get erased afterward — caching those pointers could hand back dangling/recycled values to a later real pass).
    • Only reuse a cached ref within the same MLIR block as the access site (a ref from a nested block doesn't dominate a reuse site outside it).
  • A previous, broader fix attempt (forcing every bound-method-bearing const into real storage at declaration time) was reverted for breaking interface/symbol value-passing; this fix is narrower and only affects call sites that already needed an address.
  • Moves docs/bugs/00generator_manual_next.ts into test/tester/tests/ as a real, passing regression test.

Test plan

  • Full test suite: 702/702 passing (ctest -C Debug)
  • Verified manual .next() iteration correctly advances generator state across 5 calls
  • Verified no regression in the previously-broken interface/symbol/disposable tests from the earlier fix attempt

🤖 Generated with Claude Code

`const g = gen(); g.next()` (manual iteration on a real function* generator,
as opposed to for...of) never advanced generator state: `g` is a storage-less
const binding, so each `g.next()` property access fell into
MLIRPropertyAccessCodeLogic's bound-ref fallback, which allocated a brand-new
temp alloca seeded from the pristine, unmutated original on every call site.
for...of avoided this because its lowering materializes one alloca up front
and reuses it.

Fix: a ScopedHashTable<mlir::Value, mlir::Value> cache on MLIRGenImpl, keyed
by the accessed object's SSA identity, lets repeated bound-ref accesses reuse
the ref materialized on the first access. Scoped (not just cleared) at
function entry via BoundRefCacheScopeT, mirroring symbolTable's own RAII
scoping, since nested closures re-enter mlirGenFunctionBody recursively.

Two follow-up fixes were needed after the initial full-suite run surfaced
real regressions:
- Gate the cache to real codegen passes only (!dummyRun && !allowPartialResolve).
  The discovery/type-inference pass generates then erases throwaway ops, and
  caching pointers into those ops let a later real pass read back a dangling
  or address-recycled mlir::Value, producing "null operand found" errors in
  unrelated functions.
- Restrict cache hits to the same MLIR block as the access site. A ref
  materialized inside a nested `{ }` block doesn't dominate a reuse site
  outside it ("operand does not dominate this use"). A precise fix would use
  MLIR's DominanceInfo (not used anywhere else in this codebase); same-block
  is a conservative, cheap approximation that's never wrong, just sometimes
  conservative.

A previous, broader fix attempt (forcing every bound-method-bearing const
into real storage at declaration time) was reverted for breaking
interface/symbol value-passing; this fix is narrower; it only affects call
sites that already needed an address.

Adds 00generator_manual_next.ts (moved from docs/bugs/, now a real passing
regression test). Full suite: 702/702 passing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit 2ab986f into main Jul 17, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the adv_tests2 branch July 17, 2026 19:27
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