Skip to content

-mm=own: a let given a new value after its value moved owns the new one (#528) - #530

Merged
ASDAlexander77 merged 2 commits into
mainfrom
fix-own-reassign-after-move
Oct 6, 2026
Merged

ASDAlexander77 merged 2 commits into
mainfrom
fix-own-reassign-after-move

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Closes #528.

Problem

let b = a; a = new C() and h.c = a; a = new C() were rejected under -mm=own. Phase 1 deliberately counted the assignment as a use of a after the move. rc's assignment is ReleaseSlot(a); Store(new, a), and after a move that release would free the value the receiver now owns. Rust accepts both shapes, and rc and gc print 1 2.

Fix (OwnershipInferencePass.cpp)

  • checkSlotMove: an assignment the move reaches (a ts.ReleaseSlot right before a ts.Store to the same slot) is a new value, like the slot's declaration. It is now a kill for reachability. As a result:
    • uses reached only through it are not uses after the move;
    • its own release is erased along with the move;
    • a scope-exit release reached only through it stays and destroys the new value.
  • Some paths: a release the move reaches both holding the moved value and holding a newer one is reported as moved here on some paths only.
  • decideSlot: receivers of one slot that each read a value of their own (an assignment between their reads on every path) are decided one at a time. So h.c = a; a = new C(); g.c = a is two moves.
  • readUsedAfter: a path that goes back through the read itself now uses a value read again. This is what lets keep.push(a); a = new C() work inside a loop.

After the fix, let b = a; a = new C(); print(b.value, a.value) becomes: the move erases b's RetainSlot and the assignment's ReleaseSlot, and keeps both scope-exit releases. Each object is freed exactly once.

Tests

  • New: own_move_reassign, covering:

    • a value moved into a let, then a new value;
    • a value moved into a field, then a new value;
    • two values moved in turn, then a third;
    • a move plus a new value on every loop iteration.

    It runs under own (JIT, AOT, no-counting) and under rc and gc. It isn't run under none: the loops would hold about 800 MB with nothing freed.

  • New: own_err_reassign_some_paths: a new value on one path only, so the scope-exit release has no single answer.

  • Changed: own_err_assign_after_move. Its old shape now compiles, so it becomes a read after a new value given on one path only. The borrow error is unchanged.

  • Changed: own_err_borrow_{bound_method,catch_local,finally_local}. These used let b = a; … a = new C() to force a borrow, and that is now a valid move: the old versions compile and print the right values. Each now reads a once after let b = a, so b stays a borrow and the intended error still fires at the same use.

  • Spec: new section 24.

Verification

  • Full ctest, Release, Windows: 3912 of 3912 passed. One test is disabled and the Android link test is skipped, as on main.
  • Corpus (tests/*.ts under -mm=own): 467 of 628 compile, the same set with and without the fix.
  • Peak memory of own_move_reassign: own 4.4 MB, rc 4.5 MB.
  • Quarantine heap (temporary ProcessHeap.h: blocks never reused, freed memory poisoned, abort on double free). With the fix, every shape above and both issue repros run clean under JIT and AOT. With a temporary switch that keeps the assignment's release, all of them double free or read freed memory.
  • Linux: not built locally; CI covers it.

Not changed

The issue also notes that the errors say 'this value'. That is by design: variable names come from --di locations, as test-own-err-names-the-variable pins. Without --di the pass has no name to print.

🤖 Generated with Claude Code

ASDAlexander77 and others added 2 commits October 6, 2026 19:50
…ne (#528)

`let b = a; a = new C()` and `h.c = a; a = new C()` were rejected: the
assignment was a use of `a` after the move, because rc's assignment
releases what the slot holds before storing - the moved value, which the
receiver owns. checkSlotMove now treats an assignment the move reaches
(ts.ReleaseSlot right before ts.Store of the slot) as a new value, as it
treats the declaration: uses and releases reached only through one are
the new value's. The assignment's own release goes with the move; the
releases after it stay and destroy the new value. A release reached
both holding the moved value and a newer one is "moved on some paths".

Receivers of one slot that each read a value of their own (an
assignment between every two of their reads) are decided one at a time,
so `h.c = a; a = new C(); g.c = a` moves twice. readUsedAfter also kills
at the read itself: around a loop, a path back through the read uses a
value read again.

own_move_reassign runs relay/field/twice/loop shapes under every model.
own_err_assign_after_move (the old shape now compiles) becomes a read
after an assignment on one path only; own_err_reassign_some_paths pins
the new release check. own_err_borrow_{bound_method,catch_local,
finally_local} used `let b = a; ... a = new C()` to make `b` borrow; that
is now a move, so each reads `a` once after `let b = a` to keep `b` a
borrow. Corpus under own unchanged (467/628 either way).

Closes #528

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under -mm=none nothing is freed and the test's loops hold about 800 MB,
too much for a CI run; rc and gc show the program means the same. The
spec gets section 24: an assignment after a move is a new value, the
"some paths" release rule, receivers decided per value, loops.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit b617c71 into main Oct 6, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the fix-own-reassign-after-move branch October 6, 2026 20:09
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.

-mm=own: a let assigned a new value after its value moved is still treated as moved

1 participant