-mm=rc: a parameter the body assigns owns what it is assigned - #515
Merged
Merged
Conversation
A parameter's slot is borrowed from the caller and takes no reference,
so assigning to it neither retained nor released. `x = new B(1)` stored
an instance the block that made it then released, and `x` pointed at
freed memory from there on. In `while (x instanceof A) { x = new B(1) }`
the next condition read it and crashed (#512). After an `if` or a loop,
the parameter read whatever had since been allocated over it: an
optional parameter, a method's, a string built in a loop all came back
as garbage. Only -mm=rc: a collector needs none of this, and -mm=own
refuses the assignment.
Before the body is generated, mlirGenFunctionOwnAssignedParams scans it
for assignments to each parameter that holds a heap reference: plain
and compound assignments, destructuring targets, and `for (x of ...)` /
`for (x in ...)`. Nested functions are included, since naming one too
many costs only a retain and a release. Each one found is copied into a
local of the same name. A local owns what it holds
(takeOwnershipOfLocal): a reference is taken on the caller's value, each
assignment hands the count over, and the function's exit gives it back.
The copy is listed with the function's own scope, after the parameter
cells.
00param_assigned_owned.ts: the instanceof loops from #512 (while and
for), assignment in an if, in a loop, in a block a closure captured
from, and inside a closure; optional, defaulted and `??=` parameters;
returning the assigned parameter; destructuring and for...of into it; a
method, an arrow function, a generator, an async function, a rest
parameter, a string, and an unassigned parameter. Each
reads the parameter after the heap has been reused. On main the first
assertion crashes under -mm=rc. 00for_condition_narrowing.ts loops on
the parameter again instead of a local copy.
Closes #512
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.
Closes #512.
Under
-mm=rc, a parameter's slot is borrowed from the caller and takes no reference, so assigning to it neither retained nor released.x = new B(1)stored an instance that the block it was made in then released, andxpointed at freed memory from there on.while (x instanceof A) { x = new B(1) }, the next condition read it and crashed.-mm=ownrefuses parameter assignment, unchanged.Change
mlirGenFunctionOwnAssignedParams(MLIRGenFunctions.cpp) runs before the body is generated, under rc only.=,+=,??=, ...)for (x of ...)/for (x in ...)takeOwnershipOfLocal): a reference is taken on the caller's value, each assignment hands the count over, and the function's exit gives it back. The copy is listed with the function's own scope.The scan over-approximates on purpose: it walks nested functions and counts every name in a destructuring target. A copy that wasn't needed costs one retain/release pair; a missed one is this bug.
Test
00param_assigned_owned.tsreads the parameter after reusing the heap in every case:instanceofloops from the issue (whileandfor)if, in a loop, in a block whose variable a closure captured, and inside a closure??=parametersfor...ofinto itasyncfunction and a rest parameterOn main the first assertion crashes under rc. With the change it passes under gc, rc and none, with and without
--opt, compile and JIT.--verify-ownershipis clean.00for_condition_narrowing.ts(#514) used a local copy because of this bug; itsinstanceofcase reassigns the parameter again, so it now guards #512 too.Verification
ctest -C Release: 3882/3882 passed, including the 8 ownership-verifier shards. The generator /async/ rest cases were added to the test afterwards; that test's 6 registrations and the verifier shards (14 tests) were rerun and pass. gtest unittests are not built in this tree.origin/main) built for rc with this compiler: release compile and JIT 159/160 passed, with 1 skip (a gc-only test).🤖 Generated with Claude Code