Repository navigation
rc: a const read out of an element keeps a count of its own - #641
Merged
Merged
Conversation
`const c = arr[0]` (and `const [c] = arr`) was folded into the read itself, holding no reference, so `arr[0] = d` freed the block `c` still read. Under rc such a const now gets storage like a `let`, as a const read out of a field already does (#635). That needed an out-of-bounds destructuring fixed first: `const [a, b, c] = pair` read whatever followed the array's data. Under rc a count taken of it faulted (for a `let` too), and under gc/none it was undefined behaviour that LLVM used - `print(c == null)` after it made main print nothing at all. An element past the end of an array is now the default of its type: null for a reference (TS has undefined there), 0 for a number. A runtime array checks its length; a const array literal's size is known, so no read is emitted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ASDAlexander77
added a commit
that referenced
this pull request
Oct 22, 2026
Both changed the rc const rule in processConstRef: it now takes a field read, an element read (#641) and a view over a field read (this PR). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ASDAlexander77
added a commit
that referenced
this pull request
Oct 22, 2026
… a count (#644) `const m = arr[0].get` and `const i: I = arr[1]` were folded into the view itself, holding no reference of their own, so `arr[0] = d` freed the object `m()` and `i.get()` then ran on. #641 gave a const read out of an element storage and #642 gave a view over a field read the same; a view over an element read is the remaining case. Under rc it now gets storage like a `let`. Under own the folded read stays a borrow. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
ASDAlexander77
added a commit
that referenced
this pull request
Oct 23, 2026
…global a count (#647) Two shapes the memory-model fuzzer (#646) found double-freeing under rc since #639. - holdGlobalArguments retained the argument right before the call. A later argument of the same call could give the global a new value first (`f(G, (G = mk(), x))`, `f(H.n, (H.n = mk(), x))`): its release freed the block, and the hold then retained and released a freed block. The retain (and the #645 unwind slot) now follow the read. - `const n = G` was folded into the read, holding no count. Once the global got a new value, here or in a call, `n` dangled (fuzzer R1). #639 made that a crash: `run(n.v)`, with `run` assigning G, held and released a field of the freed block. Under rc a const read out of a global, or an interface or bound method made over one, now gets storage like a const read out of a field or an element (#635, #641, #642, #644). Under own the folded read stays a borrow. 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.
const c = arr[0](andconst [c] = arr) was folded into the read itself and held no reference, so under rcarr[0] = dfreed the blockcstill read. Such a const now gets storage like aletunder rc, as a const read out of a field already does (#635).That needed an out-of-bounds destructuring fixed first, which was why #635 left elements out:
const [a, b, c] = pairread whatever followed the array's data. Under rc a count taken of it faulted (for aletas well). Under gc and none it was undefined behaviour that LLVM made use of: withprint(c == null)after it,mainprinted nothing at all and exited 127.undefinedthere), 0 for a number. A runtime array compares its length; a const array literal's size is known at compile time, so no read is emitted. Tuples past their length were already a compile error, and still are.readsElement) sees through theifthe bounds check makes, so a destructured const is covered too. It applies under rc only. Under own the folded read stays a borrow.Behaviour change in every model: reading past the end of an array in a destructuring now gives null/0, where before it read whatever memory followed. No in-bounds output changes.
Tests
00const_element_read.ts, in the default compile/jit lists and inTSLANG_CORPUS(rc + none). It covers element reads, destructured reads and string elements under churn, plus past-the-end class/string/number/literal elements. On main it fails in all three models (rc: the element's overwrite frees the const; gc/none: a past-the-end string is not null).readsElementreturn false failstest-compile-rc-corpus-00const-element-read(and its jit twin). Without the switch, it passes.ctest -C Release: 4920/4920. WSL Linux full run: 4902/4902.Merge note: this and #642 both edit the rc condition line and the comment paragraph in
processConstRef(MLIRGenImpl.h), so the second to merge needs a small rebase. Once both are in,const m = arr[0].getneeds a one-line follow-up:readsElement(viewedValue(initial)).🤖 Generated with Claude Code