Skip to content

✨ Add pass and patterns for Classical Control Replacement#1756

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

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

Conversation

@DRovara
Copy link
Copy Markdown
Collaborator

@DRovara DRovara commented Jun 2, 2026

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
Copy link
Copy Markdown
Collaborator Author

DRovara commented Jun 2, 2026

@CodeRabbit full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 2, 2026

✅ Actions performed

Full review triggered.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Dead Gate Elimination Pattern for quantum circuit optimization
    • Introduced Measurement Lifting pass for circuit reordering
    • Introduced Replace Classical Controls pass for control-flow optimization
    • Enhanced program builder with configurable return types and boolean constant support
  • Chores

    • Updated changelog and added comprehensive test coverage for new optimizations

Walkthrough

This PR introduces dead gate elimination patterns and two circuit optimization passes (measurement lifting and classical control replacement) for the QCO quantum dialect. Changes span operation trait annotations, builder API extensions, canonicalization patterns, optimization pass implementations, and comprehensive test coverage.

Changes

Dead Gate Elimination and Canonicalization

Layer / File(s) Summary
Operation traits and dialect canonicalization setup
mlir/include/mlir/Dialect/QCO/IR/QCOOps.td, mlir/include/mlir/Dialect/QCO/IR/QCODialect.td
Mark QCO operations as side-effect-free using Pure trait; remove [MemRead] qualifiers from quantum operands across MeasureOp, ResetOp, unitary ops, BarrierOp, YieldOp, CtrlOp, InvOp, and IfOp; enable dialect canonicalization.
Dead gate elimination utility and infrastructure
mlir/include/mlir/Dialect/QCO/QCOUtils.h
Introduce checkAndRemoveDeadGate helper to eliminate operations when all users are SinkOps, with type-specific replacement logic for UnitaryOpInterface, MeasureOp, and IfOp.
Canonicalization patterns for dead gate removal
mlir/lib/Dialect/QCO/IR/Operations/MeasureOp.cpp, mlir/lib/Dialect/QCO/IR/Operations/ResetOp.cpp, mlir/lib/Dialect/QCO/IR/SCF/IfOp.cpp, mlir/lib/Dialect/QCO/IR/QCOOps.cpp
Implement and register DeadMeasurementRemoval, DeadResetRemoval, and DeadIfRemoval patterns for operation-specific canonicalization; add dialect-level DeadGateElimination pattern for UnitaryOpInterface operations with memory-effect checking.
Dead gate elimination test cases
mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp
Add CheckDeadGateElimination and CheckIfOpDeadGateElimination tests verifying dead gate removal behavior with permutation-invariant module equivalence.

Builder API Extensions

Layer / File(s) Summary
Program builder API declarations and implementations
mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h, mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp
Add initialize(TypeRange returnTypes) overload for configurable main function return types, boolConstant(bool) helper for i1 constants, and finalize(ValueRange returnValues) overload for flexible return values.

Measurement and Control-Flow Optimization Passes

Layer / File(s) Summary
Pass declarations and core infrastructure
mlir/include/mlir/Dialect/QCO/Transforms/Passes.td
Declare MeasurementLifting and ReplaceClassicalControls passes targeting mlir::ModuleOp with dependent dialects QCODialect and ArithDialect.
Measurement lifting patterns and pass
mlir/lib/Dialect/QCO/Transforms/Optimizations/MeasurementLifting.cpp
Implement measurement-lifting transformation with helper predicates (isInverting, isDiagonal), three rewrite patterns (above phase gates, above inverting gates, above controls), and greedy pattern application in MeasurementLifting pass.
Measurement lifting test suite
mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_measurement_lifting.cpp
Add test fixture and 10 test cases covering measurement lifting across control scenarios, parametrized gates, single-qubit gates, phase gates, and diagonal-gate controls with permutation-invariant equivalence validation.
Classical control replacement patterns and pass
mlir/lib/Dialect/QCO/Transforms/Optimizations/ReplaceClassicalControls.cpp
Implement classical-control-replacement transformation with helpers to detect predecessor measurement outcomes and partition controls; build IfOp with conjunction conditions and nested CtrlOp in greedy pass application.
Classical control replacement test suite
mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_replace_classical_controls.cpp
Add test fixture and 8 test cases covering classical control replacement scenarios (single/multiple controls, diagonal swapping) with permutation-invariant module equivalence validation.

