Skip to content

✨ Add native gate synthesis MLIR pass and decomposition stack#1665

Draft
simon1hofmann wants to merge 44 commits intomainfrom
native_gate_synthesis
Draft

✨ Add native gate synthesis MLIR pass and decomposition stack#1665
simon1hofmann wants to merge 44 commits intomainfrom
native_gate_synthesis

Conversation

@simon1hofmann
Copy link
Copy Markdown
Contributor

Description

Introduces a native-gate-synthesis transform on QCO modules that lowers arbitrary supported unitaries to a comma-separated native gate menu, with scoring over two-qubit count, single-qubit count, and local depth.

User-visible behavior
--native-gates=<menu> on mqt-cc (empty menu = no-op), aligned with pass options documented in Passes.td (native-gates, score-weight-twoq, score-weight-oneq, score-weight-depth).

Fixes #(issue)

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.

simon1hofmann and others added 13 commits April 22, 2026 16:04
…use `std::exp` for complex exponentiation instead of `std::polar`.
… basis decomposer, Euler decomposition, and associated helper functions. This update introduces new headers and source files for managing gate sequences, unitary matrices, and decomposition strategies, enhancing the framework's capabilities for quantum circuit transformations.

Co-authored-by: Tamino Bauknecht <dev@tb6.eu>
… and additional test cases for Euler and Weyl decompositions.
…, including new utility functions and validation for gate sequences.
@simon1hofmann simon1hofmann self-assigned this Apr 24, 2026
@simon1hofmann simon1hofmann added enhancement Improvement of existing feature MLIR Anything related to MLIR labels Apr 24, 2026
@simon1hofmann simon1hofmann added this to the MLIR Support milestone Apr 24, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 27, 2026

Codecov Report

❌ Patch coverage is 98.71795% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ect/QCO/IR/Operations/StandardGates/XXPlusYYOp.cpp 83.3% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@simon1hofmann simon1hofmann marked this pull request as ready for review April 27, 2026 12:05
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: 1

♻️ Duplicate comments (2)
mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/PassTwoQubitWindows.cpp (2)

256-261: ⚠️ Potential issue | 🔴 Critical

Use-after-erase: it0/it1 are dereferenced after closeBlock erases their buckets.

closeBlock(it0->second) erases block.wireA and block.wireB from wireToBlock, and since v0 (the key of it0) equals one of those wires, it0 is invalidated. The subsequent it0->second != it1->second and closeBlock(it1->second) then read invalidated iterators — UB by DenseMap::erase's contract. Capture both indices into locals before any close call.

