Repository navigation
-mm=own, phase 1: moves decided by reachability - #401
Merged
Merged
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 0 let a fresh value move only into one taker in its own block with no use after. Now a taker may be in any block: every other use must be unreachable from it, a taker that control returns to without the value being made again is a move inside a loop (spec 2.6), and each release the move reaches must be dominated by it and is erased. A release the move cannot reach stays, the owner on the paths that did not move; one it reaches but does not dominate is "moved here on some paths only". Diagnostics follow spec 5: "is used here after its value was moved" with a note at the move. Four phase-0 negatives now report that instead of "takes a second reference". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rc reads an owning local with ts.Load (through casts that keep the block) and retains what it read for the receiver. That retain is now a move out of the slot: it is erased with every ReleaseSlot of the slot the move reaches, which must be dominated by it. Any other use of the slot the move reaches - a read, an assignment - is a use after the move, and a move whose own read comes round again without the slot being declared again is a move inside a loop. Also: a cast of null or undefined is a literal (`c: C | null = null` was rejected), and under --di the diagnostics name the variable, read from MLIRGen's NameLoc. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spec section 12: the rule as built, 1M-iteration numbers (own 4.8 MB like rc, none 547), the teeth run, the negative tests whose text changed, and phase 2's inputs. own_move_loop_body passed with the moved releases kept - it read the value before either release and allocated nothing over it. It now keeps every value past the loop and reads them after churn(), and fails without the move. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…literals `const b = a` for a `let a` is folded into a second ts.Load of a's slot, and the slot move only looked at the moving load's users, so `const b = a; let c = a; <c destroyed>; b.x` compiled and read freed memory. Every use of every read of the slot now counts as a use of `a` (own_err_const_alias_move, own_err_const_alias_field). Under --opt, and so under the JIT, CSE merges identical literal casts before inference, and a merged `0` was treated as a fresh heap value moved on every loop iteration. Only a value whose type owns heap memory is a candidate now, and a string literal - the immortal global - may be held by any number of places (own_literal_reuse). own_move_control_flow covers break/continue out of a moving loop body and returns from inside a loop; it fails with the moved releases kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Phase 1 of
-mm=own(spec §2.2 verdict 1, §2.6, §4.2 step 3, §5). The plan istslang/docs/superpowers/plans/2026-09-29-own-phase-1.mdand the results are in spec §12. OnlyOwnershipInferencePass.cppchanged. MLIRGen and the lowering are untouched, and gc, rc and none are unaffected.What changes
Phase 0 allowed a move only into one owner, in the value's own block, with no later use. Phase 1 decides a move by reachability and dominance on the affine CFG:
const a = new C(); if (x) { let b = a; return ...; } use(a).let: into anotherlet, a field (h.c = a), or a return. The move is measured from rc's retain, because at this level areturnis a store into the return slot that comes after the source's scope-exit release.const b = a(reported as'a' is used here after its value was moved).letdeclared in the loop body is fine.c: C | null = nullnow compiles (a cast of null or undefined is a literal).--dierrors name the variable.--optand JIT, CSE had merged literals, which gave false loop errors.Tests
own_move_const_branch,own_move_let,own_move_field,own_move_return,own_move_loop_body,own_move_control_flow,own_literal_reuse. Each runs under JIT and AOT, with achurn()before its reads, plus the no-counting check.own_err_move_some_paths,own_err_assign_after_move,own_err_let_twice,own_err_let_loop,own_err_const_alias_move,own_err_const_alias_field, and a--dinaming test.own_err_aliasgained a use after its move, because phase 1 accepts a barelet b = a.Verification
DictionaryAttrassert, which fails on main too.owned-asyncwas excluded; its Debug async-runtime assert also fails on main.let→let, →field and →return, at both-O3and-O1.constalias of a movedletread freed memory) and two Important findings (CSE-merged literals; test gaps). All three are fixed in the last commit, each with a test that failed first.Still rejected (spec §12.5, input to phase 2)
let;const b = a; a = new C(); b.xisn't caught: an assignment isn't a move. rc has the same bug today and prints garbage.🤖 Generated with Claude Code