Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
bec1fdf
✨ Implement `decompose-multi-controlled` pass for decomposing multi-c…
simon1hofmann Jun 17, 2026
d48be9c
✨ Extend `decompose-multi-controlled` pass to support decomposition o…
simon1hofmann Jun 24, 2026
f0de507
📝 Update copyright year in QCO dialect files to 2025 and remove outda…
simon1hofmann Jun 24, 2026
08a9dfa
📝 Refine comments in McxSynthesis.cpp for clarity and update test des…
simon1hofmann Jun 24, 2026
d47641d
🚨 Fix linter warnings
simon1hofmann Jun 24, 2026
e0b53a4
📝 Update CHANGELOG.md to include pull request link for `decompose-mul…
simon1hofmann Jun 24, 2026
0ef3f71
☂️ Increase coverage
simon1hofmann Jun 24, 2026
dff6eeb
🚨 Fix linter warnings
simon1hofmann Jun 24, 2026
9ec9e6f
🐇 Address Rabbit's comments
Jun 24, 2026
cf57d47
✨ Add project options for MLIRQCOTransforms in CMakeLists.txt
Jun 24, 2026
0292b3e
🎨 pre-commit fixes
pre-commit-ci[bot] Jun 24, 2026
b2f30eb
☂️ Increase coverage
Jun 24, 2026
55176d1
🐇 Address Rabbit's comments
Jun 24, 2026
968d06c
Merge branch 'main' into decompose-multi-controlled-gates
simon1hofmann Jun 25, 2026
1351927
Merge remote-tracking branch 'origin/main' into decompose-multi-contr…
simon1hofmann Jun 30, 2026
0b297c5
Merge branch 'main' into decompose-multi-controlled-gates
simon1hofmann Jul 5, 2026
f782391
Merge branch 'main' into decompose-multi-controlled-gates
simon1hofmann Jul 6, 2026
0905bd3
Merge branch 'main' into decompose-multi-controlled-gates
simon1hofmann Jul 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: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ with the exception that minor releases may include breaking changes.

### Added

- ✨ Add a `decompose-multi-controlled` pass for decomposing multi-controlled X
and Z gates into one- and two-qubit gates ([#1810]) ([**@simon1hofmann**])
- ✨ Add a `fuse-single-qubit-unitary-runs` pass
for fusing compile-time single-qubit unitary runs via Euler resynthesis
([#1672]) ([**@simon1hofmann**], [**@burgholzer**])
Expand Down Expand Up @@ -598,6 +600,7 @@ changelogs._

<!-- PR links -->

[#1810]: https://github.com/munich-quantum-toolkit/core/pull/1810
[#1802]: https://github.com/munich-quantum-toolkit/core/pull/1802
[#1787]: https://github.com/munich-quantum-toolkit/core/pull/1787
[#1782]: https://github.com/munich-quantum-toolkit/core/pull/1782
Expand Down
8 changes: 8 additions & 0 deletions mlir/include/mlir/Compiler/CompilerPipeline.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <mlir/Pass/PassManager.h>
#include <mlir/Support/LogicalResult.h>

#include <cstdint>
#include <string>

namespace mlir {
Expand Down Expand Up @@ -49,6 +50,13 @@ struct QuantumCompilerConfig {

/// Enable Hadamard lifting
bool enableHadamardLifting = false;

/// Decompose multi-controlled X/Z gates into elementary one- and two-qubit
/// gates.
bool enableDecomposeMultiControlled = false;
Comment thread
burgholzer marked this conversation as resolved.

/// Minimum control count for @ref enableDecomposeMultiControlled (default 2).
std::uint64_t decomposeMultiControlledMinControls = 2;
};

/**
Expand Down

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.

This might once more just be me, but is there really any value in having a dedicated header file for these functions? Couldn't these simply be part of the respective pass CPP file?
It feels like these are very particular function that do not have any public API usage or at least none that I could foresee.
Would you agree?

Edit: having gone through more of the PR, I think I just don't quite like the idea that so much of the pass logic is actively decoupled from MLIR. For the most part, we have the MLIR pass logic and that then builds some auxiliary data structures, which are passed to a collection of methods that doesn't even really care about MLIR.
I would have thought/hoped that we could integrate the respective functionality a little tighter with MLIR itself and avoid auxiliary boilerplate as much as possible.
However, I can also see that the current design also has its pro's; decoupling the infrastructure from the actual pass logic.
Maybe this is just me leaning towards the tightly integrated solution.
Tagging @munich-quantum-toolkit/mqt-cc here for visibility. Maybe someone has a comment or a different perspective

Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright (c) 2023 - 2026 Chair for Design Automation, TUM
* Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH
* All rights reserved.
*
* SPDX-License-Identifier: MIT
*
* Licensed under the MIT License
*/

#pragma once

#include <mlir/IR/Builders.h>
#include <mlir/IR/Location.h>
#include <mlir/IR/Value.h>
#include <mlir/Support/LLVM.h>

namespace mlir::qco::decomposition {

/**
* @brief Emits a decomposition of a multi-controlled X gate.
*
* @details Emits a sequence of one- and two-qubit gates that, taken together,
* implement the multi-controlled X gate that flips @p target whenever all @p
* controls are in the @f$|1\rangle@f$ state. The decomposition follows Huang
* and Palsberg (PLDI 2024), composing Iten et al. (Phys. Rev. A 93, 032318,
* 2016) dirty-ancilla subcircuits for large control counts. The emitted gates
* are `h`, `t`, `tdg`, `p`, and `cx`.
*
* @note Adapted from ``synth_mcx_noaux_hp24`` and ``synth_mcx_n_dirty_i15`` in
* the IBM Qiskit framework.
* (C) Copyright IBM 2025
*
* This code is licensed under the Apache License, Version 2.0. You may
* obtain a copy of this license in the LICENSE.txt file in the root
* directory of this source tree or at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* Any modifications or derivative works of this code must retain this
* copyright notice, and modified files need to carry a notice
* indicating that they have been altered from the originals.
*
* @param builder Builder positioned at the desired insertion point.
* @param loc Location attached to the emitted operations.
* @param controls Current SSA values of the control qubits.
* @param target Current SSA value of the target qubit.
* @return The updated SSA values, ordered as `[controls..., target]`.
*
* @pre @p controls must contain at least two qubits.
*/
[[nodiscard]] SmallVector<Value> synthesizeMcx(OpBuilder& builder, Location loc,
ValueRange controls,
Value target);

/**
* @brief Emits a decomposition of a multi-controlled Z gate.
*
* @details Emits the same gate set as @ref synthesizeMcx (`h`, `t`, `tdg`, `p`,
* and `cx`). For @f$k \ge 2@f$ controls, emits the HP24 MCX core directly,
* since @f$\mathrm{MCZ} =
* H_{\text{target}}\,(H_{\text{target}}\,\mathrm{CORE}\,
* H_{\text{target}})\,H_{\text{target}} = \mathrm{CORE}@f$ and the MCX
* Hadamard bookends cancel under this conjugation.
*
* @param builder Builder positioned at the desired insertion point.
* @param loc Location attached to the emitted operations.
* @param controls Current SSA values of the control qubits.
* @param target Current SSA value of the target qubit.
* @return The updated SSA values, ordered as `[controls..., target]`.
*
* @pre @p controls must contain at least two qubits.
*/
[[nodiscard]] SmallVector<Value> synthesizeMcz(OpBuilder& builder, Location loc,
ValueRange controls,
Value target);

} // namespace mlir::qco::decomposition
42 changes: 42 additions & 0 deletions mlir/include/mlir/Dialect/QCO/Transforms/Passes.td
Original file line number Diff line number Diff line change
Expand Up @@ -200,4 +200,46 @@ def HadamardLifting : Pass<"hadamard-lifting", "mlir::ModuleOp"> {
}];
}

//===----------------------------------------------------------------------===//
// Decomposition Passes
//===----------------------------------------------------------------------===//

def DecomposeMultiControlled
: Pass<"decompose-multi-controlled", "mlir::ModuleOp"> {
let dependentDialects = ["mlir::qco::QCODialect",
"::mlir::arith::ArithDialect"];
let summary =
"Decompose multi-controlled gates into one- and two-qubit gates";
let description = [{
Decomposes multi-controlled gates (`qco.ctrl`) into a sequence of one- and
two-qubit gates that can subsequently be handled by one- and two-qubit gate
synthesis methods.

The pass matches `qco.ctrl` operations with at least `min-controls` control
qubits whose body is a single `qco.x` or `qco.z` on one target
(multi-controlled Pauli-X or Pauli-Z). Other multi-controlled gates are left
unchanged. Support for further base gates will be added gradually.

Multi-controlled X gates are decomposed using the Huang-Palsberg (PLDI 2024)
no-ancilla synthesis algorithm with a linear number of CX gates in the
number of controls, composing Iten et al. (Phys. Rev. A 93, 032318, 2016)
dirty-ancilla subcircuits for large control counts. Multi-controlled Z gates
use the same HP24 core (algebraically equivalent to
@f$H_{\text{target}}\,\mathrm{MCX}\,H_{\text{target}}@f$, without redundant
Hadamard bookends for @f$k \ge 2@f$). The pass emits `h`, `t`, `tdg`, `p`,
and `cx` gates.

By default (`min-controls=2`), gates with at least two controls (e.g. `CCX`,
`CCZ`, and larger `MCX`/`MCZ`) are decomposed, leaving single-control gates
(e.g. `CX`, `CZ`) and bare one-qubit gates intact. Increase `min-controls`
to keep smaller multi-controlled gates intact (e.g., when `CCX` is a native
gate of the target).
}];
let options = [Option<
"minControls", "min-controls", "uint64_t", "2",

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.

IIRC tablegen also allows to directly place constraints on options/parameters. Maybe it is worth looking into whether this would simplify some of the error handling code throughout this PR.

"Minimum number of controls for which a controlled gate is "
"decomposed. Controlled gates with fewer controls are left "
"unchanged. Must be at least 2.">];
}

#endif // MLIR_DIALECT_QCO_TRANSFORMS_PASSES_TD
12 changes: 12 additions & 0 deletions mlir/lib/Compiler/CompilerPipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module,
"enabled and the record pointer to be non-null.\n";
return failure();
}
if (config_.enableDecomposeMultiControlled &&
config_.decomposeMultiControlledMinControls < 2) {
llvm::errs()
<< "decomposeMultiControlledMinControls must be at least 2 when "
"enableDecomposeMultiControlled is enabled.\n";
Comment thread
simon1hofmann marked this conversation as resolved.
return failure();
}

auto runStage = [&](auto&& populatePasses) -> LogicalResult {
PassManager pm(module.getContext());
Expand Down Expand Up @@ -147,6 +154,11 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module,
}
// Stage 5: Optimization passes
if (failed(runStage([&](PassManager& pm) {
if (config_.enableDecomposeMultiControlled) {
qco::DecomposeMultiControlledOptions options;
options.minControls = config_.decomposeMultiControlledMinControls;
pm.addPass(qco::createDecomposeMultiControlled(options));
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if (!config_.disableMergeSingleQubitRotationGates) {
pm.addPass(qco::createMergeSingleQubitRotationGates());
}
Expand Down
2 changes: 2 additions & 0 deletions mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ add_mlir_library(
DEPENDS
MLIRQCOTransformsIncGen)

mqt_mlir_target_use_project_options(MLIRQCOTransforms)

# collect header files
file(GLOB_RECURSE PASSES_HEADERS_SOURCE
${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QCO/Transforms/*.h)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* Copyright (c) 2023 - 2026 Chair for Design Automation, TUM
* Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH
* All rights reserved.
*
* SPDX-License-Identifier: MIT
*
* Licensed under the MIT License
*/

#include "mlir/Dialect/QCO/IR/QCOInterfaces.h"
#include "mlir/Dialect/QCO/IR/QCOOps.h"
#include "mlir/Dialect/QCO/Transforms/Decomposition/MultiControlled.h"
#include "mlir/Dialect/QCO/Transforms/Passes.h"
#include "mlir/Dialect/Utils/Utils.h"

#include <mlir/Dialect/Arith/IR/Arith.h> // IWYU pragma: keep (Passes.h.inc)
#include <mlir/IR/MLIRContext.h>
#include <mlir/IR/PatternMatch.h>
#include <mlir/Support/LLVM.h>
#include <mlir/Support/LogicalResult.h>
#include <mlir/Transforms/GreedyPatternRewriteDriver.h>

#include <cstdint>
#include <utility>

namespace mlir::qco {

#define GEN_PASS_DEF_DECOMPOSEMULTICONTROLLED
#include "mlir/Dialect/QCO/Transforms/Passes.h.inc"

namespace {

/**
* @brief Decomposes a multi-controlled Pauli-X or Pauli-Z gate into elementary
* one- and two-qubit gates.
*
* @details Matches `qco.ctrl` with a single `qco.x` or `qco.z` body when the
* control count is at least `minControls_` (and at least two, as enforced by
* the pass). Single-control `CX`/`CZ` and other gates are left unchanged.
*/
struct DecomposeMultiControlledPauliPattern final : OpRewritePattern<CtrlOp> {
explicit DecomposeMultiControlledPauliPattern(MLIRContext* context,
uint64_t minControls)
: OpRewritePattern<CtrlOp>(context), minControls_(minControls) {}

LogicalResult matchAndRewrite(CtrlOp op,
PatternRewriter& rewriter) const override {
if (op.getNumControls() < minControls_ || op.getNumTargets() != 1) {
return failure();
}
auto inner = utils::getSoleBodyUnitary<UnitaryOpInterface>(*op.getBody());
if (!inner) {
return failure();
}

rewriter.setInsertionPoint(op);
const auto controls = op.getControlsIn();
const auto target = op.getInputTarget(0);
const auto loc = op.getLoc();

if (isa<XOp>(inner.getOperation())) {
rewriter.replaceOp(
op, decomposition::synthesizeMcx(rewriter, loc, controls, target));
return success();
}
if (isa<ZOp>(inner.getOperation())) {
rewriter.replaceOp(
op, decomposition::synthesizeMcz(rewriter, loc, controls, target));
return success();
}
return failure();
}

private:
uint64_t minControls_;
};

/**
* @brief Pass that decomposes multi-controlled X and Z gates into elementary
* gates.
*/
struct DecomposeMultiControlled final
: impl::DecomposeMultiControlledBase<DecomposeMultiControlled> {
using DecomposeMultiControlledBase::DecomposeMultiControlledBase;

protected:
void runOnOperation() override {
if (minControls < 2) {
getOperation().emitError()
<< "decompose-multi-controlled requires min-controls >= 2";
signalPassFailure();
return;
}

RewritePatternSet patterns(&getContext());
patterns.add<DecomposeMultiControlledPauliPattern>(&getContext(),
minControls);

if (failed(applyPatternsGreedily(getOperation(), std::move(patterns)))) {
signalPassFailure();
}
}
};

} // namespace

} // namespace mlir::qco
Loading
Loading