Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
91b3ef9
feat(mlir): :sparkles: implement dead gate elimination canonicalizati…
DRovara Jun 1, 2026
ba57aa2
fix(mlir): :bug: fix tests
DRovara Jun 1, 2026
b83afab
test(mlir): :white_check_mark: add direct test for dead gate elimination
DRovara Jun 1, 2026
4243ac0
docs(mlir): :memo: update changelog
DRovara Jun 1, 2026
a50032b
style(mlir): :rotating_light: fix linter issues
DRovara Jun 1, 2026
d8c9ad5
fix(mlir): :recycle: guard RegionOp removal for child oeprations with…
DRovara Jun 2, 2026
9717547
fix(mlir): :bug: fix handling for `IfOp` removal and add specialized …
DRovara Jun 2, 2026
2d65418
fix(mlir): :bug: minor bug and code style fixes
DRovara Jun 2, 2026
278a54e
style(mlir): :rotating_light: fix linter issues on includes
DRovara Jun 2, 2026
44eaf7e
fix: :pencil2: fix typo in changelog
DRovara Jun 2, 2026
8b3af43
🎨 Optimize `checkAndRemoveDeadGate` function
burgholzer Jun 2, 2026
6998110
📝 Add to generic changelog entry for mqt-cc
burgholzer Jun 2, 2026
d56ccaf
Update mlir/lib/Dialect/QCO/IR/Operations/ResetOp.cpp
DRovara Jun 3, 2026
fb9fffb
🎨 pre-commit fixes
pre-commit-ci[bot] Jun 3, 2026
44f295a
test(mlir): :white_check_mark: improve test and add custom return typ…
DRovara Jun 3, 2026
9e8e711
Merge branch 'mlir/dead-gate-elimination' of github.com:munich-quantu…
DRovara Jun 3, 2026
b6cbf8e
style(mlir): :recycle: remove unneeded dumps
DRovara Jun 3, 2026
dee0752
style(mlir): :recycle: move `checkAndRemoveDeadGate` implementation o…
DRovara Jun 3, 2026
244f8bb
style(mlir): :recycle: implement helper function to check if a gate i…
DRovara Jun 3, 2026
6eaac40
style(mlir): :rotating_light: fix includes
DRovara Jun 3, 2026
07a3f4d
docs(mlir): :memo: add comments to document `gphase` operation as mem…
DRovara Jun 5, 2026
cab4155
test(mlir): :white_check_mark: update tests to now use return values
DRovara Jun 5, 2026
6f1c64e
Update mlir/include/mlir/Dialect/QCO/QCOUtils.h
DRovara Jun 8, 2026
7e63992
Update mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp
DRovara Jun 8, 2026
2e9f4e6
Update mlir/unittests/programs/qco_programs.cpp
DRovara Jun 8, 2026
29b0cbe
Update mlir/unittests/programs/qco_programs.h
DRovara Jun 8, 2026
225c88f
style(mlir): :recycle: address some review comments
DRovara Jun 8, 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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel
- ✨ Add conversions between `jeff` and QCO ([#1479], [#1548], [#1565], [#1637], [#1676], [#1706]) ([**@denialhaag**], [**@burgholzer**])
- ✨ Add a `place-and-route` pass for mapping circuits to architectures with restricted topologies ([#1537], [#1547], [#1568], [#1581], [#1583], [#1588], [#1600], [#1664], [#1709], [#1716], [#1748]) ([**@MatthiasReumann**], [**@burgholzer**])
- ✨ Add initial infrastructure for new QC and QCO MLIR dialects
([#1264], [#1330], [#1402], [#1428], [#1430], [#1436], [#1443], [#1446], [#1464], [#1465], [#1470], [#1471], [#1472], [#1474], [#1475], [#1506], [#1510], [#1513], [#1521], [#1542], [#1548], [#1550], [#1554], [#1567], [#1569], [#1570], [#1572], [#1573], [#1580], [#1602], [#1620], [#1623], [#1624], [#1626], [#1627], [#1635], [#1638], [#1673], [#1675], [#1700], [#1717], [#1728], [#1730], [#1749])
([#1264], [#1330], [#1402], [#1428], [#1430], [#1436], [#1443], [#1446], [#1464], [#1465], [#1470], [#1471], [#1472], [#1474], [#1475], [#1506], [#1510], [#1513], [#1521], [#1542], [#1548], [#1550], [#1554], [#1567], [#1569], [#1570], [#1572], [#1573], [#1580], [#1602], [#1620], [#1623], [#1624], [#1626], [#1627], [#1635], [#1638], [#1673], [#1675], [#1700], [#1717], [#1728], [#1730], [#1749], [#1755])
([**@burgholzer**], [**@denialhaag**], [**@taminob**], [**@DRovara**], [**@li-mingbao**], [**@Ectras**], [**@MatthiasReumann**], [**@simon1hofmann**])

### Changed
Expand Down Expand Up @@ -402,6 +402,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool

<!-- PR links -->

[#1755]: https://github.com/munich-quantum-toolkit/core/pull/1755
[#1749]: https://github.com/munich-quantum-toolkit/core/pull/1749
[#1748]: https://github.com/munich-quantum-toolkit/core/pull/1748
[#1737]: https://github.com/munich-quantum-toolkit/core/pull/1737
Expand Down
2 changes: 2 additions & 0 deletions mlir/include/mlir/Dialect/QCO/IR/QCODialect.td
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ def QCODialect : Dialect {
let cppNamespace = "::mlir::qco";

let useDefaultTypePrinterParser = 1;

let hasCanonicalizer = 1;
}

#endif // MLIR_DIALECT_QCO_IR_QCODIALECT_TD
229 changes: 118 additions & 111 deletions mlir/include/mlir/Dialect/QCO/IR/QCOOps.td

Large diffs are not rendered by default.

42 changes: 42 additions & 0 deletions mlir/include/mlir/Dialect/QCO/QCOUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#pragma once

#include <llvm/ADT/TypeSwitch.h>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this include still needed? 🤔

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.

You're right, I removed it now

#include <mlir/Dialect/Arith/IR/Arith.h>
#include <mlir/IR/PatternMatch.h>
#include <mlir/Support/LLVM.h>
Expand Down Expand Up @@ -237,4 +238,45 @@ mergeTwoTargetOneParameterWithSwappedTargets(OpType op,
return success();
}

/**
* @brief Check if a given quantum operation is unused (i.e., only used by
* sinks) and remove it if so.
*
* @param op The operation to check.
* @param rewriter The pattern rewriter.
* @return LogicalResult Success or failure of the removal.
*/
inline LogicalResult checkAndRemoveDeadGate(Operation* op,
PatternRewriter& rewriter) {
if (!llvm::all_of(op->getUsers(),
[](Operation* user) { return isa<SinkOp>(user); })) {
return failure();
}

// If the operation is only used by sinks, we can safely remove it.
return TypeSwitch<Operation*, LogicalResult>(op)
.Case<UnitaryOpInterface>([&](auto u) {
// Replace output *qubits* with input *qubits* to ignore parameters.
rewriter.replaceOp(op, u.getInputQubits());
return success();
})
.Case<MeasureOp>([&](auto m) {
// Replace output *qubits* with input *qubits* to ignore classical
// outcome.
rewriter.replaceAllUsesWith(m.getQubitOut(), m.getQubitIn());
rewriter.eraseOp(op);
return success();
})
.Case<IfOp>([&](auto i) {
// Replace output *qubits* with input *qubits* to ignore the condition.
rewriter.replaceOp(op, i.getQubits());
return success();
})
.Default([&](auto*) {
// This currently only includes the `Reset` operation.
rewriter.replaceOp(op, op->getOperands());
return success();
});
}

} // namespace mlir::qco
24 changes: 24 additions & 0 deletions mlir/lib/Dialect/QCO/IR/Operations/MeasureOp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,31 @@
*/

#include "mlir/Dialect/QCO/IR/QCOOps.h"
#include "mlir/Dialect/QCO/QCOUtils.h"

#include <mlir/IR/MLIRContext.h>
#include <mlir/IR/PatternMatch.h>
#include <mlir/Support/LogicalResult.h>

using namespace mlir;
using namespace mlir::qco;

namespace {

/**
* @brief Remove dead measurements.
*/
struct DeadMeasurementRemoval final : OpRewritePattern<MeasureOp> {
using OpRewritePattern::OpRewritePattern;

LogicalResult matchAndRewrite(MeasureOp op,
PatternRewriter& rewriter) const override {
return checkAndRemoveDeadGate(op, rewriter);
}
};
Comment on lines +26 to +39

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

At this point, I am not quite sure there is much value in the shared helper function, when the implementation here would essentially be 5 lines of code.
Maybe this also goes well with the suggestion of replacing this with a fold, which necessarily has to be defined for the op itself.

Same comment applies to the ResetOp

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.

Yeah, true. When I originally implemented it I was able to use it everywhere but due to all the edge cases it ended up getting s specialized that I might as well have individual implementations for all uses. I did that now.

Anyways, I have done something else for now: The helper function is now called checkDeadGate and it only checks if a gate is dead. This can be implemented in a general way. At least that way, if we ever decide to update the notion of a dead gate, it only takes a single change.

In an ideal world where we really want to minimize code reuse, we might even want to implement some "remove this operation" helper methods for every qco operation. Because that's essentially what's left in the current implementation after the checkIfDead check is done. But for now, I kept the individual implementations in the different operation files because it's not really an issue yet.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Related to the comment above: Wouldn't it be simpler if this were simply a canonicalization of the qco.sink op? would be a fairly central place for all related code.

@DRovara DRovara Jun 5, 2026

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.

See the answer above. I honestly don't really feel like the way it's implemented right now is wrong. Having the special operation handling in the gate files tells you exectly: "Okay I'm looking at the handling for an IfOp right now" etc. and you don't have to chain a bunch of if (isa<qco::IfOp>(op)) ...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I am not saying it is wrong by any means; it is obviously working.
But I am concerned that this might not be particularly efficient compared to the alternative.
The sink canonicalization would just contain a TypeSwitch, which is pretty efficient.


} // namespace

LogicalResult MeasureOp::verify() {
const auto registerName = getRegisterName();
const auto registerSize = getRegisterSize();
Expand All @@ -37,3 +56,8 @@ LogicalResult MeasureOp::verify() {
}
return success();
}

void MeasureOp::getCanonicalizationPatterns(RewritePatternSet& results,
MLIRContext* context) {
results.add<DeadMeasurementRemoval>(context);
}
14 changes: 14 additions & 0 deletions mlir/lib/Dialect/QCO/IR/Operations/ResetOp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/

#include "mlir/Dialect/QCO/IR/QCOOps.h"
#include "mlir/Dialect/QCO/QCOUtils.h"
#include "mlir/Dialect/QTensor/IR/QTensorOps.h"
#include "mlir/Dialect/QTensor/IR/QTensorUtils.h"

Expand Down Expand Up @@ -101,6 +102,18 @@ struct RemoveResetAfterExtract final : OpRewritePattern<ResetOp> {
}
};

/**
* @brief Remove dead resets.
*/
struct DeadResetRemoval final : OpRewritePattern<ResetOp> {
using OpRewritePattern::OpRewritePattern;

LogicalResult matchAndRewrite(ResetOp op,
PatternRewriter& rewriter) const override {
return checkAndRemoveDeadGate(op, rewriter);
}
};

} // namespace

OpFoldResult ResetOp::fold(FoldAdaptor /*adaptor*/) {
Expand All @@ -114,4 +127,5 @@ OpFoldResult ResetOp::fold(FoldAdaptor /*adaptor*/) {
void ResetOp::getCanonicalizationPatterns(RewritePatternSet& results,
MLIRContext* context) {
results.add<RemoveResetAfterExtract>(context);
results.add<DeadResetRemoval>(context);
Comment thread
DRovara marked this conversation as resolved.
Outdated
}
37 changes: 37 additions & 0 deletions mlir/lib/Dialect/QCO/IR/QCOOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,19 @@
#include "mlir/Dialect/QCO/IR/QCOOps.h"

#include "mlir/Dialect/QCO/IR/QCODialect.h" // IWYU pragma: associated
#include "mlir/Dialect/QCO/IR/QCOInterfaces.h"
#include "mlir/Dialect/QCO/QCOUtils.h"

#include <llvm/ADT/STLExtras.h>
#include <mlir/IR/Block.h>
#include <mlir/IR/MLIRContext.h>
#include <mlir/IR/OpImplementation.h>
#include <mlir/IR/Operation.h>
#include <mlir/IR/OperationSupport.h>
#include <mlir/IR/PatternMatch.h>
#include <mlir/IR/Region.h>
#include <mlir/IR/ValueRange.h>
#include <mlir/Interfaces/SideEffectInterfaces.h>
#include <mlir/Support/LLVM.h>

// The following headers are needed for some template instantiations.
Expand All @@ -30,6 +35,34 @@
using namespace mlir;
using namespace mlir::qco;

//===----------------------------------------------------------------------===//
// Dialect-Level Canonicalizers
//===----------------------------------------------------------------------===//

namespace {

/**
* @brief Remove dead gates.
*/
struct DeadGateElimination final
: public OpInterfaceRewritePattern<UnitaryOpInterface> {

explicit DeadGateElimination(MLIRContext* context)
: OpInterfaceRewritePattern(context) {}

LogicalResult matchAndRewrite(UnitaryOpInterface op,
PatternRewriter& rewriter) const override {
if (!isMemoryEffectFree(op)) {
// This effectively ignores the GPhase operation and variants such as its
// inverse or `ctrl` ops containing it, which should never be considered
// dead.
return failure();
}
return checkAndRemoveDeadGate(op.getOperation(), rewriter);
}
};
Comment thread
DRovara marked this conversation as resolved.
} // namespace
Comment on lines +37 to +62

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I am wondering whether this cannot easily be replaced with a fold instead of a canonicalization pattern.
From what I can tell from a conversation with Claude, a trait that defines the foldTrait method could do the trick here to avoid having to define the same fold over and over again. A boilerplate draft for this could be

include "mlir/IR/OpBase.td"

def DeadIfOnlySinksFoldTrait : NativeOpTrait<"DeadIfOnlySinksFold"> {
  let description = [{
    Folds unitary-like ops away when all users are qco.sink by forwarding
    output qubits to corresponding input qubits.
  }];
}

and (in QCOOps.cpp)

namespace mlir::qco {

template <typename ConcreteType>
class DeadIfOnlySinksFold
: public mlir::OpTrait::TraitBase<ConcreteType, DeadIfOnlySinksFold> {
public:
static mlir::LogicalResult foldTrait(
mlir::Operation* op, mlir::ArrayRef<mlir::Attribute> /*operands*/,
mlir::SmallVectorImpl<mlir::OpFoldResult>& results) {
// Only for side-effect-free ops.
if (!mlir::isMemoryEffectFree(op)) {
return mlir::failure();
}

    // Only fold when every user is qco::SinkOp.
    if (!llvm::all_of(op->getUsers(),
                      [](mlir::Operation* user) { return mlir::isa<SinkOp>(user); })) {
      return mlir::failure();
    }

    // Restrict to UnitaryOpInterface-bearing ops.
    auto u = mlir::dyn_cast<UnitaryOpInterface>(op);
    if (!u) {
      return mlir::failure();
    }

    // Fold by forwarding outputs to corresponding inputs.
    for (mlir::Value v : u.getInputQubits()) {
      results.push_back(v);
    }
    return mlir::success();
}
};

} // namespace mlir::qco

It seems to me that it should always be possible to implement the currently implemented canonicalizations as folds.

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.

See my high-level comment. The docs never mention it not being allowed to access the users in a fold, but Gemini, Claude, and Perplexity all said it's best not to.

Apparently, the fold framework in MLIR does not add elements to the worklist when their user changes. This means (from my interpretation) that there could be situations where we have the following chain of instructions:

"A => B => X"

Now if we have a rule that says "any operation that is followed by an X can be folded", then the worker might first look at "A", determine it can'f be folded, then look at "B", fold it, but then not check "A" again which could now also be folded.

Unfortunately, none of the AIs could give me actual sources for that (Gemini said "There are precedents of PRs in the LLVM repo being rejected because they accessed the user list inside a fold" but couldn't provide a link), but they all seem to agree on that being the reason.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I tried to look around a little because I simply was not quite trusting the LLMs.
After a bit of digging, I conclude that you/they are right. From what I could gather

A fold is meant to be:

  • Purely local to the operation
  • Cheap and side-effect-free
  • Context-independent
  • Deterministic based only on operands and attributes

Concretely, a fold implementation:

  • Operates only on:

    • The operation itself
    • Its operands (via FoldAdaptor)
    • Its attributes
  • Returns:

    • Existing values, or
    • Constants (Attribute → constant materialization)
  • Must not mutate IR directly (no erasing/rewriting arbitrary ops)

  • Must not depend on analysis or global structure

In essence, looking at operation results and their users is not allowed as it is non-local and context-dependent.

So, after all, a canonicalization it is.
This got me thinking though: Couldn't this be a single canonicalization that is implemented for the qco.sink operation, which would look at the defining op of the incoming qubit value?

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.

I think it's a valid consideration, but my gut feeling tells me it's better to be on the gates themselves.

Mainly because a transformation pattern should aim to target/modify mainly the operation it matches. So matching qco.sink and then only modifying the predecessors is not best practise.

But also conceptually, removing dead gates is not a property of the sinks, being dead is a property of the gates. If we ever decide that there's another type of operation that randers gates dead, then it is a straightforward addition to modify the current version, but it would be more of a change to add another canonicalisation for that.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I see your point. I believe my main concern was that this feels so repetitive.
And matching the sink operation would centralize all the code, but also the pattern matching itself. Instead of matching every single unitary and having to check its successor, it would only match a single operation and check the predecessor.
In terms of efficiency, this sounds way better to me.
And yeah, if another type of operation comes up, this would mean some duplication; but I would argue that even in that case, it seems more efficient to match one of two operations than to match against every single one.

At the same time, I agree that it does not feel quite right for the canonicalization to not modify the operation it matches on. But is that really so bad?


//===----------------------------------------------------------------------===//
// Custom Parsers
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -258,6 +291,10 @@ void QCODialect::initialize() {
>();
}

void QCODialect::getCanonicalizationPatterns(RewritePatternSet& results) const {
results.add<DeadGateElimination>(getContext());
}

//===----------------------------------------------------------------------===//
// Types
//===----------------------------------------------------------------------===//
Expand Down
19 changes: 19 additions & 0 deletions mlir/lib/Dialect/QCO/IR/SCF/IfOp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/

#include "mlir/Dialect/QCO/IR/QCOOps.h"
#include "mlir/Dialect/QCO/QCOUtils.h"

#include <llvm/ADT/STLExtras.h>
#include <llvm/ADT/STLFunctionalExtras.h>
Expand All @@ -25,6 +26,7 @@
#include <mlir/IR/Value.h>
#include <mlir/IR/ValueRange.h>
#include <mlir/Interfaces/ControlFlowInterfaces.h>
#include <mlir/Interfaces/SideEffectInterfaces.h>
#include <mlir/Support/LLVM.h>

#include <cassert>
Expand Down Expand Up @@ -234,11 +236,28 @@ struct ConditionPropagation : public OpRewritePattern<IfOp> {
return success(changed);
}
};

/**
* @brief Remove dead `IfOp` instructions.
*/
Comment thread
DRovara marked this conversation as resolved.
struct DeadIfRemoval final : OpRewritePattern<IfOp> {
using OpRewritePattern::OpRewritePattern;

LogicalResult matchAndRewrite(IfOp op,
PatternRewriter& rewriter) const override {
if (!isMemoryEffectFree(op)) {
// This effectively ignores `IfOp`s with memory effects.
return failure();
}
Comment thread
burgholzer marked this conversation as resolved.
Outdated
return checkAndRemoveDeadGate(op, rewriter);
}
};
} // namespace

void IfOp::getCanonicalizationPatterns(RewritePatternSet& results,
MLIRContext* context) {
results.add<RemoveStaticCondition, ConditionPropagation>(context);
results.add<DeadIfRemoval>(context);
populateRegionBranchOpInterfaceCanonicalizationPatterns(
results, IfOp::getOperationName());
}
Expand Down
108 changes: 108 additions & 0 deletions mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,114 @@ TEST_F(QCOTest, BuilderRejectsMixedStaticAndDynamicQubitAllocationModes) {
"Cannot mix dynamic and static qubit allocation modes");
}

TEST_F(QCOTest, CheckDeadGateElimination) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I understand that it does not necessarily make sense to define such specialized programs in qco_programs.h, but would it be possible to define the programs via similar functions in this file and then use QCOTestCase? 🤔

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.

I agree that this is not the cleanest place to put and handle the test. I feel like, ideally, there should be a specialised test file for general canonicalisations. I'll have a quick look and then either try that or implement it the way you suggested.

QCOProgramBuilder builder(context.get());
builder.initialize();
auto q0S0 = builder.allocQubit();
auto q1S0 = builder.allocQubit();
auto q0S1 = builder.h(q0S0);
auto [q0S2, q1S1] = builder.cx(q0S1, q1S0);
auto [q1S2, c1] = builder.measure(q1S1);
builder.sink(q0S2);
builder.sink(q1S2);
auto module = builder.finalize();

QCOProgramBuilder reference(context.get());
reference.initialize();
auto r0 = reference.allocQubit();
auto r1 = reference.allocQubit();
reference.sink(r0);
reference.sink(r1);
Comment thread
burgholzer marked this conversation as resolved.
Outdated
auto refModule = reference.finalize();

ASSERT_TRUE(module);
EXPECT_TRUE(verify(*module).succeeded());
EXPECT_TRUE(runQCOCleanupPipeline(module.get()).succeeded());
EXPECT_TRUE(verify(*module).succeeded());

ASSERT_TRUE(refModule);
EXPECT_TRUE(verify(*refModule).succeeded());
EXPECT_TRUE(runQCOCleanupPipeline(refModule.get()).succeeded());
EXPECT_TRUE(verify(*refModule).succeeded());

EXPECT_TRUE(
areModulesEquivalentWithPermutations(module.get(), refModule.get()));
}

TEST_F(QCOTest, CheckIfOpDeadGateElimination) {
QCOProgramBuilder builder(context.get());
builder.initialize();
auto q0S0 = builder.allocQubit();
auto q1S0 = builder.allocQubit();
auto q0S1 = builder.h(q0S0);
auto [q0S2, c0] = builder.measure(q0S1);

// This is an `if` with memory effects - it can't be removed.
auto q1S1 = builder.qcoIf(
c0, {q1S0},
[&](ValueRange qubits) -> SmallVector<Value> {
auto q1Then = builder.x(qubits[0]);
builder.gphase(0.5);
return SmallVector<Value>{q1Then};
},
[&](ValueRange qubits) -> SmallVector<Value> {
auto q1Else = builder.h(qubits[0]);
return SmallVector<Value>{q1Else};
})[0];

// This is an `if` without memory effects - it can be removed.
auto q1S2 = builder.qcoIf(
c0, {q1S1},
[&](ValueRange qubits) -> SmallVector<Value> {
auto q1Then = builder.x(qubits[0]);
return SmallVector<Value>{q1Then};
},
[&](ValueRange qubits) -> SmallVector<Value> {
auto q1Else = builder.h(qubits[0]);
return SmallVector<Value>{q1Else};
})[0];
builder.sink(q0S2);
builder.sink(q1S2);
auto module = builder.finalize();

QCOProgramBuilder reference(context.get());
reference.initialize();
auto r0S0 = reference.allocQubit();
auto r1S0 = reference.allocQubit();
auto r0S1 = reference.h(r0S0);
auto [r0S2, cr0] = reference.measure(r0S1);

// This is an `if` with memory effects - it can't be removed.
auto r1S1 = reference.qcoIf(
cr0, {r1S0},
[&](ValueRange qubits) -> SmallVector<Value> {
auto q1Then = reference.x(qubits[0]);
reference.gphase(0.5);
return SmallVector<Value>{q1Then};
},
[&](ValueRange qubits) -> SmallVector<Value> {
auto q1Else = reference.h(qubits[0]);
return SmallVector<Value>{q1Else};
})[0];

reference.sink(r0S2);
reference.sink(r1S1);
auto refModule = reference.finalize();
Comment thread
burgholzer marked this conversation as resolved.

ASSERT_TRUE(module);
EXPECT_TRUE(verify(*module).succeeded());
EXPECT_TRUE(runQCOCleanupPipeline(module.get()).succeeded());
EXPECT_TRUE(verify(*module).succeeded());

ASSERT_TRUE(refModule);
EXPECT_TRUE(verify(*refModule).succeeded());
EXPECT_TRUE(runQCOCleanupPipeline(refModule.get()).succeeded());
EXPECT_TRUE(verify(*refModule).succeeded());

EXPECT_TRUE(
areModulesEquivalentWithPermutations(module.get(), refModule.get()));
}
Comment thread
DRovara marked this conversation as resolved.

TEST_F(QCOTest, DirectIfBuilder) {
// Test If construction directly
QCOProgramBuilder builder(context.get());
Expand Down
Loading