Skip to content

[core] Document the StackFramePrealloc / addLowerToCFG pairing invariant - #5495

Open
AnitaGeorge404 wants to merge 5 commits into
NVIDIA:mainfrom
AnitaGeorge404:fix/5272-stackframe-prealloc-pipeline
Open

AnitaGeorge404 wants to merge 5 commits into
NVIDIA:mainfrom
AnitaGeorge404:fix/5272-stackframe-prealloc-pipeline

Conversation

@AnitaGeorge404

@AnitaGeorge404 AnitaGeorge404 commented Sep 25, 2026 •

Copy link
Copy Markdown

Record, at the one place in the compiler that pairs these two passes, why they must never be invoked separately: doing so (running StackFramePrealloc over still-structured cc.scope/cc.if/cc.loop control flow instead of the lowered CFG it analyzes) was tried during review and reproducibly broke dynamic/list-returning cudaq.run/run_async kernels and mid-circuit-measurement kernels, by letting per-iteration allocations get hoisted and shared across loop iterations instead of getting a fresh stack slot each time.

The pairing itself (addLowerToCFGAndCleanup, used at every codegen pipeline call site) already encodes the fix and is already pinned by the PIPELINE FileCheck block in
cudaq/test/Optimizer/atomic_quantum_region_codegen.qke, so this change adds no new behavior; it makes the invariant legible at the point most likely to be edited without that context.

Record, at the one place in the compiler that pairs these two passes,
why they must never be invoked separately: doing so (running
StackFramePrealloc over still-structured cc.scope/cc.if/cc.loop control
flow instead of the lowered CFG it analyzes) was tried during review and
reproducibly broke dynamic/list-returning `cudaq.run`/`run_async`
kernels and mid-circuit-measurement kernels, by letting per-iteration
allocations get hoisted and shared across loop iterations instead of
getting a fresh stack slot each time.

The pairing itself (`addLowerToCFGAndCleanup`, used at every codegen
pipeline call site) already encodes the fix and is already pinned by the
`PIPELINE` FileCheck block in
cudaq/test/Optimizer/atomic_quantum_region_codegen.qke, so this change
adds no new behavior; it makes the invariant legible at the point most
likely to be edited without that context.
@copy-pr-bot

copy-pr-bot Bot commented Sep 25, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

AnitaGeorge404 and others added 2 commits September 26, 2026 02:13
addPipelineTranslateToIQMJson hand-inlined addLowerToCFG followed by
StackFramePrealloc instead of using the addLowerToCFGAndCleanup()
helper every other codegen pipeline uses for this pair. That is a
second, independent place the two passes could be split apart without
anyone noticing -- exactly the failure mode NVIDIA#5272 documents for the
QIR codegen pipeline. Route it through the shared helper so the
pairing is enforced by reuse, not convention.

The pass order is unchanged (lower-to-cfg immediately followed by
stack-frame-prealloc); the only behavioral difference is the trailing
canonicalizer pass the helper always adds, matching every other call
site and immediately followed here by combine-quantum-allocations'
own canonicalizer, so it is a no-op cleanup pass, not a new
transformation.
@schweitzpgi

Copy link
Copy Markdown
Collaborator

Do you have a reproducer for the issue referenced?

@khalatepradnya khalatepradnya left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for working on this, @AnitaGeorge404.

This PR does not resolve #5272, so please remove Closes #5272 from the description.

#5272 asks for StackFramePrealloc to stay in createCommonTargetCodegenPipeline, and for the failures that this placement causes to be fixed. That was the outcome of the #5252 review thread that led to the issue.
The new comment does the opposite.

AnitaGeorge404 and others added 2 commits September 29, 2026 09:54
Root cause: addLowerToCFG's own cc.scope lowering (RewriteScope in
LowerToCFG.cpp) unconditionally wraps any scope with a classical
allocation in an llvm.stacksave/llvm.stackrestore pair, since a lowered
CFG has no structured construct left to express "free this memory when
the scope exits". StackFramePrealloc's job of stripping the premature
restore for a dynamically sized allocation that must survive past its
scope (a value returned from a loop, a mid-circuit measurement result)
can only happen once those stacksave/stackrestore calls exist, i.e.
after addLowerToCFG has run. That is why the experiment recorded in
NVIDIA#5272 -- keep StackFramePrealloc where it was, move only addLowerToCFG
-- broke 22 tests: it removed the invocation that actually performs
this cleanup, without providing any replacement for it.

Fix: restore StackFramePrealloc to its original position in
createCommonTargetCodegenPipeline (hoisting loop-invariant classical
allocations while control flow is still structured, which is what the
common pipeline needs it for), while leaving the addLowerToCFG delay
from NVIDIA#5252 and the paired post-addLowerToCFG StackFramePrealloc
invocation (addLowerToCFGAndCleanup) completely untouched, since that
is the invocation load-bearing for the stacksave/stackrestore cleanup
described above. The pass now runs twice, at two points that do
different, non-overlapping jobs: the early run only ever hoists
fixed-size allocations (a dynamically sized allocation is always
pinned in place, regardless of when the pass runs), so it cannot
interfere with the late run's cleanup of stacksave/stackrestore pairs
that do not exist until addLowerToCFG creates them.

Updated the PIPELINE FileCheck block in
atomic_quantum_region_codegen.qke to assert stack-frame-prealloc now
appears before combine-quantum-alloc in the common pipeline, in
addition to its existing, unchanged assertion that it also runs
immediately after convert-to-cfg further down.
@AnitaGeorge404

Copy link
Copy Markdown
Author

@khalatepradnya Thanks for the clarification. I’ve removed the Closes #5272 reference as requested. I’ll rework the implementation to address the actual issue by keeping StackFramePrealloc in createCommonTargetCodegenPipeline and fixing the resulting failures. I’ll push the updated changes soon.

@schweitzpgi

Copy link
Copy Markdown
Collaborator

Right. StackFramePrealloc is no longer dependent on the IR being in CFG form. We need to fix the bug(s), not reverse course.

@schweitzpgi

Copy link
Copy Markdown
Collaborator

I suggest closing this PR, since it was about documenting something that isn't true, and starting with a fresh PR.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

3 participants