Skip to content

✨ Add pass and patterns for Classical Control Replacement#1756

Draft
DRovara wants to merge 37 commits into
mainfrom
mlir/replace-classical-controls
Draft

✨ Add pass and patterns for Classical Control Replacement#1756
DRovara wants to merge 37 commits into
mainfrom
mlir/replace-classical-controls

Conversation

@DRovara

@DRovara DRovara commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

Description

Part 2 out of 3 of the Qubit Reuse Implementation

This PR ports the previous implementation of the Classical Control Replacement pass to the new dialect infrastructure.

This path has the goal of replacing controlled gates with "classically controlled gates" whenever a classical value for the control qubit is available because it has just been measured.
In those cases, we remove the control from the CtrlOp and instead introduce a qco.ifOp.

This is one of 3 PRs that will, in total, constitute qubit reuse:

  1. Measurement Lifitng
  2. Replacing Classical Controls
  3. Reuse Qubits

Requires #1705 to be merged first.

AI Notice: Gemini 3.1 and Claude Sonnet 4.6 were used for the generation of some code.

Checklist

  • The pull request only contains commits that are focused and relevant to this change.
  • I have added appropriate tests that cover the new/changed functionality.
  • I have updated the documentation to reflect these changes.
  • I have added entries to the changelog for any noteworthy additions, changes, fixes, or removals.
  • I have added migration instructions to the upgrade guide (if needed).
  • The changes follow the project's style guidelines and introduce no new warnings.
  • The changes are fully tested and pass the CI checks.
  • I have reviewed my own code changes.

If PR contains AI-assisted content:

  • I have disclosed the use of AI tools in the PR description as per our AI Usage Guidelines.
  • AI-assisted commits include an Assisted-by: [Model Name] via [Tool Name] footer.
  • I confirm that I have personally reviewed and understood all AI-generated content, and accept full responsibility for it.

@DRovara DRovara self-assigned this Jun 2, 2026
@DRovara DRovara added feature New feature or request c++ Anything related to C++ code MLIR Anything related to MLIR labels Jun 2, 2026
@DRovara DRovara added this to the MLIR Support milestone Jun 2, 2026
@DRovara

DRovara commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit full review

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added support for creating boolean constants in QCO programs.
    • Added measurement-lifting optimization to move measurements across compatible gates while preserving results.
    • Added classical-control replacement, converting measurement-driven controls into structured conditional operations.
    • Added optimization passes for handling diagonal, controlled, and inverting gate patterns.
  • Tests

    • Added comprehensive coverage for measurement lifting and classical-control transformations, including nested and edge-case circuits.
  • Documentation

    • Updated the changelog with the latest release references.

Walkthrough

This PR adds QCO measurement-lifting and classical-control-replacement passes, introduces a boolean constant builder helper, registers new optimization tests, and updates changelog references.

Changes

QCO Optimization Passes

Layer / File(s) Summary
Pass contracts and builder support
mlir/include/mlir/Dialect/QCO/..., mlir/lib/Dialect/QCO/Builder/..., CHANGELOG.md, mlir/unittests/.../CMakeLists.txt
Declares both passes, adds boolConstant(bool), and registers the new test sources.
Measurement lifting implementation
mlir/lib/Dialect/QCO/Transforms/Optimizations/MeasurementLifting.cpp
Lifts measurements across compatible gates and controls, including boolean inversion for X and Y gates.
Classical-control replacement implementation
mlir/lib/Dialect/QCO/Transforms/Optimizations/ReplaceClassicalControls.cpp
Converts measurement-derived controls into qco.if regions with conjunction conditions and rewired SSA results.
Measurement lifting validation
mlir/unittests/.../test_qco_measurement_lifting.cpp
Tests lifting across controlled, parametrized, diagonal, anti-diagonal, inverted, and guarded gate cases.
Classical-control replacement validation
mlir/unittests/.../test_qco_replace_classical_controls.cpp
Tests single and multiple measured controls, conjunctions, and diagonal-control swapping constraints.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ModuleOp
  participant QCOOptimizationPass
  participant RewritePatterns
  participant QCOOperations
  participant ArithOperations
  ModuleOp->>QCOOptimizationPass: run optimization
  QCOOptimizationPass->>RewritePatterns: apply patterns greedily
  RewritePatterns->>QCOOperations: reorder or replace QCO operations
  RewritePatterns->>ArithOperations: create boolean constants and conditions
  RewritePatterns-->>ModuleOp: updated QCO module
