Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
59f87ae
fix(mlir): :bug: fix one-time-use of variables for the `ctrl` modifier
DRovara Jan 9, 2026
a50d3af
Merge branch 'main' into mlir/fix/ctrl-one-use
DRovara Jan 9, 2026
fd061c7
fix(mlir): :bug: fix error introduced by merge
DRovara Jan 9, 2026
7179197
docs(mlir): :memo: update examples in QCOOps.td
DRovara Jan 9, 2026
ce1ae87
refactor(mlir): :rotating_light: fix some linter issues
DRovara Jan 9, 2026
b88acbd
refactor(mlir): :rotating_light: further linter issue fixes
DRovara Jan 9, 2026
e6a09e1
refactor(mlir): :rotating_light: fix more linter issues
DRovara Jan 9, 2026
becf786
refactor(mlir): :recycle: reuse numTargets in conversion
DRovara Jan 9, 2026
a92d2c4
refactor(mlir): :recycle: implement coderabbit suggestions
DRovara Jan 9, 2026
0059475
feat(mlir): :sparkles: implement lambda-based builder for QCO CtrlOps
DRovara Jan 9, 2026
6c190b5
fix(mlir): :bug: make `qco.ctrl` builder that take a `UnitaryOpInterf…
DRovara Jan 9, 2026
87dd322
chore(mlir): :recycle: add verification for the block argument type i…
DRovara Jan 9, 2026
dedbd64
test(mlir): :white_check_mark: add test for UnitaryOpInterface builde…
DRovara Jan 9, 2026
38a4046
test(mlir): :white_check_mark: add tests for CtrlOp verifier
DRovara Jan 9, 2026
c214261
refactor(mlir): :recycle: apply coderabbit recommendations
DRovara Jan 9, 2026
411b1d5
fix(mlir): :bug: fix datatypes of valueranges in test
DRovara Jan 9, 2026
d055ff1
refactor(mlir): :rotating_light: fix linter issues
DRovara Jan 9, 2026
98ddd5c
test(mlir): :white_check_mark: also test custom parsing
DRovara Jan 9, 2026
3c94d7b
refactor(mlir): :recycle: replace some `EXPECT` with `ASSERT` for cle…
DRovara Jan 9, 2026
2bbb63b
fix linter issues
DRovara Jan 10, 2026
d6c53a5
fix linter issues
DRovara Jan 10, 2026
eeb8b9a
🐛 body lambda function must not return ValueRange
burgholzer Jan 10, 2026
5f330fa
🎨 use `<OP>::create` over `builder.create` for better autocompletion …
burgholzer Jan 10, 2026
012e55f
🎨 micro-optimizations
burgholzer Jan 10, 2026
baff792
♻️ rework tests to rely more on the QCOProgramBuilder for more concis…
burgholzer Jan 10, 2026
d553b39
🐛 fix the QCOProgramBuilder convenience methods
burgholzer Jan 10, 2026
91474b1
🚨👌 Address clang-tidy warnings and CodeRabbit feedback
burgholzer Jan 10, 2026
d513e6b
🚨 fix missing header
burgholzer Jan 10, 2026
4a60744
👌 Address CodeRabbit feedback
burgholzer Jan 10, 2026
f1659bd
🎨 even more idiomatic MLIR code
burgholzer Jan 11, 2026
015e0b5
🚨 clang-tidy warnings
burgholzer Jan 11, 2026
3c2fae2
📝 add to changelog
burgholzer Jan 11, 2026
4fe36f2
👌 more CodeRabbit feedback
burgholzer Jan 11, 2026
20b3a7e
Merge branch 'main' into mlir/fix/ctrl-one-use
burgholzer Jan 11, 2026
631f3b3
👌 more CodeRabbit feedback
burgholzer Jan 11, 2026
a4311b3
👌 more CodeRabbit feedback
burgholzer Jan 11, 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
22 changes: 12 additions & 10 deletions mlir/include/mlir/Dialect/QCO/IR/QCOOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -1025,10 +1025,10 @@ def YieldOp : QCOOp<"yield", traits = [Terminator]> {

Example:
```mlir
%ctrl_q_out, %tgt_q_out = qco.ctrl(%ctrl_q_in) %tgt_q_in {
%tgt_q_res = qco.h %tgt_q_in : !qco.qubit -> !qco.qubit
qco.yield %tgt_q_res : !qco.qubit
} : ({!qco.qubit}, {!qco.qubit}) -> ({!qco.qubit}, {!qco.qubit})
%res_ctrl, %res_tgt:2 = qco.ctrl(%ctrl) targets(%a0 = %q0, %a1 = %q1) {
%a0_1, %a1_1 = qco.swap %a0, %a1 : !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit
qco.yield %a0_1, %a1_1
} : ({!qco.qubit}, {!qco.qubit, !qco.qubit}) -> ({!qco.qubit}, {!qco.qubit, !qco.qubit})
```
}];

Expand Down Expand Up @@ -1057,10 +1057,10 @@ def CtrlOp : QCOOp<"ctrl", traits =

Example:
```mlir
%ctrl_q_out, %tgt_q_out = qco.ctrl(%ctrl_q_in) %tgt_q_in {
%tgt_q_res = qco.h %tgt_q_in : !qco.qubit -> !qco.qubit
qco.yield %tgt_q_res : !qco.qubit
} : ({!qco.qubit}, {!qco.qubit}) -> ({!qco.qubit}, {!qco.qubit})
%res_ctrl, %res_tgt:2 = qco.ctrl(%ctrl) targets(%a0 = %q0, %a1 = %q1) {
%a0_1, %a1_1 = qco.swap %a0, %a1 : !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit
qco.yield %a0_1, %a1_1
} : ({!qco.qubit}, {!qco.qubit, !qco.qubit}) -> ({!qco.qubit}, {!qco.qubit, !qco.qubit})
```
}];

Expand All @@ -1069,8 +1069,10 @@ def CtrlOp : QCOOp<"ctrl", traits =
let results = (outs Variadic<QubitType>:$controls_out, Variadic<QubitType>:$targets_out);
let regions = (region SizedRegion<1>:$region);
let assemblyFormat = [{
`(` $controls_in `)` $targets_in
$region attr-dict `:`
`(` $controls_in `)`
`targets`
custom<TargetAliasing>($region, $targets_in)
attr-dict `:`
`(` `{` type($controls_in) `}` ( `,` `{` type($targets_in)^ `}` )? `)`
`->`
`(` `{` type($controls_out) `}` ( `,` `{` type($targets_out)^ `}` )? `)`
Expand Down
24 changes: 22 additions & 2 deletions mlir/lib/Conversion/QCOToQC/QCOToQC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -774,12 +774,32 @@ struct ConvertQCOCtrlOp final : OpConversionPattern<qco::CtrlOp> {
const auto& qcControls = adaptor.getControlsIn();

// Create qc.ctrl operation
auto qcoOp = rewriter.create<qc::CtrlOp>(op.getLoc(), qcControls);
auto qcOp = rewriter.create<qc::CtrlOp>(op.getLoc(), qcControls);

// Clone body region from QCO to QC
auto& dstRegion = qcoOp.getRegion();
auto& dstRegion = qcOp.getRegion();
rewriter.cloneRegionBefore(op.getRegion(), dstRegion, dstRegion.end());

// Remove all block arguments in the cloned region
rewriter.modifyOpInPlace(qcOp, [&] {
auto& entryBlock = dstRegion.front();
auto numArgs = entryBlock.getNumArguments();

// 1. Replace uses (Must be done BEFORE erasing)
// We iterate 0..N using indices since the block args are still stable
// here.
for (auto i = 0UL; i < numArgs; ++i) {
entryBlock.getArgument(i).replaceAllUsesWith(adaptor.getTargetsIn()[i]);
}

// 2. Erase all block arguments
// Now that they have no uses, we can safely wipe them.
// We use a bulk erase for efficiency (start index 0, count N).
if (numArgs > 0) {
entryBlock.eraseArguments(0, numArgs);
}
});

// Replace the output qubits with the same QC references
rewriter.replaceOp(op, adaptor.getOperands());

Expand Down
17 changes: 16 additions & 1 deletion mlir/lib/Conversion/QCToQCO/QCToQCO.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1124,12 +1124,27 @@ struct ConvertQCCtrlOp final : StatefulOpConversionPattern<qc::CtrlOp> {

// Update modifier information
state.inCtrlOp++;
state.targetsIn.try_emplace(state.inCtrlOp, qcoTargets);

// Clone body region from QC to QCO
auto& dstRegion = qcoOp.getRegion();
rewriter.cloneRegionBefore(op.getRegion(), dstRegion, dstRegion.end());

// Create block arguments for target qubits and store them in
// `state.targetsIn`.
auto& entryBlock = dstRegion.front();
SmallVector<Value> qcoTargetAliases;
qcoTargetAliases.reserve(numTargets);

rewriter.modifyOpInPlace(qcoOp, [&] {
for (auto i = 0UL; i < numTargets; i++) {
auto originalTarget = qcoOp.getInputTarget(i);
auto arg =
entryBlock.addArgument(originalTarget.getType(), op.getLoc());
qcoTargetAliases.push_back(arg);
}
});
state.targetsIn.try_emplace(state.inCtrlOp, qcoTargetAliases);

rewriter.eraseOp(op);
return success();
}
Expand Down
24 changes: 23 additions & 1 deletion mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -250,11 +250,19 @@ void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState,
UnitaryOpInterface bodyUnitary) {
build(odsBuilder, odsState, controls, targets);
auto& block = odsState.regions.front()->emplaceBlock();
for (const auto target : targets) {
block.addArgument(target.getType(), odsState.location);
}
auto blockArgs = block.getArguments();

// Move the unitary op into the block
const OpBuilder::InsertionGuard guard(odsBuilder);
odsBuilder.setInsertionPointToStart(&block);
auto* op = odsBuilder.clone(*bodyUnitary.getOperation());
for (size_t i = 0; i < targets.size(); ++i) {
op->replaceUsesOfWith(targets[i], blockArgs[i]);
}

odsBuilder.create<YieldOp>(odsState.location, op->getResults());
}

Expand All @@ -263,11 +271,15 @@ void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState,
const std::function<ValueRange(ValueRange)>& bodyBuilder) {
build(odsBuilder, odsState, controls, targets);
auto& block = odsState.regions.front()->emplaceBlock();
for (const auto target : targets) {
block.addArgument(target.getType(), odsState.location);
}
auto blockArgs = block.getArguments();

// Move the unitary op into the block
const OpBuilder::InsertionGuard guard(odsBuilder);
odsBuilder.setInsertionPointToStart(&block);
auto targetsOut = bodyBuilder(targets);
auto targetsOut = bodyBuilder(blockArgs);
odsBuilder.create<YieldOp>(odsState.location, targetsOut);
}

Expand All @@ -276,6 +288,16 @@ LogicalResult CtrlOp::verify() {
if (block.getOperations().size() != 2) {
return emitOpError("body region must have exactly two operations");
}
if (block.getArguments().size() != getNumTargets()) {
return emitOpError(
"number of block arguments must match number of targets");
}
for (size_t i = 0; i < getNumTargets(); ++i) {
if (block.getArgument(i).getType() != getTargetsIn()[i].getType()) {
return emitOpError("block argument type at index ")
<< i << " does not match target type";
}
}
if (!llvm::isa<UnitaryOpInterface>(block.front())) {
return emitOpError(
"first operation in body region must be a unitary operation");
Expand Down
88 changes: 88 additions & 0 deletions mlir/lib/Dialect/QCO/IR/QCOOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@

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

#include <mlir/IR/Block.h>
#include <mlir/IR/OpImplementation.h>
#include <mlir/IR/Operation.h>
#include <mlir/IR/Region.h>
#include <mlir/IR/ValueRange.h>
#include <mlir/Support/LLVM.h>
#include <mlir/Support/LogicalResult.h>

// The following headers are needed for some template instantiations.
// IWYU pragma: begin_keep
#include <llvm/ADT/TypeSwitch.h>
Expand All @@ -21,6 +29,86 @@
using namespace mlir;
using namespace mlir::qco;

//===----------------------------------------------------------------------===//
// Custom Parsers
//===----------------------------------------------------------------------===//

static ParseResult
parseTargetAliasing(OpAsmParser& parser, Region& region,
SmallVectorImpl<OpAsmParser::UnresolvedOperand>& operands) {
// 1. Parse the opening parenthesis
if (parser.parseLParen()) {
return failure();
}

// Temporary storage for block arguments we are about to create
SmallVector<OpAsmParser::Argument> blockArgs;

// 2. Prepare to parse the list
if (failed(parser.parseOptionalRParen())) {
do {
OpAsmParser::Argument newArg; // The "new" variable name
OpAsmParser::UnresolvedOperand oldOperand; // The "old" input variable

// Parse "%new"
if (parser.parseArgument(newArg)) {
return failure();
}

// Parse "="
if (parser.parseEqual()) {
return failure();
}

// Parse "%old"
if (parser.parseOperand(oldOperand)) {
return failure();
}
operands.push_back(oldOperand);

// Hard-code QubitType since targets in qco.ctrl are always qubits.
// This avoids double-binding type($targets_in) in the assembly format
// while keeping the parser simple and the assembly format clean.
const Type type = qco::QubitType::get(parser.getBuilder().getContext());
newArg.type = type;
blockArgs.push_back(newArg);

} while (succeeded(parser.parseOptionalComma()));

if (parser.parseRParen()) {
return failure();
}
}

// 4. Parse the Region
// We explicitly pass the blockArgs we just parsed so they become the entry
// block!
if (parser.parseRegion(region, blockArgs)) {
return failure();
}

return success();
}

static void printTargetAliasing(OpAsmPrinter& printer, Operation* /*op*/,
Region& region, OperandRange targetsIn) {
printer << "(";
Block& entryBlock = region.front();
auto blockArgs = entryBlock.getArguments();

for (unsigned i = 0; i < targetsIn.size(); ++i) {
if (i > 0) {
printer << ", ";
}
printer.printOperand(blockArgs[i]);
printer << " = ";
printer.printOperand(targetsIn[i]);
}
printer << ") ";

printer.printRegion(region, /*printEntryBlockArgs=*/false);
}

//===----------------------------------------------------------------------===//
// Dialect
//===----------------------------------------------------------------------===//
Expand Down
4 changes: 3 additions & 1 deletion mlir/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
# Licensed under the MIT License

add_subdirectory(pipeline)
add_subdirectory(Dialect)

add_custom_target(mqt-core-mlir-unittests)

add_dependencies(mqt-core-mlir-unittests mqt-core-mlir-compiler-pipeline-test)
add_dependencies(mqt-core-mlir-unittests mqt-core-mlir-compiler-pipeline-test
mqt-core-mlir-dialect-qco-ir-modifiers-test)
9 changes: 9 additions & 0 deletions mlir/unittests/Dialect/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# 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

add_subdirectory(QCO)
9 changes: 9 additions & 0 deletions mlir/unittests/Dialect/QCO/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# 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

add_subdirectory(IR)
9 changes: 9 additions & 0 deletions mlir/unittests/Dialect/QCO/IR/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# 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

add_subdirectory(Modifiers)
21 changes: 21 additions & 0 deletions mlir/unittests/Dialect/QCO/IR/Modifiers/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# 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

add_executable(mqt-core-mlir-dialect-qco-ir-modifiers-test test_qco_ctrl.cpp)

target_link_libraries(
mqt-core-mlir-dialect-qco-ir-modifiers-test
PRIVATE GTest::gtest_main
MQT::CoreIR
MLIRQCODialect
MLIRFuncDialect
MLIRIR
MLIRSupport
LLVMSupport)

gtest_discover_tests(mqt-core-mlir-dialect-qco-ir-modifiers-test)
Loading
Loading