Repository navigation
-mm=own, phase 0: single ownership inferred at compile time, no counting - #399
Merged
Merged
Conversation
A fourth memory model beside gc, rc and none: ownership inferred at compile time over the ownership ops MLIRGen already emits, moves and borrows instead of counts, and a compile error where neither can be proven. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Six amendments in a new section 10: a lowering error does not fail the compile today (prerequisite for every model); the phase-0 gate is a pass, not the lowering; the default library is refused under own; the header word keeps its store on native targets; expected-error tests reuse the call-arity ctest pattern; and almost every retain is a birth reference, so a gate rejecting every retain compiles only heap-free programs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 0 accepts a fresh value's single acquisition - rc's count provably never above one - as a move, and reports every other retain. Seven tasks: the lowering-error prerequisite (PR #398), the model and flag, the isRefCounted() audit site by site, destroy-shaped lowering with a retain backstop, the inference pass, positive/negative/no-counting tests, and a rebuilt measuring script. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every positive and negative program was dumped under -mm=rc on #397 and the plan now states what each produces: make() returns +1 with no retain on either side; a throw out of a try-less function leaks its local in rc too; the alias test needs `let` (a folded const emits no retain); the unknown producer is a declared function (.slice needs the default library). Also the verified call-op names, the --di-only variable name, number-typed accumulators, and the isHandOver move spelled out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sixteen of the 21 sites write something a release will read, or emit the ownership ops the inference pass judges, and become tracksOwnership(). Three retain lowerings and the captured-parameter retain are Task 4's; a cell's birth retain stays rc only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tain is an error Under -mm=own emitIfLastReference tests the header against HEAP_BLOCK_IMMORTAL instead of decrementing it, so a release is one load and a compare. A retain that reaches LLVM lowering is an error, never erased; a captured variable holding a heap value is an error (closures are phase 5); and a type descriptor carries no retain routine, since retainViaDescriptor is reached only from retain routines. emitIncRef's other callers (emitRetainCell, buildCaptureBoxRoutine(retaining), buildRetainBody) are all retain paths. --own-skip-inference (hidden) lets the backstop be tested. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s a move; every other retain is an error Under -mm=own, at the affine level before the verifier: a heap value made by an allocating op (or marked __owned_result) whose births plus retains total one has exactly one owner, so its retains are erased and the release rc already emits becomes the owner's destroy. Any other retain, a RetainCell, a RetainSlot on a hoisted (initializer-less) variable, and a slot released without ever being owned are reported. isHandOver moves to OwnershipOps.h, shared with the ownership verifier. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e source Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Six programs run under JIT and ahead of time with -mm=own, each checked to emit neither __tslang_inc_ref nor __tslang_dec_ref; five are rejected with the expected error. A literal acquired once is now a birth take too: `let s = "abc"` and `v: number[] = []` were rejected, and a cast constant is either the immortal global (its release skips it) or a copy one owner holds. The "released but never owned" rule is removed: the ownership verifier's stricter form of it is green over the corpus, so no program reaches it, and isHandOver goes back into the verifier. Teeth: with every retain kept, all 15 positive checks fail at the backstop. With the count check loosened to two, own_err_two_owners.ts compiles and segfaults under own (rc runs it: 500000, done.) - the count is what stands between a second owner and a double free. Debug tree: every own test passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The AOT measuring script, rebuilt: it links the way the test runner does (read from the generated compile.bat) and reads the peak working set from the process handle after exit - sampling while it runs read 0.0 MB for programs that finish in milliseconds. Spec section 11 records the results: own holds flat exactly where rc does (4.2 MB for 1M strings, none 80.9 MB), the teeth, and phase 0's known limits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ug build too The array-to-string cast hits llvm_unreachable (CastLogicHelper.h:856) in a Debug build before it reports anything, so the message test failed there. An array literal lowering cannot build as number[] | null reports its error the same way in Release and Debug. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… tests for various ownership violations
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.
What this adds
-mm=ownis a fourth memory model besidegc,rcandnone. Each heap block has exactly one owner, inferred at compile time, and there is no reference counting at run time. If a program's ownership can't be proven, it is a compile error at the source location. The design is indocs/superpowers/specs/2026-09-24-own-memory-model-design.md, and phase-0 results are in §11 of that file. The plan is indocs/superpowers/plans/2026-09-28-own-phase-0.md.Phase 0 (this PR):
MemoryModelOwn, the-mm=ownflag, the test-runner variant, andtracksOwnership()next toisRefCounted(). All 21isRefCounted()sites were audited; MLIRGen emits exactly what it emits under rc.__tslang_inc_refor__tslang_dec_refis ever referenced. Type descriptors and interface tags carry no retain routine. Any retain that reaches LLVM lowering is an error, never silently erased.OwnershipInferencePass, at the affine level, runs under own only. It counts the uses that take a value: an owning declaration, a store, an array insertion, a return, or any use not on a short borrow list. It also counts the temporary when rc releases it.delete, closures, and interface oranyboxing.--verify-ownershipruns before inference, so it checks rc's pairing on the IR as MLIRGen produced it.Measured
test/tester/tools/measure.ps1builds each program ahead of time at-O3and reads its peak working set:Tests
let, twolets of onenew, anddelete.main: LLVM asserts in the throw-f32, cast-to-f32, throw-bool-bigint and linkname tests; hangs in two async JIT tests (00async-await,00async-gc-threading); and output mismatches.Also in this PR
llvm_unreachableatCastLogicHelper.h:856in a Debug build before reporting anything. The test now uses an input that reports the same error in both builds.Known limits (input to phase 1)
These are rejected in phase 0:
anyboxing;delete;trybody;The review also found latent over-releases in rc itself, reachable through consumption: push inside a loop, two
lets of onenew, and use of a foldedconstafter an innerlet. They are not fixed here.🤖 Generated with Claude Code