Loading

Possibly related PRs

Suggested reviewers: burgholzer, denialhaag

Poem

I hop through gates where qubits gleam,
Lift measurements like a dream.
Boolean carrots, crisp and bright,
Guide controls through qco.if light.
Tests thump softly—green tonight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding the classical control replacement pass and its patterns.
Description check ✅ Passed The description covers the change summary, motivation, dependency on PR #1705, and includes the required checklist section.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch mlir/replace-classical-controls
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch mlir/replace-classical-controls

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
mlir/include/mlir/Dialect/QCO/IR/QCOOps.td (1)

1071-1077: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Drop Pure from qco.ctrl, qco.inv, and qco.if (region wrappers).

Pure in MLIR expands to AlwaysSpeculatable + NoMemoryEffect (via AlwaysSpeculatableImplTrait), which asserts speculatability and no memory effects. These wrapper ops already model their effects via RecursiveMemoryEffects, so keeping Pure overstates effects independently of what’s inside the nested region. Keep RecursiveMemoryEffects, but remove Pure from CtrlOp, InvOp, and IfOp (incl. ranges covering the corresponding defs around 1071-1077, 1150-1154, and 1223-1232).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mlir/include/mlir/Dialect/QCO/IR/QCOOps.td` around lines 1071 - 1077, The QCO
region wrapper ops declare the Pure trait which expands to AlwaysSpeculatable +
NoMemoryEffect and incorrectly duplicates/overrides the region-modelled effects;
remove the Pure trait from the op definitions for CtrlOp, InvOp, and IfOp (the
TD defs named CtrlOp, InvOp, IfOp) so they only keep RecursiveMemoryEffects (and
other existing traits like UnitaryOpInterface, AttrSizedOperandSegments,
AttrSizedResultSegments, SameOperandsAndResultType, SameOperandsAndResultShape,
SingleBlockImplicitTerminator) to ensure effects are solely driven by the nested
region.
mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp (1)

1105-1165: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Enforce the configured main return signature before finalizing.

After initialize(TypeRange), the no-arg finalize() still hardcodes an i64 zero, and finalize(ValueRange) never checks arity/types against main. initialize({}) or any non-i64 signature now silently builds an invalid func.return.

Suggested fix
 OwningOpRef<ModuleOp> QCOProgramBuilder::finalize() {
   checkFinalized();
-
-  auto exitCode = intConstant(0);
-  return finalize({exitCode});
+  func::FuncOp mainFunc = nullptr;
+  for (auto op : cast<ModuleOp>(module).getOps<func::FuncOp>()) {
+    if (op.getName() == "main") {
+      mainFunc = op;
+      break;
+    }
+  }
+  if (!mainFunc) {
+    llvm::reportFatalUsageError("Could not find main function");
+  }
+
+  auto resultTypes = mainFunc.getFunctionType().getResults();
+  if (resultTypes.empty()) {
+    return finalize({});
+  }
+  if (resultTypes.size() == 1 && resultTypes.front().isSignlessInteger(64)) {
+    auto exitCode = intConstant(0);
+    return finalize({exitCode});
+  }
+  llvm::reportFatalUsageError(
+      "Use finalize(returnValues) when main has custom return types");
 }
 
 OwningOpRef<ModuleOp> QCOProgramBuilder::finalize(ValueRange returnValues) {
   checkFinalized();
@@
   if (!mainFunc) {
     llvm::reportFatalUsageError("Could not find main function");
   }
+
+  auto resultTypes = mainFunc.getFunctionType().getResults();
+  if (returnValues.size() != resultTypes.size()) {
+    llvm::reportFatalUsageError(
+        "Return values must match the main function return arity");
+  }
+  for (auto [value, type] : llvm::zip_equal(returnValues, resultTypes)) {
+    if (value.getType() != type) {
+      llvm::reportFatalUsageError(
+          "Return values must match the main function return types");
+    }
+  }
@@
   func::ReturnOp::create(*this, returnValues);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp` around lines 1105 - 1165,