🐛 Proposed fix
-    if (tracked0) {
-      closeBlock(it0->second);
-    }
-    if (tracked1 && (!tracked0 || it0->second != it1->second)) {
-      closeBlock(it1->second);
-    }
+    const size_t idx0 = tracked0 ? it0->second : 0;
+    const size_t idx1 = tracked1 ? it1->second : 0;
+    if (tracked0) {
+      closeBlock(idx0);
+    }
+    if (tracked1 && (!tracked0 || idx0 != idx1)) {
+      closeBlock(idx1);
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/PassTwoQubitWindows.cpp`
around lines 256 - 261, The code dereferences iterators it0/it1 after calling
closeBlock, which erases entries from wireToBlock and invalidates those
iterators; to fix, read and store the needed block indices (e.g., auto b0 =
it0->second; auto b1 = it1->second) and any boolean flags (tracked0/tracked1)
into local variables before invoking closeBlock, then call closeBlock(b0) and
closeBlock(b1) using those locals and only compare locals (b0 != b1) instead of
it0/it1; update the logic around tracked0/tracked1 and the closeBlock calls
accordingly (functions/variables: closeBlock, it0, it1, tracked0, tracked1,
wireToBlock, block.wireA, block.wireB).

78-106: ⚠️ Potential issue | 🟡 Minor

Emission failure in materializeSingleTwoQubitBlock is still not propagated.

emitTwoQubitGateSequenceAtLoc failure on Lines 91-96 only emits a diagnostic and returns; materialize() (Lines 325-345) and the upstream pass driver in Pass.cpp never see a failure signal, so the pass cannot reliably signalPassFailure() for this path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/PassTwoQubitWindows.cpp`
around lines 78 - 106, The helper materializeSingleTwoQubitBlock currently
swallows emitTwoQubitGateSequenceAtLoc failures by only emitting a diagnostic;
change materializeSingleTwoQubitBlock to return a LogicalResult (or bool) and
return failure when emitTwoQubitGateSequenceAtLoc fails, then update the caller
materialize to check that return value and propagate failure (return failure) up
so the pass driver (Pass.cpp) can call signalPassFailure(); update all call
sites of materializeSingleTwoQubitBlock accordingly and ensure the function
signature and callers consistently use the LogicalResult type and/or LLVM
succeeded/failed helpers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/PassTwoQubitWindows.cpp`:
- Line 32: The code uses std::ranges::reverse_view and includes <ranges>;
replace that with LLVM's utility by including "llvm/ADT/STLExtras.h", remove the
<ranges> include, and change uses of std::ranges::reverse_view (in
PassTwoQubitWindows.cpp, e.g., around the loop/iteration that references
reverse_view) to use llvm::reverse or llvm::reverse(iterable) as provided by
STLExtras so the code follows MLIR/LLVM style and drops the STL ranges
dependency.

---

Duplicate comments:
In `@mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/PassTwoQubitWindows.cpp`:
- Around line 256-261: The code dereferences iterators it0/it1 after calling
closeBlock, which erases entries from wireToBlock and invalidates those
iterators; to fix, read and store the needed block indices (e.g., auto b0 =
it0->second; auto b1 = it1->second) and any boolean flags (tracked0/tracked1)
into local variables before invoking closeBlock, then call closeBlock(b0) and
closeBlock(b1) using those locals and only compare locals (b0 != b1) instead of
it0/it1; update the logic around tracked0/tracked1 and the closeBlock calls
accordingly (functions/variables: closeBlock, it0, it1, tracked0, tracked1,
wireToBlock, block.wireA, block.wireB).
- Around line 78-106: The helper materializeSingleTwoQubitBlock currently
swallows emitTwoQubitGateSequenceAtLoc failures by only emitting a diagnostic;
change materializeSingleTwoQubitBlock to return a LogicalResult (or bool) and
return failure when emitTwoQubitGateSequenceAtLoc fails, then update the caller
materialize to check that return value and propagate failure (return failure) up
so the pass driver (Pass.cpp) can call signalPassFailure(); update all call
sites of materializeSingleTwoQubitBlock accordingly and ensure the function
signature and callers consistently use the LogicalResult type and/or LLVM
succeeded/failed helpers.
🪄 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: 5e5ef0ac-1ce5-4ceb-a498-d80981852c34

📥 Commits

Reviewing files that changed from the base of the PR and between 39509e3 and 5f8ce03.

📒 Files selected for processing (1)
  • mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/PassTwoQubitWindows.cpp

Comment thread mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/PassTwoQubitWindows.cpp Outdated
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: 6

♻️ Duplicate comments (2)
mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/Pass.cpp (1)

47-47: 🧹 Nitpick | 🔵 Trivial

Prefer llvm::reverse over std::ranges::reverse_view.

Per the project's coding guideline for mlir/, use LLVM utilities. The same comment was raised on PassTwoQubitWindows.cpp and addressed there (now uses llvm::reverse); please apply the same change here so the <ranges> include can be dropped.

♻️ Proposed refactor
-#include <ranges>
 `#include` <utility>
-  for (auto& op : std::ranges::reverse_view(run.ops)) {
-    Operation* toErase = op.getOperation();
+  for (auto& op : llvm::reverse(run.ops)) {
+    Operation* toErase = op.getOperation();
     rewriter.eraseOp(toErase);
   }

As per coding guidelines: "PREFER LLVM data structures and methods in mlir/ (llvm::SmallVector, llvm::function_ref, etc.) over the STL".

Also applies to: 164-167

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/Pass.cpp` at line 47, Replace
uses of std::ranges::reverse_view in this file with llvm::reverse (e.g., wrap
the container or iterator range with llvm::reverse(...) or use llvm::make_range
+ llvm::reverse) and remove the `#include` <ranges> line; update the code
locations around the current usages (noted near lines 164-167) to call
llvm::reverse on the existing container/range so LLVM utilities are used instead
of std::ranges::reverse_view.
mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/SingleQubit.cpp (1)

372-380: ⚠️ Potential issue | 🟠 Major

U3 matrix synthesis still emits a spurious global phase of e^(i(φ+λ)/2).

This is the same issue raised on a prior commit (d98c2f50) and does not appear to have been addressed in the latest revision. After projecting matrix to SU(2), m = RZ(angles[1])·RY(angles[0])·RZ(angles[2]) (pure ZYZ Euler form). However, emitting u(angles[0], angles[1], angles[2]) produces U(θ,φ,λ) = e^(i(φ+λ)/2)·RZ(φ)·RY(θ)·RZ(λ) = e^(i(angles[1]+angles[2])/2) · m per the project's OpenQASM 2 U-gate convention. The current emitGPhaseIfNonTrivial(rewriter, loc, phase) only restores the determinant phase but leaves the U-gate intrinsic e^(i(φ+λ)/2) factor uncompensated, so the emitted sequence equals the original unitary only up to global phase — contrary to the comment on Lines 369–371.

This is masked by tests using isEquivalentUpToGlobalPhase, but becomes observable when the synthesized op is wrapped in a CtrlOp (controlled-version constructions). Per the established convention in this repo (see QuaternionMergeRotationGates), GPhase corrections must be unconditional.

🛡️ Proposed fix
   const auto angles = decomposition::EulerDecomposition::anglesFromUnitary(
       m, decomposition::EulerBasis::ZYZ);
-  emitGPhaseIfNonTrivial(rewriter, loc, phase);
+  // U(θ,φ,λ) = e^(i(φ+λ)/2)·RZ(φ)·RY(θ)·RZ(λ); subtract its intrinsic
+  // phase so the emitted GPhase + UOp pair matches `matrix` exactly.
+  emitGPhaseIfNonTrivial(rewriter, loc,
+                         phase - (angles[1] + angles[2]) / 2.0);
   return e.u(inQubit, angles[0], angles[1], angles[2]);

Please verify that anglesFromUnitary(m, ZYZ) returns pure Euler angles (not U-gate-aware angles) — if it already accounts for the U-gate intrinsic phase, the existing code would be correct:

#!/bin/bash
# Inspect anglesFromUnitary to determine its angle convention for ZYZ.
fd -t f 'EulerDecomposition\.cpp' mlir/ --exec cat
fd -t f 'EulerDecomposition\.h' mlir/ --exec cat
# Look for tests that pin down the convention
rg -nP --type=cpp -C5 'anglesFromUnitary.*ZYZ'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/SingleQubit.cpp` around lines
372 - 380, After projecting to SU(2) you use
EulerDecomposition::anglesFromUnitary(m, EulerBasis::ZYZ) which returns raw ZYZ
rotation angles, but emitting the OpenQASM-style U gate via e.u(...) implicitly
multiplies by e^(i(φ+λ)/2), so the current emitGPhaseIfNonTrivial(rewriter, loc,
phase) only restores det(m) and leaves the U-gate intrinsic global phase
uncorrected; fix by adding the U-gate intrinsic phase to the emitted global
phase before the U emission (i.e., compute extra = (angles[1] + angles[2]) / 2.0
and call emitGPhaseIfNonTrivial(rewriter, loc, phase + extra) prior to return
e.u(inQubit, angles[0], angles[1], angles[2]); also verify (or assert in a
comment) that decomposition::EulerDecomposition::anglesFromUnitary(...) indeed
returns pure ZYZ Euler angles and not U-gate-aware angles.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mlir/include/mlir/Dialect/QCO/Transforms/NativeSynthesis/SingleQubit.h`:
- Around line 23-26: The three-line Doxygen block currently sitting between the
includes and namespace should be converted into a proper file-level doc comment
or moved to the specific API docs: either change it to a "/// \\file" style
header (matching PassTwoQubitWindows.h) placed at the top of the file before the
includes, or delete the dangling block and fold its text into the doc comments
for the relevant functions: decomposeToZSXX,
computeSynthesizedSingleQubitLength, and emitSynthesizedSingleQubitFromMatrix;
ensure the final comment attaches to the intended symbol (file-level or the
three functions) rather than to namespace mlir::qco::native_synth and update
wording to match existing project Doxygen style.

In `@mlir/include/mlir/Dialect/QCO/Transforms/NativeSynthesis/TwoQubit.h`:
- Around line 44-57: The SmallVector inline-storage is inconsistent: change
collectSingleQubitCandidates to explicitly use
llvm::SmallVector<SynthesisCandidate<SingleQubitRewritePlan>, 0> so its
signature matches the two-qubit collectors
(collectTwoQubitBasisCandidatesFromMatrix and collectTwoQubitBasisCandidates);
update the declaration of collectSingleQubitCandidates accordingly to pin N=0
for predictable stack footprint and consistent API.

In `@mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/Pass.cpp`:
- Around line 661-673: The code conflates two failure modes: when
selectBestCandidate(...) returns nullptr and when emitTwoQubitGateSequence(...)
returns failure; update the control flow around selectBestCandidate,
emitTwoQubitGateSequence, and the ctrl.emitError call so each path emits a
distinct error message — e.g., if selectBestCandidate(...) is nullptr call
ctrl.emitError("controlled gate not allowed by selected profile"), else (a
candidate existed but emitTwoQubitGateSequence failed) call
ctrl.emitError("failed to emit two-qubit gate sequence for selected candidate")
— keep using rewriter.setInsertionPoint(ctrl) where needed and target the
emitTwoQubitGateSequence(...) failure branch to report the emission/synthesis
error separately.

In
`@mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_decomposition_helpers.cpp`:
- Around line 11-18: Add a direct include of Eigen/Core to make the test
self-contained: update the includes in test_decomposition_helpers.cpp so that
Eigen::Matrix2cd (used in the test at the places referencing Matrix2cd) is
available even if Helpers.h stops including Eigen; specifically add an `#include`
for Eigen/Core near the top alongside the existing includes so uses of
Eigen::Matrix2cd compile independently of transitive includes from Helpers.h.

In
`@mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_native_synthesis_pass_custom_menus.cpp`:
- Around line 169-172: The function onlyAllowsMenuNativeOps currently treats any
qco::RXOp as allowed if spec.allowX && spec.allowSX && spec.allowRZ are true;
tighten this by allowing non-allowRX rx operations only when the RXOp is exactly
the canonical X-equivalent (angle == ±π) — otherwise require spec.allowRX.
Update the RX check in onlyAllowsMenuNativeOps to inspect the qco::RXOp's angle
(use the RXOp accessor, e.g., getAngle()/angle attribute or operand representing
the rotation) and permit it under the X/SX/RZ fallback only when the angle
equals ±M_PI (or canonical ±π value representation used in the codebase); all
other RXOps must fail unless spec.allowRX is true.

In
`@mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_native_synthesis_pass_multi_qubit.cpp`:
- Around line 71-107: In verifyEquivalentAcrossProfiles, hoist the repeated
ternary that builds the circuit-name message into a single local std::string
prefix at the start of the for-loop iteration (use circuitName != nullptr ?
std::string("circuit=") + circuitName + " " : "" to build it), then replace the
four inline ternaries used in the ASSERT_TRUE/EXPECT_TRUE messages (the checks
after computeNQubitUnitaryFromModule, runNativeSynthesis/isNative, and
isEquivalentUpToGlobalPhase) with this prefix + "native-gates=" <<
profileCase.nativeGates so each assertion uses the shared prefix variable
instead of duplicating the ternary.

---

Duplicate comments:
In `@mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/Pass.cpp`:
- Line 47: Replace uses of std::ranges::reverse_view in this file with
llvm::reverse (e.g., wrap the container or iterator range with
llvm::reverse(...) or use llvm::make_range + llvm::reverse) and remove the
`#include` <ranges> line; update the code locations around the current usages
(noted near lines 164-167) to call llvm::reverse on the existing container/range
so LLVM utilities are used instead of std::ranges::reverse_view.

In `@mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/SingleQubit.cpp`:
- Around line 372-380: After projecting to SU(2) you use
EulerDecomposition::anglesFromUnitary(m, EulerBasis::ZYZ) which returns raw ZYZ
rotation angles, but emitting the OpenQASM-style U gate via e.u(...) implicitly
multiplies by e^(i(φ+λ)/2), so the current emitGPhaseIfNonTrivial(rewriter, loc,
phase) only restores det(m) and leaves the U-gate intrinsic global phase
uncorrected; fix by adding the U-gate intrinsic phase to the emitted global
phase before the U emission (i.e., compute extra = (angles[1] + angles[2]) / 2.0
and call emitGPhaseIfNonTrivial(rewriter, loc, phase + extra) prior to return
e.u(inQubit, angles[0], angles[1], angles[2]); also verify (or assert in a
comment) that decomposition::EulerDecomposition::anglesFromUnitary(...) indeed
returns pure ZYZ Euler angles and not U-gate-aware angles.
🪄 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: 6b833797-b55a-4d87-8557-b5c2c70d73fe

📥 Commits

Reviewing files that changed from the base of the PR and between 5f8ce03 and f66e820.

📒 Files selected for processing (28)
  • mlir/include/mlir/Compiler/CompilerPipeline.h
  • mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/GateSequence.h
  • mlir/include/mlir/Dialect/QCO/Transforms/NativeSynthesis/NativeSpec.h
  • mlir/include/mlir/Dialect/QCO/Transforms/NativeSynthesis/PassTwoQubitWindows.h
  • mlir/include/mlir/Dialect/QCO/Transforms/NativeSynthesis/SingleQubit.h
  • mlir/include/mlir/Dialect/QCO/Transforms/NativeSynthesis/TwoQubit.h
  • mlir/lib/Compiler/CMakeLists.txt
  • mlir/lib/Compiler/CompilerPipeline.cpp
  • mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt
  • mlir/lib/Dialect/QCO/Transforms/Decomposition/BasisDecomposer.cpp
  • mlir/lib/Dialect/QCO/Transforms/Decomposition/EulerDecomposition.cpp
  • mlir/lib/Dialect/QCO/Transforms/Decomposition/Helpers.cpp
  • mlir/lib/Dialect/QCO/Transforms/Decomposition/UnitaryMatrices.cpp
  • mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/Pass.cpp
  • mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/PassTwoQubitWindows.cpp
  • mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/Policy.cpp
  • mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/SingleQubit.cpp
  • mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/TwoQubit.cpp
  • mlir/tools/mqt-cc/mqt-cc.cpp
  • mlir/unittests/Dialect/QCO/Transforms/CMakeLists.txt
  • mlir/unittests/Dialect/QCO/Transforms/Decomposition/decomposition_test_utils.h
  • mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_basis_decomposer.cpp
  • mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_decomposition_helpers.cpp
  • mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp
  • mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_native_policy.cpp
  • mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_native_synthesis_pass_custom_menus.cpp
  • mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_native_synthesis_pass_multi_qubit.cpp
  • mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_native_synthesis_pass_scoring.cpp

Comment thread mlir/include/mlir/Dialect/QCO/Transforms/NativeSynthesis/SingleQubit.h Outdated
Comment thread mlir/include/mlir/Dialect/QCO/Transforms/NativeSynthesis/TwoQubit.h Outdated
Comment thread mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/Pass.cpp Outdated
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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/Pass.cpp`:
- Around line 698-725: The XX±YY->RZZ branch unconditionally short-circuits when
rzz is allowed, preventing weights-based comparison with general candidates;
instead, construct a unified candidates list that includes the XxPlusMinusViaRzz
candidate (use xxPlusMinusYyRzzRewriteScoringMetrics and payload=true as before)
along with the results of collectTwoQubitBasisCandidates(unitary, spec), then
call selectBestCandidate(...) on that combined ArrayRef with weights to pick the
winner; after selection, set the insertion point and either call
rewriteXXPlusMinusYYViaRzz(rewriter, op) if the chosen CandidateClass is
XxPlusMinusViaRzz or emitTwoQubitGateSequence(...) using best->payload.sequence
for general two-qubit candidates so the specialization only wins when its
weighted score is best.

In `@mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/PassTwoQubitWindows.cpp`:
- Around line 111-120: The check currently skips ops only when their immediate
parent is a CtrlOp; change it to skip any op that has a CtrlOp anywhere in its
ancestor chain by replacing the immediate-parent test with an ancestor lookup
(e.g. use op->getParentOfType<CtrlOp>() or similar) inside
collectUnitaryOpsInPreOrder (and the analogous filter around lines 161-168) so
any op nested under a CtrlOp (like qco.ctrl { qco.inv { ... } }) is ignored.
🪄 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: 89fb31fc-0ac8-42b3-89e2-4b33d9e9b009

📥 Commits

Reviewing files that changed from the base of the PR and between f66e820 and 5c0f2d1.

📒 Files selected for processing (9)
  • mlir/include/mlir/Dialect/QCO/Transforms/NativeSynthesis/PassTwoQubitWindows.h
  • mlir/include/mlir/Dialect/QCO/Transforms/NativeSynthesis/SingleQubit.h
  • mlir/include/mlir/Dialect/QCO/Transforms/NativeSynthesis/TwoQubit.h
  • mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/Pass.cpp
  • mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/PassTwoQubitWindows.cpp
  • mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/TwoQubit.cpp
  • mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_decomposition_helpers.cpp
  • mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_native_synthesis_pass_custom_menus.cpp
  • mlir/unittests/Dialect/QCO/Transforms/NativeSynthesis/test_native_synthesis_pass_multi_qubit.cpp

Comment thread mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/Pass.cpp
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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/Pass.cpp`:
- Around line 484-499: The in-place mutation
rz1.getThetaMutable().assign(newTheta) must be wrapped with the rewriter's
modifyOpInPlace to trigger operation-listener notifications; locate the block
manipulating rz1 (symbols: rz1, getThetaMutable, newTheta) and replace the bare
assign with a call to rewriter.modifyOpInPlace(rz1, ...) that performs the
assign inside the provided lambda, keeping the same computation path
(valueToDouble checks, constantFromScalar or arith::AddFOp creation,
rewriter.replaceOp(partner, ...)) unchanged.
🪄 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: 8399e11a-f1da-4511-948b-ca34bde0a3ae

📥 Commits

Reviewing files that changed from the base of the PR and between 5c0f2d1 and 01aa915.

📒 Files selected for processing (4)
  • mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/BasisDecomposer.h
  • mlir/lib/Dialect/QCO/Transforms/Decomposition/BasisDecomposer.cpp
  • mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/Pass.cpp
  • mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/PassTwoQubitWindows.cpp

Comment thread mlir/lib/Dialect/QCO/Transforms/NativeSynthesis/Pass.cpp
@simon1hofmann
Copy link
Copy Markdown
Contributor Author

@burgholzer
I’m planning to split this branch into a stacked PR series to make review easier.

  1. PR1: Euler decomposition
  2. PR2: Weyl decomposition
  3. PR3: BasisDecomposer
  4. PR4: Single-qubit native synthesis
  5. PR5: Two-qubit native synthesis

Planned dependency chain: PR1 -> PR2 -> PR3 -> PR4 -> PR5 (each PR based on the previous one).

Does this split and order look good, or would you prefer a different grouping/order?

@burgholzer
Copy link
Copy Markdown
Member

@burgholzer I’m planning to split this branch into a stacked PR series to make review easier.

  1. PR1: Euler decomposition
  2. PR2: Weyl decomposition
  3. PR3: BasisDecomposer
  4. PR4: Single-qubit native synthesis
  5. PR5: Two-qubit native synthesis

Planned dependency chain: PR1 -> PR2 -> PR3 -> PR4 -> PR5 (each PR based on the previous one).

Does this split and order look good, or would you prefer a different grouping/order?

That order looks reasonable to me. If it can be split up like that, I would certainly appreciate it! 👍🏼

…::polar for complex exponentiation, improving numerical stability and readability.
@mergify mergify Bot added the conflict label Apr 29, 2026
Signed-off-by: simon1hofmann <119581649+simon1hofmann@users.noreply.github.com>
@mergify mergify Bot removed the conflict label Apr 29, 2026
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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mlir/include/mlir/Dialect/QCO/Transforms/Passes.h`:
- Around line 42-43: Add a Doxygen comment above the public factory declaration
createNativeGateSynthesisPass describing its purpose (what the pass does),
documenting the parameter `@param` options (type NativeGateSynthesisOptions) and
any important semantics of those options, and the return value via `@return` (a
std::unique_ptr<Pass> owning the created pass). Keep wording consistent with
surrounding pass factory comments (brief one-line summary plus `@param/`@return
tags) and place it immediately above the declaration for
createNativeGateSynthesisPass.
🪄 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: 468c7c0e-7e17-4ed8-8a53-dd3ca91801ad

📥 Commits

Reviewing files that changed from the base of the PR and between db83356 and 1af1116.

📒 Files selected for processing (4)
  • mlir/include/mlir/Dialect/QCO/Transforms/Passes.h
  • mlir/lib/Compiler/CompilerPipeline.cpp
  • mlir/lib/Dialect/QCO/IR/Operations/StandardGates/XXMinusYYOp.cpp
  • mlir/lib/Dialect/QCO/IR/Operations/StandardGates/XXPlusYYOp.cpp

Comment on lines +42 to +43
std::unique_ptr<Pass>
createNativeGateSynthesisPass(const NativeGateSynthesisOptions& options);
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.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add Doxygen on the public pass factory declaration.

The new public declaration should be documented like the surrounding API surface.

Suggested patch
+/// Create the native gate synthesis pass configured with explicit options.
+///
+/// `@param` options Native gate menu and scoring weights.
+/// `@returns` A pass instance that lowers supported unitaries to the selected
+/// native gate set.
 std::unique_ptr<Pass>
 createNativeGateSynthesisPass(const NativeGateSynthesisOptions& options);

As per coding guidelines, **/*.{cpp,cc,cxx,c,h,hpp,hxx}: MUST use Doxygen-style comments in C++ code.

📝 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
std::unique_ptr<Pass>
createNativeGateSynthesisPass(const NativeGateSynthesisOptions& options);
/// Create the native gate synthesis pass configured with explicit options.
///
/// `@param` options Native gate menu and scoring weights.
/// `@returns` A pass instance that lowers supported unitaries to the selected
/// native gate set.
std::unique_ptr<Pass>
createNativeGateSynthesisPass(const NativeGateSynthesisOptions& options);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mlir/include/mlir/Dialect/QCO/Transforms/Passes.h` around lines 42 - 43, Add
a Doxygen comment above the public factory declaration
createNativeGateSynthesisPass describing its purpose (what the pass does),
documenting the parameter `@param` options (type NativeGateSynthesisOptions) and
any important semantics of those options, and the return value via `@return` (a
std::unique_ptr<Pass> owning the created pass). Keep wording consistent with
surrounding pass factory comments (brief one-line summary plus `@param/`@return
tags) and place it immediately above the declaration for
createNativeGateSynthesisPass.

@simon1hofmann simon1hofmann marked this pull request as draft April 30, 2026 11:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Improvement of existing feature MLIR Anything related to MLIR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants