Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8390ca2
freeing qubits allocated in an if branch
sacpis Aug 24, 2026
12dfd60
freeing qubits allocated in a loop body
sacpis Aug 24, 2026
6478123
freeing qubits allocated in a loop else block
sacpis Aug 24, 2026
9a89901
fixing memtoreg dropping a copy of the loop counter
sacpis Aug 24, 2026
d789931
emitting for-range loops in memory form
sacpis Aug 24, 2026
5e31634
dropping loop-carried values that nothing reads
sacpis Aug 25, 2026
de7fd8e
fixing spelling
sacpis Aug 25, 2026
e5c1d4c
Merge branch 'main' into fix_python_bridge_issues
sacpis Aug 25, 2026
fe158c0
freeing qubits on the break path out of nested scopes
sacpis Aug 25, 2026
b877171
Merge branch 'main' into fix_python_bridge_issues
sacpis Aug 25, 2026
1f4fdae
Merge branch 'main' into fix_python_bridge_issues
sacpis Aug 25, 2026
9172c7e
* making cc.continue and cc.break region-branch terminators
sacpis Aug 25, 2026
b14aa1c
running a loop's else block when the loop is unrolled or fused
sacpis Aug 25, 2026
0ae8b28
Merge branch 'main' into fix_python_bridge_issues
sacpis Aug 25, 2026
9f0b9f1
Merge branch 'main' into fix_python_bridge_issues
sacpis Aug 25, 2026
04e3fa4
Merge branch 'main' into fix_python_bridge_issues
sacpis Aug 26, 2026
e0a9f31
Merge branch 'main' into fix_python_bridge_issues
sacpis Aug 26, 2026
a6d3100
Merge branch 'main' into fix_python_bridge_issues
sacpis Aug 26, 2026
f46c960
reverting the block argument elimination attempt
sacpis Aug 26, 2026
ea82c73
dropping the for/else fixes now in #5282
sacpis Aug 26, 2026
07324e6
binding only the copy's target when a promoted def becomes a block ar…
sacpis Aug 26, 2026
22ba27b
Merge branch 'main' into fix_python_bridge_issues
sacpis Aug 26, 2026
7795ca5
Merge branch 'main' into fix_python_bridge_issues
sacpis Aug 27, 2026
1cdc065
Merge branch 'main' into fix_python_bridge_issues
sacpis Aug 27, 2026
10816b7
Merge branch 'main' into fix_python_bridge_issues
sacpis Aug 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions cudaq/include/cudaq/Optimizer/Transforms/Passes.td
Original file line number Diff line number Diff line change
Expand Up @@ -1027,6 +1027,25 @@ def LoopNormalize : Pass<"cc-loop-normalize"> {
];
}

def LoopPruneDeadArgs : Pass<"cc-loop-prune-dead-args", "mlir::func::FuncOp"> {

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.

This was going to be removed, eh?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This pass is what lets two workarounds on main go away. Both are from #5233.

  1. the alloca sinking in visit_For / __analyzeLoopLocalTargets (the bare cc.alloca in the loop body)
  2. the isNestedInLoop guard in VariableCoalesce.cpp, which refuses to raise any alloca nested in a cc.loop

This branch deletes both and prunes the dead loop-carried values in the optimizer instead. #5223 needs one of the three. With the pass disabled and nothing else changed, test_5223.py fails with the original 'arith.addi' op control-flow def-use not reversible.

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.

OK.

  1. Random stray alloca's inside high-level control-flow operations without scopes are 100% invalid IR from the front-end. The Python bridge needs to generate correct IR.
  2. The variable-coalesce pass doesn't move allocations. That's not what it is for. It merges variables based on their lifetimes. The stack-frame-prealloc pass is the pass that moves allocations to the prologue of the function. These are complimentary but orthogonal.

So the isNestedInLoop stuff to move cc.alloca around is misplaced and possibly redundant with what stack-frame-prealloc does.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Totally agreed on the front end, and that's what this PR does. The bridge emits cc.scope for if/else and loop bodies now, and the sinking that produced the bare cc.alloca is gone.

The isNestedInLoop guard was a consequence of that same front end bug. variable-coalesce says it relies on the correct and proper construction and use of cc.scope ops, and the bridge emitted none, so #5233 guarded the pass instead of fixing the IR. With scopes emitted the guard is not needed, and coalesce.qke's f2 goes back to 2 slots from 4.

stack-frame-prealloc is downstream of this (it runs in addLowerToCFGAndCleanup after lowering to CFG, whereas apply-op-specialization runs on the high-level IR, so it cannot cover this case).

let summary = "Drop loop-carried values that nothing reads.";
let description = [{
A Python local lives for the whole function, so its storage is in the entry
block and `memtoreg` promotes it to a value carried by every loop around
it. When nothing reads the value, the loop is left carrying it along with
the arithmetic that computes it.

That residue reads like a real dependence on an earlier loop's result, and
adjoint generation cannot reverse a block where a value produced by an op
it moves is read by one that stays. Where a carried value is dead, thread
the loop's own initial value around the loop instead and delete the
computations that go dead as a result.

Run this after `memtoreg` and before `cc-loop-normalize`, which rewrites
loop control and would otherwise tangle the dead value into it.
}];
}

def LoopPeeling : Pass<"cc-loop-peeling"> {
let summary = "Peeling classical do-while loops.";
let description = [{
Expand Down
1 change: 1 addition & 0 deletions cudaq/lib/Optimizer/Transforms/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ add_cudaq_library(OptTransforms
LoopAnalysis.cpp
LoopInductionFusion.cpp
LoopNormalize.cpp
LoopPruneDeadArgs.cpp
LoopPeeling.cpp
LoopUnroll.cpp
LowerPhase.cpp
Expand Down
282 changes: 282 additions & 0 deletions cudaq/lib/Optimizer/Transforms/LoopPruneDeadArgs.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
/*******************************************************************************
* Copyright (c) 2026 NVIDIA Corporation & Affiliates. *
* All rights reserved. *
* *
* This source code and the accompanying materials are made available under *
* the terms of the Apache License 2.0 which accompanies this distribution. *
******************************************************************************/

#include "PassDetails.h"
#include "cudaq/Optimizer/Dialect/CC/CCOps.h"
#include "cudaq/Optimizer/Dialect/Characteristics.h"
#include "cudaq/Optimizer/Transforms/Passes.h"
#include "mlir/Transforms/Passes.h"

namespace cudaq::opt {
#define GEN_PASS_DEF_LOOPPRUNEDEADARGS
#include "cudaq/Optimizer/Transforms/Passes.h.inc"
} // namespace cudaq::opt

#define DEBUG_TYPE "cc-loop-prune-dead-args"

using namespace mlir;

namespace {

using LoopCarriedSlot = std::pair<Operation *, unsigned>;

/// Return the slot a use passes its value into, if passing it along is all the
/// use does (a loop's initial argument, or a region terminator forwarding it
/// around the loop).
std::optional<LoopCarriedSlot> forwardedSlot(OpOperand &use) {
Operation *user = use.getOwner();
unsigned pos = use.getOperandNumber();
if (auto loop = dyn_cast<cudaq::cc::LoopOp>(user))
return LoopCarriedSlot{loop.getOperation(), pos};
auto loop = dyn_cast_or_null<cudaq::cc::LoopOp>(user->getParentOp());
if (!loop)
return std::nullopt;
if (isa<cudaq::cc::ConditionOp>(user)) {
if (pos == 0)
return std::nullopt;
return LoopCarriedSlot{loop.getOperation(), pos - 1};
}
if (isa<cudaq::cc::ContinueOp, cudaq::cc::BreakOp>(user))
return LoopCarriedSlot{loop.getOperation(), pos};
return std::nullopt;
}

/// Collect every value occupying slot pos of loop (the loop's result and the
/// matching entry block argument of each of its regions).
SmallVector<Value> valuesInSlot(cudaq::cc::LoopOp loop, unsigned pos) {
SmallVector<Value> values;
if (pos < loop.getNumResults())
values.push_back(loop.getResult(pos));
for (auto *region : loop.getRegions()) {
if (region->empty())
continue;
Block &entry = region->front();
if (pos < entry.getNumArguments())
values.push_back(entry.getArgument(pos));
}
return values;
}

/// The slots in dead can be removed from loop's signature only if every
/// terminator forwarding values around the loop sits directly in one of its
/// regions, and nothing reads the block arguments being dropped.
bool canEraseDeadSlots(cudaq::cc::LoopOp loop,
const llvm::SmallBitVector &dead) {
bool ok = true;
loop.getOperation()->walk([&](Operation *op) {
if (!isa<cudaq::cc::ConditionOp, cudaq::cc::BreakOp>(op) ||
op->getParentOp() == loop.getOperation() || !op->getNumOperands())
return;
// A break nested in a scope still exits this loop and carries its values.
for (auto *p = op->getParentOp(); p; p = p->getParentOp()) {
if (p == loop.getOperation()) {
ok = false;
return;
}
if (isa<cudaq::cc::LoopOp>(p))
return;
}
});
if (!ok)
return false;
for (auto *region : loop.getRegions()) {
if (region->empty())
continue;
Block &entry = region->front();
for (unsigned pos = 0, end = dead.size(); pos != end; ++pos)
if (dead[pos] && pos < entry.getNumArguments() &&
!entry.getArgument(pos).use_empty())
return false;
}
return true;
}

/// Rebuild loop without the slots marked in dead.
void eraseDeadSlots(cudaq::cc::LoopOp loop, const llvm::SmallBitVector &dead) {
// Trim the operands each region terminator forwards.
for (auto *region : loop.getRegions())
for (Block &block : *region) {
if (!block.hasNoSuccessors())
continue;
Operation *term = block.getTerminator();
if (term->getParentOp() != loop.getOperation() ||
!isa<cudaq::cc::ConditionOp, cudaq::cc::ContinueOp,
cudaq::cc::BreakOp>(term))
continue;
unsigned offset = isa<cudaq::cc::ConditionOp>(term) ? 1 : 0;
SmallVector<Value> keep;
for (unsigned i = 0, n = term->getNumOperands(); i != n; ++i)
if (i < offset || dead.size() <= i - offset || !dead[i - offset])
keep.push_back(term->getOperand(i));
term->setOperands(keep);
}

// Drop the matching block arguments.
for (auto *region : loop.getRegions()) {
if (region->empty())
continue;
Block &entry = region->front();
for (int pos = dead.size() - 1; pos >= 0; --pos)
if (dead[pos] && static_cast<unsigned>(pos) < entry.getNumArguments())
entry.eraseArgument(pos);
}

SmallVector<Value> newInitArgs;
SmallVector<Type> newResultTypes;
for (unsigned pos = 0, end = loop.getInitialArgs().size(); pos != end; ++pos)
if (!dead[pos]) {
newInitArgs.push_back(loop.getInitialArgs()[pos]);
if (pos < loop.getNumResults())
newResultTypes.push_back(loop.getResultTypes()[pos]);
}

OpBuilder builder(loop);
auto newLoop = cudaq::cc::LoopOp::create(
builder, loop.getLoc(), newResultTypes, newInitArgs,
loop.isPostConditional(), [](OpBuilder &, Location, Region &) {},
[](OpBuilder &, Location, Region &) {},
/*stepBuilder=*/nullptr);
newLoop->setDiscardableAttrs(loop->getDiscardableAttrDictionary());
newLoop.getWhileRegion().takeBody(loop.getWhileRegion());
newLoop.getBodyRegion().takeBody(loop.getBodyRegion());
newLoop.getStepRegion().takeBody(loop.getStepRegion());
newLoop.getElseRegion().takeBody(loop.getElseRegion());

unsigned newPos = 0;
for (unsigned pos = 0, end = loop.getNumResults(); pos != end; ++pos)
if (!dead[pos])
loop.getResult(pos).replaceAllUsesWith(newLoop.getResult(newPos++));
loop.erase();
}

void pruneDeadLoopCarriedValues(func::FuncOp func) {
SmallVector<cudaq::cc::LoopOp> loops;
func.walk([&](cudaq::cc::LoopOp loop) { loops.push_back(loop); });
if (loops.empty())
return;

// Reaching the loop-carried values means going through the arithmetic that
// computes them, and that arithmetic reads the loop's own block arguments.
SmallVector<Value> candidates;
auto isPrunableComputation = [](Operation *op) {
return op->getNumRegions() == 0 && !op->hasTrait<OpTrait::IsTerminator>() &&
!cudaq::opt::hasQuantum(*op) && isMemoryEffectFree(op);
};
for (auto loop : loops)
for (unsigned pos = 0, end = loop.getInitialArgs().size(); pos != end;
++pos)
llvm::append_range(candidates, valuesInSlot(loop, pos));
func.walk([&](Operation *op) {
if (isPrunableComputation(op))
llvm::append_range(candidates, op->getResults());
});

// Start from the assumption that every candidate is dead and propagate
// liveness until it stops spreading. A value is live if
// 1. it has an opaque use
// 2. it is forwarded into a slot already known to be live
// 3. it feeds a computation whose own result is already known to be live
// Liveness only ever grows and the candidate set is finite, so this
// terminates.
DenseSet<Value> live;
auto slotIsLive = [&](LoopCarriedSlot slot) {
auto loop = cast<cudaq::cc::LoopOp>(slot.first);
return llvm::any_of(valuesInSlot(loop, slot.second),
[&](Value val) { return live.count(val); });
};
auto hasLiveUse = [&](Value val) {
for (OpOperand &use : val.getUses()) {
if (auto forwarded = forwardedSlot(use)) {
if (slotIsLive(*forwarded))
return true;
continue;
}
Operation *user = use.getOwner();
if (!isPrunableComputation(user))
return true;
if (llvm::any_of(user->getResults(),
[&](Value res) { return live.count(res); }))
return true;
}
return false;
};
for (bool changed = true; changed;) {
changed = false;
for (Value val : candidates)
if (!live.count(val) && hasLiveUse(val)) {
live.insert(val);
changed = true;
}
}

// Short-circuit each dead slot with the loop's initial value for that slot.
// The initial value is an operand of the loop, so it dominates every use we
// rewrite.
for (auto loop : loops)
for (auto [pos, initialArg] : llvm::enumerate(loop.getInitialArgs())) {
if (slotIsLive(LoopCarriedSlot{loop.getOperation(), pos}))
continue;
for (auto *region : loop.getRegions())
for (Block &block : *region) {
if (!block.hasNoSuccessors())
continue;
Operation *term = block.getTerminator();
if (term->getParentOp() != loop.getOperation())
continue;
unsigned operandPos =
isa<cudaq::cc::ConditionOp>(term) ? pos + 1 : pos;
if (isa<cudaq::cc::ConditionOp, cudaq::cc::ContinueOp,
cudaq::cc::BreakOp>(term) &&
operandPos < term->getNumOperands())
term->setOperand(operandPos, initialArg);
}
}

// Drop each dead slot from the loop's signature. Erasing an inner loop's
// slots is what frees up the enclosing loop's, so keep going until nothing
// more can be dropped.
for (bool changed = true; changed;) {
changed = false;
SmallVector<cudaq::cc::LoopOp> current;
func.walk([&](cudaq::cc::LoopOp loop) { current.push_back(loop); });
for (auto loop : llvm::reverse(current)) {
llvm::SmallBitVector dead(loop.getInitialArgs().size());
for (unsigned pos = 0, end = dead.size(); pos != end; ++pos)
if (!slotIsLive(LoopCarriedSlot{loop.getOperation(), pos}))
dead.set(pos);
if (dead.none() || !canEraseDeadSlots(loop, dead))
continue;
eraseDeadSlots(loop, dead);
changed = true;
break;
}
}

// Delete the classical computations that just went dead.
for (bool erased = true; erased;) {
erased = false;
SmallVector<Operation *> deadOps;
func.walk([&](Operation *op) {
if (!cudaq::opt::hasQuantum(*op) && isOpTriviallyDead(op))
deadOps.push_back(op);
});
for (auto *op : deadOps) {
op->erase();
erased = true;
}
}
}

class LoopPruneDeadArgsPass
: public cudaq::opt::impl::LoopPruneDeadArgsBase<LoopPruneDeadArgsPass> {
public:
using LoopPruneDeadArgsBase::LoopPruneDeadArgsBase;

void runOnOperation() override { pruneDeadLoopCarriedValues(getOperation()); }
};
} // namespace
10 changes: 8 additions & 2 deletions cudaq/lib/Optimizer/Transforms/LowerUnwind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,12 @@ struct ScopeOpPattern : public OpRewritePattern<cudaq::cc::ScopeOp> {
assert(iter != infoMap.opParentMap.end());
bool asPrimitive =
anyPrimitiveAncestor(infoMap.opParentMap, scope.getOperation());
// A break or continue leaving this scope may still have to pass through
// the landing pad of an enclosing scope, which owns allocations of its own
// to deallocate. Ending the chain here with a `cc.break` or `cc.continue`
// is correct only when nothing but the loop itself lies above.
bool toLandingPad = asPrimitive || anyScopeAncestor(infoMap.opParentMap,
Comment thread
sacpis marked this conversation as resolved.
scope.getOperation());
LLVM_DEBUG(llvm::dbgs() << "replacing scope @" << scope.getLoc() << '\n');
auto loc = scope.getLoc();
auto *initBlock = rewriter.getInsertionBlock();
Expand Down Expand Up @@ -415,7 +421,7 @@ struct ScopeOpPattern : public OpRewritePattern<cudaq::cc::ScopeOp> {
for (auto a : llvm::reverse(qallocas))
cudaq::quake::DeallocOp::create(rewriter, a->getLoc(),
adjustedDeallocArg(a));
if (asPrimitive) {
if (toLandingPad) {
Block *landingPad = getLandingPad(infoMap, scope).continueBlock;
cf::BranchOp::create(rewriter, loc, landingPad, blk->getArguments());
} else {
Expand All @@ -429,7 +435,7 @@ struct ScopeOpPattern : public OpRewritePattern<cudaq::cc::ScopeOp> {
for (auto a : llvm::reverse(qallocas))
cudaq::quake::DeallocOp::create(rewriter, a->getLoc(),
adjustedDeallocArg(a));
if (asPrimitive) {
if (toLandingPad) {
Block *landingPad = getLandingPad(infoMap, scope).breakBlock;
cf::BranchOp::create(rewriter, loc, landingPad, blk->getArguments());
} else {
Expand Down
13 changes: 13 additions & 0 deletions cudaq/lib/Optimizer/Transforms/MemToReg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -826,10 +826,23 @@ class RegionDataFlow {
getBinding(block, std::get<0>(info)) == std::get<1>(info))
addBinding(block, std::get<0>(info), newReg);
user->replaceUsesOfWith(std::get<1>(info), newReg);
// Other variables bound to this value follow it to the block
// argument, so `x = i` in a loop body gets this iteration's value.
updateBindingsOfValue(block, std::get<1>(info), newReg);
Comment thread
sacpis marked this conversation as resolved.
Outdated
}
}
}

/// Point any binding in block that still refers to oldVal at newVal.
void updateBindingsOfValue(Block *block, Value oldVal, Value newVal) {

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.

Still looks wrongheaded.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 07324e6. The sweep is gone. The binding update is now anchored to the store being rewritten. If the user we're rewriting is a cc.store of this def's value, we rebind only that store's target. No search by value identity, so it touches exactly one variable rather than every binding that happens to match.

Note: This one is a miscompile on main today. j = i in a loop body reads i's pre-loop value, in Python and in C++ through nvq++ alike. Happy to split it into its own PR.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Here is the qke

func.func @repro(%n: i64) -> i64 {
  %c0 = arith.constant 0 : i64
  %c1 = arith.constant 1 : i64
  %ctr = cc.alloca i64
  %v = cc.alloca i64
  cc.store %c0, %ctr : !cc.ptr<i64>
  cc.store %c0, %v : !cc.ptr<i64>
  cc.loop while {
    %a = cc.load %ctr : !cc.ptr<i64>
    %c = arith.cmpi slt, %a, %n : i64
    cc.condition %c
  } do {
    %a = cc.load %ctr : !cc.ptr<i64>
    cc.store %a, %v : !cc.ptr<i64>
    cc.continue
  } step {
    %a = cc.load %ctr : !cc.ptr<i64>
    %b = arith.addi %a, %c1 : i64
    cc.store %b, %ctr : !cc.ptr<i64>
  }
  %r = cc.load %v : !cc.ptr<i64>
  return %r : i64
}

after running cudaq-opt --memtoreg=quantum=0 repro.qke

module {
  func.func @repro(%arg0: i64) -> i64 {
    %c0_i64 = arith.constant 0 : i64
    %c1_i64 = arith.constant 1 : i64
    %0 = cc.undef i64
    %1 = cc.undef i64
    %2:2 = cc.loop while ((%arg1 = %c0_i64, %arg2 = %c0_i64) -> (i64, i64)) {
      %3 = arith.cmpi slt, %arg2, %arg0 : i64
      cc.condition %3(%arg1, %arg2 : i64, i64)
    } do {
    ^bb0(%arg1: i64, %arg2: i64):
      cc.continue %c0_i64, %arg2 : i64, i64
    } step {
    ^bb0(%arg1: i64, %arg2: i64):
      %3 = arith.addi %arg2, %c1_i64 : i64
      cc.continue %arg1, %3 : i64, i64
    }
    return %2#0 : i64
  }
}

Please see cc.continue %c0_i64, %arg2 in the do region. It carries the value the counter had before the loop, so the function returns 0 for any n > 0.

With this fix on this branch, we get cc.continue %arg2, %arg2.

With this code

int i = 0, j = 0;
while (i < 3) {
    j = i;
    i = i + 1;
}
x(q[j]);

The kernel above set j to 2.
So when we run the snippet on main, it gives

{ 1000:10 }

This fix gives the correct output as

{ 0010:10 }

Hope this helps.

auto iter = rMap.find(block);
if (iter == rMap.end())
return;
for (auto &binding : iter->second)
if (binding.second == oldVal)
binding.second = newVal;
}

/// Track the memory reference \p mr as being live-out of the parent
/// operation. (\p parent is passed for the assertion check only.)
void addLiveOutOfParent(Operation *parent, MemRef mr) {
Expand Down
2 changes: 2 additions & 0 deletions cudaq/lib/Optimizer/Transforms/Pipelines.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ static void createTargetPrepPipeline(OpPassManager &pm,
pm.addNestedPass<func::FuncOp>(cudaq::opt::createUnwindLowering());
pm.addNestedPass<func::FuncOp>(createCanonicalizerPass());
pm.addNestedPass<func::FuncOp>(cudaq::opt::createClassicalMemToReg());
pm.addNestedPass<func::FuncOp>(cudaq::opt::createLoopPruneDeadArgs());
cudaq::opt::createClassicalOptimizationPipeline(
pm, std::nullopt, {options.allowEarlyExit}, std::nullopt,
{options.disableLoopUnrolling});
Expand Down Expand Up @@ -305,6 +306,7 @@ static void createPythonAOTPipeline(OpPassManager &pm,
pm.addPass(cudaq::opt::createLambdaLifting());
pm.addNestedPass<func::FuncOp>(cudaq::opt::createClassicalMemToReg());
pm.addNestedPass<func::FuncOp>(createCanonicalizerPass());
pm.addNestedPass<func::FuncOp>(cudaq::opt::createLoopPruneDeadArgs());
pm.addNestedPass<func::FuncOp>(cudaq::opt::createLoopNormalize());
pm.addNestedPass<func::FuncOp>(cudaq::opt::createLoopInductionFusion());
pm.addPass(cudaq::opt::createApplySpecialization());
Expand Down
Loading
Loading