The finalize paths currently emit a func::ReturnOp without validating or
matching the main function's declared result types (initialize(TypeRange) may
set a non-i64 or different arity), so update QCOProgramBuilder::finalize() and
QCOProgramBuilder::finalize(ValueRange) to first query
mainFunc.getType().getResults(), assert that returnValues.size() equals that
result list size and that each returnValues[i].getType() == expectedResults[i],
and if the no-arg finalize() is used produce appropriate default constants per
expectedResults type (not always an i64 zero) instead of unconditionally calling
intConstant(0); then create the func::ReturnOp with validated/matched values
(and emit a clear error via llvm::reportFatalUsageError if arity/types
mismatch). Ensure references: finalize(), finalize(ValueRange),
initialize(TypeRange), func::ReturnOp::create, mainFunc.getType().getResults(),
and intConstant are used when implementing the checks and per-type default
constant construction.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Line 14: Replace the incorrect changelog bullet "✨ Add Dead Gate Elimination
Pattern ([`#1755`]) ([**`@DRovara`**])" with an entry that references this PR
(`#1756`) and accurately describes the implemented feature (measurement lifting /
classical-control replacement), e.g., update the PR number to 1756 and change
the description to mention "Measurement lifting and classical-control
replacement" so the released notes match the actual code changes.

In `@mlir/include/mlir/Dialect/QCO/QCOUtils.h`:
- Around line 240-276: The header's inline function checkAndRemoveDeadGate uses
QCO-specific types (SinkOp, UnitaryOpInterface, MeasureOp, IfOp, Reset) but
doesn't include their declarations, making the header order-dependent; fix by
making the header self-contained: add a direct include of the QCO ops/declaring
header (e.g., include "mlir/Dialect/QCO/QCOOps.h") at the top of
mlir/include/mlir/Dialect/QCO/QCOUtils.h so dyn_cast/isa and method calls
resolve, or alternatively move the implementation of checkAndRemoveDeadGate out
of the header into a .cpp that includes QCOOps.h if you prefer to avoid
additional header dependencies.

In `@mlir/lib/Dialect/QCO/Transforms/Optimizations/ReplaceClassicalControls.cpp`:
- Around line 143-155: The lambda passed to IfOp::create currently always builds
a CtrlOp (via CtrlOp::create) even when newControls (derived from toKeep.size())
is empty, which yields invalid IR because qco::CtrlOp requires >=1 control;
modify the lambda to branch on newControls.empty(): if non-empty, keep the
existing flow (create CtrlOp, inline newCtrl.getRegion() into op.getRegion(),
return newCtrl.getOutputQubits()); if empty, instead directly clone or move the
original unitary body into the IfOp region (use rewriter to inline/clone the
original region/body into the new IfOp region) and return the appropriate output
qubits, ensuring no zero-control CtrlOp is constructed (affects
ReplaceClassicalControls.cpp paths like replaceClassicalControlsOnlyControl and
replaceClassicalControlsTwoOfTwoControls).
- Around line 58-63: The isDiagonal predicate omits RzOp so controlled-rz paths
are not recognized; update the isDiagonal function (the dyn_cast<InvOp> branch
and the isa<> return) to include RzOp among the recognized diagonal ops (i.e.,
add RzOp to the isa<ZOp, SOp, TOp, POp, SdgOp, TdgOp, IdOp> list) so
controlled-`rz` will take the diagonal-swap path.

In
`@mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_replace_classical_controls.cpp`:
- Around line 377-380: Remove the leftover debug printing by deleting the
module->dump() call at the end of the test in
test_qco_replace_classical_controls.cpp; keep the existing assertions
runReplaceClassicalControlsPass(module.get()).succeeded() and
runCanonicalizerPass(reference.get()).succeeded() but remove the module->dump()
line so the test no longer prints the transformed module to CI logs.

---

