forked from WebKit/WebKit
-
Notifications
You must be signed in to change notification settings - Fork 52
[JSC] ConservativeRoots: no past-the-end butterfly slack for cells that cannot hold a butterfly (MarkedBlock rule + PreciseAllocation::contains) #398
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
dylan-conway
merged 3 commits into
main
from
claude/conservative-scan-past-end-jscell-blocks
Aug 9, 2026
+11
−4
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
6cb6d08
[JSC] ConservativeRoots: only apply the past-the-end butterfly rule i…
dylan-conway e501c5c
[JSC] ConservativeRoots: no past-the-end slack for precise allocation…
dylan-conway 9b999ae
[JSC] ConservativeRoots: keep the exact one-past-the-end pointer for …
dylan-conway File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Commit 9b999ae relaxed the PreciseAllocation branch to
<= endbecause compiler-rewritten loops over trailing in-cell storage (DirectArguments, JSLexicalEnvironment, JSBigInt digits, large JSFinalObject inline storage) can materialise the exact one-past-the-end pointer as the only live reference — but that same rationale applies to those types when they live in a MarkedBlock withobjectSize == cellSize, and here the newmayHaveIndexingHeader(cellKind)guard now drops that case (when the right neighbour is dead,tryPointer(alignedPointer - cellSize)used to mark the cell; now it's skipped). Consider allowing the exact-boundary case here too, e.g.(mayHaveIndexingHeader(cellKind) || pointer == alignedPointer) && ..., or add a comment explaining why the asymmetry vs. the PreciseAllocation path is intentional.Extended reasoning...
What changed and where the asymmetry appears
Commit 9b999ae (the review-feedback follow-up on this PR) changed the PreciseAllocation non-butterfly bound from
pointer < cell + cellSizetopointer <= cell + cellSize, with the explicit rationale:That fix was applied only to the
attemptLargehunk. The MarkedBlock hunk at line 197 still gates the entire left-neighbour fallback onmayHaveIndexingHeader(cellKind), so for a non-butterfly block a stack word that lands exactly oncell + cellSizeno longer markscell.Why the same rationale reaches the MarkedBlock path
The types listed in 9b999ae's message all live in MarkedBlocks when small enough.
MarkedSpace's precise size classes cover every multiple ofsizeStep = 16up topreciseCutoff, so e.g. aDirectArgumentsorJSLexicalEnvironmentwhoseallocationSize(N)is a multiple of 16 gets a block whereobjectSize == cellSizeexactly. A C++-compiler-rewritten loop over its trailing storage can then hold onlycell + cellSize— which is the start of the right-hand neighbour cell.Step-by-step trace at line 197
Take a non-butterfly block (
cellKind == JSCell), a live cellCat atomN, a dead cell at atomN+1, and a stack wordpointer == start(N+1) == C + cellSize:isJSCellKind(cellKind)is true andpointeris atom-aligned, sotryPointer(pointer)runs on cellN+1. It's dead →isLive == false→ returnsfalse.alignedPointer = cellAlign(pointer) = start(N+1) = pointer.tryPointer(alignedPointer)again hits deadN+1→ returnsfalse.candidateAtomNumber(alignedPointer) > 0 && pointer <= alignedPointer + 8→ true →tryPointer(alignedPointer - cellSize)=tryPointer(C)→Cis marked.mayHaveIndexingHeader(cellKind)isfalse→ the whole condition short-circuits →Cis not marked.So this PR removes the case where the MarkedBlock path did handle the exact one-past-the-end pointer, while the review-feedback commit on the same PR asserts that case matters for the PreciseAllocation path.
Why the PR description's defence doesn't cover this
The PR description says "plain JSCells are never referenced by an end/past-the-end pointer as their only reference (JIT/LLInt keep the base)". That addresses JIT-generated code, but 9b999ae's rationale is about C++-compiler-rewritten loops over trailing in-cell storage — a different codepath, and one the author accepted as valid one commit later. Either that rationale applies to both allocation paths (in which case line 197 should also let
pointer == alignedPointerthrough), or it applies to neither (in which case 9b999ae is unnecessary). Applying it to only one hunk looks like an oversight.There is a plausible argument for keeping the asymmetry: accepting
pointer == alignedPointeras a reference to the previous MarkedBlock cell reintroduces a false-positive path (a stale base pointer to a dead cellN+1marks its live predecessorN) that PreciseAllocation doesn't have because precise cells have no adjacent neighbour. And the MarkedBlock one-past-the-end case was already unreliable pre-PR — a live right neighbour short-circuits at theisLive && !mayHaveIndexingHeaderearly-return before ever reaching the fallback. But if that tradeoff is the intent, it's worth a comment; "was already broken half the time" is a weak reason to remove the half that worked.Suggested fix
This mirrors the PreciseAllocation path's
<= end: for non-butterfly blocks, only the exact boundary (pointer == alignedPointer, i.e.endof the left cell) triggers the fallback; the(end, end+8]IndexingHeader slack stays gated onmayHaveIndexingHeader.Severity
Nit. The trigger is narrow (
objectSize == cellSizeexactly, C++ compiler drops the base pointer across a GC safepoint, right neighbour dead), there's no known repro (the Bun repro wasend+8in a PreciseAllocation), and the pre-PR MarkedBlock behaviour was already inconsistent. But it's exactly the class of feedback the author already acted on in 9b999ae for the other hunk, so worth either fixing symmetrically or documenting why not.