Repository navigation
A Shared global lent to a call that assigns it: the caller holds a count - #643
Merged
Merged
Conversation
`run(bare)`, with `bare` a `Shared<T>` global and `run` giving `bare` a new value, freed the block the parameter still read, under rc and own alike: #639's hold of a global argument across a call left handles out, since a held count shows in `Shared.count`. A handle is now held, under rc and own, across a call that may assign the global it was read from - directly, in a callee, or through a call that names no function - and only there, so `Shared.count` elsewhere is still the number of handles (spec 23.1); no existing count changes. Under rc a handle read out of a global's field is held the same way, and so is a value read through a global handle (`run(G.value)`); under own a read out of a field stays a borrow, rejected as before. The comment about calls in a try body is corrected: this pass runs before the affine lowering makes them `ts.Invoke`, so they were already held on the normal path. A throw out of such a call skips the release (one count leaked), which is left open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`run(G.value)`, with `run` doing `G.value = mk(..)`, failed on main under rc and is held by this branch; the test now pins it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ASDAlexander77
added a commit
that referenced
this pull request
Oct 22, 2026
holdGlobalArguments (#639, #643) retains a global read given to a call that may overwrite it and releases it after the call. A throw out of the call skipped the release: a try around the call caught the exception, and the global's block kept a count nobody gave back - under rc for any held value, under own and rc for a Shared<T> handle, where `Shared.count` showed it. When a try in the same function takes the call's exceptions, the held value now also goes into a slot made at the function's entry, null outside the call, and the handler that runs first (the try's cleanup, else its catch clause, else its finally; the finally for a call in a catch clause - the landing pad's own order) begins with a release of that slot. An exception that leaves the function still leaks the count, as it does for locals. 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.
Follow-up to #639, covering the two cases it left out of
holdGlobalArguments.Shared handles: fixed
run(bare), wherebareis aShared<T>global andrungivesbarea new value, freed the block the parameter still read. This happened under rc and own alike: own counts handles with rc's routines, and its #639 rule (rejectGlobalArgumentsDropped) skips handles.Holding every handle argument would change what
Shared.countreports inside the callee. Spec 23.1 defines that count as the number of handles, and a parameter is a borrow, not a handle. So the hold is narrowed instead:s.value) counts as assigning it.Shared.countexpectation changes: everyown_shared_*test passes unmodified. Inside a call that does assign the global, the hold is the count that keeps the block alive once the global has moved on. The new test asserts it is then the only count.run(G.value)) is now also recognised as global-rooted and held, like a field read.Calls in a try body (
ts.Invoke): did not reproduce as describedThis pass runs before the affine lowering, and it is the affine lowering that turns a call inside a try body into a
ts.Invoke. So those calls were already held:Retainbefore the call, and theReleaseends up first in the Invoke's normal successor (checked in--emit=mlir-affine). The remaining gap is the unwind edge: when the callee throws, that one count leaks. It can't be observed for a plain class, and--verify-ownershipdoes not see unwind edges. Fixing it needs a per-call release on the exception path, which is a new mechanism. It is left open, and the comment that said try-body calls were skipped is corrected.Tests
own/own_shared_global_argument.ts, in theshared_modelsforeach (gc/none/rc/own) and the rc/own--verify-ownershiplist. It covers a direct assignment, an assignment through a callee, a call in a try body, and checks that a call which doesn't assign takes no count. On main it fails under rc and own.tests/00shared_global_field_argument.ts, in the default lists andTSLANG_CORPUS(rc + none): a handle read out of a global's field. On main it fails under rc.test-compile-rc-own_shared_global_argument,test-compile-own-own_shared_global_argumentandtest-compile-rc-corpus-00shared-global-field-argument(and their jit twins). Without the switch they pass.00shared_global_field_argument.tsalso pinsrun(G.value): a value read through a global handle, given to a call that replaces it (fails on main under rc).ctest -C Release: 4930/4930. WSL Linux full run: 4912/4912 (before the G.value case was added; that test-only commit was re-run on Windows only).🤖 Generated with Claude Code