Outside diff comments:
In `@mlir/include/mlir/Dialect/QCO/IR/QCOOps.td`:
- Around line 1071-1077: The QCO region wrapper ops declare the Pure trait which
expands to AlwaysSpeculatable + NoMemoryEffect and incorrectly
duplicates/overrides the region-modelled effects; remove the Pure trait from the
op definitions for CtrlOp, InvOp, and IfOp (the TD defs named CtrlOp, InvOp,
IfOp) so they only keep RecursiveMemoryEffects (and other existing traits like
UnitaryOpInterface, AttrSizedOperandSegments, AttrSizedResultSegments,
SameOperandsAndResultType, SameOperandsAndResultShape,
SingleBlockImplicitTerminator) to ensure effects are solely driven by the nested
region.

In `@mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp`:
- Around line 1105-1165: The finalize paths currently emit a func::ReturnOp
without validating or matching the main function's declared result types
(initialize(TypeRange) may set a non-i64 or different arity), so update
QCOProgramBuilder::finalize() and QCOProgramBuilder::finalize(ValueRange) to
first query mainFunc.getType().getResults(), assert that returnValues.size()
equals that result list size and that each returnValues[i].getType() ==
expectedResults[i], and if the no-arg finalize() is used produce appropriate
default constants per expectedResults type (not always an i64 zero) instead of
unconditionally calling intConstant(0); then create the func::ReturnOp with
validated/matched values (and emit a clear error via llvm::reportFatalUsageError
if arity/types mismatch). Ensure references: finalize(), finalize(ValueRange),
initialize(TypeRange), func::ReturnOp::create, mainFunc.getType().getResults(),
and intConstant are used when implementing the checks and per-type default
constant construction.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 404b26d5-75c2-4dfc-86eb-990ccae1574b

📥 Commits

Reviewing files that changed from the base of the PR and between e868fe9 and c1fda94.

📒 Files selected for processing (17)
  • CHANGELOG.md
  • mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h
  • mlir/include/mlir/Dialect/QCO/IR/QCODialect.td
  • mlir/include/mlir/Dialect/QCO/IR/QCOOps.td
  • mlir/include/mlir/Dialect/QCO/QCOUtils.h
  • mlir/include/mlir/Dialect/QCO/Transforms/Passes.td
  • mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp
  • mlir/lib/Dialect/QCO/IR/Operations/MeasureOp.cpp
  • mlir/lib/Dialect/QCO/IR/Operations/ResetOp.cpp
  • mlir/lib/Dialect/QCO/IR/QCOOps.cpp
  • mlir/lib/Dialect/QCO/IR/SCF/IfOp.cpp
  • mlir/lib/Dialect/QCO/Transforms/Optimizations/MeasurementLifting.cpp
  • mlir/lib/Dialect/QCO/Transforms/Optimizations/ReplaceClassicalControls.cpp
  • mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp
  • mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt
  • mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_measurement_lifting.cpp
  • mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_replace_classical_controls.cpp

Comment thread CHANGELOG.md Outdated
Comment thread mlir/include/mlir/Dialect/QCO/QCOUtils.h Outdated
Comment thread mlir/lib/Dialect/QCO/Transforms/Optimizations/ReplaceClassicalControls.cpp Outdated
@codecov

codecov Bot commented Jun 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.57534% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...nsforms/Optimizations/ReplaceClassicalControls.cpp 94.2% 4 Missing ⚠️
...CO/Transforms/Optimizations/MeasurementLifting.cpp 98.6% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@DRovara DRovara mentioned this pull request Jun 3, 2026
11 tasks
@mergify mergify Bot added the conflict label Jun 3, 2026
@DRovara
DRovara force-pushed the mlir/replace-classical-controls branch from ad1be84 to febb216 Compare July 14, 2026 04:51
@mergify mergify Bot removed the conflict label Jul 14, 2026
@mergify mergify Bot added the conflict label Jul 15, 2026
@DRovara