Test Infrastructure and Documentation

Layer / File(s) Summary
Build configuration and changelog
mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt, CHANGELOG.md
Add test files to CMake executable targets; record PR #1755 dead gate elimination addition in changelog.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • munich-quantum-toolkit/core#1506: Introduces the qco.if operation and SCF-style IfOp infrastructure that this PR extends with DeadIfRemoval canonicalization.
  • munich-quantum-toolkit/core#1569: Renames qco.dealloc to qco.sink operations, which directly enables the dead-gate elimination logic's "only users are SinkOp" condition.

Suggested reviewers

  • burgholzer
  • denialhaag

Poem

🐰 A rabbit hops through quantum gates,
Removing those that nothing waits—
Dead gates fall, measurements rise high,
Classical controls catch the eye.
With traits pure and passes bright,
The circuit optimization takes flight! ✨

🚥 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 PR title partially captures a key component (Classical Control Replacement pass) but obscures the broader context that this is part 2 of 3 for Qubit Reuse Implementation, and the emoji usage adds noise.
Description check ✅ Passed The PR description covers the main objective, acknowledges it as part of a multi-PR feature, discloses AI assistance, and marks applicable checklist items, but does not explicitly address all required sections with adequate detail.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch mlir/replace-classical-controls

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Infer (1.2.0)
mlir/lib/Dialect/QCO/IR/Operations/MeasureOp.cpp

mlir/lib/Dialect/QCO/IR/Operations/MeasureOp.cpp:11:10: fatal error: 'mlir/Dialect/QCO/IR/QCOOps.h' file not found
11 | #include "mlir/Dialect/QCO/IR/QCOOps.h"
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
Error: the following clang command did not run successfully:
/opt/infer-linux-x86_64-v1.2.0/lib/infer/facebook-clang-plugins/clang/install/bin/clang-18
@/tmp/coderabbit-infer/c1fda941ec958c2ce928531f8e60043b713c821c-9980b91e00cfd1b9/tmp/clang_command_.tmp.95ee66.txt
++Contents of '/tmp/coderabbit-infer/c1fda941ec958c2ce928531f8e60043b713c821c-9980b91e00cfd1b9/tmp/clang_command_.tmp.95ee66.txt':
"-cc1" "-load"
"/opt/infer-linux-x86_64-v1.2.0/lib/infer/infer/bin/../../facebook-clang-plugins/libtooling/build/FacebookClangPlugin.dylib"
"-add-plugin" "BiniouASTExporter" "-plugin-arg-BiniouASTExporter" "-"
"-plugin-arg-BiniouASTExporter" "PREPEND_CURRENT_DIR=1"
"-plugin-arg-BiniouASTExporter" "MAX_STRING_SIZE=65535" "-cc1" "-triple"

... [truncated 1160 characters] ...

ude"
"-internal-isystem" "/usr/local/include" "-internal-isystem"
"/usr/lib/gcc/x86_64-linux-gnu/12/../../../../x86_64-linux-gnu/include"
"-internal-externc-isystem" "/usr/include/x86_64-linux-gnu"
"-internal-externc-isystem" "/include" "-internal-externc-isystem"
"/usr/include" "-Wno-ignored-optimization-argument" "-Wno-everything"
"-fdeprecated-macro" "-ferror-limit" "19" "-fgnuc-version=4.2.1"
"-fskip-odr-check-in-gmf" "-fcxx-exceptions" "-fexceptions"
"-D__GCC_HAVE_DWARF2_CFI_ASM=1" "-o"
"/tmp/coderabbit-infer/9980b91e00cfd1b9/file.o" "-x" "c++"
"mlir/lib/Dialect/QCO/IR/Operations/MeasureOp.cpp" "-O0" "-fno-builtin"
"-include"
"/opt/infer-linux-x86_64-v1.2.0/lib/infer/infer/bin/../lib/clang_wrappers/global_defines.h"
"-Wno-everything"

mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp

mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp:11:10: fatal error: 'mlir/Dialect/QCO/Builder/QCOProgramBuilder.h' file not found
11 | #include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h"
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
Error: the following clang command did not run successfully:
/opt/infer-linux-x86_64-v1.2.0/lib/infer/facebook-clang-plugins/clang/install/bin/clang-18
@/tmp/coderabbit-infer/c1fda941ec958c2ce928531f8e60043b713c821c-b57f3385e6a1e027/tmp/clang_command_.tmp.109469.txt
++Contents of '/tmp/coderabbit-infer/c1fda941ec958c2ce928531f8e60043b713c821c-b57f3385e6a1e027/tmp/clang_command_.tmp.109469.txt':
"-cc1" "-load"
"/opt/infer-linux-x86_64-v1.2.0/lib/infer/infer/bin/../../facebook-clang-plugins/libtooling/build/FacebookClangPlugin.dylib"
"-add-plugin" "BiniouASTExporter" "-plugin-arg-BiniouASTExporter" "-"
"-plugin-arg-BiniouASTExporter" "PREPEND_CURRENT_DIR=1"
"-plugin-arg-BiniouASTExpo

... [truncated 1220 characters] ...

e"
"-internal-isystem" "/usr/local/include" "-internal-isystem"
"/usr/lib/gcc/x86_64-linux-gnu/12/../../../../x86_64-linux-gnu/include"
"-internal-externc-isystem" "/usr/include/x86_64-linux-gnu"
"-internal-externc-isystem" "/include" "-internal-externc-isystem"
"/usr/include" "-Wno-ignored-optimization-argument" "-Wno-everything"
"-fdeprecated-macro" "-ferror-limit" "19" "-fgnuc-version=4.2.1"
"-fskip-odr-check-in-gmf" "-fcxx-exceptions" "-fexceptions"
"-D__GCC_HAVE_DWARF2_CFI_ASM=1" "-o"
"/tmp/coderabbit-infer/b57f3385e6a1e027/file.o" "-x" "c++"
"mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp" "-O0" "-fno-builtin"
"-include"
"/opt/infer-linux-x86_64-v1.2.0/lib/infer/infer/bin/../lib/clang_wrappers/global_defines.h"
"-Wno-everything"

mlir/lib/Dialect/QCO/IR/Operations/ResetOp.cpp

mlir/lib/Dialect/QCO/IR/Operations/ResetOp.cpp:11:10: fatal error: 'mlir/Dialect/QCO/IR/QCOOps.h' file not found
11 | #include "mlir/Dialect/QCO/IR/QCOOps.h"
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
mlir/lib/Dialect/QCO/IR/Operations/ResetOp.cpp:74:3-10: ERROR translating statement 'ReturnStmt'
Aborting translation of method 'originatesFromQTensorAlloc' in file 'mlir/lib/Dialect/QCO/IR/Operations/ResetOp.cpp': "Assert_failure src/clang/cAst_utils.ml:249:53"
Uncaught Internal Error: "Assert_failure src/clang/cAst_utils.ml:249:53"
Error backtrace:
Raised at ClangFrontend__CAst_utils.get_decl_from_typ_ptr in file "src/clang/cAst_utils.ml", line 249, characters 53-65
Called from ClangFrontend__CTrans.CTrans_funct.get_destructor_decl_ref in file "src/clang/cTrans.ml", line 658, characters 12-59
Called from ClangFrontend__CTrans.CTrans_funct.destructor_calls.(fun) in file "src/clang/cTrans.ml", line 2048, characters 12-69
Called from Base__List.rev_filter_map.loo

... [truncated 2200 characters] ...

led from ClangFrontend__CTrans.CTrans_funct.instruction_log.(fun) in file "src/clang/cTrans.ml", line 4784, characters 10-1023
Called from ClangFrontend__CTrans.CTrans_funct.instruction in file "src/clang/cTrans.ml" (inlined), line 4765, characters 38-71
Called from ClangFrontend__CTrans.CTrans_funct.exec_with_node_creation in file "src/clang/cTrans.ml" (inlined), line 104, characters 20-38
Called from ClangFrontend__CTrans.CTrans_funct.get_clang_stmt_trans in file "src/clang/cTrans.ml" (inlined), line 5395, characters 4-69
Called from ClangFrontend__CTrans.CTrans_funct.get_custom_stmt_trans in file "src/clang/cTrans.ml", line 5401, characters 8-55
Called from ClangFrontend__CTrans.CTrans_funct.exec_trans_instrs.exec_trans_instrs_rev in file "src/clang/cTrans.ml" (inlined), line 5365, char

  • 7 others

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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
Comment thread mlir/include/mlir/Dialect/QCO/QCOUtils.h
Comment thread mlir/lib/Dialect/QCO/Transforms/Optimizations/ReplaceClassicalControls.cpp Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 2, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

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