Don't push a poison aggregate onto the sret rooting worklist - #3152
Open
kshyatt wants to merge 1 commit into
Open
Don't push a poison aggregate onto the sret rooting worklist#3152kshyatt wants to merge 1 commit into
kshyatt wants to merge 1 commit into
Conversation
kshyatt
force-pushed
the
fixup-poison-insertvalue-base
branch
from
August 15, 2026 09:57
d3d7590 to
e41cac3
Compare
Collaborator
Author
|
I got added to the repo so you can unapprove now :D |
`needsReRooting` walks every value stored into an sret argument to check each
tracked pointer is also parked in the returnRoots array. Walking back through an
insertvalue chain, the branch for an insert of an untracked field pushed the
aggregate operand unconditionally:
CountTrackedPointers tracked(IVI->getInsertedValueOperand()->getType());
if (tracked.count == 0) { storedValues.push_back(IVI->getAggregateOperand()); continue; }
When that operand is the poison the chain was built from, the bare poison lands
on the worklist. The struct handling below then looks for an extractvalue
covering poison's tracked fields, finds none, and reports
failed to find extracted pointer for %tape poison at index 1
before falling through to the pointer-shaped handling, whose assert(0) fires on
any non-pointer value. Julia hits this whenever a nested sub-tape survives SROA
as a whole-aggregate store and its first field is untracked -- reverse mode over
PEPSKit's CTMRG aborts the process this way, since the shipped Enzyme_jll
RelWithDebInfo build leaves assertions live.
A poison, undef, or zeroinitializer aggregate has no live pointer in any field,
so it needs no root regardless of which field the insertvalue overwrites. Hoist
the trivial-base test above both branches and drop such a base, which is the
reasoning the tracked branch three lines down already relies on. In the Julia
case the tracked pointers are separately stored into the returnRoots, so the
walk then completes and the pass correctly concludes it has no work to do.
Also un-shadow `legal` in the struct handling. The inner declaration meant the
coverage failure set a variable nobody read and fell into that same assert;
report it through CustomErrorHandler like the pointer path does and reroot
conservatively. This is not a behaviour change for NDEBUG builds, which already
fell through to exactly that handler.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
kshyatt
force-pushed
the
fixup-poison-insertvalue-base
branch
from
August 16, 2026 18:32
e41cac3 to
17915ea
Compare
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.
Another 🤖 assisted PR, I ran into this while trying to fix things for a very long tape fed into Enzyme.jl:
needsReRootingwalks every value stored into an sret argument to check each tracked pointer is also parked in the returnRoots array. Walking back through an insertvalue chain, the branch for an insert of an untracked field pushed the aggregate operand unconditionally:When that operand is the poison the chain was built from, the bare poison lands on the worklist. The struct handling below then looks for an extractvalue covering poison's tracked fields, finds none, and reports
before falling through to the pointer-shaped handling, whose assert(0) fires on any non-pointer value. Julia hits this whenever a nested sub-tape survives SROA as a whole-aggregate store and its first field is untracked -- reverse mode over PEPSKit's CTMRG aborts the process this way, since the shipped Enzyme_jll RelWithDebInfo build leaves assertions live.
A poison, undef, or zeroinitializer aggregate has no live pointer in any field, so it needs no root regardless of which field the insertvalue overwrites. Hoist the trivial-base test above both branches and drop such a base, which is the reasoning the tracked branch three lines down already relies on. In the Julia case the tracked pointers are separately stored into the returnRoots, so the walk then completes and the pass correctly concludes it has no work to do.
Also un-shadow
legalin the struct handling. The inner declaration meant the coverage failure set a variable nobody read and fell into that same assert; report it through CustomErrorHandler like the pointer path does and reroot conservatively. This is not a behaviour change for NDEBUG builds, which already fell through to exactly that handler.