DRovara commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_measurement_lifting.cpp`:
- Around line 534-574: Correct the expected module in
dontLiftMeasurementMultipleGatesInControl by keeping the control region before
measuring its control qubit, matching the transformed program’s operation order.
Update the referenceBuilder sequence so r0S0 is passed through ctrl first, then
measure the resulting control state, while preserving the multi-gate z/x control
body and final comparison.

In
`@mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_replace_classical_controls.cpp`:
- Line 137: Remove the const qualifier from the ValueRange parameters in all
five lambdas, including those in the QCO optimization test locations identified
by the symbols or surrounding lambda signatures. Keep the parameter types as
ValueRange and change only the signature qualifiers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 74274d8e-01d5-487e-9595-db52fcb37ce0

📥 Commits

Reviewing files that changed from the base of the PR and between e78c99a and 5e482a9.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h
  • mlir/include/mlir/Dialect/QCO/Transforms/Passes.td
  • mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp
  • mlir/lib/Dialect/QCO/Transforms/Optimizations/MeasurementLifting.cpp
  • mlir/lib/Dialect/QCO/Transforms/Optimizations/ReplaceClassicalControls.cpp
  • mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt
  • mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_measurement_lifting.cpp
  • mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_replace_classical_controls.cpp

Comment on lines +534 to +574
TEST_F(QCOMeasurementLiftingTest, dontLiftMeasurementMultipleGatesInControl) {
programBuilder.initialize(
{programBuilder.getI1Type(), referenceBuilder.getI1Type()});
auto q0S0 = programBuilder.allocQubit();
auto q1S0 = programBuilder.allocQubit();

auto [q0S1, q1S1] = programBuilder.ctrl({q0S0}, q1S0, [&](Value target) {
auto t = programBuilder.z(target);
return programBuilder.x(t);
});

auto [q0S2, c0] = programBuilder.measure(q0S1);
auto [q1S2, c1] = programBuilder.measure(q1S1);

programBuilder.sink(q0S2);
programBuilder.sink(q1S2);
module = programBuilder.finalize({c0, c1});

referenceBuilder.initialize(
{referenceBuilder.getI1Type(), referenceBuilder.getI1Type()});
auto r0S0 = referenceBuilder.allocQubit();
auto r1S0 = referenceBuilder.allocQubit();

auto [r0S1, cr0] = referenceBuilder.measure(r0S0);

auto [r0S2, r1S1] = referenceBuilder.ctrl({r0S1}, r1S0, [&](Value target) {
auto t = referenceBuilder.z(target);
return referenceBuilder.x(t);
});

auto [r1S2, cr1] = referenceBuilder.measure(r1S1);

referenceBuilder.sink(r0S2);
referenceBuilder.sink(r1S2);
reference = referenceBuilder.finalize({cr0, cr1});

ASSERT_TRUE(runMeasurementLiftingPass(module.get()).succeeded());
ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded());

EXPECT_TRUE(
areModulesEquivalentWithPermutations(module.get(), reference.get()));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep the measurement after the multi-gate control region.

This test says lifting must not occur, but the reference measures r0S0 before ctrl, thereby expecting the forbidden transformation. A correct implementation that leaves the measurement in place will fail this test.

Proposed correction
-  auto [r0S1, cr0] = referenceBuilder.measure(r0S0);
-
-  auto [r0S2, r1S1] = referenceBuilder.ctrl({r0S1}, r1S0, [&](Value target) {
+  auto [r0S1, r1S1] = referenceBuilder.ctrl({r0S0}, r1S0, [&](Value target) {
     auto t = referenceBuilder.z(target);
     return referenceBuilder.x(t);
   });
 
+  auto [r0S2, cr0] = referenceBuilder.measure(r0S1);
   auto [r1S2, cr1] = referenceBuilder.measure(r1S1);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
TEST_F(QCOMeasurementLiftingTest, dontLiftMeasurementMultipleGatesInControl) {
programBuilder.initialize(
{programBuilder.getI1Type(), referenceBuilder.getI1Type()});
auto q0S0 = programBuilder.allocQubit();
auto q1S0 = programBuilder.allocQubit();
auto [q0S1, q1S1] = programBuilder.ctrl({q0S0}, q1S0, [&](Value target) {
auto t = programBuilder.z(target);
return programBuilder.x(t);
});
auto [q0S2, c0] = programBuilder.measure(q0S1);
auto [q1S2, c1] = programBuilder.measure(q1S1);
programBuilder.sink(q0S2);
programBuilder.sink(q1S2);
module = programBuilder.finalize({c0, c1});
referenceBuilder.initialize(
{referenceBuilder.getI1Type(), referenceBuilder.getI1Type()});
auto r0S0 = referenceBuilder.allocQubit();
auto r1S0 = referenceBuilder.allocQubit();
auto [r0S1, cr0] = referenceBuilder.measure(r0S0);
auto [r0S2, r1S1] = referenceBuilder.ctrl({r0S1}, r1S0, [&](Value target) {
auto t = referenceBuilder.z(target);
return referenceBuilder.x(t);
});
auto [r1S2, cr1] = referenceBuilder.measure(r1S1);
referenceBuilder.sink(r0S2);
referenceBuilder.sink(r1S2);
reference = referenceBuilder.finalize({cr0, cr1});
ASSERT_TRUE(runMeasurementLiftingPass(module.get()).succeeded());
ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded());
EXPECT_TRUE(
areModulesEquivalentWithPermutations(module.get(), reference.get()));
TEST_F(QCOMeasurementLiftingTest, dontLiftMeasurementMultipleGatesInControl) {
programBuilder.initialize(
{programBuilder.getI1Type(), referenceBuilder.getI1Type()});
auto q0S0 = programBuilder.allocQubit();
auto q1S0 = programBuilder.allocQubit();
auto [q0S1, q1S1] = programBuilder.ctrl({q0S0}, q1S0, [&](Value target) {
auto t = programBuilder.z(target);
return programBuilder.x(t);
});
auto [q0S2, c0] = programBuilder.measure(q0S1);
auto [q1S2, c1] = programBuilder.measure(q1S1);
programBuilder.sink(q0S2);
programBuilder.sink(q1S2);
module = programBuilder.finalize({c0, c1});
referenceBuilder.initialize(
{referenceBuilder.getI1Type(), referenceBuilder.getI1Type()});
auto r0S0 = referenceBuilder.allocQubit();
auto r1S0 = referenceBuilder.allocQubit();
auto [r0S1, r1S1] = referenceBuilder.ctrl({r0S0}, r1S0, [&](Value target) {
auto t = referenceBuilder.z(target);
return referenceBuilder.x(t);
});
auto [r0S2, cr0] = referenceBuilder.measure(r0S1);
auto [r1S2, cr1] = referenceBuilder.measure(r1S1);
referenceBuilder.sink(r0S2);
referenceBuilder.sink(r1S2);
reference = referenceBuilder.finalize({cr0, cr1});
ASSERT_TRUE(runMeasurementLiftingPass(module.get()).succeeded());
ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded());
EXPECT_TRUE(
areModulesEquivalentWithPermutations(module.get(), reference.get()));
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_measurement_lifting.cpp`
around lines 534 - 574, Correct the expected module in
dontLiftMeasurementMultipleGatesInControl by keeping the control region before
measuring its control qubit, matching the transformed program’s operation order.
Update the referenceBuilder sequence so r0S0 is passed through ctrl first, then
measure the resulting control state, while preserving the multi-gate z/x control
body and final comparison.


auto [q01S1, q2S1] =
programBuilder.ctrl({q0S1, q1S0}, {q2S0},
[&](const ValueRange targets) -> SmallVector<Value> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove const from the ValueRange parameters.

Core MLIR handle types should not be const-qualified in signatures.

Proposed fix
-                          [&](const ValueRange targets) -> SmallVector<Value> {
+                          [&](ValueRange targets) -> SmallVector<Value> {

Apply this replacement at all five locations.

Based on learnings, avoid const qualifiers on core MLIR types in function and lambda signatures.

Also applies to: 193-193, 251-251, 395-395, 446-446

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_replace_classical_controls.cpp`
at line 137, Remove the const qualifier from the ValueRange parameters in all
five lambdas, including those in the QCO optimization test locations identified
by the symbols or surrounding lambda signatures. Keep the parameter types as
ValueRange and change only the signature qualifiers.

Source: Learnings

@mergify mergify Bot removed the conflict label Jul 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++ Anything related to C++ code feature New feature or request MLIR Anything related to MLIR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant