From 3e9099e82ccbd1fcad0c6977da5bea90a3945fc4 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 17 Mar 2026 14:14:51 +0100 Subject: [PATCH 01/57] =?UTF-8?q?=E2=9C=A8=20Update=20QCO=20operations=20t?= =?UTF-8?q?o=20reflect=20memory=20effects=20and=20enhance=20deallocation?= =?UTF-8?q?=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/include/mlir/Dialect/QCO/IR/QCOOps.td | 2 +- mlir/lib/Conversion/QCOToQC/QCOToQC.cpp | 14 ++++- mlir/lib/Conversion/QCToQCO/QCToQCO.cpp | 64 +++++++++++++++++++++- 3 files changed, 74 insertions(+), 6 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td index b5b82127f7..c226508825 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td @@ -94,7 +94,7 @@ def DeallocOp : QCOOp<"dealloc", [MemoryEffects<[MemFree]>]> { let hasCanonicalizer = 1; } -def StaticOp : QCOOp<"static", [Pure]> { +def StaticOp : QCOOp<"static", [MemoryEffects<[MemAlloc]>]> { let summary = "Retrieve a static qubit by index"; let description = [{ The `qco.static` operation produces an SSA value representing a qubit diff --git a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp index 381486b02b..2f9dd1bf2f 100644 --- a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -96,7 +96,7 @@ struct ConvertQCOAllocOp final : OpConversionPattern { }; /** - * @brief Converts qco.dealloc to qc.dealloc + * @brief Converts qco.dealloc to qc.dealloc (except for static qubits). * * @details * Deallocates a qubit, releasing its resources. The OpAdaptor automatically @@ -109,6 +109,9 @@ struct ConvertQCOAllocOp final : OpConversionPattern { * // becomes: * qc.dealloc %q_qc : !qc.qubit * ``` + * + * For static qubits, we erase `qco.dealloc` because QC does + * not require explicit sinks for static qubit handles. */ struct ConvertQCODeallocOp final : OpConversionPattern { using OpConversionPattern::OpConversionPattern; @@ -116,8 +119,13 @@ struct ConvertQCODeallocOp final : OpConversionPattern { LogicalResult matchAndRewrite(qco::DeallocOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { - // OpAdaptor provides the already type-converted qubit - rewriter.replaceOpWithNewOp(op, adaptor.getQubit()); + auto qcQubit = adaptor.getQubit(); + if (llvm::isa_and_nonnull(qcQubit.getDefiningOp())) { + rewriter.eraseOp(op); + return success(); + } + + rewriter.replaceOpWithNewOp(op, qcQubit); return success(); } }; diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index 474f80aacf..f90a008973 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -111,6 +111,59 @@ class StatefulOpConversionPattern : public OpConversionPattern { LoweringState* state_; }; +/** + * @brief Converts func.return and sinks remaining live qubits. + * + * @details + * QC uses reference semantics and does not enforce linear typing for qubits. + * After conversion, QCO requires that every qubit SSA value is consumed + * exactly once. For allocations (including static qubits), the sink is + * `qco.dealloc`. This pattern inserts `qco.dealloc` operations for all + * still-live qubits tracked in the lowering state right before the return. + */ +struct ConvertFuncReturnOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(func::ReturnOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + auto& state = getState(); + + // Only insert sinks at function exit (never inside nested modifier + // regions). + if (state.inNestedRegion == 0) { + llvm::SmallVector liveQubits; + liveQubits.reserve(state.qubitMap.size()); + + llvm::DenseSet seen; + for (const auto& [/*qcQubit*/ _, qcoQubit] : state.qubitMap) { + if (seen.insert(qcoQubit).second) { + liveQubits.push_back(qcoQubit); + } + } + + // Sort deterministically (mirrors QCOProgramBuilder::finalize()). + llvm::sort(liveQubits, [](Value a, Value b) { + auto* opA = a.getDefiningOp(); + auto* opB = b.getDefiningOp(); + if (!opA || !opB || opA->getBlock() != opB->getBlock()) { + return a.getAsOpaquePointer() < b.getAsOpaquePointer(); + } + return opA->isBeforeInBlock(opB); + }); + + for (auto qubit : liveQubits) { + rewriter.create(op.getLoc(), qubit); + } + + state.qubitMap.clear(); + } + + rewriter.replaceOpWithNewOp(op, adaptor.getOperands()); + return success(); + } +}; + /** * @brief Type converter for QC-to-QCO conversion * @@ -1158,9 +1211,16 @@ struct QCToQCO final : impl::QCToQCOBase { }); // Conversion of qc types in func.return + // + // Note: `func.return` may already be type-legal even though we still need + // to insert `qco.dealloc` sinks for remaining live qubits. Therefore, we + // make it dynamically illegal unless the lowering state has no remaining + // qubits. + patterns.add(typeConverter, context, &state); populateReturnOpTypeConversionPattern(patterns, typeConverter); - target.addDynamicallyLegalOp( - [&](const func::ReturnOp op) { return typeConverter.isLegal(op); }); + target.addDynamicallyLegalOp([&](const func::ReturnOp op) { + return typeConverter.isLegal(op) && state.qubitMap.empty(); + }); // Conversion of qc types in func.call populateCallOpTypeConversionPattern(patterns, typeConverter); From 6c77dbbd77df1b52293584d432a508491102f95d Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 17 Mar 2026 14:15:22 +0100 Subject: [PATCH 02/57] =?UTF-8?q?=E2=9C=A8=20Add=20`staticQubitWithOp`=20f?= =?UTF-8?q?unction=20to=20allocate=20a=20static=20qubit=20and=20apply=20an?= =?UTF-8?q?=20operation=20across=20multiple=20program=20builders=20(QC,=20?= =?UTF-8?q?QCO,=20QIR).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/unittests/programs/qc_programs.cpp | 5 +++++ mlir/unittests/programs/qc_programs.h | 3 +++ mlir/unittests/programs/qco_programs.cpp | 5 +++++ mlir/unittests/programs/qco_programs.h | 3 +++ mlir/unittests/programs/qir_programs.cpp | 5 +++++ mlir/unittests/programs/qir_programs.h | 3 +++ 6 files changed, 24 insertions(+) diff --git a/mlir/unittests/programs/qc_programs.cpp b/mlir/unittests/programs/qc_programs.cpp index 2134b1f368..bceaa14154 100644 --- a/mlir/unittests/programs/qc_programs.cpp +++ b/mlir/unittests/programs/qc_programs.cpp @@ -34,6 +34,11 @@ void staticQubits(QCProgramBuilder& b) { b.staticQubit(1); } +void staticQubitWithOp(QCProgramBuilder& b) { + auto q = b.staticQubit(0); + b.h(q); +} + void allocDeallocPair(QCProgramBuilder& b) { auto q = b.allocQubit(); b.dealloc(q); diff --git a/mlir/unittests/programs/qc_programs.h b/mlir/unittests/programs/qc_programs.h index 21225c5b44..816b5cc73c 100644 --- a/mlir/unittests/programs/qc_programs.h +++ b/mlir/unittests/programs/qc_programs.h @@ -33,6 +33,9 @@ void allocLargeRegister(QCProgramBuilder& b); /// Allocates two inline qubits. void staticQubits(QCProgramBuilder& b); +/// Allocates a static qubit and applies an operation. +void staticQubitWithOp(QCProgramBuilder& b); + /// Allocates and explicitly deallocates a single qubit. void allocDeallocPair(QCProgramBuilder& b); diff --git a/mlir/unittests/programs/qco_programs.cpp b/mlir/unittests/programs/qco_programs.cpp index 1ef16df6d0..fd8fd1ae7b 100644 --- a/mlir/unittests/programs/qco_programs.cpp +++ b/mlir/unittests/programs/qco_programs.cpp @@ -38,6 +38,11 @@ void staticQubits(QCOProgramBuilder& b) { b.staticQubit(1); } +void staticQubitWithOp(QCOProgramBuilder& b) { + auto q = b.staticQubit(0); + b.h(q); +} + void allocDeallocPair(QCOProgramBuilder& b) { auto q = b.allocQubit(); b.dealloc(q); diff --git a/mlir/unittests/programs/qco_programs.h b/mlir/unittests/programs/qco_programs.h index ba26e42969..15687411e8 100644 --- a/mlir/unittests/programs/qco_programs.h +++ b/mlir/unittests/programs/qco_programs.h @@ -33,6 +33,9 @@ void allocLargeRegister(QCOProgramBuilder& b); /// Allocates two inline qubits. void staticQubits(QCOProgramBuilder& b); +/// Allocates a static qubit and applies an operation. +void staticQubitWithOp(QCOProgramBuilder& b); + /// Allocates and explicitly deallocates a single qubit. void allocDeallocPair(QCOProgramBuilder& b); diff --git a/mlir/unittests/programs/qir_programs.cpp b/mlir/unittests/programs/qir_programs.cpp index 883cf59af8..38d88fa08f 100644 --- a/mlir/unittests/programs/qir_programs.cpp +++ b/mlir/unittests/programs/qir_programs.cpp @@ -32,6 +32,11 @@ void staticQubits(QIRProgramBuilder& b) { b.staticQubit(1); } +void staticQubitWithOp(QIRProgramBuilder& b) { + auto q = b.staticQubit(0); + b.h(q); +} + void singleMeasurementToSingleBit(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); const auto c = b.allocClassicalBitRegister(1); diff --git a/mlir/unittests/programs/qir_programs.h b/mlir/unittests/programs/qir_programs.h index f379f19785..1c2e5754a2 100644 --- a/mlir/unittests/programs/qir_programs.h +++ b/mlir/unittests/programs/qir_programs.h @@ -33,6 +33,9 @@ void allocLargeRegister(QIRProgramBuilder& b); /// Allocates two inline qubits. void staticQubits(QIRProgramBuilder& b); +/// Allocates a static qubit and applies an operation. +void staticQubitWithOp(QIRProgramBuilder& b); + // --- MeasureOp ------------------------------------------------------------ // /// Measures a single qubit into a single classical bit. From 646cf3e82ebfcebfa361939e78a053f7fc3612ff Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 17 Mar 2026 14:15:51 +0100 Subject: [PATCH 03/57] =?UTF-8?q?=E2=9C=85=20Add=20test=20cases=20for=20`s?= =?UTF-8?q?taticQubitWithOp`=20across=20various=20dialects=20(QC,=20QCO,?= =?UTF-8?q?=20QIR)=20to=20enhance=20coverage=20and=20ensure=20functionalit?= =?UTF-8?q?y.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Compiler/test_compiler_pipeline.cpp | 5 ++++ .../Conversion/QCOToQC/test_qco_to_qc.cpp | 12 +++++++++ .../Conversion/QCToQCO/test_qc_to_qco.cpp | 12 +++++++++ .../Conversion/QCToQIR/test_qc_to_qir.cpp | 3 +++ mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp | 2 ++ mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp | 2 ++ mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp | 27 ++++++++++--------- 7 files changed, 50 insertions(+), 13 deletions(-) diff --git a/mlir/unittests/Compiler/test_compiler_pipeline.cpp b/mlir/unittests/Compiler/test_compiler_pipeline.cpp index a81f6a7c01..7d79ca50b1 100644 --- a/mlir/unittests/Compiler/test_compiler_pipeline.cpp +++ b/mlir/unittests/Compiler/test_compiler_pipeline.cpp @@ -193,6 +193,11 @@ INSTANTIATE_TEST_SUITE_P( "StaticQubits", nullptr, MQT_NAMED_BUILDER(mlir::qc::staticQubits), MQT_NAMED_BUILDER(mlir::qc::staticQubits), MQT_NAMED_BUILDER(mlir::qir::staticQubits), false}, + CompilerPipelineTestCase{ + "StaticQubitWithOp", nullptr, + MQT_NAMED_BUILDER(mlir::qc::staticQubitWithOp), + MQT_NAMED_BUILDER(mlir::qc::staticQubitWithOp), + MQT_NAMED_BUILDER(mlir::qir::staticQubitWithOp), false}, CompilerPipelineTestCase{"AllocQubit", MQT_NAMED_BUILDER(qc::allocQubit), nullptr, MQT_NAMED_BUILDER(mlir::qc::allocQubit), diff --git a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp index bb81b94ecc..b144901b3b 100644 --- a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp +++ b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp @@ -113,6 +113,18 @@ TEST_P(QCOToQCTest, ProgramEquivalence) { areModulesEquivalentWithPermutations(program.get(), reference.get())); } +/// \name QCOToQC/QubitManagement/QubitManagement.cpp +/// @{ +INSTANTIATE_TEST_SUITE_P( + QCOQubitManagementTest, QCOToQCTest, + testing::Values(QCOToQCTestCase{"StaticQubits", + MQT_NAMED_BUILDER(qco::staticQubits), + MQT_NAMED_BUILDER(qc::staticQubits)}, + QCOToQCTestCase{"StaticQubitWithOp", + MQT_NAMED_BUILDER(qco::staticQubitWithOp), + MQT_NAMED_BUILDER(qc::staticQubitWithOp)})); +/// @} + /// \name QCOToQC/Modifiers/InvOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( diff --git a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp index 98d6c9e012..60b6441182 100644 --- a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp +++ b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp @@ -112,6 +112,18 @@ TEST_P(QCToQCOTest, ProgramEquivalence) { areModulesEquivalentWithPermutations(program.get(), reference.get())); } +/// \name QCToQCO/QubitManagement/StaticOp.cpp +/// @{ +INSTANTIATE_TEST_SUITE_P( + QCStaticOpTest, QCToQCOTest, + testing::Values( + QCToQCOTestCase{"StaticQubits", MQT_NAMED_BUILDER(qc::staticQubits), + MQT_NAMED_BUILDER(qco::staticQubits)}, + QCToQCOTestCase{"StaticQubitWithOp", + MQT_NAMED_BUILDER(qc::staticQubitWithOp), + MQT_NAMED_BUILDER(qco::staticQubitWithOp)})); +/// @} + /// \name QCToQCO/Modifiers/InvOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( diff --git a/mlir/unittests/Conversion/QCToQIR/test_qc_to_qir.cpp b/mlir/unittests/Conversion/QCToQIR/test_qc_to_qir.cpp index 82ab251e41..90f311d6e1 100644 --- a/mlir/unittests/Conversion/QCToQIR/test_qc_to_qir.cpp +++ b/mlir/unittests/Conversion/QCToQIR/test_qc_to_qir.cpp @@ -624,6 +624,9 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(qir::emptyQIR)}, QCToQIRTestCase{"StaticQubits", MQT_NAMED_BUILDER(qc::staticQubits), MQT_NAMED_BUILDER(qir::emptyQIR)}, + QCToQIRTestCase{"StaticQubitWithOp", + MQT_NAMED_BUILDER(qc::staticQubitWithOp), + MQT_NAMED_BUILDER(qir::staticQubitWithOp)}, QCToQIRTestCase{"AllocDeallocPair", MQT_NAMED_BUILDER(qc::allocDeallocPair), MQT_NAMED_BUILDER(qir::emptyQIR)})); diff --git a/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp b/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp index 221b750f7e..48a22bcb90 100644 --- a/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp +++ b/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp @@ -899,6 +899,8 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(emptyQC)}, QCTestCase{"StaticQubits", MQT_NAMED_BUILDER(staticQubits), MQT_NAMED_BUILDER(emptyQC)}, + QCTestCase{"StaticQubitWithOp", MQT_NAMED_BUILDER(staticQubitWithOp), + MQT_NAMED_BUILDER(staticQubitWithOp)}, QCTestCase{"AllocDeallocPair", MQT_NAMED_BUILDER(allocDeallocPair), MQT_NAMED_BUILDER(emptyQC)})); /// @} diff --git a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp index ad4981d1b7..29c6ec7ac2 100644 --- a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp +++ b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp @@ -1060,6 +1060,8 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(emptyQCO)}, QCOTestCase{"StaticQubits", MQT_NAMED_BUILDER(staticQubits), MQT_NAMED_BUILDER(emptyQCO)}, + QCOTestCase{"StaticQubitWithOp", MQT_NAMED_BUILDER(staticQubitWithOp), + MQT_NAMED_BUILDER(staticQubitWithOp)}, QCOTestCase{"AllocDeallocPair", MQT_NAMED_BUILDER(allocDeallocPair), MQT_NAMED_BUILDER(emptyQCO)})); /// @} diff --git a/mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp b/mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp index e177c90329..20adcb1b73 100644 --- a/mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp +++ b/mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp @@ -522,17 +522,18 @@ INSTANTIATE_TEST_SUITE_P( /// @{ INSTANTIATE_TEST_SUITE_P( QIRQubitManagementTest, QIRTest, - testing::Values(QIRTestCase{"AllocQubit", MQT_NAMED_BUILDER(allocQubit), - MQT_NAMED_BUILDER(allocQubit)}, - QIRTestCase{"AllocQubitRegister", - MQT_NAMED_BUILDER(allocQubitRegister), - MQT_NAMED_BUILDER(allocQubitRegister)}, - QIRTestCase{"AllocMultipleQubitRegisters", - MQT_NAMED_BUILDER(allocMultipleQubitRegisters), - MQT_NAMED_BUILDER(allocMultipleQubitRegisters)}, - QIRTestCase{"AllocLargeRegister", - MQT_NAMED_BUILDER(allocLargeRegister), - MQT_NAMED_BUILDER(allocLargeRegister)}, - QIRTestCase{"StaticQubits", MQT_NAMED_BUILDER(staticQubits), - MQT_NAMED_BUILDER(staticQubits)})); + testing::Values( + QIRTestCase{"AllocQubit", MQT_NAMED_BUILDER(allocQubit), + MQT_NAMED_BUILDER(allocQubit)}, + QIRTestCase{"AllocQubitRegister", MQT_NAMED_BUILDER(allocQubitRegister), + MQT_NAMED_BUILDER(allocQubitRegister)}, + QIRTestCase{"AllocMultipleQubitRegisters", + MQT_NAMED_BUILDER(allocMultipleQubitRegisters), + MQT_NAMED_BUILDER(allocMultipleQubitRegisters)}, + QIRTestCase{"AllocLargeRegister", MQT_NAMED_BUILDER(allocLargeRegister), + MQT_NAMED_BUILDER(allocLargeRegister)}, + QIRTestCase{"StaticQubits", MQT_NAMED_BUILDER(staticQubits), + MQT_NAMED_BUILDER(staticQubits)}, + QIRTestCase{"StaticQubitWithOp", MQT_NAMED_BUILDER(staticQubitWithOp), + MQT_NAMED_BUILDER(staticQubitWithOp)})); /// @} From ea7b6751b407a142e48603a4e1a4c3380eabd45f Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 17 Mar 2026 14:37:54 +0100 Subject: [PATCH 04/57] =?UTF-8?q?=F0=9F=94=A7=20Add=20missing=20include=20?= =?UTF-8?q?for=20LLVM=20casting=20support=20in=20QCOToQC.cpp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/lib/Conversion/QCOToQC/QCOToQC.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp index 2f9dd1bf2f..da347a958d 100644 --- a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -15,6 +15,7 @@ #include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" +#include #include #include #include From b82428411100c98eeaad8effad7cf8c849a242bd Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 17 Mar 2026 15:07:58 +0100 Subject: [PATCH 05/57] =?UTF-8?q?=E2=9C=A8=20Rename=20`staticQubitWithOp`?= =?UTF-8?q?=20to=20`staticQubitsWithOps`=20and=20update=20related=20test?= =?UTF-8?q?=20cases=20across=20various=20dialects=20and=20programs=20to=20?= =?UTF-8?q?reflect=20the=20new=20functionality=20of=20allocating=20multipl?= =?UTF-8?q?e=20static=20qubits=20and=20applying=20operations.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Compiler/test_compiler_pipeline.cpp | 8 ++--- .../Conversion/QCOToQC/test_qco_to_qc.cpp | 12 ++++---- .../Conversion/QCToQCO/test_qc_to_qco.cpp | 6 ++-- .../Conversion/QCToQIR/test_qc_to_qir.cpp | 6 ++-- mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp | 5 ++-- mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp | 5 ++-- mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp | 30 ++++++++++--------- mlir/unittests/programs/qc_programs.cpp | 8 +++-- mlir/unittests/programs/qc_programs.h | 4 +-- mlir/unittests/programs/qco_programs.cpp | 8 +++-- mlir/unittests/programs/qco_programs.h | 4 +-- mlir/unittests/programs/qir_programs.cpp | 8 +++-- mlir/unittests/programs/qir_programs.h | 4 +-- 13 files changed, 59 insertions(+), 49 deletions(-) diff --git a/mlir/unittests/Compiler/test_compiler_pipeline.cpp b/mlir/unittests/Compiler/test_compiler_pipeline.cpp index 7d79ca50b1..eaddb27a5c 100644 --- a/mlir/unittests/Compiler/test_compiler_pipeline.cpp +++ b/mlir/unittests/Compiler/test_compiler_pipeline.cpp @@ -194,10 +194,10 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(mlir::qc::staticQubits), MQT_NAMED_BUILDER(mlir::qir::staticQubits), false}, CompilerPipelineTestCase{ - "StaticQubitWithOp", nullptr, - MQT_NAMED_BUILDER(mlir::qc::staticQubitWithOp), - MQT_NAMED_BUILDER(mlir::qc::staticQubitWithOp), - MQT_NAMED_BUILDER(mlir::qir::staticQubitWithOp), false}, + "StaticQubitsWithOps", nullptr, + MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithOps), + MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithOps), + MQT_NAMED_BUILDER(mlir::qir::staticQubitsWithOps), false}, CompilerPipelineTestCase{"AllocQubit", MQT_NAMED_BUILDER(qc::allocQubit), nullptr, MQT_NAMED_BUILDER(mlir::qc::allocQubit), diff --git a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp index b144901b3b..35524c0f23 100644 --- a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp +++ b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp @@ -117,12 +117,12 @@ TEST_P(QCOToQCTest, ProgramEquivalence) { /// @{ INSTANTIATE_TEST_SUITE_P( QCOQubitManagementTest, QCOToQCTest, - testing::Values(QCOToQCTestCase{"StaticQubits", - MQT_NAMED_BUILDER(qco::staticQubits), - MQT_NAMED_BUILDER(qc::staticQubits)}, - QCOToQCTestCase{"StaticQubitWithOp", - MQT_NAMED_BUILDER(qco::staticQubitWithOp), - MQT_NAMED_BUILDER(qc::staticQubitWithOp)})); + testing::Values( + QCOToQCTestCase{"StaticQubits", MQT_NAMED_BUILDER(qco::staticQubits), + MQT_NAMED_BUILDER(qc::staticQubits)}, + QCOToQCTestCase{"StaticQubitsWithOps", + MQT_NAMED_BUILDER(qco::staticQubitsWithOps), + MQT_NAMED_BUILDER(qc::staticQubitsWithOps)})); /// @} /// \name QCOToQC/Modifiers/InvOp.cpp diff --git a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp index 60b6441182..be82dc9680 100644 --- a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp +++ b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp @@ -119,9 +119,9 @@ INSTANTIATE_TEST_SUITE_P( testing::Values( QCToQCOTestCase{"StaticQubits", MQT_NAMED_BUILDER(qc::staticQubits), MQT_NAMED_BUILDER(qco::staticQubits)}, - QCToQCOTestCase{"StaticQubitWithOp", - MQT_NAMED_BUILDER(qc::staticQubitWithOp), - MQT_NAMED_BUILDER(qco::staticQubitWithOp)})); + QCToQCOTestCase{"StaticQubitsWithOps", + MQT_NAMED_BUILDER(qc::staticQubitsWithOps), + MQT_NAMED_BUILDER(qco::staticQubitsWithOps)})); /// @} /// \name QCToQCO/Modifiers/InvOp.cpp diff --git a/mlir/unittests/Conversion/QCToQIR/test_qc_to_qir.cpp b/mlir/unittests/Conversion/QCToQIR/test_qc_to_qir.cpp index 90f311d6e1..670aeb39aa 100644 --- a/mlir/unittests/Conversion/QCToQIR/test_qc_to_qir.cpp +++ b/mlir/unittests/Conversion/QCToQIR/test_qc_to_qir.cpp @@ -624,9 +624,9 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(qir::emptyQIR)}, QCToQIRTestCase{"StaticQubits", MQT_NAMED_BUILDER(qc::staticQubits), MQT_NAMED_BUILDER(qir::emptyQIR)}, - QCToQIRTestCase{"StaticQubitWithOp", - MQT_NAMED_BUILDER(qc::staticQubitWithOp), - MQT_NAMED_BUILDER(qir::staticQubitWithOp)}, + QCToQIRTestCase{"StaticQubitsWithOps", + MQT_NAMED_BUILDER(qc::staticQubitsWithOps), + MQT_NAMED_BUILDER(qir::staticQubitsWithOps)}, QCToQIRTestCase{"AllocDeallocPair", MQT_NAMED_BUILDER(qc::allocDeallocPair), MQT_NAMED_BUILDER(qir::emptyQIR)})); diff --git a/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp b/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp index 48a22bcb90..4d99bdb168 100644 --- a/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp +++ b/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp @@ -899,8 +899,9 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(emptyQC)}, QCTestCase{"StaticQubits", MQT_NAMED_BUILDER(staticQubits), MQT_NAMED_BUILDER(emptyQC)}, - QCTestCase{"StaticQubitWithOp", MQT_NAMED_BUILDER(staticQubitWithOp), - MQT_NAMED_BUILDER(staticQubitWithOp)}, + QCTestCase{"StaticQubitsWithOps", + MQT_NAMED_BUILDER(staticQubitsWithOps), + MQT_NAMED_BUILDER(staticQubitsWithOps)}, QCTestCase{"AllocDeallocPair", MQT_NAMED_BUILDER(allocDeallocPair), MQT_NAMED_BUILDER(emptyQC)})); /// @} diff --git a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp index 29c6ec7ac2..483029d6ae 100644 --- a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp +++ b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp @@ -1060,8 +1060,9 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(emptyQCO)}, QCOTestCase{"StaticQubits", MQT_NAMED_BUILDER(staticQubits), MQT_NAMED_BUILDER(emptyQCO)}, - QCOTestCase{"StaticQubitWithOp", MQT_NAMED_BUILDER(staticQubitWithOp), - MQT_NAMED_BUILDER(staticQubitWithOp)}, + QCOTestCase{"StaticQubitsWithOps", + MQT_NAMED_BUILDER(staticQubitsWithOps), + MQT_NAMED_BUILDER(staticQubitsWithOps)}, QCOTestCase{"AllocDeallocPair", MQT_NAMED_BUILDER(allocDeallocPair), MQT_NAMED_BUILDER(emptyQCO)})); /// @} diff --git a/mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp b/mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp index 20adcb1b73..9ce94c3eda 100644 --- a/mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp +++ b/mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp @@ -522,18 +522,20 @@ INSTANTIATE_TEST_SUITE_P( /// @{ INSTANTIATE_TEST_SUITE_P( QIRQubitManagementTest, QIRTest, - testing::Values( - QIRTestCase{"AllocQubit", MQT_NAMED_BUILDER(allocQubit), - MQT_NAMED_BUILDER(allocQubit)}, - QIRTestCase{"AllocQubitRegister", MQT_NAMED_BUILDER(allocQubitRegister), - MQT_NAMED_BUILDER(allocQubitRegister)}, - QIRTestCase{"AllocMultipleQubitRegisters", - MQT_NAMED_BUILDER(allocMultipleQubitRegisters), - MQT_NAMED_BUILDER(allocMultipleQubitRegisters)}, - QIRTestCase{"AllocLargeRegister", MQT_NAMED_BUILDER(allocLargeRegister), - MQT_NAMED_BUILDER(allocLargeRegister)}, - QIRTestCase{"StaticQubits", MQT_NAMED_BUILDER(staticQubits), - MQT_NAMED_BUILDER(staticQubits)}, - QIRTestCase{"StaticQubitWithOp", MQT_NAMED_BUILDER(staticQubitWithOp), - MQT_NAMED_BUILDER(staticQubitWithOp)})); + testing::Values(QIRTestCase{"AllocQubit", MQT_NAMED_BUILDER(allocQubit), + MQT_NAMED_BUILDER(allocQubit)}, + QIRTestCase{"AllocQubitRegister", + MQT_NAMED_BUILDER(allocQubitRegister), + MQT_NAMED_BUILDER(allocQubitRegister)}, + QIRTestCase{"AllocMultipleQubitRegisters", + MQT_NAMED_BUILDER(allocMultipleQubitRegisters), + MQT_NAMED_BUILDER(allocMultipleQubitRegisters)}, + QIRTestCase{"AllocLargeRegister", + MQT_NAMED_BUILDER(allocLargeRegister), + MQT_NAMED_BUILDER(allocLargeRegister)}, + QIRTestCase{"StaticQubits", MQT_NAMED_BUILDER(staticQubits), + MQT_NAMED_BUILDER(staticQubits)}, + QIRTestCase{"StaticQubitsWithOps", + MQT_NAMED_BUILDER(staticQubitsWithOps), + MQT_NAMED_BUILDER(staticQubitsWithOps)})); /// @} diff --git a/mlir/unittests/programs/qc_programs.cpp b/mlir/unittests/programs/qc_programs.cpp index bceaa14154..c0302528c6 100644 --- a/mlir/unittests/programs/qc_programs.cpp +++ b/mlir/unittests/programs/qc_programs.cpp @@ -34,9 +34,11 @@ void staticQubits(QCProgramBuilder& b) { b.staticQubit(1); } -void staticQubitWithOp(QCProgramBuilder& b) { - auto q = b.staticQubit(0); - b.h(q); +void staticQubitsWithOps(QCProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + b.h(q0); + b.h(q1); } void allocDeallocPair(QCProgramBuilder& b) { diff --git a/mlir/unittests/programs/qc_programs.h b/mlir/unittests/programs/qc_programs.h index 816b5cc73c..44510006bd 100644 --- a/mlir/unittests/programs/qc_programs.h +++ b/mlir/unittests/programs/qc_programs.h @@ -33,8 +33,8 @@ void allocLargeRegister(QCProgramBuilder& b); /// Allocates two inline qubits. void staticQubits(QCProgramBuilder& b); -/// Allocates a static qubit and applies an operation. -void staticQubitWithOp(QCProgramBuilder& b); +/// Allocates two static qubits and applies operations. +void staticQubitsWithOps(QCProgramBuilder& b); /// Allocates and explicitly deallocates a single qubit. void allocDeallocPair(QCProgramBuilder& b); diff --git a/mlir/unittests/programs/qco_programs.cpp b/mlir/unittests/programs/qco_programs.cpp index fd8fd1ae7b..4944898ac4 100644 --- a/mlir/unittests/programs/qco_programs.cpp +++ b/mlir/unittests/programs/qco_programs.cpp @@ -38,9 +38,11 @@ void staticQubits(QCOProgramBuilder& b) { b.staticQubit(1); } -void staticQubitWithOp(QCOProgramBuilder& b) { - auto q = b.staticQubit(0); - b.h(q); +void staticQubitsWithOps(QCOProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + q0 = b.h(q0); + q1 = b.h(q1); } void allocDeallocPair(QCOProgramBuilder& b) { diff --git a/mlir/unittests/programs/qco_programs.h b/mlir/unittests/programs/qco_programs.h index 15687411e8..61b2f4a0cf 100644 --- a/mlir/unittests/programs/qco_programs.h +++ b/mlir/unittests/programs/qco_programs.h @@ -33,8 +33,8 @@ void allocLargeRegister(QCOProgramBuilder& b); /// Allocates two inline qubits. void staticQubits(QCOProgramBuilder& b); -/// Allocates a static qubit and applies an operation. -void staticQubitWithOp(QCOProgramBuilder& b); +/// Allocates two static qubits and applies operations. +void staticQubitsWithOps(QCOProgramBuilder& b); /// Allocates and explicitly deallocates a single qubit. void allocDeallocPair(QCOProgramBuilder& b); diff --git a/mlir/unittests/programs/qir_programs.cpp b/mlir/unittests/programs/qir_programs.cpp index 38d88fa08f..75421f804b 100644 --- a/mlir/unittests/programs/qir_programs.cpp +++ b/mlir/unittests/programs/qir_programs.cpp @@ -32,9 +32,11 @@ void staticQubits(QIRProgramBuilder& b) { b.staticQubit(1); } -void staticQubitWithOp(QIRProgramBuilder& b) { - auto q = b.staticQubit(0); - b.h(q); +void staticQubitsWithOps(QIRProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + b.h(q0); + b.h(q1); } void singleMeasurementToSingleBit(QIRProgramBuilder& b) { diff --git a/mlir/unittests/programs/qir_programs.h b/mlir/unittests/programs/qir_programs.h index 1c2e5754a2..b601d297ea 100644 --- a/mlir/unittests/programs/qir_programs.h +++ b/mlir/unittests/programs/qir_programs.h @@ -33,8 +33,8 @@ void allocLargeRegister(QIRProgramBuilder& b); /// Allocates two inline qubits. void staticQubits(QIRProgramBuilder& b); -/// Allocates a static qubit and applies an operation. -void staticQubitWithOp(QIRProgramBuilder& b); +/// Allocates two static qubits and applies operations. +void staticQubitsWithOps(QIRProgramBuilder& b); // --- MeasureOp ------------------------------------------------------------ // From c2d414f44804ba41f55d2c3505362d06a255bf6f Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 17 Mar 2026 15:46:31 +0100 Subject: [PATCH 06/57] =?UTF-8?q?=F0=9F=94=A7=20Enhance=20`ConvertFuncRetu?= =?UTF-8?q?rnOp`=20to=20exclude=20escaped=20qubits=20from=20the=20live=20q?= =?UTF-8?q?ubits=20list.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/lib/Conversion/QCToQCO/QCToQCO.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index f90a008973..32e0a35167 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -135,9 +135,14 @@ struct ConvertFuncReturnOp final : StatefulOpConversionPattern { llvm::SmallVector liveQubits; liveQubits.reserve(state.qubitMap.size()); + llvm::DenseSet escapedQubits; + for (Value returned : adaptor.getOperands()) { + escapedQubits.insert(returned); + } + llvm::DenseSet seen; for (const auto& [/*qcQubit*/ _, qcoQubit] : state.qubitMap) { - if (seen.insert(qcoQubit).second) { + if (!escapedQubits.contains(qcoQubit) && seen.insert(qcoQubit).second) { liveQubits.push_back(qcoQubit); } } From 0b81e9c8941d22627ac1eb2ab0a580cc4f0df1c8 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Thu, 19 Mar 2026 09:30:19 +0100 Subject: [PATCH 07/57] =?UTF-8?q?=E2=9C=A8=20Enhance=20QubitType=20to=20su?= =?UTF-8?q?pport=20static=20and=20dynamic=20qubits,=20updating=20related?= =?UTF-8?q?=20operations=20and=20conversions=20in=20QCO=20and=20QC=20diale?= =?UTF-8?q?cts.=20Add=20tests=20for=20static=20qubit=20type=20propagation?= =?UTF-8?q?=20and=20ensure=20correct=20handling=20in=20various=20operation?= =?UTF-8?q?s.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 +- mlir/include/mlir/Dialect/QC/IR/QCOps.td | 21 +++- mlir/include/mlir/Dialect/QCO/IR/QCOOps.td | 118 +++++++++++++----- mlir/lib/Conversion/QCOToQC/QCOToQC.cpp | 29 ++--- mlir/lib/Conversion/QCToQCO/QCToQCO.cpp | 87 +++++++------ .../Dialect/QC/Builder/QCProgramBuilder.cpp | 4 +- mlir/lib/Dialect/QC/IR/QCOps.cpp | 18 +++ .../Dialect/QCO/Builder/QCOProgramBuilder.cpp | 9 +- mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp | 6 +- mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp | 6 +- mlir/lib/Dialect/QCO/IR/QCOOps.cpp | 18 +++ mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp | 70 +++++++++++ mlir/unittests/programs/qco_programs.cpp | 34 +++++ mlir/unittests/programs/qco_programs.h | 15 +++ 14 files changed, 326 insertions(+), 112 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a4f014b36..4a5dee64df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel - ✨ Add conversions between Jeff and QCO ([#1479], [#1548], [#1565]) ([**@denialhaag**]) - ✨ Add a `place-and-route` pass for mapping circuits to architectures with restricted topologies ([#1537], [#1547], [#1568]) ([**@MatthiasReumann**]) - ✨ Add initial infrastructure for new QC and QCO MLIR dialects - ([#1264], [#1330], [#1402], [#1428], [#1430], [#1436], [#1443], [#1446], [#1464], [#1465], [#1470], [#1471], [#1472], [#1474], [#1475], [#1506], [#1510], [#1513], [#1521], [#1542], [#1548], [#1550], [#1554], [#1570]) + ([#1264], [#1330], [#1402], [#1428], [#1430], [#1436], [#1443], [#1446], [#1464], [#1465], [#1470], [#1471], [#1472], [#1474], [#1475], [#1506], [#1510], [#1513], [#1521], [#1542], [#1548], [#1550], [#1554], [#1569], [#1570]) ([**@burgholzer**], [**@denialhaag**], [**@taminob**], [**@DRovara**], [**@li-mingbao**], [**@Ectras**], [**@MatthiasReumann**], [**@simon1hofmann**]) ### Changed @@ -335,6 +335,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool [#1571]: https://github.com/munich-quantum-toolkit/core/pull/1571 [#1570]: https://github.com/munich-quantum-toolkit/core/pull/1570 +[#1569]: https://github.com/munich-quantum-toolkit/core/pull/1569 [#1568]: https://github.com/munich-quantum-toolkit/core/pull/1568 [#1565]: https://github.com/munich-quantum-toolkit/core/pull/1565 [#1564]: https://github.com/munich-quantum-toolkit/core/pull/1564 diff --git a/mlir/include/mlir/Dialect/QC/IR/QCOps.td b/mlir/include/mlir/Dialect/QC/IR/QCOps.td index 8093095868..3842c3d112 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCOps.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCOps.td @@ -78,10 +78,16 @@ def AllocOp : QCOp<"alloc", [MemoryEffects<[MemAlloc]>]> { let hasVerifier = 1; } +def DynamicQubit : Type< + And<[CPred<"::mlir::isa<::mlir::qc::QubitType>($_self)">, + CPred<"!::mlir::cast<::mlir::qc::QubitType>($_self).getIsStatic()">]>, + "dynamic qubit type (!qc.qubit)">; + def DeallocOp : QCOp<"dealloc", [MemoryEffects<[MemFree]>]> { - let summary = "Deallocate a qubit"; + let summary = "Deallocate a dynamically allocated qubit"; let description = [{ - Deallocates a qubit, releasing its resources. + Deallocates a dynamically allocated qubit, releasing its resources. + Static qubits (`!qc.qubit`) cannot be deallocated. Example: ```mlir @@ -89,7 +95,7 @@ def DeallocOp : QCOp<"dealloc", [MemoryEffects<[MemFree]>]> { ``` }]; - let arguments = (ins QubitType:$qubit); + let arguments = (ins DynamicQubit:$qubit); let assemblyFormat = "$qubit attr-dict `:` type($qubit)"; let hasCanonicalizer = 1; @@ -104,13 +110,20 @@ def StaticOp : QCOp<"static", [Pure]> { Example: ```mlir - %q = qc.static 0 : !qc.qubit + %q = qc.static 0 : !qc.qubit ``` }]; let arguments = (ins ConfinedAttr:$index); let results = (outs QubitType:$qubit); let assemblyFormat = "$index attr-dict `:` type($qubit)"; + + let builders = [ + OpBuilder<(ins "int64_t":$index), [{ + build($_builder, $_state, QubitType::get($_builder.getContext(), /*isStatic=*/true), + $_builder.getI64IntegerAttr(index)); + }]> + ]; } //===----------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td index c226508825..12b605869d 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td @@ -81,7 +81,12 @@ def AllocOp : QCOOp<"alloc", [MemoryEffects<[MemAlloc]>]> { def DeallocOp : QCOOp<"dealloc", [MemoryEffects<[MemFree]>]> { let summary = "Deallocate a qubit"; let description = [{ - Deallocates a qubit, releasing its resources. + Deallocates a qubit. + + In QCO's value/linear semantics, this operation also serves as the sink + that consumes the qubit SSA value (ensuring every qubit value is used + exactly once). When lowering back to QC (reference semantics), deallocs + corresponding to static qubits may be erased. Example: ```mlir @@ -103,20 +108,29 @@ def StaticOp : QCOOp<"static", [MemoryEffects<[MemAlloc]>]> { Example: ```mlir - %q = qco.static 0 : !qco.qubit + %q = qco.static 0 : !qco.qubit ``` }]; let arguments = (ins ConfinedAttr:$index); let results = (outs QubitType:$qubit); let assemblyFormat = "$index attr-dict `:` type($qubit)"; + + let builders = [ + OpBuilder<(ins "int64_t":$index), [{ + build($_builder, $_state, QubitType::get($_builder.getContext(), /*isStatic=*/true), + $_builder.getI64IntegerAttr(index)); + }]> + ]; } //===----------------------------------------------------------------------===// // Measurement and Reset Operations //===----------------------------------------------------------------------===// -def MeasureOp : QCOOp<"measure"> { +def MeasureOp : QCOOp<"measure", + [TypesMatchWith<"qubit output type matches input", + "qubit_in", "qubit_out", "$_self">]> { let summary = "Measure a qubit in the computational basis"; let description = [{ Measures a qubit in the computational (Z) basis, collapsing the state @@ -151,7 +165,7 @@ def MeasureOp : QCOOp<"measure"> { let builders = [ OpBuilder<(ins "Value":$qubit_in), [{ - build($_builder, $_state, QubitType::get($_builder.getContext()), $_builder.getI1Type(), + build($_builder, $_state, qubit_in.getType(), $_builder.getI1Type(), qubit_in, nullptr, nullptr, nullptr); }]> ]; @@ -225,7 +239,8 @@ def GPhaseOp : QCOOp<"gphase", traits = [UnitaryOpInterface, ZeroTargetOneParame let hasCanonicalizer = 1; } -def IdOp : QCOOp<"id", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def IdOp : QCOOp<"id", traits = [UnitaryOpInterface, OneTargetZeroParameter, + TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply an Id gate to a qubit"; let description = [{ Applies an Id gate to a qubit and returns the transformed qubit. @@ -248,7 +263,8 @@ def IdOp : QCOOp<"id", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let hasCanonicalizer = 1; } -def XOp : QCOOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def XOp : QCOOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter, + TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply an X gate to a qubit"; let description = [{ Applies an X gate to a qubit and returns the transformed qubit. @@ -271,7 +287,8 @@ def XOp : QCOOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let hasCanonicalizer = 1; } -def YOp : QCOOp<"y", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def YOp : QCOOp<"y", traits = [UnitaryOpInterface, OneTargetZeroParameter, + TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply a Y gate to a qubit"; let description = [{ Applies a Y gate to a qubit and returns the transformed qubit. @@ -294,7 +311,8 @@ def YOp : QCOOp<"y", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let hasCanonicalizer = 1; } -def ZOp : QCOOp<"z", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def ZOp : QCOOp<"z", traits = [UnitaryOpInterface, OneTargetZeroParameter, + TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply a Z gate to a qubit"; let description = [{ Applies a Z gate to a qubit and returns the transformed qubit. @@ -317,7 +335,8 @@ def ZOp : QCOOp<"z", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let hasCanonicalizer = 1; } -def HOp : QCOOp<"h", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def HOp : QCOOp<"h", traits = [UnitaryOpInterface, OneTargetZeroParameter, + TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply a H gate to a qubit"; let description = [{ Applies a H gate to a qubit and returns the transformed qubit. @@ -340,7 +359,8 @@ def HOp : QCOOp<"h", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let hasCanonicalizer = 1; } -def SOp : QCOOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def SOp : QCOOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter, + TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply an S gate to a qubit"; let description = [{ Applies an S gate to a qubit and returns the transformed qubit. @@ -363,7 +383,8 @@ def SOp : QCOOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let hasCanonicalizer = 1; } -def SdgOp : QCOOp<"sdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def SdgOp : QCOOp<"sdg", traits = [UnitaryOpInterface, OneTargetZeroParameter, + TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply an Sdg gate to a qubit"; let description = [{ Applies an Sdg gate to a qubit and returns the transformed qubit. @@ -386,7 +407,8 @@ def SdgOp : QCOOp<"sdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> let hasCanonicalizer = 1; } -def TOp : QCOOp<"t", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def TOp : QCOOp<"t", traits = [UnitaryOpInterface, OneTargetZeroParameter, + TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply a T gate to a qubit"; let description = [{ Applies a T gate to a qubit and returns the transformed qubit. @@ -409,7 +431,8 @@ def TOp : QCOOp<"t", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let hasCanonicalizer = 1; } -def TdgOp : QCOOp<"tdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def TdgOp : QCOOp<"tdg", traits = [UnitaryOpInterface, OneTargetZeroParameter, + TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply a Tdg gate to a qubit"; let description = [{ Applies a Tdg gate to a qubit and returns the transformed qubit. @@ -432,7 +455,8 @@ def TdgOp : QCOOp<"tdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> let hasCanonicalizer = 1; } -def SXOp : QCOOp<"sx", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def SXOp : QCOOp<"sx", traits = [UnitaryOpInterface, OneTargetZeroParameter, + TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply an SX gate to a qubit"; let description = [{ Applies an SX gate to a qubit and returns the transformed qubit. @@ -455,7 +479,8 @@ def SXOp : QCOOp<"sx", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let hasCanonicalizer = 1; } -def SXdgOp : QCOOp<"sxdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { +def SXdgOp : QCOOp<"sxdg", traits = [UnitaryOpInterface, OneTargetZeroParameter, + TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply an SXdg gate to a qubit"; let description = [{ Applies an SXdg gate to a qubit and returns the transformed qubit. @@ -478,7 +503,8 @@ def SXdgOp : QCOOp<"sxdg", traits = [UnitaryOpInterface, OneTargetZeroParameter] let hasCanonicalizer = 1; } -def RXOp : QCOOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> { +def RXOp : QCOOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter, + TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply an RX gate to a qubit"; let description = [{ Applies an RX gate to a qubit and returns the transformed qubit. @@ -506,7 +532,8 @@ def RXOp : QCOOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let hasCanonicalizer = 1; } -def RYOp : QCOOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter]> { +def RYOp : QCOOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter, + TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply an RY gate to a qubit"; let description = [{ Applies an RY gate to a qubit and returns the transformed qubit. @@ -534,7 +561,8 @@ def RYOp : QCOOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let hasCanonicalizer = 1; } -def RZOp : QCOOp<"rz", traits = [UnitaryOpInterface, OneTargetOneParameter]> { +def RZOp : QCOOp<"rz", traits = [UnitaryOpInterface, OneTargetOneParameter, + TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply an RZ gate to a qubit"; let description = [{ Applies an RZ gate to a qubit and returns the transformed qubit. @@ -562,7 +590,8 @@ def RZOp : QCOOp<"rz", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let hasCanonicalizer = 1; } -def POp : QCOOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter]> { +def POp : QCOOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter, + TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply a P gate to a qubit"; let description = [{ Applies a P gate to a qubit and returns the transformed qubit. @@ -590,7 +619,8 @@ def POp : QCOOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let hasCanonicalizer = 1; } -def ROp : QCOOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { +def ROp : QCOOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter, + TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply an R gate to a qubit"; let description = [{ Applies an R gate to a qubit and returns the transformed qubit. @@ -619,7 +649,8 @@ def ROp : QCOOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { let hasCanonicalizer = 1; } -def U2Op : QCOOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { +def U2Op : QCOOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter, + TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply a U2 gate to a qubit"; let description = [{ Applies a U2 gate to a qubit and returns the transformed qubit. @@ -648,7 +679,8 @@ def U2Op : QCOOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { let hasCanonicalizer = 1; } -def UOp : QCOOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter]> { +def UOp : QCOOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter, + TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply a U gate to a qubit"; let description = [{ Applies a U gate to a qubit and returns the transformed qubit. @@ -678,7 +710,9 @@ def UOp : QCOOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter]> { let hasCanonicalizer = 1; } -def SWAPOp : QCOOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { +def SWAPOp : QCOOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParameter, + TypesMatchWith<"result type matches input", "qubit0_in", "qubit0_out", "$_self">, + TypesMatchWith<"result type matches input", "qubit1_in", "qubit1_out", "$_self">]> { let summary = "Apply a SWAP gate to two qubits"; let description = [{ Applies a SWAP gate to two qubits and returns the transformed qubits. @@ -702,7 +736,9 @@ def SWAPOp : QCOOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParameter] let hasCanonicalizer = 1; } -def iSWAPOp : QCOOp<"iswap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { +def iSWAPOp : QCOOp<"iswap", traits = [UnitaryOpInterface, TwoTargetZeroParameter, + TypesMatchWith<"result type matches input", "qubit0_in", "qubit0_out", "$_self">, + TypesMatchWith<"result type matches input", "qubit1_in", "qubit1_out", "$_self">]> { let summary = "Apply a iSWAP gate to two qubits"; let description = [{ Applies a iSWAP gate to two qubits and returns the transformed qubits. @@ -724,7 +760,9 @@ def iSWAPOp : QCOOp<"iswap", traits = [UnitaryOpInterface, TwoTargetZeroParamete }]; } -def DCXOp : QCOOp<"dcx", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { +def DCXOp : QCOOp<"dcx", traits = [UnitaryOpInterface, TwoTargetZeroParameter, + TypesMatchWith<"result type matches input", "qubit0_in", "qubit0_out", "$_self">, + TypesMatchWith<"result type matches input", "qubit1_in", "qubit1_out", "$_self">]> { let summary = "Apply a DCX gate to two qubits"; let description = [{ Applies a DCX gate to two qubits and returns the transformed qubits. @@ -748,7 +786,9 @@ def DCXOp : QCOOp<"dcx", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> let hasCanonicalizer = 1; } -def ECROp : QCOOp<"ecr", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { +def ECROp : QCOOp<"ecr", traits = [UnitaryOpInterface, TwoTargetZeroParameter, + TypesMatchWith<"result type matches input", "qubit0_in", "qubit0_out", "$_self">, + TypesMatchWith<"result type matches input", "qubit1_in", "qubit1_out", "$_self">]> { let summary = "Apply an ECR gate to two qubits"; let description = [{ Applies an ECR gate to two qubits and returns the transformed qubits. @@ -772,7 +812,9 @@ def ECROp : QCOOp<"ecr", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> let hasCanonicalizer = 1; } -def RXXOp : QCOOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { +def RXXOp : QCOOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter, + TypesMatchWith<"result type matches input", "qubit0_in", "qubit0_out", "$_self">, + TypesMatchWith<"result type matches input", "qubit1_in", "qubit1_out", "$_self">]> { let summary = "Apply an RXX gate to two qubits"; let description = [{ Applies an RXX gate to two qubits and returns the transformed qubits. @@ -801,7 +843,9 @@ def RXXOp : QCOOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let hasCanonicalizer = 1; } -def RYYOp : QCOOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { +def RYYOp : QCOOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter, + TypesMatchWith<"result type matches input", "qubit0_in", "qubit0_out", "$_self">, + TypesMatchWith<"result type matches input", "qubit1_in", "qubit1_out", "$_self">]> { let summary = "Apply an RYY gate to two qubits"; let description = [{ Applies an RYY gate to two qubits and returns the transformed qubits. @@ -830,7 +874,9 @@ def RYYOp : QCOOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let hasCanonicalizer = 1; } -def RZXOp : QCOOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { +def RZXOp : QCOOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter, + TypesMatchWith<"result type matches input", "qubit0_in", "qubit0_out", "$_self">, + TypesMatchWith<"result type matches input", "qubit1_in", "qubit1_out", "$_self">]> { let summary = "Apply an RZX gate to two qubits"; let description = [{ Applies an RZX gate to two qubits and returns the transformed qubits. @@ -859,7 +905,9 @@ def RZXOp : QCOOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let hasCanonicalizer = 1; } -def RZZOp : QCOOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { +def RZZOp : QCOOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter, + TypesMatchWith<"result type matches input", "qubit0_in", "qubit0_out", "$_self">, + TypesMatchWith<"result type matches input", "qubit1_in", "qubit1_out", "$_self">]> { let summary = "Apply an RZZ gate to two qubits"; let description = [{ Applies an RZZ gate to two qubits and returns the transformed qubits. @@ -888,7 +936,9 @@ def RZZOp : QCOOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let hasCanonicalizer = 1; } -def XXPlusYYOp : QCOOp<"xx_plus_yy", traits = [UnitaryOpInterface, TwoTargetTwoParameter]> { +def XXPlusYYOp : QCOOp<"xx_plus_yy", traits = [UnitaryOpInterface, TwoTargetTwoParameter, + TypesMatchWith<"result type matches input", "qubit0_in", "qubit0_out", "$_self">, + TypesMatchWith<"result type matches input", "qubit1_in", "qubit1_out", "$_self">]> { let summary = "Apply an XX+YY gate to two qubits"; let description = [{ Applies an XX+YY gate to two qubits and returns the transformed qubits. @@ -918,7 +968,9 @@ def XXPlusYYOp : QCOOp<"xx_plus_yy", traits = [UnitaryOpInterface, TwoTargetTwoP let hasCanonicalizer = 1; } -def XXMinusYYOp : QCOOp<"xx_minus_yy", traits = [UnitaryOpInterface, TwoTargetTwoParameter]> { +def XXMinusYYOp : QCOOp<"xx_minus_yy", traits = [UnitaryOpInterface, TwoTargetTwoParameter, + TypesMatchWith<"result type matches input", "qubit0_in", "qubit0_out", "$_self">, + TypesMatchWith<"result type matches input", "qubit1_in", "qubit1_out", "$_self">]> { let summary = "Apply an XX-YY gate to two qubits"; let description = [{ Applies an XX-YY gate to two qubits and returns the transformed qubits. @@ -1009,7 +1061,7 @@ def YieldOp : QCOOp<"yield", traits = [Terminator, ReturnLike]> { }]; let arguments = (ins Variadic:$targets); - let assemblyFormat = "$targets attr-dict"; + let assemblyFormat = "$targets attr-dict (`:` type($targets)^)?"; } def CtrlOp : QCOOp<"ctrl", traits = diff --git a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp index da347a958d..68dcb509b3 100644 --- a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -56,9 +56,9 @@ class QCOToQCTypeConverter final : public TypeConverter { // Identity conversion for all types by default addConversion([](Type type) { return type; }); - // Convert QCO qubit values to QC qubit references - addConversion([ctx](qco::QubitType /*type*/) -> Type { - return qc::QubitType::get(ctx); + // Convert QCO qubit values to QC qubit references, preserving isStatic + addConversion([ctx](qco::QubitType type) -> Type { + return qc::QubitType::get(ctx, type.getIsStatic()); }); } }; @@ -97,22 +97,12 @@ struct ConvertQCOAllocOp final : OpConversionPattern { }; /** - * @brief Converts qco.dealloc to qc.dealloc (except for static qubits). + * @brief Converts qco.dealloc to qc.dealloc (dynamic) or erases it (static). * * @details - * Deallocates a qubit, releasing its resources. The OpAdaptor automatically - * provides the type-converted qubit operand (!qc.qubit instead of - * !qco.qubit), so we simply pass it through to the new operation. - * - * Example transformation: - * ```mlir - * qco.dealloc %q_qco : !qco.qubit - * // becomes: - * qc.dealloc %q_qc : !qc.qubit - * ``` - * - * For static qubits, we erase `qco.dealloc` because QC does - * not require explicit sinks for static qubit handles. + * For dynamic qubits (`!qco.qubit`), lowers to `qc.dealloc`. + * For static qubits (`!qco.qubit`), erases the op since QC does not + * require explicit deallocation of static qubits. */ struct ConvertQCODeallocOp final : OpConversionPattern { using OpConversionPattern::OpConversionPattern; @@ -120,13 +110,12 @@ struct ConvertQCODeallocOp final : OpConversionPattern { LogicalResult matchAndRewrite(qco::DeallocOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { - auto qcQubit = adaptor.getQubit(); - if (llvm::isa_and_nonnull(qcQubit.getDefiningOp())) { + if (op.getQubit().getType().getIsStatic()) { rewriter.eraseOp(op); return success(); } - rewriter.replaceOpWithNewOp(op, qcQubit); + rewriter.replaceOpWithNewOp(op, adaptor.getQubit()); return success(); } }; diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index 32e0a35167..82ae93d32c 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -129,42 +129,55 @@ struct ConvertFuncReturnOp final : StatefulOpConversionPattern { ConversionPatternRewriter& rewriter) const override { auto& state = getState(); - // Only insert sinks at function exit (never inside nested modifier - // regions). - if (state.inNestedRegion == 0) { - llvm::SmallVector liveQubits; - liveQubits.reserve(state.qubitMap.size()); + if (state.inNestedRegion != 0) { + rewriter.replaceOpWithNewOp(op, adaptor.getOperands()); + return success(); + } - llvm::DenseSet escapedQubits; - for (Value returned : adaptor.getOperands()) { - escapedQubits.insert(returned); + // Build return values from qubitMap (adaptor.getOperands() may carry stale + // root values because gate patterns use eraseOp instead of replaceOp). + llvm::SmallVector returnValues; + llvm::DenseSet escapedQubits; + returnValues.reserve(op.getNumOperands()); + for (auto [qcOperand, adaptorOperand] : + llvm::zip(op.getOperands(), adaptor.getOperands())) { + if (state.qubitMap.contains(qcOperand)) { + auto latest = state.qubitMap[qcOperand]; + returnValues.push_back(latest); + escapedQubits.insert(latest); + } else { + returnValues.push_back(adaptorOperand); } + } - llvm::DenseSet seen; - for (const auto& [/*qcQubit*/ _, qcoQubit] : state.qubitMap) { - if (!escapedQubits.contains(qcoQubit) && seen.insert(qcoQubit).second) { - liveQubits.push_back(qcoQubit); - } + // Collect non-escaped live qubits for deallocation. + llvm::SmallVector liveQubits; + liveQubits.reserve(state.qubitMap.size()); + llvm::DenseSet seen; + for (const auto& [qcQubit, qcoQubit] : state.qubitMap) { + if (escapedQubits.contains(qcoQubit) || !seen.insert(qcoQubit).second) { + continue; } + liveQubits.emplace_back(qcoQubit); + } - // Sort deterministically (mirrors QCOProgramBuilder::finalize()). - llvm::sort(liveQubits, [](Value a, Value b) { - auto* opA = a.getDefiningOp(); - auto* opB = b.getDefiningOp(); - if (!opA || !opB || opA->getBlock() != opB->getBlock()) { - return a.getAsOpaquePointer() < b.getAsOpaquePointer(); - } - return opA->isBeforeInBlock(opB); - }); - - for (auto qubit : liveQubits) { - rewriter.create(op.getLoc(), qubit); + // Sort deterministically (mirrors QCOProgramBuilder::finalize()). + llvm::sort(liveQubits, [](Value a, Value b) { + auto* opA = a.getDefiningOp(); + auto* opB = b.getDefiningOp(); + if (!opA || !opB || opA->getBlock() != opB->getBlock()) { + return a.getAsOpaquePointer() < b.getAsOpaquePointer(); } + return opA->isBeforeInBlock(opB); + }); - state.qubitMap.clear(); + for (Value qubit : liveQubits) { + rewriter.create(op.getLoc(), qubit); } - rewriter.replaceOpWithNewOp(op, adaptor.getOperands()); + state.qubitMap.clear(); + + rewriter.replaceOpWithNewOp(op, returnValues); return success(); } }; @@ -186,9 +199,9 @@ class QCToQCOTypeConverter final : public TypeConverter { // Identity conversion for all types by default addConversion([](Type type) { return type; }); - // Convert QC qubit references to QCO qubit values - addConversion([ctx](qc::QubitType /*type*/) -> Type { - return qco::QubitType::get(ctx); + // Convert QC qubit references to QCO qubit values, preserving isStatic + addConversion([ctx](qc::QubitType type) -> Type { + return qco::QubitType::get(ctx, type.getIsStatic()); }); } }; @@ -1008,11 +1021,11 @@ struct ConvertQCCtrlOp final : StatefulOpConversionPattern { "QC ctrl region unexpectedly has entry block arguments"); SmallVector qcoTargetAliases; qcoTargetAliases.reserve(numTargets); - const auto qubitType = qco::QubitType::get(qcoOp.getContext()); const auto opLoc = op.getLoc(); rewriter.modifyOpInPlace(qcoOp, [&] { for (auto i = 0UL; i < numTargets; i++) { - qcoTargetAliases.emplace_back(entryBlock.addArgument(qubitType, opLoc)); + qcoTargetAliases.emplace_back( + entryBlock.addArgument(qcoTargets[i].getType(), opLoc)); } }); state.targetsIn[state.inNestedRegion] = std::move(qcoTargetAliases); @@ -1092,11 +1105,11 @@ struct ConvertQCInvOp final : StatefulOpConversionPattern { "QC inv region unexpectedly has entry block arguments"); SmallVector qcoTargetAliases; qcoTargetAliases.reserve(numTargets); - const auto qubitType = qco::QubitType::get(qcoOp.getContext()); const auto opLoc = op.getLoc(); rewriter.modifyOpInPlace(qcoOp, [&] { for (auto i = 0UL; i < numTargets; i++) { - qcoTargetAliases.emplace_back(entryBlock.addArgument(qubitType, opLoc)); + qcoTargetAliases.emplace_back( + entryBlock.addArgument(qcoTargets[i].getType(), opLoc)); } }); targetsIn[inNestedRegion] = std::move(qcoTargetAliases); @@ -1218,9 +1231,9 @@ struct QCToQCO final : impl::QCToQCOBase { // Conversion of qc types in func.return // // Note: `func.return` may already be type-legal even though we still need - // to insert `qco.dealloc` sinks for remaining live qubits. Therefore, we - // make it dynamically illegal unless the lowering state has no remaining - // qubits. + // to insert sink operations (`qco.dealloc`) for remaining live + // qubits. Therefore, we make it dynamically illegal unless the lowering + // state has no remaining qubits. patterns.add(typeConverter, context, &state); populateReturnOpTypeConversionPattern(patterns, typeConverter); target.addDynamicallyLegalOp([&](const func::ReturnOp op) { diff --git a/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp b/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp index da5cf312dd..80b7d0c7b3 100644 --- a/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp +++ b/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp @@ -86,9 +86,7 @@ Value QCProgramBuilder::staticQubit(const int64_t index) { llvm::reportFatalUsageError("Index must be non-negative"); } - // Create the StaticOp with the given index - auto indexAttr = getI64IntegerAttr(index); - auto staticOp = StaticOp::create(*this, indexAttr); + auto staticOp = StaticOp::create(*this, index); return staticOp.getQubit(); } diff --git a/mlir/lib/Dialect/QC/IR/QCOps.cpp b/mlir/lib/Dialect/QC/IR/QCOps.cpp index 5b93c2ebaa..4f2ecde79b 100644 --- a/mlir/lib/Dialect/QC/IR/QCOps.cpp +++ b/mlir/lib/Dialect/QC/IR/QCOps.cpp @@ -46,6 +46,24 @@ void QCDialect::initialize() { // Types //===----------------------------------------------------------------------===// +/// Print `!qc.qubit` (dynamic, default) or `!qc.qubit`. +void QubitType::print(AsmPrinter& printer) const { + if (getIsStatic()) { + printer << ""; + } +} + +/// Parse `!qc.qubit` or `!qc.qubit`. +Type QubitType::parse(AsmParser& parser) { + if (succeeded(parser.parseOptionalLess())) { + if (parser.parseKeyword("static") || parser.parseGreater()) { + return {}; + } + return get(parser.getContext(), /*isStatic=*/true); + } + return get(parser.getContext(), /*isStatic=*/false); +} + #define GET_TYPEDEF_CLASSES #include "mlir/Dialect/QC/IR/QCOpsTypes.cpp.inc" diff --git a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp index 05b55ff6dc..b23f189209 100644 --- a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp +++ b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp @@ -90,8 +90,7 @@ Value QCOProgramBuilder::staticQubit(const int64_t index) { llvm::reportFatalUsageError("Index must be non-negative"); } - auto indexAttr = getI64IntegerAttr(index); - auto staticOp = StaticOp::create(*this, indexAttr); + auto staticOp = StaticOp::create(*this, index); const auto qubit = staticOp.getQubit(); // Track the static qubit as valid @@ -747,9 +746,8 @@ std::pair QCOProgramBuilder::ctrl( auto ctrlOp = CtrlOp::create(*this, controls, targets); auto& block = ctrlOp.getBodyRegion().emplaceBlock(); - const auto qubitType = QubitType::get(getContext()); for (const auto target : targets) { - const auto arg = block.addArgument(qubitType, getLoc()); + const auto arg = block.addArgument(target.getType(), getLoc()); updateQubitTracking(target, arg); } const InsertionGuard guard(*this); @@ -785,9 +783,8 @@ ValueRange QCOProgramBuilder::inv( // Add block arguments for all qubits auto& block = invOp.getBodyRegion().emplaceBlock(); - const auto qubitType = QubitType::get(getContext()); for (const auto qubit : qubits) { - const auto arg = block.addArgument(qubitType, getLoc()); + const auto arg = block.addArgument(qubit.getType(), getLoc()); updateQubitTracking(qubit, arg); } diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp index 96c811eed8..1e20d2e7ff 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp @@ -242,9 +242,8 @@ void CtrlOp::build( build(odsBuilder, odsState, controls, targets); auto& block = odsState.regions.front()->emplaceBlock(); - const auto qubitType = QubitType::get(odsBuilder.getContext()); for (size_t i = 0; i < targets.size(); ++i) { - block.addArgument(qubitType, odsState.location); + block.addArgument(targets[i].getType(), odsState.location); } const OpBuilder::InsertionGuard guard(odsBuilder); @@ -263,9 +262,8 @@ LogicalResult CtrlOp::verify() { return emitOpError( "number of block arguments must match the number of targets"); } - const auto qubitType = QubitType::get(getContext()); for (size_t i = 0; i < numTargets; ++i) { - if (block.getArgument(i).getType() != qubitType) { + if (block.getArgument(i).getType() != getTargetsIn()[i].getType()) { return emitOpError("block argument type at index ") << i << " does not match target type"; } diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp index 18cc331c5b..77d51a0e55 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp @@ -326,9 +326,8 @@ void InvOp::build( build(odsBuilder, odsState, qubits); auto& block = odsState.regions.front()->emplaceBlock(); - const auto qubitType = QubitType::get(odsBuilder.getContext()); for (size_t i = 0; i < qubits.size(); ++i) { - block.addArgument(qubitType, odsState.location); + block.addArgument(qubits[i].getType(), odsState.location); } const OpBuilder::InsertionGuard guard(odsBuilder); @@ -347,9 +346,8 @@ LogicalResult InvOp::verify() { return emitOpError( "number of block arguments must match the number of targets"); } - const auto qubitType = QubitType::get(getContext()); for (size_t i = 0; i < numTargets; ++i) { - if (block.getArgument(i).getType() != qubitType) { + if (block.getArgument(i).getType() != getQubitsIn()[i].getType()) { return emitOpError("block argument type at index ") << i << " does not match target type"; } diff --git a/mlir/lib/Dialect/QCO/IR/QCOOps.cpp b/mlir/lib/Dialect/QCO/IR/QCOOps.cpp index 36aa66ad54..dca9ca8ce9 100644 --- a/mlir/lib/Dialect/QCO/IR/QCOOps.cpp +++ b/mlir/lib/Dialect/QCO/IR/QCOOps.cpp @@ -202,6 +202,24 @@ void QCODialect::initialize() { // Types //===----------------------------------------------------------------------===// +/// Print `!qco.qubit` (dynamic, default) or `!qco.qubit`. +void QubitType::print(AsmPrinter& printer) const { + if (getIsStatic()) { + printer << ""; + } +} + +/// Parse `!qco.qubit` or `!qco.qubit`. +Type QubitType::parse(AsmParser& parser) { + if (succeeded(parser.parseOptionalLess())) { + if (parser.parseKeyword("static") || parser.parseGreater()) { + return {}; + } + return get(parser.getContext(), /*isStatic=*/true); + } + return get(parser.getContext(), /*isStatic=*/false); +} + #define GET_TYPEDEF_CLASSES #include "mlir/Dialect/QCO/IR/QCOOpsTypes.cpp.inc" diff --git a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp index 483029d6ae..dfd647c736 100644 --- a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp +++ b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp @@ -127,6 +127,61 @@ TEST_F(QCOTest, DirectIfBuilder) { refBuilder.get())); } +TEST_F(QCOTest, StaticQubitTypePropagation) { + auto staticType = QubitType::get(context.get(), /*isStatic=*/true); + auto dynamicType = QubitType::get(context.get(), /*isStatic=*/false); + + qco::QCOProgramBuilder builder(context.get()); + builder.initialize(); + + auto sQ = StaticOp::create(builder, 0); + auto dQ = AllocOp::create(builder); + + // One-target-zero-parameter: static in → static out + auto hStatic = HOp::create(builder, sQ.getQubit()); + EXPECT_EQ(hStatic.getQubitOut().getType(), staticType); + + // One-target-zero-parameter: dynamic in → dynamic out + auto hDynamic = HOp::create(builder, dQ.getResult()); + EXPECT_EQ(hDynamic.getQubitOut().getType(), dynamicType); + + // One-target-one-parameter: static in → static out + auto pStatic = POp::create(builder, hStatic.getQubitOut(), 0.5); + EXPECT_EQ(pStatic.getQubitOut().getType(), staticType); + + // One-target-two-parameter: static in → static out + auto rStatic = ROp::create(builder, pStatic.getQubitOut(), 0.5, 0.3); + EXPECT_EQ(rStatic.getQubitOut().getType(), staticType); + + // One-target-three-parameter: static in → static out + auto uStatic = UOp::create(builder, rStatic.getQubitOut(), 0.1, 0.2, 0.3); + EXPECT_EQ(uStatic.getQubitOut().getType(), staticType); + + // Two-target-zero-parameter: preserves both types independently + auto swapOp = + SWAPOp::create(builder, uStatic.getQubitOut(), hDynamic.getQubitOut()); + EXPECT_EQ(swapOp.getQubit0Out().getType(), staticType); + EXPECT_EQ(swapOp.getQubit1Out().getType(), dynamicType); + + // Two-target-one-parameter: preserves both types independently + auto rxxOp = + RXXOp::create(builder, swapOp.getQubit0Out(), swapOp.getQubit1Out(), 0.5); + EXPECT_EQ(rxxOp.getQubit0Out().getType(), staticType); + EXPECT_EQ(rxxOp.getQubit1Out().getType(), dynamicType); + + // Two-target-two-parameter: preserves both types independently + auto xxpyOp = XXPlusYYOp::create(builder, rxxOp.getQubit0Out(), + rxxOp.getQubit1Out(), 0.5, 0.3); + EXPECT_EQ(xxpyOp.getQubit0Out().getType(), staticType); + EXPECT_EQ(xxpyOp.getQubit1Out().getType(), dynamicType); + + DeallocOp::create(builder, xxpyOp.getQubit0Out()); + DeallocOp::create(builder, xxpyOp.getQubit1Out()); + auto module = builder.finalize(); + ASSERT_TRUE(module); + EXPECT_TRUE(verify(*module).succeeded()); +} + /// \name QCO/SCF/IfOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( @@ -1063,6 +1118,21 @@ INSTANTIATE_TEST_SUITE_P( QCOTestCase{"StaticQubitsWithOps", MQT_NAMED_BUILDER(staticQubitsWithOps), MQT_NAMED_BUILDER(staticQubitsWithOps)}, + QCOTestCase{"StaticQubitsWithParametricOps", + MQT_NAMED_BUILDER(staticQubitsWithParametricOps), + MQT_NAMED_BUILDER(staticQubitsWithParametricOps)}, + QCOTestCase{"StaticQubitsWithTwoTargetOps", + MQT_NAMED_BUILDER(staticQubitsWithTwoTargetOps), + MQT_NAMED_BUILDER(staticQubitsWithTwoTargetOps)}, + QCOTestCase{"StaticQubitsWithCtrl", + MQT_NAMED_BUILDER(staticQubitsWithCtrl), + MQT_NAMED_BUILDER(staticQubitsWithCtrl)}, + QCOTestCase{"StaticQubitsWithInv", + MQT_NAMED_BUILDER(staticQubitsWithInv), + MQT_NAMED_BUILDER(staticQubitsWithInv)}, + QCOTestCase{"MixedStaticDynamicQubits", + MQT_NAMED_BUILDER(mixedStaticDynamicQubits), + MQT_NAMED_BUILDER(mixedStaticDynamicQubits)}, QCOTestCase{"AllocDeallocPair", MQT_NAMED_BUILDER(allocDeallocPair), MQT_NAMED_BUILDER(emptyQCO)})); /// @} diff --git a/mlir/unittests/programs/qco_programs.cpp b/mlir/unittests/programs/qco_programs.cpp index 4944898ac4..0c15af90b5 100644 --- a/mlir/unittests/programs/qco_programs.cpp +++ b/mlir/unittests/programs/qco_programs.cpp @@ -45,6 +45,40 @@ void staticQubitsWithOps(QCOProgramBuilder& b) { q1 = b.h(q1); } +void staticQubitsWithParametricOps(QCOProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + q0 = b.rx(std::numbers::pi / 4., q0); + q1 = b.p(std::numbers::pi / 2., q1); +} + +void staticQubitsWithTwoTargetOps(QCOProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + std::tie(q0, q1) = b.swap(q0, q1); +} + +void staticQubitsWithCtrl(QCOProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + std::tie(q0, q1) = b.cx(q0, q1); +} + +void staticQubitsWithInv(QCOProgramBuilder& b) { + auto q0 = b.staticQubit(0); + q0 = b.inv({q0}, [&](auto targets) -> llvm::SmallVector { + return {b.t(targets[0])}; + })[0]; +} + +void mixedStaticDynamicQubits(QCOProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.allocQubit(); + std::tie(q0, q1) = b.swap(q0, q1); + q0 = b.h(q0); + q1 = b.h(q1); +} + void allocDeallocPair(QCOProgramBuilder& b) { auto q = b.allocQubit(); b.dealloc(q); diff --git a/mlir/unittests/programs/qco_programs.h b/mlir/unittests/programs/qco_programs.h index 61b2f4a0cf..29768bb731 100644 --- a/mlir/unittests/programs/qco_programs.h +++ b/mlir/unittests/programs/qco_programs.h @@ -36,6 +36,21 @@ void staticQubits(QCOProgramBuilder& b); /// Allocates two static qubits and applies operations. void staticQubitsWithOps(QCOProgramBuilder& b); +/// Allocates two static qubits and applies parametric gates. +void staticQubitsWithParametricOps(QCOProgramBuilder& b); + +/// Allocates two static qubits and applies a two-target gate. +void staticQubitsWithTwoTargetOps(QCOProgramBuilder& b); + +/// Allocates two static qubits and applies a controlled gate. +void staticQubitsWithCtrl(QCOProgramBuilder& b); + +/// Allocates a static qubit and applies an inverse modifier. +void staticQubitsWithInv(QCOProgramBuilder& b); + +/// Allocates one static and one dynamic qubit and applies mixed operations. +void mixedStaticDynamicQubits(QCOProgramBuilder& b); + /// Allocates and explicitly deallocates a single qubit. void allocDeallocPair(QCOProgramBuilder& b); From 0221058dd113af14883b44140aaaed71eb6e9c62 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Thu, 19 Mar 2026 09:48:57 +0100 Subject: [PATCH 08/57] =?UTF-8?q?=E2=9C=A8=20Update=20QubitType=20handling?= =?UTF-8?q?=20in=20QCO=20and=20QC=20dialects=20to=20support=20static=20all?= =?UTF-8?q?ocation,=20modifying=20builders=20and=20operations=20accordingl?= =?UTF-8?q?y.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/include/mlir/Dialect/QC/IR/QCOps.td | 4 ++-- mlir/include/mlir/Dialect/QC/IR/QCTypes.td | 11 +++++++++++ mlir/include/mlir/Dialect/QCO/IR/QCOOps.td | 4 ++-- mlir/include/mlir/Dialect/QCO/IR/QCOTypes.td | 11 +++++++++++ mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp | 2 +- mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp | 4 ++-- mlir/lib/Dialect/QCO/IR/QCOOps.cpp | 3 ++- mlir/lib/Dialect/QTensor/IR/Operations/AllocOp.cpp | 6 +++--- 8 files changed, 34 insertions(+), 11 deletions(-) diff --git a/mlir/include/mlir/Dialect/QC/IR/QCOps.td b/mlir/include/mlir/Dialect/QC/IR/QCOps.td index 3842c3d112..a0fc3d03cb 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCOps.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCOps.td @@ -65,12 +65,12 @@ def AllocOp : QCOp<"alloc", [MemoryEffects<[MemAlloc]>]> { let builders = [ OpBuilder<(ins), [{ - build($_builder, $_state, QubitType::get($_builder.getContext()), nullptr, nullptr, nullptr); + build($_builder, $_state, QubitType::get($_builder.getContext(), /*isStatic=*/false), nullptr, nullptr, nullptr); }]>, OpBuilder<(ins "::mlir::StringAttr":$register_name, "::mlir::IntegerAttr":$register_size, "::mlir::IntegerAttr":$register_index), [{ - build($_builder, $_state, QubitType::get($_builder.getContext()), + build($_builder, $_state, QubitType::get($_builder.getContext(), /*isStatic=*/false), register_name, register_size, register_index); }]> ]; diff --git a/mlir/include/mlir/Dialect/QC/IR/QCTypes.td b/mlir/include/mlir/Dialect/QC/IR/QCTypes.td index db830ad3ca..c40d17b143 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCTypes.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCTypes.td @@ -25,7 +25,18 @@ def QubitType : QCType<"Qubit", "qubit"> { QC dialect. Operations using this type modify qubits in place using reference semantics, similar to how classical imperative languages handle mutable references. + + `!qc.qubit` (default) denotes a dynamically allocated qubit. + `!qc.qubit` denotes a qubit that has been mapped to a fixed + hardware position. }]; + let parameters = (ins DefaultValuedParameter<"bool", "false">:$isStatic); + let builders = [ + TypeBuilder<(ins), [{ + return $_get($_ctxt, /*isStatic=*/false); + }]> + ]; + let hasCustomAssemblyFormat = 1; } #endif // MLIR_DIALECT_QC_IR_QCTYPES_TD diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td index 12b605869d..538b3aaed5 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td @@ -65,12 +65,12 @@ def AllocOp : QCOOp<"alloc", [MemoryEffects<[MemAlloc]>]> { let builders = [ OpBuilder<(ins), [{ - build($_builder, $_state, QubitType::get($_builder.getContext()), nullptr, nullptr, nullptr); + build($_builder, $_state, QubitType::get($_builder.getContext(), /*isStatic=*/false), nullptr, nullptr, nullptr); }]>, OpBuilder<(ins "::mlir::StringAttr":$register_name, "::mlir::IntegerAttr":$register_size, "::mlir::IntegerAttr":$register_index), [{ - build($_builder, $_state, QubitType::get($_builder.getContext()), + build($_builder, $_state, QubitType::get($_builder.getContext(), /*isStatic=*/false), register_name, register_size, register_index); }]> ]; diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOTypes.td b/mlir/include/mlir/Dialect/QCO/IR/QCOTypes.td index 120fe0e54a..de63dc8aaf 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOTypes.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOTypes.td @@ -26,6 +26,10 @@ def QubitType : QCOType<"Qubit", "qubit"> { and produce new output qubits following value semantics and the SSA paradigm, enabling powerful dataflow analysis and optimization. + `!qco.qubit` (default) denotes a dynamically allocated qubit. + `!qco.qubit` denotes a qubit that has been mapped to a fixed + hardware position. + Example: ```mlir %q0 = qco.alloc : !qco.qubit @@ -33,6 +37,13 @@ def QubitType : QCOType<"Qubit", "qubit"> { %q2 = qco.x %q1 : !qco.qubit -> !qco.qubit ``` }]; + let parameters = (ins DefaultValuedParameter<"bool", "false">:$isStatic); + let builders = [ + TypeBuilder<(ins), [{ + return $_get($_ctxt, /*isStatic=*/false); + }]> + ]; + let hasCustomAssemblyFormat = 1; } #endif // MLIR_DIALECT_QCO_IR_QCOTYPES_TD diff --git a/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp b/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp index 5599753f96..2776a048eb 100644 --- a/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp +++ b/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp @@ -902,7 +902,7 @@ class JeffToQCOTypeConverter final : public TypeConverter { addConversion([](Type type) { return type; }); addConversion([ctx](jeff::QubitType /*type*/) -> Type { - return qco::QubitType::get(ctx); + return qco::QubitType::get(ctx, /*isStatic=*/false); }); } }; diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp index 1e20d2e7ff..34b919d9fe 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp @@ -126,8 +126,8 @@ struct ReduceCtrl final : OpRewritePattern { op->setAttr(opResultSegmentsAttrName, newSegments); // Add a block argument for the target qubit - auto arg = op.getBody()->addArgument(QubitType::get(rewriter.getContext()), - op.getLoc()); + auto arg = op.getBody()->addArgument( + QubitType::get(rewriter.getContext(), /*isStatic=*/false), op.getLoc()); // Replace the current GPhaseOp with a PhaseOp const OpBuilder::InsertionGuard guard(rewriter); diff --git a/mlir/lib/Dialect/QCO/IR/QCOOps.cpp b/mlir/lib/Dialect/QCO/IR/QCOOps.cpp index dca9ca8ce9..5be5258b7b 100644 --- a/mlir/lib/Dialect/QCO/IR/QCOOps.cpp +++ b/mlir/lib/Dialect/QCO/IR/QCOOps.cpp @@ -71,7 +71,8 @@ parseTargetAliasing(OpAsmParser& parser, Region& region, // 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. - newArg.type = QubitType::get(parser.getBuilder().getContext()); + newArg.type = + QubitType::get(parser.getBuilder().getContext(), /*isStatic=*/false); blockArgs.push_back(newArg); } while (succeeded(parser.parseOptionalComma())); diff --git a/mlir/lib/Dialect/QTensor/IR/Operations/AllocOp.cpp b/mlir/lib/Dialect/QTensor/IR/Operations/AllocOp.cpp index 898b8b6412..8c2b2a1e36 100644 --- a/mlir/lib/Dialect/QTensor/IR/Operations/AllocOp.cpp +++ b/mlir/lib/Dialect/QTensor/IR/Operations/AllocOp.cpp @@ -31,9 +31,9 @@ void AllocOp::build(OpBuilder& builder, OperationState& result, Value size) { assert(*sizeValue > 0 && "qtensor.alloc size must be positive"); } - auto resultType = - RankedTensorType::get({sizeValue ? *sizeValue : ShapedType::kDynamic}, - qco::QubitType::get(builder.getContext())); + auto resultType = RankedTensorType::get( + {sizeValue ? *sizeValue : ShapedType::kDynamic}, + qco::QubitType::get(builder.getContext(), /*isStatic=*/false)); build(builder, result, resultType, size); } From a5ffdef4972ac7d237d927ac36743d22b3699c8f Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Thu, 19 Mar 2026 10:12:53 +0100 Subject: [PATCH 09/57] =?UTF-8?q?=F0=9F=94=A7=20Refactor=20QCO=20and=20QC?= =?UTF-8?q?=20dialects:=20remove=20unnecessary=20includes,=20simplify=20lo?= =?UTF-8?q?op=20constructs=20in=20CtrlOp=20and=20InvOp,=20and=20ensure=20p?= =?UTF-8?q?roper=20type=20casting=20in=20StaticOp=20creation.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/lib/Conversion/QCOToQC/QCOToQC.cpp | 1 - mlir/lib/Conversion/QCToQCO/QCToQCO.cpp | 3 ++- mlir/lib/Dialect/QC/IR/QCOps.cpp | 2 ++ mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp | 4 ++-- mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp | 5 ++--- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp index 68dcb509b3..0acbf66ad5 100644 --- a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -15,7 +15,6 @@ #include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" -#include #include #include #include diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index 82ae93d32c..fe86b1aca2 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -311,7 +311,8 @@ struct ConvertQCStaticOp final : StatefulOpConversionPattern { auto qcQubit = op.getQubit(); // Create new qco.static operation with the same index - auto qcoOp = qco::StaticOp::create(rewriter, op.getLoc(), op.getIndex()); + auto qcoOp = qco::StaticOp::create(rewriter, op.getLoc(), + static_cast(op.getIndex())); // Collect QCO qubit SSA value auto qcoQubit = qcoOp.getQubit(); diff --git a/mlir/lib/Dialect/QC/IR/QCOps.cpp b/mlir/lib/Dialect/QC/IR/QCOps.cpp index 4f2ecde79b..0b573461f0 100644 --- a/mlir/lib/Dialect/QC/IR/QCOps.cpp +++ b/mlir/lib/Dialect/QC/IR/QCOps.cpp @@ -16,6 +16,8 @@ // IWYU pragma: begin_keep #include #include +#include +#include // IWYU pragma: end_keep using namespace mlir; diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp index 34b919d9fe..54e631ceac 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp @@ -242,8 +242,8 @@ void CtrlOp::build( build(odsBuilder, odsState, controls, targets); auto& block = odsState.regions.front()->emplaceBlock(); - for (size_t i = 0; i < targets.size(); ++i) { - block.addArgument(targets[i].getType(), odsState.location); + for (auto target : targets) { + block.addArgument(target.getType(), odsState.location); } const OpBuilder::InsertionGuard guard(odsBuilder); diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp index 77d51a0e55..dd97a43687 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp @@ -8,7 +8,6 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" #include @@ -326,8 +325,8 @@ void InvOp::build( build(odsBuilder, odsState, qubits); auto& block = odsState.regions.front()->emplaceBlock(); - for (size_t i = 0; i < qubits.size(); ++i) { - block.addArgument(qubits[i].getType(), odsState.location); + for (auto qubit : qubits) { + block.addArgument(qubit.getType(), odsState.location); } const OpBuilder::InsertionGuard guard(odsBuilder); From 6af63dbe1f3a7f0828888b76cac8dea98837ace6 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Thu, 19 Mar 2026 10:26:13 +0100 Subject: [PATCH 10/57] =?UTF-8?q?=E2=9C=A8=20Introduce=20StaticQubit=20typ?= =?UTF-8?q?e=20in=20QC=20and=20QCO=20dialects,=20enhance=20StaticOp=20veri?= =?UTF-8?q?fication=20to=20ensure=20static=20qubit=20type,=20and=20update?= =?UTF-8?q?=20related=20operations=20to=20support=20static=20qubit=20handl?= =?UTF-8?q?ing.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/include/mlir/Dialect/QC/IR/QCOps.td | 6 ++++++ mlir/include/mlir/Dialect/QCO/IR/QCOOps.td | 1 + mlir/lib/Dialect/QC/IR/QCOps.cpp | 9 +++++++++ mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp | 7 ++++--- mlir/lib/Dialect/QCO/IR/QCOOps.cpp | 9 +++++++++ mlir/lib/Dialect/QTensor/IR/Operations/AllocOp.cpp | 7 +++++++ 6 files changed, 36 insertions(+), 3 deletions(-) diff --git a/mlir/include/mlir/Dialect/QC/IR/QCOps.td b/mlir/include/mlir/Dialect/QC/IR/QCOps.td index a0fc3d03cb..7c1693846d 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCOps.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCOps.td @@ -83,6 +83,11 @@ def DynamicQubit : Type< CPred<"!::mlir::cast<::mlir::qc::QubitType>($_self).getIsStatic()">]>, "dynamic qubit type (!qc.qubit)">; +def StaticQubit : Type< + And<[CPred<"::mlir::isa<::mlir::qc::QubitType>($_self)">, + CPred<"::mlir::cast<::mlir::qc::QubitType>($_self).getIsStatic()">]>, + "static qubit type (!qc.qubit)">; + def DeallocOp : QCOp<"dealloc", [MemoryEffects<[MemFree]>]> { let summary = "Deallocate a dynamically allocated qubit"; let description = [{ @@ -117,6 +122,7 @@ def StaticOp : QCOp<"static", [Pure]> { let arguments = (ins ConfinedAttr:$index); let results = (outs QubitType:$qubit); let assemblyFormat = "$index attr-dict `:` type($qubit)"; + let hasVerifier = 1; let builders = [ OpBuilder<(ins "int64_t":$index), [{ diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td index 538b3aaed5..a0b45d146e 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td @@ -115,6 +115,7 @@ def StaticOp : QCOOp<"static", [MemoryEffects<[MemAlloc]>]> { let arguments = (ins ConfinedAttr:$index); let results = (outs QubitType:$qubit); let assemblyFormat = "$index attr-dict `:` type($qubit)"; + let hasVerifier = 1; let builders = [ OpBuilder<(ins "int64_t":$index), [{ diff --git a/mlir/lib/Dialect/QC/IR/QCOps.cpp b/mlir/lib/Dialect/QC/IR/QCOps.cpp index 0b573461f0..a9a46d61cb 100644 --- a/mlir/lib/Dialect/QC/IR/QCOps.cpp +++ b/mlir/lib/Dialect/QC/IR/QCOps.cpp @@ -81,3 +81,12 @@ Type QubitType::parse(AsmParser& parser) { #define GET_OP_CLASSES #include "mlir/Dialect/QC/IR/QCOps.cpp.inc" + +LogicalResult StaticOp::verify() { + auto qubitType = getQubit().getType(); + if (auto qt = dyn_cast(qubitType); qt && !qt.getIsStatic()) { + return emitOpError( + "result must be a static qubit type (!qc.qubit)"); + } + return success(); +} diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp index 54e631ceac..7885bb68a0 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp @@ -125,9 +125,10 @@ struct ReduceCtrl final : OpRewritePattern { const auto opResultSegmentsAttrName = CtrlOp::getResultSegmentSizeAttr(); op->setAttr(opResultSegmentsAttrName, newSegments); - // Add a block argument for the target qubit - auto arg = op.getBody()->addArgument( - QubitType::get(rewriter.getContext(), /*isStatic=*/false), op.getLoc()); + // Add a block argument for the promoted target qubit, preserving the + // control's type (including isStatic) + auto promotedControlType = op.getControlsIn().back().getType(); + auto arg = op.getBody()->addArgument(promotedControlType, op.getLoc()); // Replace the current GPhaseOp with a PhaseOp const OpBuilder::InsertionGuard guard(rewriter); diff --git a/mlir/lib/Dialect/QCO/IR/QCOOps.cpp b/mlir/lib/Dialect/QCO/IR/QCOOps.cpp index 5be5258b7b..50960a2821 100644 --- a/mlir/lib/Dialect/QCO/IR/QCOOps.cpp +++ b/mlir/lib/Dialect/QCO/IR/QCOOps.cpp @@ -236,3 +236,12 @@ Type QubitType::parse(AsmParser& parser) { #define GET_OP_CLASSES #include "mlir/Dialect/QCO/IR/QCOOps.cpp.inc" + +LogicalResult StaticOp::verify() { + auto qubitType = getQubit().getType(); + if (auto qt = dyn_cast(qubitType); qt && !qt.getIsStatic()) { + return emitOpError( + "result must be a static qubit type (!qco.qubit)"); + } + return success(); +} diff --git a/mlir/lib/Dialect/QTensor/IR/Operations/AllocOp.cpp b/mlir/lib/Dialect/QTensor/IR/Operations/AllocOp.cpp index 8c2b2a1e36..335b0e3620 100644 --- a/mlir/lib/Dialect/QTensor/IR/Operations/AllocOp.cpp +++ b/mlir/lib/Dialect/QTensor/IR/Operations/AllocOp.cpp @@ -57,5 +57,12 @@ LogicalResult AllocOp::verify() { << resultSize << ")"; } + auto elementType = resultType.getElementType(); + if (auto qubitType = dyn_cast(elementType); + qubitType && qubitType.getIsStatic()) { + return emitOpError("qtensor.alloc cannot allocate static qubits; element " + "type must be a dynamic qubit type (!qco.qubit)"); + } + return success(); } From 08410f6620ef98ff2ace13da8786d0a2387241f5 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Thu, 19 Mar 2026 10:49:29 +0100 Subject: [PATCH 11/57] =?UTF-8?q?=F0=9F=94=A7=20Update=20QCO=20and=20QC=20?= =?UTF-8?q?dialects:=20add=20missing=20LLVM=20casting=20includes,=20enhanc?= =?UTF-8?q?e=20QubitType=20handling=20in=20parser=20and=20printer=20for=20?= =?UTF-8?q?static=20qubits,=20and=20ensure=20proper=20type=20preservation?= =?UTF-8?q?=20during=20control=20operation=20adjustments.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/lib/Dialect/QC/IR/QCOps.cpp | 3 ++- mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp | 6 +++-- mlir/lib/Dialect/QCO/IR/QCOOps.cpp | 25 ++++++++++++++++---- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/mlir/lib/Dialect/QC/IR/QCOps.cpp b/mlir/lib/Dialect/QC/IR/QCOps.cpp index a9a46d61cb..35c090816c 100644 --- a/mlir/lib/Dialect/QC/IR/QCOps.cpp +++ b/mlir/lib/Dialect/QC/IR/QCOps.cpp @@ -15,6 +15,7 @@ // The following headers are needed for some template instantiations. // IWYU pragma: begin_keep #include +#include #include #include #include @@ -84,7 +85,7 @@ Type QubitType::parse(AsmParser& parser) { LogicalResult StaticOp::verify() { auto qubitType = getQubit().getType(); - if (auto qt = dyn_cast(qubitType); qt && !qt.getIsStatic()) { + if (auto qt = llvm::dyn_cast(qubitType); qt && !qt.getIsStatic()) { return emitOpError( "result must be a static qubit type (!qc.qubit)"); } diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp index 7885bb68a0..d443c5d237 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp @@ -8,7 +8,6 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" #include @@ -115,6 +114,10 @@ struct ReduceCtrl final : OpRewritePattern { return success(); } + // Capture the promoted control's type before adjusting segments; after + // setAttr, getControlsIn().back() would point to a different control. + const auto promotedControlType = op.getControlsIn().back().getType(); + // Adjust the segment sizes of the control and target operands const auto opSegmentsAttrName = CtrlOp::getOperandSegmentSizeAttr(); auto segmentsAttr = @@ -127,7 +130,6 @@ struct ReduceCtrl final : OpRewritePattern { // Add a block argument for the promoted target qubit, preserving the // control's type (including isStatic) - auto promotedControlType = op.getControlsIn().back().getType(); auto arg = op.getBody()->addArgument(promotedControlType, op.getLoc()); // Replace the current GPhaseOp with a PhaseOp diff --git a/mlir/lib/Dialect/QCO/IR/QCOOps.cpp b/mlir/lib/Dialect/QCO/IR/QCOOps.cpp index 50960a2821..4776815fc4 100644 --- a/mlir/lib/Dialect/QCO/IR/QCOOps.cpp +++ b/mlir/lib/Dialect/QCO/IR/QCOOps.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -68,11 +69,19 @@ parseTargetAliasing(OpAsmParser& parser, Region& region, } 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. - newArg.type = - QubitType::get(parser.getBuilder().getContext(), /*isStatic=*/false); + // Parse optional inline type to preserve isStatic; when absent, default + // to dynamic to avoid double-binding type($targets_in) in the assembly + // format. + Type operandType; + if (succeeded(parser.parseOptionalColon())) { + if (parser.parseType(operandType)) { + return failure(); + } + } else { + operandType = QubitType::get(parser.getBuilder().getContext(), + /*isStatic=*/false); + } + newArg.type = operandType; blockArgs.push_back(newArg); } while (succeeded(parser.parseOptionalComma())); @@ -110,6 +119,12 @@ static void printTargetAliasing(OpAsmPrinter& printer, Operation* /*op*/, printer.printOperand(entryBlock.getArgument(i)); printer << " = "; printer.printOperand(targetsIn[i]); + // Print inline type when static to preserve isStatic on round-trip + if (auto qubitType = llvm::dyn_cast(targetsIn[i].getType()); + qubitType && qubitType.getIsStatic()) { + printer << " : "; + printer.printType(qubitType); + } } printer << ") "; From ec4f2fa946e8b4267312889d650ecd7cc19dc397 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Thu, 19 Mar 2026 11:09:46 +0100 Subject: [PATCH 12/57] =?UTF-8?q?=F0=9F=94=A7=20Update=20StaticOp=20to=20o?= =?UTF-8?q?utput=20StaticQubit=20type=20and=20remove=20unnecessary=20verif?= =?UTF-8?q?ication=20logic=20for=20static=20qubit=20handling.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/include/mlir/Dialect/QC/IR/QCOps.td | 3 +-- mlir/lib/Dialect/QC/IR/QCOps.cpp | 10 ---------- mlir/lib/Dialect/QCO/IR/QCOOps.cpp | 1 + 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/mlir/include/mlir/Dialect/QC/IR/QCOps.td b/mlir/include/mlir/Dialect/QC/IR/QCOps.td index 7c1693846d..0145af464a 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCOps.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCOps.td @@ -120,9 +120,8 @@ def StaticOp : QCOp<"static", [Pure]> { }]; let arguments = (ins ConfinedAttr:$index); - let results = (outs QubitType:$qubit); + let results = (outs StaticQubit:$qubit); let assemblyFormat = "$index attr-dict `:` type($qubit)"; - let hasVerifier = 1; let builders = [ OpBuilder<(ins "int64_t":$index), [{ diff --git a/mlir/lib/Dialect/QC/IR/QCOps.cpp b/mlir/lib/Dialect/QC/IR/QCOps.cpp index 35c090816c..0b573461f0 100644 --- a/mlir/lib/Dialect/QC/IR/QCOps.cpp +++ b/mlir/lib/Dialect/QC/IR/QCOps.cpp @@ -15,7 +15,6 @@ // The following headers are needed for some template instantiations. // IWYU pragma: begin_keep #include -#include #include #include #include @@ -82,12 +81,3 @@ Type QubitType::parse(AsmParser& parser) { #define GET_OP_CLASSES #include "mlir/Dialect/QC/IR/QCOps.cpp.inc" - -LogicalResult StaticOp::verify() { - auto qubitType = getQubit().getType(); - if (auto qt = llvm::dyn_cast(qubitType); qt && !qt.getIsStatic()) { - return emitOpError( - "result must be a static qubit type (!qc.qubit)"); - } - return success(); -} diff --git a/mlir/lib/Dialect/QCO/IR/QCOOps.cpp b/mlir/lib/Dialect/QCO/IR/QCOOps.cpp index 4776815fc4..f50f982d36 100644 --- a/mlir/lib/Dialect/QCO/IR/QCOOps.cpp +++ b/mlir/lib/Dialect/QCO/IR/QCOOps.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include From 6ff64e4629c7483fec5aca1dc3f992aedefee1a3 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Thu, 19 Mar 2026 15:09:54 +0100 Subject: [PATCH 13/57] =?UTF-8?q?=F0=9F=94=A7=20Refactor=20QubitType=20def?= =?UTF-8?q?initions=20in=20QC=20dialect:=20reintroduce=20DynamicQubit=20an?= =?UTF-8?q?d=20StaticQubit=20types,=20update=20conversion=20logic=20for=20?= =?UTF-8?q?deallocation=20operations,=20and=20enhance=20documentation=20fo?= =?UTF-8?q?r=20clarity.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/include/mlir/Dialect/QC/IR/QCOps.td | 24 +++++++++++++--------- mlir/include/mlir/Dialect/QCO/IR/QCOOps.td | 2 +- mlir/lib/Conversion/QCOToQC/QCOToQC.cpp | 19 +++++++++++++++-- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/mlir/include/mlir/Dialect/QC/IR/QCOps.td b/mlir/include/mlir/Dialect/QC/IR/QCOps.td index 0145af464a..42383de241 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCOps.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCOps.td @@ -26,6 +26,20 @@ include "mlir/Interfaces/SideEffectInterfaces.td" class QCOp traits = []> : Op; +//===----------------------------------------------------------------------===// +// Type Constraints +//===----------------------------------------------------------------------===// + +def DynamicQubit : Type< + And<[CPred<"::mlir::isa<::mlir::qc::QubitType>($_self)">, + CPred<"!::mlir::cast<::mlir::qc::QubitType>($_self).getIsStatic()">]>, + "dynamic qubit type (!qc.qubit)">; + +def StaticQubit : Type< + And<[CPred<"::mlir::isa<::mlir::qc::QubitType>($_self)">, + CPred<"::mlir::cast<::mlir::qc::QubitType>($_self).getIsStatic()">]>, + "static qubit type (!qc.qubit)">; + //===----------------------------------------------------------------------===// // Resource Operations //===----------------------------------------------------------------------===// @@ -78,16 +92,6 @@ def AllocOp : QCOp<"alloc", [MemoryEffects<[MemAlloc]>]> { let hasVerifier = 1; } -def DynamicQubit : Type< - And<[CPred<"::mlir::isa<::mlir::qc::QubitType>($_self)">, - CPred<"!::mlir::cast<::mlir::qc::QubitType>($_self).getIsStatic()">]>, - "dynamic qubit type (!qc.qubit)">; - -def StaticQubit : Type< - And<[CPred<"::mlir::isa<::mlir::qc::QubitType>($_self)">, - CPred<"::mlir::cast<::mlir::qc::QubitType>($_self).getIsStatic()">]>, - "static qubit type (!qc.qubit)">; - def DeallocOp : QCOp<"dealloc", [MemoryEffects<[MemFree]>]> { let summary = "Deallocate a dynamically allocated qubit"; let description = [{ diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td index a0b45d146e..2137873e37 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td @@ -85,7 +85,7 @@ def DeallocOp : QCOOp<"dealloc", [MemoryEffects<[MemFree]>]> { In QCO's value/linear semantics, this operation also serves as the sink that consumes the qubit SSA value (ensuring every qubit value is used - exactly once). When lowering back to QC (reference semantics), deallocs + exactly once). When converting back to QC (reference semantics), deallocs corresponding to static qubits may be erased. Example: diff --git a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp index 0acbf66ad5..cca115156e 100644 --- a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -99,9 +99,23 @@ struct ConvertQCOAllocOp final : OpConversionPattern { * @brief Converts qco.dealloc to qc.dealloc (dynamic) or erases it (static). * * @details - * For dynamic qubits (`!qco.qubit`), lowers to `qc.dealloc`. + * For dynamic qubits (`!qco.qubit`), converts to `qc.dealloc`. * For static qubits (`!qco.qubit`), erases the op since QC does not * require explicit deallocation of static qubits. + * + * Example transformation (dynamic): + * ```mlir + * qco.dealloc %q_qco : !qco.qubit + * // becomes: + * qc.dealloc %q_qc : !qc.qubit + * ``` + * + * Example transformation (static): + * ```mlir + * qco.dealloc %q_qco : !qco.qubit + * // becomes: + * (erased) + * ``` */ struct ConvertQCODeallocOp final : OpConversionPattern { using OpConversionPattern::OpConversionPattern; @@ -141,7 +155,8 @@ struct ConvertQCOStaticOp final : OpConversionPattern { matchAndRewrite(qco::StaticOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { // Create qc.static with the same index - rewriter.replaceOpWithNewOp(op, op.getIndex()); + rewriter.replaceOpWithNewOp( + op, static_cast(op.getIndex())); return success(); } }; From e0689dcc21976784e3e4f65f15c03341f504ef1a Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Thu, 19 Mar 2026 15:41:23 +0100 Subject: [PATCH 14/57] =?UTF-8?q?=F0=9F=94=A7=20Add=20DynamicQubit=20and?= =?UTF-8?q?=20StaticQubit=20type=20definitions=20in=20QCO=20dialect,=20upd?= =?UTF-8?q?ate=20StaticOp=20to=20return=20StaticQubit,=20and=20improve=20t?= =?UTF-8?q?ype=20casting=20in=20mapping=20transformations=20for=20better?= =?UTF-8?q?=20static=20qubit=20handling.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/include/mlir/Dialect/QCO/IR/QCOOps.td | 17 +++++++++++++++-- mlir/lib/Conversion/QCOToQC/QCOToQC.cpp | 1 + mlir/lib/Dialect/QCO/IR/QCOOps.cpp | 9 --------- .../Dialect/QCO/Transforms/Mapping/Mapping.cpp | 5 +++-- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td index 2137873e37..2d96319cc9 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td @@ -26,6 +26,20 @@ include "mlir/Interfaces/SideEffectInterfaces.td" class QCOOp traits = []> : Op; +//===----------------------------------------------------------------------===// +// Type Constraints +//===----------------------------------------------------------------------===// + +def DynamicQubit : Type< + And<[CPred<"::mlir::isa<::mlir::qco::QubitType>($_self)">, + CPred<"!::mlir::cast<::mlir::qco::QubitType>($_self).getIsStatic()">]>, + "dynamic qubit type (!qco.qubit)">; + +def StaticQubit : Type< + And<[CPred<"::mlir::isa<::mlir::qco::QubitType>($_self)">, + CPred<"::mlir::cast<::mlir::qco::QubitType>($_self).getIsStatic()">]>, + "static qubit type (!qco.qubit)">; + //===----------------------------------------------------------------------===// // Resource Operations //===----------------------------------------------------------------------===// @@ -113,9 +127,8 @@ def StaticOp : QCOOp<"static", [MemoryEffects<[MemAlloc]>]> { }]; let arguments = (ins ConfinedAttr:$index); - let results = (outs QubitType:$qubit); + let results = (outs StaticQubit:$qubit); let assemblyFormat = "$index attr-dict `:` type($qubit)"; - let hasVerifier = 1; let builders = [ OpBuilder<(ins "int64_t":$index), [{ diff --git a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp index cca115156e..32ff6ec01b 100644 --- a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -26,6 +26,7 @@ #include #include +#include #include namespace mlir { diff --git a/mlir/lib/Dialect/QCO/IR/QCOOps.cpp b/mlir/lib/Dialect/QCO/IR/QCOOps.cpp index f50f982d36..02105cdc8b 100644 --- a/mlir/lib/Dialect/QCO/IR/QCOOps.cpp +++ b/mlir/lib/Dialect/QCO/IR/QCOOps.cpp @@ -252,12 +252,3 @@ Type QubitType::parse(AsmParser& parser) { #define GET_OP_CLASSES #include "mlir/Dialect/QCO/IR/QCOOps.cpp.inc" - -LogicalResult StaticOp::verify() { - auto qubitType = getQubit().getType(); - if (auto qt = dyn_cast(qubitType); qt && !qt.getIsStatic()) { - return emitOpError( - "result must be a static qubit type (!qco.qubit)"); - } - return success(); -} diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index ebd8fa2cf8..2aac29f605 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -487,7 +488,7 @@ struct MappingPass : impl::MappingPassBase { const auto hw = layout.getHardwareIndex(p); rewriter.setInsertionPoint(q.getDefiningOp()); auto op = rewriter.replaceOpWithNewOp(q.getDefiningOp(), hw); - statics[hw] = op.getQubit(); + statics[hw] = llvm::cast>(op.getQubit()); } // 2. Create static qubits for the remaining (unused) hardware indices. @@ -497,7 +498,7 @@ struct MappingPass : impl::MappingPassBase { auto op = StaticOp::create(rewriter, rewriter.getUnknownLoc(), hw); rewriter.setInsertionPoint(funcBody.back().getTerminator()); DeallocOp::create(rewriter, rewriter.getUnknownLoc(), op.getQubit()); - statics[hw] = op.getQubit(); + statics[hw] = llvm::cast>(op.getQubit()); } return statics; From c624c3d361a920dd586b354d41ef1655c300a0eb Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Thu, 19 Mar 2026 16:11:51 +0100 Subject: [PATCH 15/57] =?UTF-8?q?=F0=9F=94=A7=20Update=20AllocOp=20to=20re?= =?UTF-8?q?turn=20DynamicQubit=20type,=20refactor=20ConvertFuncReturnOp=20?= =?UTF-8?q?to=20improve=20live=20qubit=20collection,=20and=20enhance=20dyn?= =?UTF-8?q?amic=20qubit=20handling=20in=20mapping=20transformations.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/include/mlir/Dialect/QCO/IR/QCOOps.td | 2 +- mlir/lib/Conversion/QCToQCO/QCToQCO.cpp | 17 +++++------------ .../Dialect/QCO/Transforms/Mapping/Mapping.cpp | 6 ++++-- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td index 2d96319cc9..d793a69bb5 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td @@ -71,7 +71,7 @@ def AllocOp : QCOOp<"alloc", [MemoryEffects<[MemAlloc]>]> { let arguments = (ins OptionalAttr:$register_name, OptionalAttr>:$register_size, OptionalAttr>:$register_index); - let results = (outs QubitType:$result); + let results = (outs DynamicQubit:$result); let assemblyFormat = [{ (`(` $register_name^ `,` $register_size `,` $register_index `)`)? attr-dict `:` type($result) diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index fe86b1aca2..25ce2d8087 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -129,11 +129,6 @@ struct ConvertFuncReturnOp final : StatefulOpConversionPattern { ConversionPatternRewriter& rewriter) const override { auto& state = getState(); - if (state.inNestedRegion != 0) { - rewriter.replaceOpWithNewOp(op, adaptor.getOperands()); - return success(); - } - // Build return values from qubitMap (adaptor.getOperands() may carry stale // root values because gate patterns use eraseOp instead of replaceOp). llvm::SmallVector returnValues; @@ -151,15 +146,13 @@ struct ConvertFuncReturnOp final : StatefulOpConversionPattern { } // Collect non-escaped live qubits for deallocation. - llvm::SmallVector liveQubits; - liveQubits.reserve(state.qubitMap.size()); - llvm::DenseSet seen; + llvm::DenseSet liveQubitsSet; for (const auto& [qcQubit, qcoQubit] : state.qubitMap) { - if (escapedQubits.contains(qcoQubit) || !seen.insert(qcoQubit).second) { - continue; - } - liveQubits.emplace_back(qcoQubit); + if (!escapedQubits.contains(qcoQubit)) + liveQubitsSet.insert(qcoQubit); } + llvm::SmallVector liveQubits(liveQubitsSet.begin(), + liveQubitsSet.end()); // Sort deterministically (mirrors QCOProgramBuilder::finalize()). llvm::sort(liveQubits, [](Value a, Value b) { diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 2aac29f605..f6266d6335 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -455,8 +455,10 @@ struct MappingPass : impl::MappingPassBase { */ [[nodiscard]] static SmallVector collectDynamicQubits(Region& funcBody) { - return SmallVector(map_range( - funcBody.getOps(), [](AllocOp op) { return op.getResult(); })); + return SmallVector( + map_range(funcBody.getOps(), [](AllocOp op) { + return llvm::cast>(op.getResult()); + })); } /** From 8499977bf76bd0f732e6ac44fa747393ae711ffe Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Thu, 19 Mar 2026 17:19:49 +0100 Subject: [PATCH 16/57] =?UTF-8?q?=F0=9F=94=A7=20Enhance=20QCO=20operations?= =?UTF-8?q?=20with=20type=20verification:=20add=20input-output=20type=20eq?= =?UTF-8?q?uality=20checks=20for=20BarrierOp=20and=20CtrlOp,=20and=20updat?= =?UTF-8?q?e=20BarrierOp=20documentation=20for=20clarity=20on=20type=20pre?= =?UTF-8?q?servation.=20Refactor=20ConvertFuncReturnOp=20to=20improve=20li?= =?UTF-8?q?ve=20qubit=20handling.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/include/mlir/Dialect/QCO/IR/QCOOps.td | 6 ++++-- mlir/lib/Conversion/QCToQCO/QCToQCO.cpp | 4 ++-- mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp | 20 +++++++++++++++++++ .../IR/Operations/StandardGates/BarrierOp.cpp | 15 ++++++++++++++ .../QCO/Transforms/Mapping/Mapping.cpp | 6 ++++-- 5 files changed, 45 insertions(+), 6 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td index d793a69bb5..6d16e699d3 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td @@ -1018,6 +1018,8 @@ def BarrierOp : QCOOp<"barrier", traits = [UnitaryOpInterface]> { let summary = "Apply a barrier gate to a set of qubits"; let description = [{ Applies a barrier gate to a set of qubits and returns the transformed qubits. + Each output qubit type must match its corresponding input type (pairwise + type preservation, e.g., disallows !qco.qubit -> !qco.qubit). Example: ```mlir @@ -1029,6 +1031,8 @@ def BarrierOp : QCOOp<"barrier", traits = [UnitaryOpInterface]> { let results = (outs Variadic:$qubits_out); let assemblyFormat = "$qubits_in attr-dict `:` type($qubits_in) `->` type($qubits_out)"; + let hasVerifier = 1; + let extraClassDeclaration = [{ size_t getNumQubits() { return getNumTargets(); } size_t getNumTargets() { return getQubitsIn().size(); } @@ -1083,8 +1087,6 @@ def CtrlOp : QCOOp<"ctrl", traits = UnitaryOpInterface, AttrSizedOperandSegments, AttrSizedResultSegments, - SameOperandsAndResultType, - SameOperandsAndResultShape, SingleBlockImplicitTerminator<"::mlir::qco::YieldOp">, RecursiveMemoryEffects ]> { diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index 25ce2d8087..0eb242742a 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -148,8 +148,9 @@ struct ConvertFuncReturnOp final : StatefulOpConversionPattern { // Collect non-escaped live qubits for deallocation. llvm::DenseSet liveQubitsSet; for (const auto& [qcQubit, qcoQubit] : state.qubitMap) { - if (!escapedQubits.contains(qcoQubit)) + if (!escapedQubits.contains(qcoQubit)) { liveQubitsSet.insert(qcoQubit); + } } llvm::SmallVector liveQubits(liveQubitsSet.begin(), liveQubitsSet.end()); @@ -1229,7 +1230,6 @@ struct QCToQCO final : impl::QCToQCOBase { // qubits. Therefore, we make it dynamically illegal unless the lowering // state has no remaining qubits. patterns.add(typeConverter, context, &state); - populateReturnOpTypeConversionPattern(patterns, typeConverter); target.addDynamicallyLegalOp([&](const func::ReturnOp op) { return typeConverter.isLegal(op) && state.qubitMap.empty(); }); diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp index d443c5d237..42711c7c5b 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp @@ -256,6 +256,26 @@ void CtrlOp::build( } LogicalResult CtrlOp::verify() { + // Pairwise input->output type equality for controls and targets separately + // (allows !qco.qubit and !qco.qubit to differ between + // controls/targets) + for (size_t i = 0; i < getNumControls(); ++i) { + if (getControlsIn()[i].getType() != getControlsOut()[i].getType()) { + return emitOpError("qco.ctrl control ") + << i << " input type must match output type (got " + << getControlsIn()[i].getType() << " vs " + << getControlsOut()[i].getType() << ")"; + } + } + for (size_t i = 0; i < getNumTargets(); ++i) { + if (getTargetsIn()[i].getType() != getTargetsOut()[i].getType()) { + return emitOpError("qco.ctrl target ") + << i << " input type must match output type (got " + << getTargetsIn()[i].getType() << " vs " + << getTargetsOut()[i].getType() << ")"; + } + } + auto& block = *getBody(); if (block.getOperations().size() < 2) { return emitOpError("body region must have at least two operations"); diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/BarrierOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/BarrierOp.cpp index d168624987..b8d116e591 100644 --- a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/BarrierOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/BarrierOp.cpp @@ -77,6 +77,21 @@ struct MergeSubsequentBarrier final : OpRewritePattern { } // namespace +LogicalResult BarrierOp::verify() { + // Pairwise input->output type equality (disallows e.g. !qco.qubit -> + // !qco.qubit) + const auto numTargets = getNumTargets(); + for (size_t i = 0; i < numTargets; ++i) { + if (getQubitsIn()[i].getType() != getQubitsOut()[i].getType()) { + return emitOpError("qco.barrier qubit ") + << i << " input type must match output type (got " + << getQubitsIn()[i].getType() << " vs " + << getQubitsOut()[i].getType() << ")"; + } + } + return success(); +} + Value BarrierOp::getInputTarget(const size_t i) { if (i < getNumTargets()) { return getQubitsIn()[i]; diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index f6266d6335..c7d0322d09 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -489,7 +489,8 @@ struct MappingPass : impl::MappingPassBase { for (const auto [p, q] : enumerate(dynQubits)) { const auto hw = layout.getHardwareIndex(p); rewriter.setInsertionPoint(q.getDefiningOp()); - auto op = rewriter.replaceOpWithNewOp(q.getDefiningOp(), hw); + auto op = rewriter.replaceOpWithNewOp(q.getDefiningOp(), + static_cast(hw)); statics[hw] = llvm::cast>(op.getQubit()); } @@ -497,7 +498,8 @@ struct MappingPass : impl::MappingPassBase { for (std::size_t p = dynQubits.size(); p < layout.nqubits(); ++p) { rewriter.setInsertionPointToStart(&funcBody.front()); const auto hw = layout.getHardwareIndex(p); - auto op = StaticOp::create(rewriter, rewriter.getUnknownLoc(), hw); + auto op = StaticOp::create(rewriter, rewriter.getUnknownLoc(), + static_cast(hw)); rewriter.setInsertionPoint(funcBody.back().getTerminator()); DeallocOp::create(rewriter, rewriter.getUnknownLoc(), op.getQubit()); statics[hw] = llvm::cast>(op.getQubit()); From a16da1473bb337614c66bdc544d39e3ee9ddd4f8 Mon Sep 17 00:00:00 2001 From: simon1hofmann <119581649+simon1hofmann@users.noreply.github.com> Date: Fri, 20 Mar 2026 13:30:59 +0100 Subject: [PATCH 17/57] Apply suggestions from code review Co-authored-by: Lukas Burgholzer Signed-off-by: simon1hofmann <119581649+simon1hofmann@users.noreply.github.com> --- mlir/include/mlir/Dialect/QC/IR/QCTypes.td | 3 +-- mlir/include/mlir/Dialect/QCO/IR/QCOTypes.td | 3 +-- mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp | 2 +- mlir/lib/Conversion/QCToQCO/QCToQCO.cpp | 6 +++--- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/mlir/include/mlir/Dialect/QC/IR/QCTypes.td b/mlir/include/mlir/Dialect/QC/IR/QCTypes.td index c40d17b143..c1b98c91b2 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCTypes.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCTypes.td @@ -27,8 +27,7 @@ def QubitType : QCType<"Qubit", "qubit"> { mutable references. `!qc.qubit` (default) denotes a dynamically allocated qubit. - `!qc.qubit` denotes a qubit that has been mapped to a fixed - hardware position. + `!qc.qubit` denotes a qubit with a statically known identifier. }]; let parameters = (ins DefaultValuedParameter<"bool", "false">:$isStatic); let builders = [ diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOTypes.td b/mlir/include/mlir/Dialect/QCO/IR/QCOTypes.td index de63dc8aaf..39a7884570 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOTypes.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOTypes.td @@ -27,8 +27,7 @@ def QubitType : QCOType<"Qubit", "qubit"> { paradigm, enabling powerful dataflow analysis and optimization. `!qco.qubit` (default) denotes a dynamically allocated qubit. - `!qco.qubit` denotes a qubit that has been mapped to a fixed - hardware position. + `!qco.qubit` denotes a qubit with a statically known identifier. Example: ```mlir diff --git a/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp b/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp index 2776a048eb..5599753f96 100644 --- a/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp +++ b/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp @@ -902,7 +902,7 @@ class JeffToQCOTypeConverter final : public TypeConverter { addConversion([](Type type) { return type; }); addConversion([ctx](jeff::QubitType /*type*/) -> Type { - return qco::QubitType::get(ctx, /*isStatic=*/false); + return qco::QubitType::get(ctx); }); } }; diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index 0eb242742a..c5fb34f6ec 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -138,15 +138,15 @@ struct ConvertFuncReturnOp final : StatefulOpConversionPattern { llvm::zip(op.getOperands(), adaptor.getOperands())) { if (state.qubitMap.contains(qcOperand)) { auto latest = state.qubitMap[qcOperand]; - returnValues.push_back(latest); + returnValues.emplace_back(latest); escapedQubits.insert(latest); } else { - returnValues.push_back(adaptorOperand); + returnValues.emplace_back(adaptorOperand); } } // Collect non-escaped live qubits for deallocation. - llvm::DenseSet liveQubitsSet; + llvm::DenseSet liveQubits; for (const auto& [qcQubit, qcoQubit] : state.qubitMap) { if (!escapedQubits.contains(qcoQubit)) { liveQubitsSet.insert(qcoQubit); From adb519247f8637a12966709df698183030fddadf Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Fri, 20 Mar 2026 14:06:35 +0100 Subject: [PATCH 18/57] =?UTF-8?q?=F0=9F=94=A7=20Refactor=20QCO=20operation?= =?UTF-8?q?s=20to=20improve=20static=20qubit=20handling:=20update=20Static?= =?UTF-8?q?Op=20to=20use=20Pure=20memory=20effects,=20introduce=20SSABefor?= =?UTF-8?q?eForDeallocOrder=20for=20deterministic=20sorting=20of=20SSA=20v?= =?UTF-8?q?alues,=20and=20enhance=20conversion=20patterns=20for=20static?= =?UTF-8?q?=20qubits=20across=20various=20dialects.=20Add=20new=20utility?= =?UTF-8?q?=20functions=20for=20better=20qubit=20management=20in=20tests.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/include/mlir/Dialect/QCO/IR/QCOOps.td | 2 +- .../mlir/Dialect/QCO/Utils/ValueOrdering.h | 36 ++++++++++++ mlir/lib/Conversion/QCOToQC/QCOToQC.cpp | 3 +- mlir/lib/Conversion/QCToQCO/QCToQCO.cpp | 27 ++++----- mlir/lib/Conversion/QCToQIR/QCToQIR.cpp | 2 +- .../Dialect/QCO/Builder/QCOProgramBuilder.cpp | 14 +---- mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp | 27 +++------ .../IR/Operations/StandardGates/BarrierOp.cpp | 13 +---- .../Compiler/test_compiler_pipeline.cpp | 30 +++++++++- .../Conversion/QCOToQC/test_qco_to_qc.cpp | 17 +++++- .../Conversion/QCToQCO/test_qc_to_qco.cpp | 17 +++++- .../Conversion/QCToQIR/test_qc_to_qir.cpp | 15 +++++ mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp | 15 +++++ mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp | 55 ------------------- mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp | 46 ++++++++++------ mlir/unittests/programs/qc_programs.cpp | 32 +++++++++++ mlir/unittests/programs/qc_programs.h | 15 +++++ mlir/unittests/programs/qir_programs.cpp | 35 ++++++++++++ mlir/unittests/programs/qir_programs.h | 15 +++++ 19 files changed, 281 insertions(+), 135 deletions(-) create mode 100644 mlir/include/mlir/Dialect/QCO/Utils/ValueOrdering.h diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td index 6d16e699d3..993d0c9799 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td @@ -113,7 +113,7 @@ def DeallocOp : QCOOp<"dealloc", [MemoryEffects<[MemFree]>]> { let hasCanonicalizer = 1; } -def StaticOp : QCOOp<"static", [MemoryEffects<[MemAlloc]>]> { +def StaticOp : QCOOp<"static", [Pure]> { let summary = "Retrieve a static qubit by index"; let description = [{ The `qco.static` operation produces an SSA value representing a qubit diff --git a/mlir/include/mlir/Dialect/QCO/Utils/ValueOrdering.h b/mlir/include/mlir/Dialect/QCO/Utils/ValueOrdering.h new file mode 100644 index 0000000000..c094874f3d --- /dev/null +++ b/mlir/include/mlir/Dialect/QCO/Utils/ValueOrdering.h @@ -0,0 +1,36 @@ +/* + * 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 +#include + +namespace mlir::qco { + +/** + * @brief Strict weak ordering for sorting SSA values before inserting deallocs. + * + * @details Matches QCOProgramBuilder::finalize(): use block order when both + * defining ops are in the same block; otherwise fall back to opaque pointer + * order for a deterministic total order. + */ +struct SSABeforeForDeallocOrder { + bool operator()(Value a, Value b) const { + auto* opA = a.getDefiningOp(); + auto* opB = b.getDefiningOp(); + if (!opA || !opB || opA->getBlock() != opB->getBlock()) { + return a.getAsOpaquePointer() < b.getAsOpaquePointer(); + } + return opA->isBeforeInBlock(opB); + } +}; + +} // namespace mlir::qco diff --git a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp index 32ff6ec01b..5fe548a446 100644 --- a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -156,8 +156,7 @@ struct ConvertQCOStaticOp final : OpConversionPattern { matchAndRewrite(qco::StaticOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { // Create qc.static with the same index - rewriter.replaceOpWithNewOp( - op, static_cast(op.getIndex())); + rewriter.replaceOpWithNewOp(op, op.getIndex()); return success(); } }; diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index c5fb34f6ec..c95d0948f8 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -14,6 +14,7 @@ #include "mlir/Dialect/QC/IR/QCOps.h" #include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" +#include "mlir/Dialect/QCO/Utils/ValueOrdering.h" #include #include @@ -147,25 +148,18 @@ struct ConvertFuncReturnOp final : StatefulOpConversionPattern { // Collect non-escaped live qubits for deallocation. llvm::DenseSet liveQubits; - for (const auto& [qcQubit, qcoQubit] : state.qubitMap) { + for (Value qcoQubit : llvm::make_second_range(state.qubitMap)) { if (!escapedQubits.contains(qcoQubit)) { - liveQubitsSet.insert(qcoQubit); + liveQubits.insert(qcoQubit); } } - llvm::SmallVector liveQubits(liveQubitsSet.begin(), - liveQubitsSet.end()); - - // Sort deterministically (mirrors QCOProgramBuilder::finalize()). - llvm::sort(liveQubits, [](Value a, Value b) { - auto* opA = a.getDefiningOp(); - auto* opB = b.getDefiningOp(); - if (!opA || !opB || opA->getBlock() != opB->getBlock()) { - return a.getAsOpaquePointer() < b.getAsOpaquePointer(); - } - return opA->isBeforeInBlock(opB); - }); + // Copy to a vector before sorting: DenseSet iterators are not + // random-access. + llvm::SmallVector liveQubitsSorted(liveQubits.begin(), + liveQubits.end()); + llvm::sort(liveQubitsSorted, SSABeforeForDeallocOrder{}); - for (Value qubit : liveQubits) { + for (Value qubit : liveQubitsSorted) { rewriter.create(op.getLoc(), qubit); } @@ -305,8 +299,7 @@ struct ConvertQCStaticOp final : StatefulOpConversionPattern { auto qcQubit = op.getQubit(); // Create new qco.static operation with the same index - auto qcoOp = qco::StaticOp::create(rewriter, op.getLoc(), - static_cast(op.getIndex())); + auto qcoOp = qco::StaticOp::create(rewriter, op.getLoc(), op.getIndex()); // Collect QCO qubit SSA value auto qcoQubit = qcoOp.getQubit(); diff --git a/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp b/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp index cd1d2721b7..645eac9909 100644 --- a/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp +++ b/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp @@ -341,7 +341,7 @@ struct ConvertQCStaticQIR final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(StaticOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - const auto index = static_cast(op.getIndex()); + const auto index = op.getIndex(); auto& state = getState(); // Get or create a pointer to the qubit Value val{}; diff --git a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp index b23f189209..1e4a4cc628 100644 --- a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp +++ b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp @@ -12,6 +12,7 @@ #include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" +#include "mlir/Dialect/QCO/Utils/ValueOrdering.h" #include "mlir/Dialect/QTensor/IR/QTensorDialect.h" #include "mlir/Dialect/QTensor/IR/QTensorOps.h" #include "mlir/Dialect/Utils/Utils.h" @@ -921,19 +922,10 @@ OwningOpRef QCOProgramBuilder::finalize() { "Insertion point is not in entry block of main function"); } - auto blockOrderComparator = [](Value a, Value b) { - auto* opA = a.getDefiningOp(); - auto* opB = b.getDefiningOp(); - if (!opA || !opB || opA->getBlock() != opB->getBlock()) { - return a.getAsOpaquePointer() < b.getAsOpaquePointer(); - } - return opA->isBeforeInBlock(opB); - }; - // Automatically deallocate all still-allocated qubits // Sort qubits for deterministic output llvm::SmallVector sortedQubits(validQubits.begin(), validQubits.end()); - llvm::sort(sortedQubits, blockOrderComparator); + llvm::sort(sortedQubits, SSABeforeForDeallocOrder{}); for (auto qubit : sortedQubits) { DeallocOp::create(*this, qubit); @@ -943,7 +935,7 @@ OwningOpRef QCOProgramBuilder::finalize() { // Sort tensors for deterministic output llvm::SmallVector sortedTensors(validTensors.begin(), validTensors.end()); - llvm::sort(sortedTensors, blockOrderComparator); + llvm::sort(sortedTensors, SSABeforeForDeallocOrder{}); for (auto tensor : sortedTensors) { qtensor::DeallocOp::create(*this, tensor); diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp index 42711c7c5b..91db9e582a 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp @@ -256,24 +256,15 @@ void CtrlOp::build( } LogicalResult CtrlOp::verify() { - // Pairwise input->output type equality for controls and targets separately - // (allows !qco.qubit and !qco.qubit to differ between - // controls/targets) - for (size_t i = 0; i < getNumControls(); ++i) { - if (getControlsIn()[i].getType() != getControlsOut()[i].getType()) { - return emitOpError("qco.ctrl control ") - << i << " input type must match output type (got " - << getControlsIn()[i].getType() << " vs " - << getControlsOut()[i].getType() << ")"; - } - } - for (size_t i = 0; i < getNumTargets(); ++i) { - if (getTargetsIn()[i].getType() != getTargetsOut()[i].getType()) { - return emitOpError("qco.ctrl target ") - << i << " input type must match output type (got " - << getTargetsIn()[i].getType() << " vs " - << getTargetsOut()[i].getType() << ")"; - } + // Allows !qco.qubit and !qco.qubit to differ between controls and + // targets, but requires pairwise equality within each group. + if (!llvm::equal(getControlsIn().getTypes(), getControlsOut().getTypes())) { + return emitOpError("qco.ctrl control qubit input types must match output " + "types pairwise"); + } + if (!llvm::equal(getTargetsIn().getTypes(), getTargetsOut().getTypes())) { + return emitOpError("qco.ctrl target qubit input types must match output " + "types pairwise"); } auto& block = *getBody(); diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/BarrierOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/BarrierOp.cpp index b8d116e591..c7488fd6b0 100644 --- a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/BarrierOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/BarrierOp.cpp @@ -78,16 +78,9 @@ struct MergeSubsequentBarrier final : OpRewritePattern { } // namespace LogicalResult BarrierOp::verify() { - // Pairwise input->output type equality (disallows e.g. !qco.qubit -> - // !qco.qubit) - const auto numTargets = getNumTargets(); - for (size_t i = 0; i < numTargets; ++i) { - if (getQubitsIn()[i].getType() != getQubitsOut()[i].getType()) { - return emitOpError("qco.barrier qubit ") - << i << " input type must match output type (got " - << getQubitsIn()[i].getType() << " vs " - << getQubitsOut()[i].getType() << ")"; - } + if (!llvm::equal(getQubitsIn().getTypes(), getQubitsOut().getTypes())) { + return emitOpError("qco.barrier qubit input types must match output types " + "pairwise"); } return success(); } diff --git a/mlir/unittests/Compiler/test_compiler_pipeline.cpp b/mlir/unittests/Compiler/test_compiler_pipeline.cpp index eaddb27a5c..5f648fe113 100644 --- a/mlir/unittests/Compiler/test_compiler_pipeline.cpp +++ b/mlir/unittests/Compiler/test_compiler_pipeline.cpp @@ -198,6 +198,31 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithOps), MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithOps), MQT_NAMED_BUILDER(mlir::qir::staticQubitsWithOps), false}, + CompilerPipelineTestCase{ + "StaticQubitsWithParametricOps", nullptr, + MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithParametricOps), + MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithParametricOps), + MQT_NAMED_BUILDER(mlir::qir::staticQubitsWithParametricOps), false}, + CompilerPipelineTestCase{ + "StaticQubitsWithTwoTargetOps", nullptr, + MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithTwoTargetOps), + MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithTwoTargetOps), + MQT_NAMED_BUILDER(mlir::qir::staticQubitsWithTwoTargetOps), false}, + CompilerPipelineTestCase{ + "StaticQubitsWithCtrl", nullptr, + MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithCtrl), + MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithCtrl), + MQT_NAMED_BUILDER(mlir::qir::staticQubitsWithCtrl), false}, + CompilerPipelineTestCase{ + "StaticQubitsWithInv", nullptr, + MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithInv), + MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithInv), + MQT_NAMED_BUILDER(mlir::qir::staticQubitsWithInv), false}, + CompilerPipelineTestCase{ + "MixedStaticDynamicQubits", nullptr, + MQT_NAMED_BUILDER(mlir::qc::mixedStaticDynamicQubits), + MQT_NAMED_BUILDER(mlir::qc::mixedStaticDynamicQubits), + MQT_NAMED_BUILDER(mlir::qir::mixedStaticDynamicQubits), false}, CompilerPipelineTestCase{"AllocQubit", MQT_NAMED_BUILDER(qc::allocQubit), nullptr, MQT_NAMED_BUILDER(mlir::qc::allocQubit), @@ -420,8 +445,9 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(mlir::qc::p), MQT_NAMED_BUILDER(mlir::qir::p)}, CompilerPipelineTestCase{ - "SingleControlledP", MQT_NAMED_BUILDER(qc::singleControlledP), - nullptr, MQT_NAMED_BUILDER(mlir::qc::singleControlledP), + "SingleControlledP", + MQT_NAMED_BUILDER(qc::singleControlledP), nullptr, + MQT_NAMED_BUILDER(mlir::qc::singleControlledP), MQT_NAMED_BUILDER(mlir::qir::singleControlledP)}, CompilerPipelineTestCase{ "MultipleControlledP", MQT_NAMED_BUILDER(qc::multipleControlledP), diff --git a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp index 35524c0f23..3112362ac9 100644 --- a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp +++ b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp @@ -122,7 +122,22 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(qc::staticQubits)}, QCOToQCTestCase{"StaticQubitsWithOps", MQT_NAMED_BUILDER(qco::staticQubitsWithOps), - MQT_NAMED_BUILDER(qc::staticQubitsWithOps)})); + MQT_NAMED_BUILDER(qc::staticQubitsWithOps)}, + QCOToQCTestCase{"StaticQubitsWithParametricOps", + MQT_NAMED_BUILDER(qco::staticQubitsWithParametricOps), + MQT_NAMED_BUILDER(qc::staticQubitsWithParametricOps)}, + QCOToQCTestCase{"StaticQubitsWithTwoTargetOps", + MQT_NAMED_BUILDER(qco::staticQubitsWithTwoTargetOps), + MQT_NAMED_BUILDER(qc::staticQubitsWithTwoTargetOps)}, + QCOToQCTestCase{"StaticQubitsWithCtrl", + MQT_NAMED_BUILDER(qco::staticQubitsWithCtrl), + MQT_NAMED_BUILDER(qc::staticQubitsWithCtrl)}, + QCOToQCTestCase{"StaticQubitsWithInv", + MQT_NAMED_BUILDER(qco::staticQubitsWithInv), + MQT_NAMED_BUILDER(qc::staticQubitsWithInv)}, + QCOToQCTestCase{"MixedStaticDynamicQubits", + MQT_NAMED_BUILDER(qco::mixedStaticDynamicQubits), + MQT_NAMED_BUILDER(qc::mixedStaticDynamicQubits)})); /// @} /// \name QCOToQC/Modifiers/InvOp.cpp diff --git a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp index be82dc9680..ba43c30b98 100644 --- a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp +++ b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp @@ -121,7 +121,22 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(qco::staticQubits)}, QCToQCOTestCase{"StaticQubitsWithOps", MQT_NAMED_BUILDER(qc::staticQubitsWithOps), - MQT_NAMED_BUILDER(qco::staticQubitsWithOps)})); + MQT_NAMED_BUILDER(qco::staticQubitsWithOps)}, + QCToQCOTestCase{"StaticQubitsWithParametricOps", + MQT_NAMED_BUILDER(qc::staticQubitsWithParametricOps), + MQT_NAMED_BUILDER(qco::staticQubitsWithParametricOps)}, + QCToQCOTestCase{"StaticQubitsWithTwoTargetOps", + MQT_NAMED_BUILDER(qc::staticQubitsWithTwoTargetOps), + MQT_NAMED_BUILDER(qco::staticQubitsWithTwoTargetOps)}, + QCToQCOTestCase{"StaticQubitsWithCtrl", + MQT_NAMED_BUILDER(qc::staticQubitsWithCtrl), + MQT_NAMED_BUILDER(qco::staticQubitsWithCtrl)}, + QCToQCOTestCase{"StaticQubitsWithInv", + MQT_NAMED_BUILDER(qc::staticQubitsWithInv), + MQT_NAMED_BUILDER(qco::staticQubitsWithInv)}, + QCToQCOTestCase{"MixedStaticDynamicQubits", + MQT_NAMED_BUILDER(qc::mixedStaticDynamicQubits), + MQT_NAMED_BUILDER(qco::mixedStaticDynamicQubits)})); /// @} /// \name QCToQCO/Modifiers/InvOp.cpp diff --git a/mlir/unittests/Conversion/QCToQIR/test_qc_to_qir.cpp b/mlir/unittests/Conversion/QCToQIR/test_qc_to_qir.cpp index 670aeb39aa..58e832305b 100644 --- a/mlir/unittests/Conversion/QCToQIR/test_qc_to_qir.cpp +++ b/mlir/unittests/Conversion/QCToQIR/test_qc_to_qir.cpp @@ -627,6 +627,21 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRTestCase{"StaticQubitsWithOps", MQT_NAMED_BUILDER(qc::staticQubitsWithOps), MQT_NAMED_BUILDER(qir::staticQubitsWithOps)}, + QCToQIRTestCase{"StaticQubitsWithParametricOps", + MQT_NAMED_BUILDER(qc::staticQubitsWithParametricOps), + MQT_NAMED_BUILDER(qir::staticQubitsWithParametricOps)}, + QCToQIRTestCase{"StaticQubitsWithTwoTargetOps", + MQT_NAMED_BUILDER(qc::staticQubitsWithTwoTargetOps), + MQT_NAMED_BUILDER(qir::staticQubitsWithTwoTargetOps)}, + QCToQIRTestCase{"StaticQubitsWithCtrl", + MQT_NAMED_BUILDER(qc::staticQubitsWithCtrl), + MQT_NAMED_BUILDER(qir::staticQubitsWithCtrl)}, + QCToQIRTestCase{"StaticQubitsWithInv", + MQT_NAMED_BUILDER(qc::staticQubitsWithInv), + MQT_NAMED_BUILDER(qir::staticQubitsWithInv)}, + QCToQIRTestCase{"MixedStaticDynamicQubits", + MQT_NAMED_BUILDER(qc::mixedStaticDynamicQubits), + MQT_NAMED_BUILDER(qir::mixedStaticDynamicQubits)}, QCToQIRTestCase{"AllocDeallocPair", MQT_NAMED_BUILDER(qc::allocDeallocPair), MQT_NAMED_BUILDER(qir::emptyQIR)})); diff --git a/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp b/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp index 4d99bdb168..33b8f0ac75 100644 --- a/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp +++ b/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp @@ -902,6 +902,21 @@ INSTANTIATE_TEST_SUITE_P( QCTestCase{"StaticQubitsWithOps", MQT_NAMED_BUILDER(staticQubitsWithOps), MQT_NAMED_BUILDER(staticQubitsWithOps)}, + QCTestCase{"StaticQubitsWithParametricOps", + MQT_NAMED_BUILDER(staticQubitsWithParametricOps), + MQT_NAMED_BUILDER(staticQubitsWithParametricOps)}, + QCTestCase{"StaticQubitsWithTwoTargetOps", + MQT_NAMED_BUILDER(staticQubitsWithTwoTargetOps), + MQT_NAMED_BUILDER(staticQubitsWithTwoTargetOps)}, + QCTestCase{"StaticQubitsWithCtrl", + MQT_NAMED_BUILDER(staticQubitsWithCtrl), + MQT_NAMED_BUILDER(staticQubitsWithCtrl)}, + QCTestCase{"StaticQubitsWithInv", + MQT_NAMED_BUILDER(staticQubitsWithInv), + MQT_NAMED_BUILDER(staticQubitsWithInv)}, + QCTestCase{"MixedStaticDynamicQubits", + MQT_NAMED_BUILDER(mixedStaticDynamicQubits), + MQT_NAMED_BUILDER(mixedStaticDynamicQubits)}, QCTestCase{"AllocDeallocPair", MQT_NAMED_BUILDER(allocDeallocPair), MQT_NAMED_BUILDER(emptyQC)})); /// @} diff --git a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp index dfd647c736..aad6ebe783 100644 --- a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp +++ b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp @@ -127,61 +127,6 @@ TEST_F(QCOTest, DirectIfBuilder) { refBuilder.get())); } -TEST_F(QCOTest, StaticQubitTypePropagation) { - auto staticType = QubitType::get(context.get(), /*isStatic=*/true); - auto dynamicType = QubitType::get(context.get(), /*isStatic=*/false); - - qco::QCOProgramBuilder builder(context.get()); - builder.initialize(); - - auto sQ = StaticOp::create(builder, 0); - auto dQ = AllocOp::create(builder); - - // One-target-zero-parameter: static in → static out - auto hStatic = HOp::create(builder, sQ.getQubit()); - EXPECT_EQ(hStatic.getQubitOut().getType(), staticType); - - // One-target-zero-parameter: dynamic in → dynamic out - auto hDynamic = HOp::create(builder, dQ.getResult()); - EXPECT_EQ(hDynamic.getQubitOut().getType(), dynamicType); - - // One-target-one-parameter: static in → static out - auto pStatic = POp::create(builder, hStatic.getQubitOut(), 0.5); - EXPECT_EQ(pStatic.getQubitOut().getType(), staticType); - - // One-target-two-parameter: static in → static out - auto rStatic = ROp::create(builder, pStatic.getQubitOut(), 0.5, 0.3); - EXPECT_EQ(rStatic.getQubitOut().getType(), staticType); - - // One-target-three-parameter: static in → static out - auto uStatic = UOp::create(builder, rStatic.getQubitOut(), 0.1, 0.2, 0.3); - EXPECT_EQ(uStatic.getQubitOut().getType(), staticType); - - // Two-target-zero-parameter: preserves both types independently - auto swapOp = - SWAPOp::create(builder, uStatic.getQubitOut(), hDynamic.getQubitOut()); - EXPECT_EQ(swapOp.getQubit0Out().getType(), staticType); - EXPECT_EQ(swapOp.getQubit1Out().getType(), dynamicType); - - // Two-target-one-parameter: preserves both types independently - auto rxxOp = - RXXOp::create(builder, swapOp.getQubit0Out(), swapOp.getQubit1Out(), 0.5); - EXPECT_EQ(rxxOp.getQubit0Out().getType(), staticType); - EXPECT_EQ(rxxOp.getQubit1Out().getType(), dynamicType); - - // Two-target-two-parameter: preserves both types independently - auto xxpyOp = XXPlusYYOp::create(builder, rxxOp.getQubit0Out(), - rxxOp.getQubit1Out(), 0.5, 0.3); - EXPECT_EQ(xxpyOp.getQubit0Out().getType(), staticType); - EXPECT_EQ(xxpyOp.getQubit1Out().getType(), dynamicType); - - DeallocOp::create(builder, xxpyOp.getQubit0Out()); - DeallocOp::create(builder, xxpyOp.getQubit1Out()); - auto module = builder.finalize(); - ASSERT_TRUE(module); - EXPECT_TRUE(verify(*module).succeeded()); -} - /// \name QCO/SCF/IfOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( diff --git a/mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp b/mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp index 9ce94c3eda..157fb5cabc 100644 --- a/mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp +++ b/mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp @@ -522,20 +522,34 @@ INSTANTIATE_TEST_SUITE_P( /// @{ INSTANTIATE_TEST_SUITE_P( QIRQubitManagementTest, QIRTest, - testing::Values(QIRTestCase{"AllocQubit", MQT_NAMED_BUILDER(allocQubit), - MQT_NAMED_BUILDER(allocQubit)}, - QIRTestCase{"AllocQubitRegister", - MQT_NAMED_BUILDER(allocQubitRegister), - MQT_NAMED_BUILDER(allocQubitRegister)}, - QIRTestCase{"AllocMultipleQubitRegisters", - MQT_NAMED_BUILDER(allocMultipleQubitRegisters), - MQT_NAMED_BUILDER(allocMultipleQubitRegisters)}, - QIRTestCase{"AllocLargeRegister", - MQT_NAMED_BUILDER(allocLargeRegister), - MQT_NAMED_BUILDER(allocLargeRegister)}, - QIRTestCase{"StaticQubits", MQT_NAMED_BUILDER(staticQubits), - MQT_NAMED_BUILDER(staticQubits)}, - QIRTestCase{"StaticQubitsWithOps", - MQT_NAMED_BUILDER(staticQubitsWithOps), - MQT_NAMED_BUILDER(staticQubitsWithOps)})); + testing::Values( + QIRTestCase{"AllocQubit", MQT_NAMED_BUILDER(allocQubit), + MQT_NAMED_BUILDER(allocQubit)}, + QIRTestCase{"AllocQubitRegister", MQT_NAMED_BUILDER(allocQubitRegister), + MQT_NAMED_BUILDER(allocQubitRegister)}, + QIRTestCase{"AllocMultipleQubitRegisters", + MQT_NAMED_BUILDER(allocMultipleQubitRegisters), + MQT_NAMED_BUILDER(allocMultipleQubitRegisters)}, + QIRTestCase{"AllocLargeRegister", MQT_NAMED_BUILDER(allocLargeRegister), + MQT_NAMED_BUILDER(allocLargeRegister)}, + QIRTestCase{"StaticQubits", MQT_NAMED_BUILDER(staticQubits), + MQT_NAMED_BUILDER(staticQubits)}, + QIRTestCase{"StaticQubitsWithOps", + MQT_NAMED_BUILDER(staticQubitsWithOps), + MQT_NAMED_BUILDER(staticQubitsWithOps)}, + QIRTestCase{"StaticQubitsWithParametricOps", + MQT_NAMED_BUILDER(staticQubitsWithParametricOps), + MQT_NAMED_BUILDER(staticQubitsWithParametricOps)}, + QIRTestCase{"StaticQubitsWithTwoTargetOps", + MQT_NAMED_BUILDER(staticQubitsWithTwoTargetOps), + MQT_NAMED_BUILDER(staticQubitsWithTwoTargetOps)}, + QIRTestCase{"StaticQubitsWithCtrl", + MQT_NAMED_BUILDER(staticQubitsWithCtrl), + MQT_NAMED_BUILDER(staticQubitsWithCtrl)}, + QIRTestCase{"StaticQubitsWithInv", + MQT_NAMED_BUILDER(staticQubitsWithInv), + MQT_NAMED_BUILDER(staticQubitsWithInv)}, + QIRTestCase{"MixedStaticDynamicQubits", + MQT_NAMED_BUILDER(mixedStaticDynamicQubits), + MQT_NAMED_BUILDER(mixedStaticDynamicQubits)})); /// @} diff --git a/mlir/unittests/programs/qc_programs.cpp b/mlir/unittests/programs/qc_programs.cpp index c0302528c6..12da2d382e 100644 --- a/mlir/unittests/programs/qc_programs.cpp +++ b/mlir/unittests/programs/qc_programs.cpp @@ -41,6 +41,38 @@ void staticQubitsWithOps(QCProgramBuilder& b) { b.h(q1); } +void staticQubitsWithParametricOps(QCProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + b.rx(std::numbers::pi / 4., q0); + b.p(std::numbers::pi / 2., q1); +} + +void staticQubitsWithTwoTargetOps(QCProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + b.swap(q0, q1); +} + +void staticQubitsWithCtrl(QCProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + b.cx(q0, q1); +} + +void staticQubitsWithInv(QCProgramBuilder& b) { + auto q0 = b.staticQubit(0); + b.inv([&]() { b.t(q0); }); +} + +void mixedStaticDynamicQubits(QCProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.allocQubit(); + b.swap(q0, q1); + b.h(q0); + b.h(q1); +} + void allocDeallocPair(QCProgramBuilder& b) { auto q = b.allocQubit(); b.dealloc(q); diff --git a/mlir/unittests/programs/qc_programs.h b/mlir/unittests/programs/qc_programs.h index 44510006bd..a0c379197c 100644 --- a/mlir/unittests/programs/qc_programs.h +++ b/mlir/unittests/programs/qc_programs.h @@ -36,6 +36,21 @@ void staticQubits(QCProgramBuilder& b); /// Allocates two static qubits and applies operations. void staticQubitsWithOps(QCProgramBuilder& b); +/// Allocates two static qubits and applies parametric gates. +void staticQubitsWithParametricOps(QCProgramBuilder& b); + +/// Allocates two static qubits and applies a two-target gate. +void staticQubitsWithTwoTargetOps(QCProgramBuilder& b); + +/// Allocates two static qubits and applies a controlled gate. +void staticQubitsWithCtrl(QCProgramBuilder& b); + +/// Allocates a static qubit and applies an inverse modifier. +void staticQubitsWithInv(QCProgramBuilder& b); + +/// Allocates one static and one dynamic qubit and applies mixed operations. +void mixedStaticDynamicQubits(QCProgramBuilder& b); + /// Allocates and explicitly deallocates a single qubit. void allocDeallocPair(QCProgramBuilder& b); diff --git a/mlir/unittests/programs/qir_programs.cpp b/mlir/unittests/programs/qir_programs.cpp index 75421f804b..6126693337 100644 --- a/mlir/unittests/programs/qir_programs.cpp +++ b/mlir/unittests/programs/qir_programs.cpp @@ -12,6 +12,8 @@ #include "mlir/Dialect/QIR/Builder/QIRProgramBuilder.h" +#include + namespace mlir::qir { void emptyQIR([[maybe_unused]] QIRProgramBuilder& builder) {} @@ -39,6 +41,39 @@ void staticQubitsWithOps(QIRProgramBuilder& b) { b.h(q1); } +void staticQubitsWithParametricOps(QIRProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + b.rx(std::numbers::pi / 4., q0); + b.p(std::numbers::pi / 2., q1); +} + +void staticQubitsWithTwoTargetOps(QIRProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + b.swap(q0, q1); +} + +void staticQubitsWithCtrl(QIRProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto q1 = b.staticQubit(1); + b.cx(q0, q1); +} + +void staticQubitsWithInv(QIRProgramBuilder& b) { + auto q0 = b.staticQubit(0); + b.tdg(q0); +} + +void mixedStaticDynamicQubits(QIRProgramBuilder& b) { + auto q0 = b.staticQubit(0); + auto qDyn = b.allocQubitRegister(1); + auto q1 = qDyn[0]; + b.swap(q0, q1); + b.h(q0); + b.h(q1); +} + void singleMeasurementToSingleBit(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); const auto c = b.allocClassicalBitRegister(1); diff --git a/mlir/unittests/programs/qir_programs.h b/mlir/unittests/programs/qir_programs.h index b601d297ea..d14a5d57dc 100644 --- a/mlir/unittests/programs/qir_programs.h +++ b/mlir/unittests/programs/qir_programs.h @@ -36,6 +36,21 @@ void staticQubits(QIRProgramBuilder& b); /// Allocates two static qubits and applies operations. void staticQubitsWithOps(QIRProgramBuilder& b); +/// Allocates two static qubits and applies parametric gates. +void staticQubitsWithParametricOps(QIRProgramBuilder& b); + +/// Allocates two static qubits and applies a two-target gate. +void staticQubitsWithTwoTargetOps(QIRProgramBuilder& b); + +/// Allocates two static qubits and applies a controlled gate. +void staticQubitsWithCtrl(QIRProgramBuilder& b); + +/// Allocates a static qubit and applies the inverse of a T gate (Tdg). +void staticQubitsWithInv(QIRProgramBuilder& b); + +/// Allocates one static and one dynamic qubit and applies mixed operations. +void mixedStaticDynamicQubits(QIRProgramBuilder& b); + // --- MeasureOp ------------------------------------------------------------ // /// Measures a single qubit into a single classical bit. From ea8b1552f834f9b7846fde315cd6159997d14fa3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 13:22:31 +0000 Subject: [PATCH 19/57] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/include/mlir/Dialect/QC/IR/QCOps.td | 23 +- mlir/include/mlir/Dialect/QC/IR/QCTypes.td | 10 +- mlir/include/mlir/Dialect/QCO/IR/QCOOps.td | 257 ++++++++++++------- mlir/include/mlir/Dialect/QCO/IR/QCOTypes.td | 10 +- 4 files changed, 188 insertions(+), 112 deletions(-) diff --git a/mlir/include/mlir/Dialect/QC/IR/QCOps.td b/mlir/include/mlir/Dialect/QC/IR/QCOps.td index 46e8c9dc09..4744f27ac6 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCOps.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCOps.td @@ -30,15 +30,16 @@ class QCOp traits = []> // Type Constraints //===----------------------------------------------------------------------===// -def DynamicQubit : Type< - And<[CPred<"::mlir::isa<::mlir::qc::QubitType>($_self)">, - CPred<"!::mlir::cast<::mlir::qc::QubitType>($_self).getIsStatic()">]>, - "dynamic qubit type (!qc.qubit)">; +def DynamicQubit + : Type($_self)">, + CPred<"!::mlir::cast<::mlir::qc::QubitType>($_self)." + "getIsStatic()">]>, + "dynamic qubit type (!qc.qubit)">; -def StaticQubit : Type< - And<[CPred<"::mlir::isa<::mlir::qc::QubitType>($_self)">, - CPred<"::mlir::cast<::mlir::qc::QubitType>($_self).getIsStatic()">]>, - "static qubit type (!qc.qubit)">; +def StaticQubit : Type($_self)">, + CPred<"::mlir::cast<::mlir::qc::QubitType>($_self)." + "getIsStatic()">]>, + "static qubit type (!qc.qubit)">; //===----------------------------------------------------------------------===// // Resource Operations @@ -82,7 +83,8 @@ def AllocOp : QCOp<"alloc", [MemoryEffects<[MemAlloc]>]> { }]>, OpBuilder<(ins "::mlir::StringAttr":$register_name, "::mlir::IntegerAttr":$register_size, - "::mlir::IntegerAttr":$register_index), [{ + "::mlir::IntegerAttr":$register_index), + [{ build($_builder, $_state, QubitType::get($_builder.getContext(), /*isStatic=*/false), register_name, register_size, register_index); }]>]; @@ -128,8 +130,7 @@ def StaticOp : QCOp<"static", [Pure]> { let builders = [OpBuilder<(ins "int64_t":$index), [{ build($_builder, $_state, QubitType::get($_builder.getContext(), /*isStatic=*/true), $_builder.getI64IntegerAttr(index)); - }]> - ]; + }]>]; } //===----------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/QC/IR/QCTypes.td b/mlir/include/mlir/Dialect/QC/IR/QCTypes.td index c4453a2ec8..bc4ab8852c 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCTypes.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCTypes.td @@ -29,13 +29,11 @@ def QubitType : QCType<"Qubit", "qubit"> { `!qc.qubit` (default) denotes a dynamically allocated qubit. `!qc.qubit` denotes a qubit with a statically known identifier. }]; - let parameters = (ins DefaultValuedParameter<"bool", "false">:$isStatic); - let builders = [ - TypeBuilder<(ins), [{ + let parameters = (ins DefaultValuedParameter<"bool", "false">:$isStatic); + let builders = [TypeBuilder<(ins), [{ return $_get($_ctxt, /*isStatic=*/false); - }]> - ]; - let hasCustomAssemblyFormat = 1; + }]>]; + let hasCustomAssemblyFormat = 1; } #endif // MLIR_DIALECT_QC_IR_QCTYPES_TD diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td index 8d784aafe0..a53fcfa83e 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td @@ -30,15 +30,17 @@ class QCOOp traits = []> // Type Constraints //===----------------------------------------------------------------------===// -def DynamicQubit : Type< - And<[CPred<"::mlir::isa<::mlir::qco::QubitType>($_self)">, - CPred<"!::mlir::cast<::mlir::qco::QubitType>($_self).getIsStatic()">]>, - "dynamic qubit type (!qco.qubit)">; - -def StaticQubit : Type< - And<[CPred<"::mlir::isa<::mlir::qco::QubitType>($_self)">, - CPred<"::mlir::cast<::mlir::qco::QubitType>($_self).getIsStatic()">]>, - "static qubit type (!qco.qubit)">; +def DynamicQubit + : Type($_self)">, + CPred<"!::mlir::cast<::mlir::qco::QubitType>($_self)." + "getIsStatic()">]>, + "dynamic qubit type (!qco.qubit)">; + +def StaticQubit + : Type($_self)">, + CPred<"::mlir::cast<::mlir::qco::QubitType>($_self)." + "getIsStatic()">]>, + "static qubit type (!qco.qubit)">; //===----------------------------------------------------------------------===// // Resource Operations @@ -82,7 +84,8 @@ def AllocOp : QCOOp<"alloc", [MemoryEffects<[MemAlloc]>]> { }]>, OpBuilder<(ins "::mlir::StringAttr":$register_name, "::mlir::IntegerAttr":$register_size, - "::mlir::IntegerAttr":$register_index), [{ + "::mlir::IntegerAttr":$register_index), + [{ build($_builder, $_state, QubitType::get($_builder.getContext(), /*isStatic=*/false), register_name, register_size, register_index); }]>]; @@ -131,17 +134,16 @@ def StaticOp : QCOOp<"static", [Pure]> { let builders = [OpBuilder<(ins "int64_t":$index), [{ build($_builder, $_state, QubitType::get($_builder.getContext(), /*isStatic=*/true), $_builder.getI64IntegerAttr(index)); - }]> - ]; + }]>]; } //===----------------------------------------------------------------------===// // Measurement and Reset Operations //===----------------------------------------------------------------------===// -def MeasureOp : QCOOp<"measure", - [TypesMatchWith<"qubit output type matches input", - "qubit_in", "qubit_out", "$_self">]> { +def MeasureOp + : QCOOp<"measure", [TypesMatchWith<"qubit output type matches input", + "qubit_in", "qubit_out", "$_self">]> { let summary = "Measure a qubit in the computational basis"; let description = [{ Measures a qubit in the computational (Z) basis, collapsing the state @@ -252,8 +254,10 @@ def GPhaseOp let hasCanonicalizer = 1; } -def IdOp : QCOOp<"id", traits = [UnitaryOpInterface, OneTargetZeroParameter, - TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { +def IdOp : QCOOp<"id", + traits = [UnitaryOpInterface, OneTargetZeroParameter, + TypesMatchWith<"result type matches input", + "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply an Id gate to a qubit"; let description = [{ Applies an Id gate to a qubit and returns the transformed qubit. @@ -277,8 +281,10 @@ def IdOp : QCOOp<"id", traits = [UnitaryOpInterface, OneTargetZeroParameter, let hasCanonicalizer = 1; } -def XOp : QCOOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter, - TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { +def XOp + : QCOOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter, + TypesMatchWith<"result type matches input", + "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply an X gate to a qubit"; let description = [{ Applies an X gate to a qubit and returns the transformed qubit. @@ -302,8 +308,10 @@ def XOp : QCOOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter, let hasCanonicalizer = 1; } -def YOp : QCOOp<"y", traits = [UnitaryOpInterface, OneTargetZeroParameter, - TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { +def YOp + : QCOOp<"y", traits = [UnitaryOpInterface, OneTargetZeroParameter, + TypesMatchWith<"result type matches input", + "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply a Y gate to a qubit"; let description = [{ Applies a Y gate to a qubit and returns the transformed qubit. @@ -327,8 +335,10 @@ def YOp : QCOOp<"y", traits = [UnitaryOpInterface, OneTargetZeroParameter, let hasCanonicalizer = 1; } -def ZOp : QCOOp<"z", traits = [UnitaryOpInterface, OneTargetZeroParameter, - TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { +def ZOp + : QCOOp<"z", traits = [UnitaryOpInterface, OneTargetZeroParameter, + TypesMatchWith<"result type matches input", + "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply a Z gate to a qubit"; let description = [{ Applies a Z gate to a qubit and returns the transformed qubit. @@ -352,8 +362,10 @@ def ZOp : QCOOp<"z", traits = [UnitaryOpInterface, OneTargetZeroParameter, let hasCanonicalizer = 1; } -def HOp : QCOOp<"h", traits = [UnitaryOpInterface, OneTargetZeroParameter, - TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { +def HOp + : QCOOp<"h", traits = [UnitaryOpInterface, OneTargetZeroParameter, + TypesMatchWith<"result type matches input", + "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply a H gate to a qubit"; let description = [{ Applies a H gate to a qubit and returns the transformed qubit. @@ -377,8 +389,10 @@ def HOp : QCOOp<"h", traits = [UnitaryOpInterface, OneTargetZeroParameter, let hasCanonicalizer = 1; } -def SOp : QCOOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter, - TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { +def SOp + : QCOOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter, + TypesMatchWith<"result type matches input", + "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply an S gate to a qubit"; let description = [{ Applies an S gate to a qubit and returns the transformed qubit. @@ -402,8 +416,11 @@ def SOp : QCOOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter, let hasCanonicalizer = 1; } -def SdgOp : QCOOp<"sdg", traits = [UnitaryOpInterface, OneTargetZeroParameter, - TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { +def SdgOp + : QCOOp<"sdg", + traits = [UnitaryOpInterface, OneTargetZeroParameter, + TypesMatchWith<"result type matches input", "qubit_in", + "qubit_out", "$_self">]> { let summary = "Apply an Sdg gate to a qubit"; let description = [{ Applies an Sdg gate to a qubit and returns the transformed qubit. @@ -427,8 +444,10 @@ def SdgOp : QCOOp<"sdg", traits = [UnitaryOpInterface, OneTargetZeroParameter, let hasCanonicalizer = 1; } -def TOp : QCOOp<"t", traits = [UnitaryOpInterface, OneTargetZeroParameter, - TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { +def TOp + : QCOOp<"t", traits = [UnitaryOpInterface, OneTargetZeroParameter, + TypesMatchWith<"result type matches input", + "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply a T gate to a qubit"; let description = [{ Applies a T gate to a qubit and returns the transformed qubit. @@ -452,8 +471,11 @@ def TOp : QCOOp<"t", traits = [UnitaryOpInterface, OneTargetZeroParameter, let hasCanonicalizer = 1; } -def TdgOp : QCOOp<"tdg", traits = [UnitaryOpInterface, OneTargetZeroParameter, - TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { +def TdgOp + : QCOOp<"tdg", + traits = [UnitaryOpInterface, OneTargetZeroParameter, + TypesMatchWith<"result type matches input", "qubit_in", + "qubit_out", "$_self">]> { let summary = "Apply a Tdg gate to a qubit"; let description = [{ Applies a Tdg gate to a qubit and returns the transformed qubit. @@ -477,8 +499,10 @@ def TdgOp : QCOOp<"tdg", traits = [UnitaryOpInterface, OneTargetZeroParameter, let hasCanonicalizer = 1; } -def SXOp : QCOOp<"sx", traits = [UnitaryOpInterface, OneTargetZeroParameter, - TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { +def SXOp : QCOOp<"sx", + traits = [UnitaryOpInterface, OneTargetZeroParameter, + TypesMatchWith<"result type matches input", + "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply an SX gate to a qubit"; let description = [{ Applies an SX gate to a qubit and returns the transformed qubit. @@ -502,8 +526,11 @@ def SXOp : QCOOp<"sx", traits = [UnitaryOpInterface, OneTargetZeroParameter, let hasCanonicalizer = 1; } -def SXdgOp : QCOOp<"sxdg", traits = [UnitaryOpInterface, OneTargetZeroParameter, - TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { +def SXdgOp + : QCOOp<"sxdg", + traits = [UnitaryOpInterface, OneTargetZeroParameter, + TypesMatchWith<"result type matches input", "qubit_in", + "qubit_out", "$_self">]> { let summary = "Apply an SXdg gate to a qubit"; let description = [{ Applies an SXdg gate to a qubit and returns the transformed qubit. @@ -527,8 +554,10 @@ def SXdgOp : QCOOp<"sxdg", traits = [UnitaryOpInterface, OneTargetZeroParameter, let hasCanonicalizer = 1; } -def RXOp : QCOOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter, - TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { +def RXOp : QCOOp<"rx", + traits = [UnitaryOpInterface, OneTargetOneParameter, + TypesMatchWith<"result type matches input", + "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply an RX gate to a qubit"; let description = [{ Applies an RX gate to a qubit and returns the transformed qubit. @@ -556,8 +585,10 @@ def RXOp : QCOOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter, let hasCanonicalizer = 1; } -def RYOp : QCOOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter, - TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { +def RYOp : QCOOp<"ry", + traits = [UnitaryOpInterface, OneTargetOneParameter, + TypesMatchWith<"result type matches input", + "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply an RY gate to a qubit"; let description = [{ Applies an RY gate to a qubit and returns the transformed qubit. @@ -585,8 +616,10 @@ def RYOp : QCOOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter, let hasCanonicalizer = 1; } -def RZOp : QCOOp<"rz", traits = [UnitaryOpInterface, OneTargetOneParameter, - TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { +def RZOp : QCOOp<"rz", + traits = [UnitaryOpInterface, OneTargetOneParameter, + TypesMatchWith<"result type matches input", + "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply an RZ gate to a qubit"; let description = [{ Applies an RZ gate to a qubit and returns the transformed qubit. @@ -614,8 +647,10 @@ def RZOp : QCOOp<"rz", traits = [UnitaryOpInterface, OneTargetOneParameter, let hasCanonicalizer = 1; } -def POp : QCOOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter, - TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { +def POp + : QCOOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter, + TypesMatchWith<"result type matches input", + "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply a P gate to a qubit"; let description = [{ Applies a P gate to a qubit and returns the transformed qubit. @@ -643,8 +678,10 @@ def POp : QCOOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter, let hasCanonicalizer = 1; } -def ROp : QCOOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter, - TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { +def ROp + : QCOOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter, + TypesMatchWith<"result type matches input", + "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply an R gate to a qubit"; let description = [{ Applies an R gate to a qubit and returns the transformed qubit. @@ -674,8 +711,10 @@ def ROp : QCOOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter, let hasCanonicalizer = 1; } -def U2Op : QCOOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter, - TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { +def U2Op : QCOOp<"u2", + traits = [UnitaryOpInterface, OneTargetTwoParameter, + TypesMatchWith<"result type matches input", + "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply a U2 gate to a qubit"; let description = [{ Applies a U2 gate to a qubit and returns the transformed qubit. @@ -705,8 +744,10 @@ def U2Op : QCOOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter, let hasCanonicalizer = 1; } -def UOp : QCOOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter, - TypesMatchWith<"result type matches input", "qubit_in", "qubit_out", "$_self">]> { +def UOp + : QCOOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter, + TypesMatchWith<"result type matches input", + "qubit_in", "qubit_out", "$_self">]> { let summary = "Apply a U gate to a qubit"; let description = [{ Applies a U gate to a qubit and returns the transformed qubit. @@ -738,9 +779,13 @@ def UOp : QCOOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter, let hasCanonicalizer = 1; } -def SWAPOp : QCOOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParameter, - TypesMatchWith<"result type matches input", "qubit0_in", "qubit0_out", "$_self">, - TypesMatchWith<"result type matches input", "qubit1_in", "qubit1_out", "$_self">]> { +def SWAPOp + : QCOOp<"swap", + traits = [UnitaryOpInterface, TwoTargetZeroParameter, + TypesMatchWith<"result type matches input", "qubit0_in", + "qubit0_out", "$_self">, + TypesMatchWith<"result type matches input", "qubit1_in", + "qubit1_out", "$_self">]> { let summary = "Apply a SWAP gate to two qubits"; let description = [{ Applies a SWAP gate to two qubits and returns the transformed qubits. @@ -767,9 +812,13 @@ def SWAPOp : QCOOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParameter, let hasCanonicalizer = 1; } -def iSWAPOp : QCOOp<"iswap", traits = [UnitaryOpInterface, TwoTargetZeroParameter, - TypesMatchWith<"result type matches input", "qubit0_in", "qubit0_out", "$_self">, - TypesMatchWith<"result type matches input", "qubit1_in", "qubit1_out", "$_self">]> { +def iSWAPOp + : QCOOp<"iswap", + traits = [UnitaryOpInterface, TwoTargetZeroParameter, + TypesMatchWith<"result type matches input", "qubit0_in", + "qubit0_out", "$_self">, + TypesMatchWith<"result type matches input", "qubit1_in", + "qubit1_out", "$_self">]> { let summary = "Apply a iSWAP gate to two qubits"; let description = [{ Applies a iSWAP gate to two qubits and returns the transformed qubits. @@ -794,9 +843,13 @@ def iSWAPOp : QCOOp<"iswap", traits = [UnitaryOpInterface, TwoTargetZeroParamete }]; } -def DCXOp : QCOOp<"dcx", traits = [UnitaryOpInterface, TwoTargetZeroParameter, - TypesMatchWith<"result type matches input", "qubit0_in", "qubit0_out", "$_self">, - TypesMatchWith<"result type matches input", "qubit1_in", "qubit1_out", "$_self">]> { +def DCXOp + : QCOOp<"dcx", + traits = [UnitaryOpInterface, TwoTargetZeroParameter, + TypesMatchWith<"result type matches input", "qubit0_in", + "qubit0_out", "$_self">, + TypesMatchWith<"result type matches input", "qubit1_in", + "qubit1_out", "$_self">]> { let summary = "Apply a DCX gate to two qubits"; let description = [{ Applies a DCX gate to two qubits and returns the transformed qubits. @@ -823,9 +876,13 @@ def DCXOp : QCOOp<"dcx", traits = [UnitaryOpInterface, TwoTargetZeroParameter, let hasCanonicalizer = 1; } -def ECROp : QCOOp<"ecr", traits = [UnitaryOpInterface, TwoTargetZeroParameter, - TypesMatchWith<"result type matches input", "qubit0_in", "qubit0_out", "$_self">, - TypesMatchWith<"result type matches input", "qubit1_in", "qubit1_out", "$_self">]> { +def ECROp + : QCOOp<"ecr", + traits = [UnitaryOpInterface, TwoTargetZeroParameter, + TypesMatchWith<"result type matches input", "qubit0_in", + "qubit0_out", "$_self">, + TypesMatchWith<"result type matches input", "qubit1_in", + "qubit1_out", "$_self">]> { let summary = "Apply an ECR gate to two qubits"; let description = [{ Applies an ECR gate to two qubits and returns the transformed qubits. @@ -852,9 +909,13 @@ def ECROp : QCOOp<"ecr", traits = [UnitaryOpInterface, TwoTargetZeroParameter, let hasCanonicalizer = 1; } -def RXXOp : QCOOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter, - TypesMatchWith<"result type matches input", "qubit0_in", "qubit0_out", "$_self">, - TypesMatchWith<"result type matches input", "qubit1_in", "qubit1_out", "$_self">]> { +def RXXOp + : QCOOp<"rxx", + traits = [UnitaryOpInterface, TwoTargetOneParameter, + TypesMatchWith<"result type matches input", "qubit0_in", + "qubit0_out", "$_self">, + TypesMatchWith<"result type matches input", "qubit1_in", + "qubit1_out", "$_self">]> { let summary = "Apply an RXX gate to two qubits"; let description = [{ Applies an RXX gate to two qubits and returns the transformed qubits. @@ -885,9 +946,13 @@ def RXXOp : QCOOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter, let hasCanonicalizer = 1; } -def RYYOp : QCOOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter, - TypesMatchWith<"result type matches input", "qubit0_in", "qubit0_out", "$_self">, - TypesMatchWith<"result type matches input", "qubit1_in", "qubit1_out", "$_self">]> { +def RYYOp + : QCOOp<"ryy", + traits = [UnitaryOpInterface, TwoTargetOneParameter, + TypesMatchWith<"result type matches input", "qubit0_in", + "qubit0_out", "$_self">, + TypesMatchWith<"result type matches input", "qubit1_in", + "qubit1_out", "$_self">]> { let summary = "Apply an RYY gate to two qubits"; let description = [{ Applies an RYY gate to two qubits and returns the transformed qubits. @@ -918,9 +983,13 @@ def RYYOp : QCOOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter, let hasCanonicalizer = 1; } -def RZXOp : QCOOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter, - TypesMatchWith<"result type matches input", "qubit0_in", "qubit0_out", "$_self">, - TypesMatchWith<"result type matches input", "qubit1_in", "qubit1_out", "$_self">]> { +def RZXOp + : QCOOp<"rzx", + traits = [UnitaryOpInterface, TwoTargetOneParameter, + TypesMatchWith<"result type matches input", "qubit0_in", + "qubit0_out", "$_self">, + TypesMatchWith<"result type matches input", "qubit1_in", + "qubit1_out", "$_self">]> { let summary = "Apply an RZX gate to two qubits"; let description = [{ Applies an RZX gate to two qubits and returns the transformed qubits. @@ -951,9 +1020,13 @@ def RZXOp : QCOOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter, let hasCanonicalizer = 1; } -def RZZOp : QCOOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter, - TypesMatchWith<"result type matches input", "qubit0_in", "qubit0_out", "$_self">, - TypesMatchWith<"result type matches input", "qubit1_in", "qubit1_out", "$_self">]> { +def RZZOp + : QCOOp<"rzz", + traits = [UnitaryOpInterface, TwoTargetOneParameter, + TypesMatchWith<"result type matches input", "qubit0_in", + "qubit0_out", "$_self">, + TypesMatchWith<"result type matches input", "qubit1_in", + "qubit1_out", "$_self">]> { let summary = "Apply an RZZ gate to two qubits"; let description = [{ Applies an RZZ gate to two qubits and returns the transformed qubits. @@ -984,9 +1057,13 @@ def RZZOp : QCOOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter, let hasCanonicalizer = 1; } -def XXPlusYYOp : QCOOp<"xx_plus_yy", traits = [UnitaryOpInterface, TwoTargetTwoParameter, - TypesMatchWith<"result type matches input", "qubit0_in", "qubit0_out", "$_self">, - TypesMatchWith<"result type matches input", "qubit1_in", "qubit1_out", "$_self">]> { +def XXPlusYYOp + : QCOOp<"xx_plus_yy", + traits = [UnitaryOpInterface, TwoTargetTwoParameter, + TypesMatchWith<"result type matches input", "qubit0_in", + "qubit0_out", "$_self">, + TypesMatchWith<"result type matches input", "qubit1_in", + "qubit1_out", "$_self">]> { let summary = "Apply an XX+YY gate to two qubits"; let description = [{ Applies an XX+YY gate to two qubits and returns the transformed qubits. @@ -1019,9 +1096,13 @@ def XXPlusYYOp : QCOOp<"xx_plus_yy", traits = [UnitaryOpInterface, TwoTargetTwoP let hasCanonicalizer = 1; } -def XXMinusYYOp : QCOOp<"xx_minus_yy", traits = [UnitaryOpInterface, TwoTargetTwoParameter, - TypesMatchWith<"result type matches input", "qubit0_in", "qubit0_out", "$_self">, - TypesMatchWith<"result type matches input", "qubit1_in", "qubit1_out", "$_self">]> { +def XXMinusYYOp + : QCOOp<"xx_minus_yy", + traits = [UnitaryOpInterface, TwoTargetTwoParameter, + TypesMatchWith<"result type matches input", "qubit0_in", + "qubit0_out", "$_self">, + TypesMatchWith<"result type matches input", "qubit1_in", + "qubit1_out", "$_self">]> { let summary = "Apply an XX-YY gate to two qubits"; let description = [{ Applies an XX-YY gate to two qubits and returns the transformed qubits. @@ -1122,14 +1203,12 @@ def YieldOp : QCOOp<"yield", traits = [Terminator, ReturnLike]> { let assemblyFormat = "$targets attr-dict (`:` type($targets)^)?"; } -def CtrlOp : QCOOp<"ctrl", traits = - [ - UnitaryOpInterface, - AttrSizedOperandSegments, - AttrSizedResultSegments, - SingleBlockImplicitTerminator<"::mlir::qco::YieldOp">, - RecursiveMemoryEffects - ]> { +def CtrlOp + : QCOOp<"ctrl", + traits = [UnitaryOpInterface, AttrSizedOperandSegments, + AttrSizedResultSegments, + SingleBlockImplicitTerminator<"::mlir::qco::YieldOp">, + RecursiveMemoryEffects]> { let summary = "Add control qubits to a unitary operation"; let description = [{ A modifier operation that adds control qubits to the unitary operation diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOTypes.td b/mlir/include/mlir/Dialect/QCO/IR/QCOTypes.td index 2e092cc68e..358afffc8d 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOTypes.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOTypes.td @@ -36,13 +36,11 @@ def QubitType : QCOType<"Qubit", "qubit"> { %q2 = qco.x %q1 : !qco.qubit -> !qco.qubit ``` }]; - let parameters = (ins DefaultValuedParameter<"bool", "false">:$isStatic); - let builders = [ - TypeBuilder<(ins), [{ + let parameters = (ins DefaultValuedParameter<"bool", "false">:$isStatic); + let builders = [TypeBuilder<(ins), [{ return $_get($_ctxt, /*isStatic=*/false); - }]> - ]; - let hasCustomAssemblyFormat = 1; + }]>]; + let hasCustomAssemblyFormat = 1; } #endif // MLIR_DIALECT_QCO_IR_QCOTYPES_TD From 95970d6cc16db9f71530cc82003f0915b0b2cb81 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Fri, 20 Mar 2026 14:40:39 +0100 Subject: [PATCH 20/57] =?UTF-8?q?=F0=9F=94=A7=20Implement=20synchronizeMap?= =?UTF-8?q?pedQubitTypes=20function=20to=20align=20qubit=20result=20types?= =?UTF-8?q?=20with=20operand=20types=20post-placement,=20ensuring=20type?= =?UTF-8?q?=20consistency=20in=20QCO=20operations.=20This=20update=20enhan?= =?UTF-8?q?ces=20verification=20processes=20for=20CtrlOp=20and=20InvOp=20b?= =?UTF-8?q?y=20refreshing=20block=20argument=20types.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../QCO/Transforms/Mapping/Mapping.cpp | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index c7d0322d09..90a64f08b7 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -58,6 +58,60 @@ namespace mlir::qco { namespace { +/** + * @brief Align declared qubit result types with operand types after placement. + * + * @details Replacing `qco.alloc` with `qco.static` reroutes SSA uses, so + * operand types become `!qco.qubit` while many gates still declare + * `!qco.qubit` results from before placement. Ops with `TypesMatchWith` / + * same-type traits then fail verification until results are refreshed. + */ +void synchronizeMappedQubitTypes(Region& region) { + region.walk([](Operation* op) { + // Region entry arguments are fixed at build time; operand Value types + // update after alloc→static placement but block argument types do not. + if (auto ctrl = dyn_cast(op)) { + Block& body = *ctrl.getBody(); + for (unsigned i = 0; i < ctrl.getNumTargets(); ++i) { + const Type expected = ctrl.getTargetsIn()[i].getType(); + if (body.getArgument(i).getType() != expected) { + body.getArgument(i).setType(expected); + } + } + } else if (auto inv = dyn_cast(op)) { + Block& body = *inv.getBody(); + for (unsigned i = 0; i < inv.getNumTargets(); ++i) { + const Type expected = inv.getQubitsIn()[i].getType(); + if (body.getArgument(i).getType() != expected) { + body.getArgument(i).setType(expected); + } + } + } + + const unsigned numOp = op->getNumOperands(); + const unsigned numRes = op->getNumResults(); + if (numOp == numRes) { + for (unsigned i = 0; i < numOp; ++i) { + const auto qIn = dyn_cast(op->getOperand(i).getType()); + const auto qOut = dyn_cast(op->getResult(i).getType()); + if (qIn && qOut && qIn != qOut) { + op->getResult(i).setType(qIn); + } + } + return WalkResult::advance(); + } + // e.g. qco.measure: qubit in, (qubit_out, i1) + if (numOp >= 1 && numRes >= 1) { + const auto qIn = dyn_cast(op->getOperand(0).getType()); + const auto qOut = dyn_cast(op->getResult(0).getType()); + if (qIn && qOut && qIn != qOut) { + op->getResult(0).setType(qIn); + } + } + return WalkResult::advance(); + }); +} + struct MappingPass : impl::MappingPassBase { private: using QubitValue = TypedValue; @@ -379,6 +433,7 @@ struct MappingPass : impl::MappingPassBase { } commitTrial(*best, dyn, func.getFunctionBody(), rewriter); + synchronizeMappedQubitTypes(func.getFunctionBody()); } } From 0ed81fa4531cbf442c2efc795a0d34308f73356c Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Fri, 20 Mar 2026 14:50:48 +0100 Subject: [PATCH 21/57] =?UTF-8?q?=F0=9F=94=A7=20Update=20conversion=20patt?= =?UTF-8?q?erns=20in=20QCO,=20QC,=20and=20QIR=20dialects=20to=20ensure=20c?= =?UTF-8?q?onsistent=20type=20casting=20for=20StaticOp=20indices.=20This?= =?UTF-8?q?=20change=20enhances=20type=20safety=20by=20explicitly=20castin?= =?UTF-8?q?g=20indices=20to=20int64=5Ft=20during=20operation=20creation=20?= =?UTF-8?q?and=20replacement.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/lib/Conversion/QCOToQC/QCOToQC.cpp | 4 ++-- mlir/lib/Conversion/QCToQCO/QCToQCO.cpp | 3 ++- mlir/lib/Conversion/QCToQIR/QCToQIR.cpp | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp index 5fe548a446..cca115156e 100644 --- a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -26,7 +26,6 @@ #include #include -#include #include namespace mlir { @@ -156,7 +155,8 @@ struct ConvertQCOStaticOp final : OpConversionPattern { matchAndRewrite(qco::StaticOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { // Create qc.static with the same index - rewriter.replaceOpWithNewOp(op, op.getIndex()); + rewriter.replaceOpWithNewOp( + op, static_cast(op.getIndex())); return success(); } }; diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index c95d0948f8..f3f6642f48 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -299,7 +299,8 @@ struct ConvertQCStaticOp final : StatefulOpConversionPattern { auto qcQubit = op.getQubit(); // Create new qco.static operation with the same index - auto qcoOp = qco::StaticOp::create(rewriter, op.getLoc(), op.getIndex()); + auto qcoOp = qco::StaticOp::create(rewriter, op.getLoc(), + static_cast(op.getIndex())); // Collect QCO qubit SSA value auto qcoQubit = qcoOp.getQubit(); diff --git a/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp b/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp index 645eac9909..cd1d2721b7 100644 --- a/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp +++ b/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp @@ -341,7 +341,7 @@ struct ConvertQCStaticQIR final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(StaticOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - const auto index = op.getIndex(); + const auto index = static_cast(op.getIndex()); auto& state = getState(); // Get or create a pointer to the qubit Value val{}; From c9d1b8ff8feeefb11638620bfb4046f7ff853bb2 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Fri, 20 Mar 2026 15:13:05 +0100 Subject: [PATCH 22/57] =?UTF-8?q?=F0=9F=94=A7=20Add=20missing=20includes?= =?UTF-8?q?=20in=20QCOToQC=20and=20Mapping.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/lib/Conversion/QCOToQC/QCOToQC.cpp | 1 + mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp index cca115156e..32ff6ec01b 100644 --- a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -26,6 +26,7 @@ #include #include +#include #include namespace mlir { diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 90a64f08b7..0fcf4c6b8d 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -56,8 +57,6 @@ namespace mlir::qco { #define GEN_PASS_DEF_MAPPINGPASS #include "mlir/Dialect/QCO/Transforms/Passes.h.inc" -namespace { - /** * @brief Align declared qubit result types with operand types after placement. * @@ -66,7 +65,7 @@ namespace { * `!qco.qubit` results from before placement. Ops with `TypesMatchWith` / * same-type traits then fail verification until results are refreshed. */ -void synchronizeMappedQubitTypes(Region& region) { +static void synchronizeMappedQubitTypes(Region& region) { region.walk([](Operation* op) { // Region entry arguments are fixed at build time; operand Value types // update after alloc→static placement but block argument types do not. @@ -112,6 +111,8 @@ void synchronizeMappedQubitTypes(Region& region) { }); } +namespace { + struct MappingPass : impl::MappingPassBase { private: using QubitValue = TypedValue; From acdc0cbafaece5e492e5d438eb742cf7f26af4b0 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Fri, 20 Mar 2026 15:46:59 +0100 Subject: [PATCH 23/57] =?UTF-8?q?=F0=9F=94=A7=20Refactor=20qubit=20mapping?= =?UTF-8?q?=20in=20QCToQCO=20conversion=20patterns=20to=20use=20per-region?= =?UTF-8?q?=20maps,=20improving=20state=20management=20during=20operation?= =?UTF-8?q?=20conversions.=20.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/lib/Conversion/QCToQCO/QCToQCO.cpp | 60 ++++++++++++++++--------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index f3f6642f48..511b7adb6d 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -73,8 +74,13 @@ namespace { * - %q2 after the X gate */ struct LoweringState { - /// Map from original QC qubit references to their latest QCO SSA values - llvm::DenseMap qubitMap; + /// Per-region map from QC qubit references to latest QCO SSA values. + /// + /// @details Keys are `Operation::getParentRegion()` for ops being converted + /// (typically a `func.func` body, or a `qc.ctrl` / `qc.inv` region). This + /// avoids clearing state at the first `func.return` while later functions + /// still convert. + llvm::DenseMap> qubitMap; /// Modifier information int64_t inNestedRegion = 0; @@ -129,6 +135,8 @@ struct ConvertFuncReturnOp final : StatefulOpConversionPattern { matchAndRewrite(func::ReturnOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { auto& state = getState(); + Region* funcRegion = op->getParentRegion(); + auto& map = state.qubitMap[funcRegion]; // Build return values from qubitMap (adaptor.getOperands() may carry stale // root values because gate patterns use eraseOp instead of replaceOp). @@ -137,8 +145,8 @@ struct ConvertFuncReturnOp final : StatefulOpConversionPattern { returnValues.reserve(op.getNumOperands()); for (auto [qcOperand, adaptorOperand] : llvm::zip(op.getOperands(), adaptor.getOperands())) { - if (state.qubitMap.contains(qcOperand)) { - auto latest = state.qubitMap[qcOperand]; + if (map.contains(qcOperand)) { + auto latest = map[qcOperand]; returnValues.emplace_back(latest); escapedQubits.insert(latest); } else { @@ -148,7 +156,7 @@ struct ConvertFuncReturnOp final : StatefulOpConversionPattern { // Collect non-escaped live qubits for deallocation. llvm::DenseSet liveQubits; - for (Value qcoQubit : llvm::make_second_range(state.qubitMap)) { + for (Value qcoQubit : llvm::make_second_range(map)) { if (!escapedQubits.contains(qcoQubit)) { liveQubits.insert(qcoQubit); } @@ -163,7 +171,7 @@ struct ConvertFuncReturnOp final : StatefulOpConversionPattern { rewriter.create(op.getLoc(), qubit); } - state.qubitMap.clear(); + state.qubitMap.erase(funcRegion); rewriter.replaceOpWithNewOp(op, returnValues); return success(); @@ -218,7 +226,7 @@ struct ConvertQCAllocOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(qc::AllocOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& qubitMap = getState().qubitMap; + auto& qubitMap = getState().qubitMap[op->getParentRegion()]; auto qcQubit = op.getResult(); // Create the qco.alloc operation with preserved register metadata @@ -257,7 +265,7 @@ struct ConvertQCDeallocOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(qc::DeallocOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& qubitMap = getState().qubitMap; + auto& qubitMap = getState().qubitMap[op->getParentRegion()]; auto qcQubit = op.getQubit(); // Look up the latest QCO value for this QC qubit @@ -295,7 +303,7 @@ struct ConvertQCStaticOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(qc::StaticOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& qubitMap = getState().qubitMap; + auto& qubitMap = getState().qubitMap[op->getParentRegion()]; auto qcQubit = op.getQubit(); // Create new qco.static operation with the same index @@ -344,7 +352,7 @@ struct ConvertQCMeasureOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(qc::MeasureOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& qubitMap = getState().qubitMap; + auto& qubitMap = getState().qubitMap[op->getParentRegion()]; auto qcQubit = op.getQubit(); // Get the latest QCO qubit value from the state map @@ -395,7 +403,7 @@ struct ConvertQCResetOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(qc::ResetOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& qubitMap = getState().qubitMap; + auto& qubitMap = getState().qubitMap[op->getParentRegion()]; auto qcQubit = op.getQubit(); // Get the latest QCO qubit value from the state map @@ -480,7 +488,7 @@ struct ConvertQCOneTargetZeroParameterToQCO final matchAndRewrite(QCOpType op, QCOpType::Adaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = this->getState(); - auto& qubitMap = state.qubitMap; + auto& qubitMap = state.qubitMap[op->getParentRegion()]; const auto inNestedRegion = state.inNestedRegion; // Get the latest QCO qubit @@ -537,7 +545,7 @@ struct ConvertQCOneTargetOneParameterToQCO final matchAndRewrite(QCOpType op, QCOpType::Adaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = this->getState(); - auto& qubitMap = state.qubitMap; + auto& qubitMap = state.qubitMap[op->getParentRegion()]; const auto inNestedRegion = state.inNestedRegion; // Get the latest QCO qubit @@ -595,7 +603,7 @@ struct ConvertQCOneTargetTwoParameterToQCO final matchAndRewrite(QCOpType op, QCOpType::Adaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = this->getState(); - auto& qubitMap = state.qubitMap; + auto& qubitMap = state.qubitMap[op->getParentRegion()]; const auto inNestedRegion = state.inNestedRegion; // Get the latest QCO qubit @@ -653,7 +661,7 @@ struct ConvertQCOneTargetThreeParameterToQCO final matchAndRewrite(QCOpType op, QCOpType::Adaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = this->getState(); - auto& qubitMap = state.qubitMap; + auto& qubitMap = state.qubitMap[op->getParentRegion()]; const auto inNestedRegion = state.inNestedRegion; // Get the latest QCO qubit @@ -713,7 +721,7 @@ struct ConvertQCTwoTargetZeroParameterToQCO final matchAndRewrite(QCOpType op, QCOpType::Adaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = this->getState(); - auto& qubitMap = state.qubitMap; + auto& qubitMap = state.qubitMap[op->getParentRegion()]; const auto inNestedRegion = state.inNestedRegion; // Get the latest QCO qubits @@ -779,7 +787,7 @@ struct ConvertQCTwoTargetOneParameterToQCO final matchAndRewrite(QCOpType op, QCOpType::Adaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = this->getState(); - auto& qubitMap = state.qubitMap; + auto& qubitMap = state.qubitMap[op->getParentRegion()]; const auto inNestedRegion = state.inNestedRegion; // Get the latest QCO qubits @@ -846,7 +854,7 @@ struct ConvertQCTwoTargetTwoParameterToQCO final matchAndRewrite(QCOpType op, QCOpType::Adaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = this->getState(); - auto& qubitMap = state.qubitMap; + auto& qubitMap = state.qubitMap[op->getParentRegion()]; const auto inNestedRegion = state.inNestedRegion; // Get the latest QCO qubits @@ -908,7 +916,7 @@ struct ConvertQCBarrierOp final : StatefulOpConversionPattern { matchAndRewrite(qc::BarrierOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = getState(); - auto& qubitMap = state.qubitMap; + auto& qubitMap = state.qubitMap[op->getParentRegion()]; // Get QCO qubits from state map auto qcQubits = op.getQubits(); @@ -957,7 +965,7 @@ struct ConvertQCCtrlOp final : StatefulOpConversionPattern { matchAndRewrite(qc::CtrlOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = getState(); - auto& qubitMap = state.qubitMap; + auto& qubitMap = state.qubitMap[op->getParentRegion()]; // Get QCO controls from state map auto qcControls = op.getControls(); @@ -1047,7 +1055,11 @@ struct ConvertQCInvOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(qc::InvOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& [qubitMap, inNestedRegion, targetsIn, targetsOut] = getState(); + auto& state = getState(); + auto& qubitMap = state.qubitMap[op->getParentRegion()]; + auto& inNestedRegion = state.inNestedRegion; + auto& targetsIn = state.targetsIn; + auto& targetsOut = state.targetsOut; // Get QCO targets from state map const auto numTargets = op.getNumTargets(); @@ -1225,7 +1237,11 @@ struct QCToQCO final : impl::QCToQCOBase { // state has no remaining qubits. patterns.add(typeConverter, context, &state); target.addDynamicallyLegalOp([&](const func::ReturnOp op) { - return typeConverter.isLegal(op) && state.qubitMap.empty(); + if (!typeConverter.isLegal(op)) { + return false; + } + const auto it = state.qubitMap.find(op->getParentRegion()); + return it == state.qubitMap.end() || it->second.empty(); }); // Conversion of qc types in func.call From c3e1b2a54c333ccc28b0ca4aa36a552bc0a94bb2 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 20 Mar 2026 17:04:42 +0100 Subject: [PATCH 24/57] =?UTF-8?q?=F0=9F=90=A7=20Fix=20compile=20error=20on?= =?UTF-8?q?=20Linux=20due=20to=20missing=20link=20library?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/lib/Dialect/QTensor/IR/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/mlir/lib/Dialect/QTensor/IR/CMakeLists.txt b/mlir/lib/Dialect/QTensor/IR/CMakeLists.txt index 98aa36b0c5..3be9dcf3f1 100644 --- a/mlir/lib/Dialect/QTensor/IR/CMakeLists.txt +++ b/mlir/lib/Dialect/QTensor/IR/CMakeLists.txt @@ -20,6 +20,7 @@ add_mlir_dialect_library( PRIVATE MLIRIR MLIRArithDialect + MLIRDialectUtils MLIRInferTypeOpInterface MLIRSideEffectInterfaces PUBLIC From 3dcb30aec42a34ba8e5d151fe3d540dbcb17ee9a Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 20 Mar 2026 17:05:40 +0100 Subject: [PATCH 25/57] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Rename=20SSABeforeFo?= =?UTF-8?q?rDeallocOrder=20to=20SSAOrder=20and=20tweak=20docstring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/include/mlir/Dialect/QCO/Utils/ValueOrdering.h | 9 ++++----- mlir/lib/Conversion/QCToQCO/QCToQCO.cpp | 2 +- mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp | 4 ++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Utils/ValueOrdering.h b/mlir/include/mlir/Dialect/QCO/Utils/ValueOrdering.h index c094874f3d..e111820e06 100644 --- a/mlir/include/mlir/Dialect/QCO/Utils/ValueOrdering.h +++ b/mlir/include/mlir/Dialect/QCO/Utils/ValueOrdering.h @@ -16,13 +16,12 @@ namespace mlir::qco { /** - * @brief Strict weak ordering for sorting SSA values before inserting deallocs. + * @brief Deterministic order for SSA values. * - * @details Matches QCOProgramBuilder::finalize(): use block order when both - * defining ops are in the same block; otherwise fall back to opaque pointer - * order for a deterministic total order. + * @details Uses block order when both defining ops are in the same block; + * otherwise fall back to opaque pointer order for a deterministic total order. */ -struct SSABeforeForDeallocOrder { +struct SSAOrder { bool operator()(Value a, Value b) const { auto* opA = a.getDefiningOp(); auto* opB = b.getDefiningOp(); diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index 511b7adb6d..89b70bbf2a 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -165,7 +165,7 @@ struct ConvertFuncReturnOp final : StatefulOpConversionPattern { // random-access. llvm::SmallVector liveQubitsSorted(liveQubits.begin(), liveQubits.end()); - llvm::sort(liveQubitsSorted, SSABeforeForDeallocOrder{}); + llvm::sort(liveQubitsSorted, SSAOrder{}); for (Value qubit : liveQubitsSorted) { rewriter.create(op.getLoc(), qubit); diff --git a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp index 1e4a4cc628..a547b9f003 100644 --- a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp +++ b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp @@ -925,7 +925,7 @@ OwningOpRef QCOProgramBuilder::finalize() { // Automatically deallocate all still-allocated qubits // Sort qubits for deterministic output llvm::SmallVector sortedQubits(validQubits.begin(), validQubits.end()); - llvm::sort(sortedQubits, SSABeforeForDeallocOrder{}); + llvm::sort(sortedQubits, SSAOrder{}); for (auto qubit : sortedQubits) { DeallocOp::create(*this, qubit); @@ -935,7 +935,7 @@ OwningOpRef QCOProgramBuilder::finalize() { // Sort tensors for deterministic output llvm::SmallVector sortedTensors(validTensors.begin(), validTensors.end()); - llvm::sort(sortedTensors, SSABeforeForDeallocOrder{}); + llvm::sort(sortedTensors, SSAOrder{}); for (auto tensor : sortedTensors) { qtensor::DeallocOp::create(*this, tensor); From 417857a98b882c2d02aa69e5a9ab3c44b07b6ff3 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 20 Mar 2026 17:09:24 +0100 Subject: [PATCH 26/57] =?UTF-8?q?=F0=9F=8E=A8=20Replace=20two-qubit=20gate?= =?UTF-8?q?=20programs=20with=20gate=20that=20is=20less=20likely=20to=20be?= =?UTF-8?q?=20optimized=20away?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/unittests/programs/qc_programs.cpp | 4 ++-- mlir/unittests/programs/qco_programs.cpp | 4 ++-- mlir/unittests/programs/qir_programs.cpp | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mlir/unittests/programs/qc_programs.cpp b/mlir/unittests/programs/qc_programs.cpp index 12da2d382e..c8c5d7d9c4 100644 --- a/mlir/unittests/programs/qc_programs.cpp +++ b/mlir/unittests/programs/qc_programs.cpp @@ -51,7 +51,7 @@ void staticQubitsWithParametricOps(QCProgramBuilder& b) { void staticQubitsWithTwoTargetOps(QCProgramBuilder& b) { auto q0 = b.staticQubit(0); auto q1 = b.staticQubit(1); - b.swap(q0, q1); + b.rzz(0.123, q0, q1); } void staticQubitsWithCtrl(QCProgramBuilder& b) { @@ -68,7 +68,7 @@ void staticQubitsWithInv(QCProgramBuilder& b) { void mixedStaticDynamicQubits(QCProgramBuilder& b) { auto q0 = b.staticQubit(0); auto q1 = b.allocQubit(); - b.swap(q0, q1); + b.rzz(0.123, q0, q1); b.h(q0); b.h(q1); } diff --git a/mlir/unittests/programs/qco_programs.cpp b/mlir/unittests/programs/qco_programs.cpp index 0c15af90b5..5c2995f34a 100644 --- a/mlir/unittests/programs/qco_programs.cpp +++ b/mlir/unittests/programs/qco_programs.cpp @@ -55,7 +55,7 @@ void staticQubitsWithParametricOps(QCOProgramBuilder& b) { void staticQubitsWithTwoTargetOps(QCOProgramBuilder& b) { auto q0 = b.staticQubit(0); auto q1 = b.staticQubit(1); - std::tie(q0, q1) = b.swap(q0, q1); + std::tie(q0, q1) = b.rzz(0.123, q0, q1); } void staticQubitsWithCtrl(QCOProgramBuilder& b) { @@ -74,7 +74,7 @@ void staticQubitsWithInv(QCOProgramBuilder& b) { void mixedStaticDynamicQubits(QCOProgramBuilder& b) { auto q0 = b.staticQubit(0); auto q1 = b.allocQubit(); - std::tie(q0, q1) = b.swap(q0, q1); + std::tie(q0, q1) = b.rzz(0.123, q0, q1); q0 = b.h(q0); q1 = b.h(q1); } diff --git a/mlir/unittests/programs/qir_programs.cpp b/mlir/unittests/programs/qir_programs.cpp index 6126693337..fa1102d04d 100644 --- a/mlir/unittests/programs/qir_programs.cpp +++ b/mlir/unittests/programs/qir_programs.cpp @@ -51,7 +51,7 @@ void staticQubitsWithParametricOps(QIRProgramBuilder& b) { void staticQubitsWithTwoTargetOps(QIRProgramBuilder& b) { auto q0 = b.staticQubit(0); auto q1 = b.staticQubit(1); - b.swap(q0, q1); + b.rzz(0.123, q0, q1); } void staticQubitsWithCtrl(QIRProgramBuilder& b) { @@ -69,7 +69,7 @@ void mixedStaticDynamicQubits(QIRProgramBuilder& b) { auto q0 = b.staticQubit(0); auto qDyn = b.allocQubitRegister(1); auto q1 = qDyn[0]; - b.swap(q0, q1); + b.rzz(0.123, q0, q1); b.h(q0); b.h(q1); } From 4c8c0f99f674af5cafd965c009070d7ae1932269 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 20 Mar 2026 17:28:30 +0100 Subject: [PATCH 27/57] =?UTF-8?q?=F0=9F=9A=A8=20Fix=20use=20of=20outdated?= =?UTF-8?q?=20rewrite=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/lib/Conversion/QCToQCO/QCToQCO.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index 89b70bbf2a..2fb6a1070a 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -168,7 +168,7 @@ struct ConvertFuncReturnOp final : StatefulOpConversionPattern { llvm::sort(liveQubitsSorted, SSAOrder{}); for (Value qubit : liveQubitsSorted) { - rewriter.create(op.getLoc(), qubit); + qco::DeallocOp::create(rewriter, op.getLoc(), qubit); } state.qubitMap.erase(funcRegion); From d6ad7c4841901abe0d23daf85906a209ad89c2f8 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 20 Mar 2026 17:56:32 +0100 Subject: [PATCH 28/57] =?UTF-8?q?=F0=9F=8E=A8=20Refine=20static=20qubit=20?= =?UTF-8?q?handling=20to=20require=20less=20casting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h | 4 ++-- mlir/include/mlir/Dialect/QC/IR/QCOps.td | 5 ++--- .../include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h | 4 ++-- mlir/include/mlir/Dialect/QCO/IR/QCOOps.td | 5 ++--- mlir/lib/Conversion/QCToQCO/QCToQCO.cpp | 3 +-- mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp | 6 +----- mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp | 6 +----- mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp | 9 +++------ 8 files changed, 14 insertions(+), 28 deletions(-) diff --git a/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h b/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h index fdf5ab7310..f602903950 100644 --- a/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h @@ -110,7 +110,7 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { /** * @brief Get a static qubit by index - * @param index The qubit index (must be non-negative) + * @param index The qubit index * @return A qubit reference * * @par Example: @@ -121,7 +121,7 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { * %q0 = qc.static 0 : !qc.qubit * ``` */ - Value staticQubit(int64_t index); + Value staticQubit(uint64_t index); /** * @brief Allocate a qubit register diff --git a/mlir/include/mlir/Dialect/QC/IR/QCOps.td b/mlir/include/mlir/Dialect/QC/IR/QCOps.td index 4744f27ac6..0d0dd57280 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCOps.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCOps.td @@ -127,9 +127,8 @@ def StaticOp : QCOp<"static", [Pure]> { let results = (outs StaticQubit:$qubit); let assemblyFormat = "$index attr-dict `:` type($qubit)"; - let builders = [OpBuilder<(ins "int64_t":$index), [{ - build($_builder, $_state, QubitType::get($_builder.getContext(), /*isStatic=*/true), - $_builder.getI64IntegerAttr(index)); + let builders = [OpBuilder<(ins "uint64_t":$index), [{ + build($_builder, $_state, QubitType::get($_builder.getContext(), /*isStatic=*/true), index); }]>]; } diff --git a/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h b/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h index a09c49ea7a..f86737ba5a 100644 --- a/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h @@ -118,7 +118,7 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { /** * @brief Get a static qubit by index - * @param index The qubit index (must be non-negative) + * @param index The qubit index * @return A tracked, valid qubit SSA value * * @par Example: @@ -129,7 +129,7 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { * %q0 = qco.static 0 : !qco.qubit * ``` */ - Value staticQubit(int64_t index); + Value staticQubit(uint64_t index); /** * @brief Allocate a qubit register diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td index a53fcfa83e..dcdeb1bcbb 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td @@ -131,9 +131,8 @@ def StaticOp : QCOOp<"static", [Pure]> { let results = (outs StaticQubit:$qubit); let assemblyFormat = "$index attr-dict `:` type($qubit)"; - let builders = [OpBuilder<(ins "int64_t":$index), [{ - build($_builder, $_state, QubitType::get($_builder.getContext(), /*isStatic=*/true), - $_builder.getI64IntegerAttr(index)); + let builders = [OpBuilder<(ins "uint64_t":$index), [{ + build($_builder, $_state, QubitType::get($_builder.getContext(), /*isStatic=*/true), index); }]>]; } diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index 2fb6a1070a..20aea7b5d3 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -307,8 +307,7 @@ struct ConvertQCStaticOp final : StatefulOpConversionPattern { auto qcQubit = op.getQubit(); // Create new qco.static operation with the same index - auto qcoOp = qco::StaticOp::create(rewriter, op.getLoc(), - static_cast(op.getIndex())); + auto qcoOp = qco::StaticOp::create(rewriter, op.getLoc(), op.getIndex()); // Collect QCO qubit SSA value auto qcoQubit = qcoOp.getQubit(); diff --git a/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp b/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp index 80b7d0c7b3..991dba1c1b 100644 --- a/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp +++ b/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp @@ -79,13 +79,9 @@ Value QCProgramBuilder::allocQubit() { return qubit; } -Value QCProgramBuilder::staticQubit(const int64_t index) { +Value QCProgramBuilder::staticQubit(const uint64_t index) { checkFinalized(); - if (index < 0) { - llvm::reportFatalUsageError("Index must be non-negative"); - } - auto staticOp = StaticOp::create(*this, index); return staticOp.getQubit(); } diff --git a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp index a547b9f003..327266b804 100644 --- a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp +++ b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp @@ -84,13 +84,9 @@ Value QCOProgramBuilder::allocQubit() { return qubit; } -Value QCOProgramBuilder::staticQubit(const int64_t index) { +Value QCOProgramBuilder::staticQubit(const uint64_t index) { checkFinalized(); - if (index < 0) { - llvm::reportFatalUsageError("Index must be non-negative"); - } - auto staticOp = StaticOp::create(*this, index); const auto qubit = staticOp.getQubit(); diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 0fcf4c6b8d..a06d58475b 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -118,7 +118,6 @@ struct MappingPass : impl::MappingPassBase { using QubitValue = TypedValue; using IndexType = std::size_t; using IndexGate = std::pair; - using IndexGateSet = DenseSet; using Layer = DenseSet; /** @@ -545,8 +544,7 @@ struct MappingPass : impl::MappingPassBase { for (const auto [p, q] : enumerate(dynQubits)) { const auto hw = layout.getHardwareIndex(p); rewriter.setInsertionPoint(q.getDefiningOp()); - auto op = rewriter.replaceOpWithNewOp(q.getDefiningOp(), - static_cast(hw)); + auto op = rewriter.replaceOpWithNewOp(q.getDefiningOp(), hw); statics[hw] = llvm::cast>(op.getQubit()); } @@ -554,10 +552,9 @@ struct MappingPass : impl::MappingPassBase { for (std::size_t p = dynQubits.size(); p < layout.nqubits(); ++p) { rewriter.setInsertionPointToStart(&funcBody.front()); const auto hw = layout.getHardwareIndex(p); - auto op = StaticOp::create(rewriter, rewriter.getUnknownLoc(), - static_cast(hw)); + auto op = StaticOp::create(rewriter, funcBody.getLoc(), hw); rewriter.setInsertionPoint(funcBody.back().getTerminator()); - DeallocOp::create(rewriter, rewriter.getUnknownLoc(), op.getQubit()); + DeallocOp::create(rewriter, funcBody.getLoc(), op.getQubit()); statics[hw] = llvm::cast>(op.getQubit()); } From 9c807b788f6051e68609571965664810cefcfecc Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 20 Mar 2026 18:22:37 +0100 Subject: [PATCH 29/57] =?UTF-8?q?=F0=9F=A9=B9=20Mark=20result=20of=20`qc.a?= =?UTF-8?q?lloc`=20as=20dynamic=20qubit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/include/mlir/Dialect/QC/IR/QCOps.td | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/include/mlir/Dialect/QC/IR/QCOps.td b/mlir/include/mlir/Dialect/QC/IR/QCOps.td index 0d0dd57280..1272bc0f24 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCOps.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCOps.td @@ -72,7 +72,7 @@ def AllocOp : QCOp<"alloc", [MemoryEffects<[MemAlloc]>]> { let arguments = (ins OptionalAttr:$register_name, OptionalAttr>:$register_size, OptionalAttr>:$register_index); - let results = (outs QubitType:$result); + let results = (outs DynamicQubit:$result); let assemblyFormat = [{ (`(` $register_name^ `,` $register_size `,` $register_index `)`)? attr-dict `:` type($result) From ef2bdaa73586f5a295c1f517cd2262d99bf5f240 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 20 Mar 2026 22:37:41 +0100 Subject: [PATCH 30/57] =?UTF-8?q?=F0=9F=8E=A8=20one=20cast=20less?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/lib/Conversion/QCOToQC/QCOToQC.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp index 32ff6ec01b..5fe548a446 100644 --- a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -156,8 +156,7 @@ struct ConvertQCOStaticOp final : OpConversionPattern { matchAndRewrite(qco::StaticOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { // Create qc.static with the same index - rewriter.replaceOpWithNewOp( - op, static_cast(op.getIndex())); + rewriter.replaceOpWithNewOp(op, op.getIndex()); return success(); } }; From bf3b82eba36a90f92234add38be43238c06b5d19 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 20 Mar 2026 23:06:01 +0100 Subject: [PATCH 31/57] =?UTF-8?q?=F0=9F=8E=A8=20Incremental=20code=20quali?= =?UTF-8?q?ty=20improvements=20to=20the=20QCToQCO=20conversion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/lib/Conversion/QCToQCO/QCToQCO.cpp | 120 ++++++++++-------------- 1 file changed, 51 insertions(+), 69 deletions(-) diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index 20aea7b5d3..1e70f85053 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -118,6 +118,23 @@ class StatefulOpConversionPattern : public OpConversionPattern { LoweringState* state_; }; +/** + * @brief Helper function to look up the latest QCO qubit value for a given QC + * qubit reference + * + * @param qubitMap The mapping from QC qubits to QCO qubits for the current + * region + * @param qcQubit The QC qubit reference to look up + * @return The latest QCO qubit value corresponding to the given QC qubit + * reference + */ +[[nodiscard]] Value lookupMappedQubit(llvm::DenseMap& qubitMap, + Value qcQubit) { + auto it = qubitMap.find(qcQubit); + assert(it != qubitMap.end() && "QC qubit not found"); + return it->second; +} + /** * @brief Converts func.return and sinks remaining live qubits. * @@ -144,7 +161,7 @@ struct ConvertFuncReturnOp final : StatefulOpConversionPattern { llvm::DenseSet escapedQubits; returnValues.reserve(op.getNumOperands()); for (auto [qcOperand, adaptorOperand] : - llvm::zip(op.getOperands(), adaptor.getOperands())) { + llvm::zip_equal(op.getOperands(), adaptor.getOperands())) { if (map.contains(qcOperand)) { auto latest = map[qcOperand]; returnValues.emplace_back(latest); @@ -266,11 +283,8 @@ struct ConvertQCDeallocOp final : StatefulOpConversionPattern { matchAndRewrite(qc::DeallocOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& qubitMap = getState().qubitMap[op->getParentRegion()]; - auto qcQubit = op.getQubit(); - - // Look up the latest QCO value for this QC qubit - assert(qubitMap.contains(qcQubit) && "QC qubit not found"); - auto qcoQubit = qubitMap[qcQubit]; + Value qcQubit = op.getQubit(); + Value qcoQubit = lookupMappedQubit(qubitMap, qcQubit); // Create the dealloc operation rewriter.replaceOpWithNewOp(op, qcoQubit); @@ -304,19 +318,10 @@ struct ConvertQCStaticOp final : StatefulOpConversionPattern { matchAndRewrite(qc::StaticOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& qubitMap = getState().qubitMap[op->getParentRegion()]; - auto qcQubit = op.getQubit(); - - // Create new qco.static operation with the same index - auto qcoOp = qco::StaticOp::create(rewriter, op.getLoc(), op.getIndex()); + Value qcQubit = op.getQubit(); - // Collect QCO qubit SSA value - auto qcoQubit = qcoOp.getQubit(); - - // Establish mapping from QC reference to QCO value - qubitMap[qcQubit] = qcoQubit; - - // Replace the old operation result with the new result - rewriter.replaceOp(op, qcoQubit); + auto qcoOp = rewriter.replaceOpWithNewOp(op, op.getIndex()); + qubitMap[qcQubit] = qcoOp.getQubit(); return success(); } @@ -352,25 +357,19 @@ struct ConvertQCMeasureOp final : StatefulOpConversionPattern { matchAndRewrite(qc::MeasureOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& qubitMap = getState().qubitMap[op->getParentRegion()]; - auto qcQubit = op.getQubit(); - - // Get the latest QCO qubit value from the state map - assert(qubitMap.contains(qcQubit) && "QC qubit not found"); - auto qcoQubit = qubitMap[qcQubit]; + Value qcQubit = op.getQubit(); + Value qcoQubit = lookupMappedQubit(qubitMap, qcQubit); // Create qco.measure (returns both output qubit and bit result) auto qcoOp = qco::MeasureOp::create( rewriter, op.getLoc(), qcoQubit, op.getRegisterNameAttr(), op.getRegisterSizeAttr(), op.getRegisterIndexAttr()); - auto outQcoQubit = qcoOp.getQubitOut(); - auto newBit = qcoOp.getResult(); - // Update mapping: the QC qubit now corresponds to the output qubit - qubitMap[qcQubit] = outQcoQubit; + qubitMap[qcQubit] = qcoOp.getQubitOut(); // Replace the QC operation's bit result with the QCO bit result - rewriter.replaceOp(op, newBit); + rewriter.replaceOp(op, qcoOp.getResult()); return success(); } @@ -403,11 +402,8 @@ struct ConvertQCResetOp final : StatefulOpConversionPattern { matchAndRewrite(qc::ResetOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& qubitMap = getState().qubitMap[op->getParentRegion()]; - auto qcQubit = op.getQubit(); - - // Get the latest QCO qubit value from the state map - assert(qubitMap.contains(qcQubit) && "QC qubit not found"); - auto qcoQubit = qubitMap[qcQubit]; + Value qcQubit = op.getQubit(); + Value qcoQubit = lookupMappedQubit(qubitMap, qcQubit); // Create qco.reset (consumes input, produces output) auto qcoOp = qco::ResetOp::create(rewriter, op.getLoc(), qcoQubit); @@ -494,8 +490,7 @@ struct ConvertQCOneTargetZeroParameterToQCO final auto qcQubit = op.getQubitIn(); Value qcoQubit; if (inNestedRegion == 0) { - assert(qubitMap.contains(qcQubit) && "QC qubit not found"); - qcoQubit = qubitMap[qcQubit]; + qcoQubit = lookupMappedQubit(qubitMap, qcQubit); } else { assert(state.targetsIn[inNestedRegion].size() == 1 && "Invalid number of input targets"); @@ -551,8 +546,7 @@ struct ConvertQCOneTargetOneParameterToQCO final auto qcQubit = op.getQubitIn(); Value qcoQubit; if (inNestedRegion == 0) { - assert(qubitMap.contains(qcQubit) && "QC qubit not found"); - qcoQubit = qubitMap[qcQubit]; + qcoQubit = lookupMappedQubit(qubitMap, qcQubit); } else { assert(state.targetsIn[inNestedRegion].size() == 1 && "Invalid number of input targets"); @@ -609,8 +603,7 @@ struct ConvertQCOneTargetTwoParameterToQCO final auto qcQubit = op.getQubitIn(); Value qcoQubit; if (inNestedRegion == 0) { - assert(qubitMap.contains(qcQubit) && "QC qubit not found"); - qcoQubit = qubitMap[qcQubit]; + qcoQubit = lookupMappedQubit(qubitMap, qcQubit); } else { assert(state.targetsIn[inNestedRegion].size() == 1 && "Invalid number of input targets"); @@ -667,8 +660,7 @@ struct ConvertQCOneTargetThreeParameterToQCO final auto qcQubit = op.getQubitIn(); Value qcoQubit; if (inNestedRegion == 0) { - assert(qubitMap.contains(qcQubit) && "QC qubit not found"); - qcoQubit = qubitMap[qcQubit]; + qcoQubit = lookupMappedQubit(qubitMap, qcQubit); } else { assert(state.targetsIn[inNestedRegion].size() == 1 && "Invalid number of input targets"); @@ -729,10 +721,8 @@ struct ConvertQCTwoTargetZeroParameterToQCO final Value qcoQubit0; Value qcoQubit1; if (inNestedRegion == 0) { - assert(qubitMap.contains(qcQubit0) && "QC qubit not found"); - assert(qubitMap.contains(qcQubit1) && "QC qubit not found"); - qcoQubit0 = qubitMap[qcQubit0]; - qcoQubit1 = qubitMap[qcQubit1]; + qcoQubit0 = lookupMappedQubit(qubitMap, qcQubit0); + qcoQubit1 = lookupMappedQubit(qubitMap, qcQubit1); } else { assert(state.targetsIn[inNestedRegion].size() == 2 && "Invalid number of input targets"); @@ -795,10 +785,8 @@ struct ConvertQCTwoTargetOneParameterToQCO final Value qcoQubit0; Value qcoQubit1; if (inNestedRegion == 0) { - assert(qubitMap.contains(qcQubit0) && "QC qubit not found"); - assert(qubitMap.contains(qcQubit1) && "QC qubit not found"); - qcoQubit0 = qubitMap[qcQubit0]; - qcoQubit1 = qubitMap[qcQubit1]; + qcoQubit0 = lookupMappedQubit(qubitMap, qcQubit0); + qcoQubit1 = lookupMappedQubit(qubitMap, qcQubit1); } else { assert(state.targetsIn[inNestedRegion].size() == 2 && "Invalid number of input targets"); @@ -862,10 +850,8 @@ struct ConvertQCTwoTargetTwoParameterToQCO final Value qcoQubit0; Value qcoQubit1; if (inNestedRegion == 0) { - assert(qubitMap.contains(qcQubit0) && "QC qubit not found"); - assert(qubitMap.contains(qcQubit1) && "QC qubit not found"); - qcoQubit0 = qubitMap[qcQubit0]; - qcoQubit1 = qubitMap[qcQubit1]; + qcoQubit0 = lookupMappedQubit(qubitMap, qcQubit0); + qcoQubit1 = lookupMappedQubit(qubitMap, qcQubit1); } else { assert(state.targetsIn[inNestedRegion].size() == 2 && "Invalid number of input targets"); @@ -922,8 +908,7 @@ struct ConvertQCBarrierOp final : StatefulOpConversionPattern { SmallVector qcoQubits; qcoQubits.reserve(qcQubits.size()); for (const auto& qcQubit : qcQubits) { - assert(qubitMap.contains(qcQubit) && "QC qubit not found"); - qcoQubits.push_back(qubitMap[qcQubit]); + qcoQubits.emplace_back(lookupMappedQubit(qubitMap, qcQubit)); } // Create qco.barrier @@ -931,7 +916,7 @@ struct ConvertQCBarrierOp final : StatefulOpConversionPattern { // Update the state map for (const auto& [qcQubit, qcoQubitOut] : - llvm::zip(qcQubits, qcoOp.getQubitsOut())) { + llvm::zip_equal(qcQubits, qcoOp.getQubitsOut())) { qubitMap[qcQubit] = qcoQubitOut; } @@ -971,8 +956,7 @@ struct ConvertQCCtrlOp final : StatefulOpConversionPattern { SmallVector qcoControls; qcoControls.reserve(qcControls.size()); for (const auto& qcControl : qcControls) { - assert(qubitMap.contains(qcControl) && "QC qubit not found"); - qcoControls.push_back(qubitMap[qcControl]); + qcoControls.emplace_back(lookupMappedQubit(qubitMap, qcControl)); } // Get QCO targets from state map @@ -980,10 +964,9 @@ struct ConvertQCCtrlOp final : StatefulOpConversionPattern { SmallVector qcoTargets; qcoTargets.reserve(numTargets); for (size_t i = 0; i < numTargets; ++i) { - auto qcTarget = op.getTarget(i); - assert(qubitMap.contains(qcTarget) && "QC qubit not found"); - auto qcoTarget = qubitMap[qcTarget]; - qcoTargets.push_back(qcoTarget); + Value qcTarget = op.getTarget(i); + Value qcoTarget = lookupMappedQubit(qubitMap, qcTarget); + qcoTargets.emplace_back(qcoTarget); } // Create qco.ctrl @@ -994,7 +977,7 @@ struct ConvertQCCtrlOp final : StatefulOpConversionPattern { // Nested CtrlOps are managed via the targetsIn and targetsOut maps if (state.inNestedRegion == 0) { for (const auto& [qcControl, qcoControl] : - llvm::zip(qcControls, qcoOp.getControlsOut())) { + llvm::zip_equal(qcControls, qcoOp.getControlsOut())) { qubitMap[qcControl] = qcoControl; } auto qcoTargetsOut = qcoOp.getTargetsOut(); @@ -1019,9 +1002,9 @@ struct ConvertQCCtrlOp final : StatefulOpConversionPattern { qcoTargetAliases.reserve(numTargets); const auto opLoc = op.getLoc(); rewriter.modifyOpInPlace(qcoOp, [&] { - for (auto i = 0UL; i < numTargets; i++) { + for (Value qcoTarget : qcoTargets) { qcoTargetAliases.emplace_back( - entryBlock.addArgument(qcoTargets[i].getType(), opLoc)); + entryBlock.addArgument(qcoTarget.getType(), opLoc)); } }); state.targetsIn[state.inNestedRegion] = std::move(qcoTargetAliases); @@ -1067,8 +1050,7 @@ struct ConvertQCInvOp final : StatefulOpConversionPattern { qcoTargets.reserve(numTargets); for (size_t i = 0; i < numTargets; ++i) { auto qcTarget = op.getTarget(i); - assert(qubitMap.contains(qcTarget) && "QC qubit not found"); - qcoTargets.emplace_back(qubitMap[qcTarget]); + qcoTargets.emplace_back(lookupMappedQubit(qubitMap, qcTarget)); } } else { assert(targetsIn[inNestedRegion].size() == numTargets && @@ -1107,9 +1089,9 @@ struct ConvertQCInvOp final : StatefulOpConversionPattern { qcoTargetAliases.reserve(numTargets); const auto opLoc = op.getLoc(); rewriter.modifyOpInPlace(qcoOp, [&] { - for (auto i = 0UL; i < numTargets; i++) { + for (Value qcoTarget : qcoTargets) { qcoTargetAliases.emplace_back( - entryBlock.addArgument(qcoTargets[i].getType(), opLoc)); + entryBlock.addArgument(qcoTarget.getType(), opLoc)); } }); targetsIn[inNestedRegion] = std::move(qcoTargetAliases); From 57e150abf80204498b4054a694694f35f788546c Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sat, 21 Mar 2026 00:01:57 +0100 Subject: [PATCH 32/57] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Streamline=20QCToQCO?= =?UTF-8?q?=20conversion=20by=20introducing=20helper=20functions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/lib/Conversion/QCToQCO/QCToQCO.cpp | 519 ++++++++++-------------- 1 file changed, 216 insertions(+), 303 deletions(-) diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index 1e70f85053..6ce04cd98b 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -17,11 +17,11 @@ #include "mlir/Dialect/QCO/Utils/ValueOrdering.h" #include +#include #include #include #include #include -#include #include #include #include @@ -32,7 +32,6 @@ #include #include -#include #include namespace mlir { @@ -74,6 +73,15 @@ namespace { * - %q2 after the X gate */ struct LoweringState { + struct ModifierFrame { + /// QC qubits yielded from the current modifier region, in yield order. + SmallVector yieldOrder; + + /// Latest QCO SSA values for QC qubits that are remapped inside the + /// modifier region. + llvm::DenseMap currentQubits; + }; + /// Per-region map from QC qubit references to latest QCO SSA values. /// /// @details Keys are `Operation::getParentRegion()` for ops being converted @@ -82,10 +90,8 @@ struct LoweringState { /// still convert. llvm::DenseMap> qubitMap; - /// Modifier information - int64_t inNestedRegion = 0; - DenseMap> targetsIn; - DenseMap> targetsOut; + /// Stack of active modifier regions (`qc.ctrl` / `qc.inv`). + SmallVector modifierFrames; }; /** @@ -135,6 +141,130 @@ class StatefulOpConversionPattern : public OpConversionPattern { return it->second; } +/** @brief Returns whether lowering currently processes a modifier body. */ +[[nodiscard]] bool isInsideModifier(const LoweringState& state) { + return !state.modifierFrames.empty(); +} + +/** @brief Returns the active modifier frame. */ +[[nodiscard]] LoweringState::ModifierFrame& +currentModifierFrame(LoweringState& state) { + assert(isInsideModifier(state) && "expected active modifier frame"); + return state.modifierFrames.back(); +} + +/** @brief Finds the nearest region-local qubit map containing @p qcQubit. */ +[[nodiscard]] llvm::DenseMap* +findMappedQubitMap(LoweringState& state, Operation* anchor, Value qcQubit) { + for (Region* current = anchor->getParentRegion(); current != nullptr; + current = current->getParentRegion()) { + auto mapIt = state.qubitMap.find(current); + if (mapIt != state.qubitMap.end() && mapIt->second.contains(qcQubit)) { + return &mapIt->second; + } + } + return nullptr; +} + +/** @brief Resolves the latest QCO SSA value for a QC qubit reference. */ +[[nodiscard]] Value lookupMappedQubit(LoweringState& state, Operation* anchor, + Value qcQubit) { + if (isInsideModifier(state)) { + auto& frame = currentModifierFrame(state); + if (auto it = frame.currentQubits.find(qcQubit); + it != frame.currentQubits.end()) { + return it->second; + } + } + + auto* qubitMap = findMappedQubitMap(state, anchor, qcQubit); + assert(qubitMap != nullptr && "QC qubit not found"); + return lookupMappedQubit(*qubitMap, qcQubit); +} + +/** @brief Updates the latest QCO SSA value for a QC qubit reference. */ +void assignMappedQubit(LoweringState& state, Operation* anchor, Value qcQubit, + Value qcoQubit) { + if (isInsideModifier(state)) { + auto& frame = currentModifierFrame(state); + if (auto it = frame.currentQubits.find(qcQubit); + it != frame.currentQubits.end()) { + it->second = qcoQubit; + return; + } + } + + if (auto* qubitMap = findMappedQubitMap(state, anchor, qcQubit)) { + (*qubitMap)[qcQubit] = qcoQubit; + return; + } + + state.qubitMap[anchor->getParentRegion()][qcQubit] = qcoQubit; +} + +/** @brief Resolves a range of QC qubits to their latest QCO values. */ +template +[[nodiscard]] SmallVector resolveMappedQubits(LoweringState& state, + Operation* anchor, + const Range& qcQubits) { + return llvm::to_vector(llvm::map_range(qcQubits, [&](Value qcQubit) { + return lookupMappedQubit(state, anchor, qcQubit); + })); +} + +/** @brief Updates mappings for matching QC and QCO qubit ranges. */ +template +void assignMappedQubits(LoweringState& state, Operation* anchor, + const QcRange& qcQubits, const QcoRange& qcoQubits) { + for (auto [qcQubit, qcoQubit] : llvm::zip_equal(qcQubits, qcoQubits)) { + assignMappedQubit(state, anchor, qcQubit, qcoQubit); + } +} + +/** @brief Collects the target qubits of a variadic QC unitary op. */ +template +[[nodiscard]] SmallVector collectTargets(OpType op) { + SmallVector targets; + targets.reserve(op.getNumTargets()); + for (size_t i = 0; i < op.getNumTargets(); ++i) { + targets.emplace_back(op.getTarget(i)); + } + return targets; +} + +/** @brief Pushes a new modifier frame seeded with aliased target values. */ +void pushModifierFrame(LoweringState& state, ValueRange qcTargets, + ValueRange qcoTargets) { + auto& [yieldOrder, currentQubits] = state.modifierFrames.emplace_back(); + llvm::append_range(yieldOrder, qcTargets); + for (auto [qcTarget, qcoTarget] : llvm::zip_equal(qcTargets, qcoTargets)) { + currentQubits.try_emplace(qcTarget, qcoTarget); + } +} + +/** @brief Pops the active modifier frame after lowering its yield. */ +void popModifierFrame(LoweringState& state) { + assert(isInsideModifier(state) && "expected active modifier frame"); + state.modifierFrames.pop_back(); +} + +/** @brief Adds entry block aliases for modifier target values. */ +template +[[nodiscard]] SmallVector addModifierAliases(OpType op, + ValueRange qcoTargets, + PatternRewriter& rewriter) { + auto& entryBlock = op.getRegion().front(); + SmallVector aliases; + aliases.reserve(qcoTargets.size()); + const auto opLoc = op.getLoc(); + rewriter.modifyOpInPlace(op, [&] { + for (Value qcoTarget : qcoTargets) { + aliases.emplace_back(entryBlock.addArgument(qcoTarget.getType(), opLoc)); + } + }); + return aliases; +} + /** * @brief Converts func.return and sinks remaining live qubits. * @@ -243,7 +373,8 @@ struct ConvertQCAllocOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(qc::AllocOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& qubitMap = getState().qubitMap[op->getParentRegion()]; + auto& state = getState(); + auto* operation = op.getOperation(); auto qcQubit = op.getResult(); // Create the qco.alloc operation with preserved register metadata @@ -255,7 +386,7 @@ struct ConvertQCAllocOp final : StatefulOpConversionPattern { // Establish initial mapping: this QC qubit reference now corresponds // to this QCO SSA value - qubitMap.try_emplace(qcQubit, qcoQubit); + assignMappedQubit(state, operation, qcQubit, qcoQubit); return success(); } @@ -282,9 +413,12 @@ struct ConvertQCDeallocOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(qc::DeallocOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& qubitMap = getState().qubitMap[op->getParentRegion()]; + auto& state = getState(); + auto* operation = op.getOperation(); + auto* region = operation->getParentRegion(); + auto& qubitMap = state.qubitMap[region]; Value qcQubit = op.getQubit(); - Value qcoQubit = lookupMappedQubit(qubitMap, qcQubit); + Value qcoQubit = lookupMappedQubit(state, operation, qcQubit); // Create the dealloc operation rewriter.replaceOpWithNewOp(op, qcoQubit); @@ -317,11 +451,12 @@ struct ConvertQCStaticOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(qc::StaticOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& qubitMap = getState().qubitMap[op->getParentRegion()]; + auto& state = getState(); + auto* operation = op.getOperation(); Value qcQubit = op.getQubit(); auto qcoOp = rewriter.replaceOpWithNewOp(op, op.getIndex()); - qubitMap[qcQubit] = qcoOp.getQubit(); + assignMappedQubit(state, operation, qcQubit, qcoOp.getQubit()); return success(); } @@ -356,9 +491,10 @@ struct ConvertQCMeasureOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(qc::MeasureOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& qubitMap = getState().qubitMap[op->getParentRegion()]; + auto& state = getState(); + auto* operation = op.getOperation(); Value qcQubit = op.getQubit(); - Value qcoQubit = lookupMappedQubit(qubitMap, qcQubit); + Value qcoQubit = lookupMappedQubit(state, operation, qcQubit); // Create qco.measure (returns both output qubit and bit result) auto qcoOp = qco::MeasureOp::create( @@ -366,7 +502,7 @@ struct ConvertQCMeasureOp final : StatefulOpConversionPattern { op.getRegisterSizeAttr(), op.getRegisterIndexAttr()); // Update mapping: the QC qubit now corresponds to the output qubit - qubitMap[qcQubit] = qcoOp.getQubitOut(); + assignMappedQubit(state, operation, qcQubit, qcoOp.getQubitOut()); // Replace the QC operation's bit result with the QCO bit result rewriter.replaceOp(op, qcoOp.getResult()); @@ -401,15 +537,16 @@ struct ConvertQCResetOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(qc::ResetOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& qubitMap = getState().qubitMap[op->getParentRegion()]; + auto& state = getState(); + auto* operation = op.getOperation(); Value qcQubit = op.getQubit(); - Value qcoQubit = lookupMappedQubit(qubitMap, qcQubit); + Value qcoQubit = lookupMappedQubit(state, operation, qcQubit); // Create qco.reset (consumes input, produces output) auto qcoOp = qco::ResetOp::create(rewriter, op.getLoc(), qcoQubit); // Update mapping: the QC qubit now corresponds to the reset output - qubitMap[qcQubit] = qcoOp.getQubitOut(); + assignMappedQubit(state, operation, qcQubit, qcoOp.getQubitOut()); // Erase the old (it has no results to replace) rewriter.eraseOp(op); @@ -441,18 +578,8 @@ struct ConvertQCZeroTargetOneParameterToQCO final LogicalResult matchAndRewrite(QCOpType op, QCOpType::Adaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { - auto& state = this->getState(); - const auto inNestedRegion = state.inNestedRegion; - QCOOpType::create(rewriter, op.getLoc(), op.getParameter(0)); - // Update the state - if (inNestedRegion != 0) { - state.targetsIn.erase(inNestedRegion); - const SmallVector targetsOut; - state.targetsOut.try_emplace(inNestedRegion, targetsOut); - } - rewriter.eraseOp(op); return success(); @@ -483,31 +610,14 @@ struct ConvertQCOneTargetZeroParameterToQCO final matchAndRewrite(QCOpType op, QCOpType::Adaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = this->getState(); - auto& qubitMap = state.qubitMap[op->getParentRegion()]; - const auto inNestedRegion = state.inNestedRegion; - - // Get the latest QCO qubit - auto qcQubit = op.getQubitIn(); - Value qcoQubit; - if (inNestedRegion == 0) { - qcoQubit = lookupMappedQubit(qubitMap, qcQubit); - } else { - assert(state.targetsIn[inNestedRegion].size() == 1 && - "Invalid number of input targets"); - qcoQubit = state.targetsIn[inNestedRegion].front(); - } + auto* operation = op.getOperation(); + Value qcQubit = op.getQubitIn(); + Value qcoQubit = lookupMappedQubit(state, operation, qcQubit); // Create the QCO operation (consumes input, produces output) auto qcoOp = QCOOpType::create(rewriter, op.getLoc(), qcoQubit); - // Update the state map - if (inNestedRegion == 0) { - qubitMap[qcQubit] = qcoOp.getQubitOut(); - } else { - state.targetsIn.erase(inNestedRegion); - const SmallVector targetsOut({qcoOp.getQubitOut()}); - state.targetsOut.try_emplace(inNestedRegion, targetsOut); - } + assignMappedQubit(state, operation, qcQubit, qcoOp.getQubitOut()); rewriter.eraseOp(op); @@ -539,32 +649,15 @@ struct ConvertQCOneTargetOneParameterToQCO final matchAndRewrite(QCOpType op, QCOpType::Adaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = this->getState(); - auto& qubitMap = state.qubitMap[op->getParentRegion()]; - const auto inNestedRegion = state.inNestedRegion; - - // Get the latest QCO qubit - auto qcQubit = op.getQubitIn(); - Value qcoQubit; - if (inNestedRegion == 0) { - qcoQubit = lookupMappedQubit(qubitMap, qcQubit); - } else { - assert(state.targetsIn[inNestedRegion].size() == 1 && - "Invalid number of input targets"); - qcoQubit = state.targetsIn[inNestedRegion].front(); - } + auto* operation = op.getOperation(); + Value qcQubit = op.getQubitIn(); + Value qcoQubit = lookupMappedQubit(state, operation, qcQubit); // Create the QCO operation (consumes input, produces output) auto qcoOp = QCOOpType::create(rewriter, op.getLoc(), qcoQubit, op.getParameter(0)); - // Update the state map - if (inNestedRegion == 0) { - qubitMap[qcQubit] = qcoOp.getQubitOut(); - } else { - state.targetsIn.erase(inNestedRegion); - const SmallVector targetsOut({qcoOp.getQubitOut()}); - state.targetsOut.try_emplace(inNestedRegion, targetsOut); - } + assignMappedQubit(state, operation, qcQubit, qcoOp.getQubitOut()); rewriter.eraseOp(op); @@ -596,32 +689,15 @@ struct ConvertQCOneTargetTwoParameterToQCO final matchAndRewrite(QCOpType op, QCOpType::Adaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = this->getState(); - auto& qubitMap = state.qubitMap[op->getParentRegion()]; - const auto inNestedRegion = state.inNestedRegion; - - // Get the latest QCO qubit - auto qcQubit = op.getQubitIn(); - Value qcoQubit; - if (inNestedRegion == 0) { - qcoQubit = lookupMappedQubit(qubitMap, qcQubit); - } else { - assert(state.targetsIn[inNestedRegion].size() == 1 && - "Invalid number of input targets"); - qcoQubit = state.targetsIn[inNestedRegion].front(); - } + auto* operation = op.getOperation(); + Value qcQubit = op.getQubitIn(); + Value qcoQubit = lookupMappedQubit(state, operation, qcQubit); // Create the QCO operation (consumes input, produces output) auto qcoOp = QCOOpType::create(rewriter, op.getLoc(), qcoQubit, op.getParameter(0), op.getParameter(1)); - // Update the state map - if (inNestedRegion == 0) { - qubitMap[qcQubit] = qcoOp.getQubitOut(); - } else { - state.targetsIn.erase(inNestedRegion); - const SmallVector targetsOut({qcoOp.getQubitOut()}); - state.targetsOut.try_emplace(inNestedRegion, targetsOut); - } + assignMappedQubit(state, operation, qcQubit, qcoOp.getQubitOut()); rewriter.eraseOp(op); @@ -653,33 +729,16 @@ struct ConvertQCOneTargetThreeParameterToQCO final matchAndRewrite(QCOpType op, QCOpType::Adaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = this->getState(); - auto& qubitMap = state.qubitMap[op->getParentRegion()]; - const auto inNestedRegion = state.inNestedRegion; - - // Get the latest QCO qubit - auto qcQubit = op.getQubitIn(); - Value qcoQubit; - if (inNestedRegion == 0) { - qcoQubit = lookupMappedQubit(qubitMap, qcQubit); - } else { - assert(state.targetsIn[inNestedRegion].size() == 1 && - "Invalid number of input targets"); - qcoQubit = state.targetsIn[inNestedRegion].front(); - } + auto* operation = op.getOperation(); + Value qcQubit = op.getQubitIn(); + Value qcoQubit = lookupMappedQubit(state, operation, qcQubit); // Create the QCO operation (consumes input, produces output) auto qcoOp = QCOOpType::create(rewriter, op.getLoc(), qcoQubit, op.getParameter(0), op.getParameter(1), op.getParameter(2)); - // Update the state map - if (inNestedRegion == 0) { - qubitMap[qcQubit] = qcoOp.getQubitOut(); - } else { - state.targetsIn.erase(inNestedRegion); - const SmallVector targetsOut({qcoOp.getQubitOut()}); - state.targetsOut.try_emplace(inNestedRegion, targetsOut); - } + assignMappedQubit(state, operation, qcQubit, qcoOp.getQubitOut()); rewriter.eraseOp(op); @@ -712,38 +771,17 @@ struct ConvertQCTwoTargetZeroParameterToQCO final matchAndRewrite(QCOpType op, QCOpType::Adaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = this->getState(); - auto& qubitMap = state.qubitMap[op->getParentRegion()]; - const auto inNestedRegion = state.inNestedRegion; - - // Get the latest QCO qubits - auto qcQubit0 = op.getQubit0In(); - auto qcQubit1 = op.getQubit1In(); - Value qcoQubit0; - Value qcoQubit1; - if (inNestedRegion == 0) { - qcoQubit0 = lookupMappedQubit(qubitMap, qcQubit0); - qcoQubit1 = lookupMappedQubit(qubitMap, qcQubit1); - } else { - assert(state.targetsIn[inNestedRegion].size() == 2 && - "Invalid number of input targets"); - const auto& targetsIn = state.targetsIn[inNestedRegion]; - qcoQubit0 = targetsIn[0]; - qcoQubit1 = targetsIn[1]; - } + auto* operation = op.getOperation(); + Value qcQubit0 = op.getQubit0In(); + Value qcQubit1 = op.getQubit1In(); + Value qcoQubit0 = lookupMappedQubit(state, operation, qcQubit0); + Value qcoQubit1 = lookupMappedQubit(state, operation, qcQubit1); // Create the QCO operation (consumes input, produces output) auto qcoOp = QCOOpType::create(rewriter, op.getLoc(), qcoQubit0, qcoQubit1); - // Update the state map - if (inNestedRegion == 0) { - qubitMap[qcQubit0] = qcoOp.getQubit0Out(); - qubitMap[qcQubit1] = qcoOp.getQubit1Out(); - } else { - state.targetsIn.erase(inNestedRegion); - const SmallVector targetsOut( - {qcoOp.getQubit0Out(), qcoOp.getQubit1Out()}); - state.targetsOut.try_emplace(inNestedRegion, targetsOut); - } + assignMappedQubit(state, operation, qcQubit0, qcoOp.getQubit0Out()); + assignMappedQubit(state, operation, qcQubit1, qcoOp.getQubit1Out()); rewriter.eraseOp(op); @@ -776,39 +814,18 @@ struct ConvertQCTwoTargetOneParameterToQCO final matchAndRewrite(QCOpType op, QCOpType::Adaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = this->getState(); - auto& qubitMap = state.qubitMap[op->getParentRegion()]; - const auto inNestedRegion = state.inNestedRegion; - - // Get the latest QCO qubits - auto qcQubit0 = op.getQubit0In(); - auto qcQubit1 = op.getQubit1In(); - Value qcoQubit0; - Value qcoQubit1; - if (inNestedRegion == 0) { - qcoQubit0 = lookupMappedQubit(qubitMap, qcQubit0); - qcoQubit1 = lookupMappedQubit(qubitMap, qcQubit1); - } else { - assert(state.targetsIn[inNestedRegion].size() == 2 && - "Invalid number of input targets"); - const auto& targetsIn = state.targetsIn[inNestedRegion]; - qcoQubit0 = targetsIn[0]; - qcoQubit1 = targetsIn[1]; - } + auto* operation = op.getOperation(); + Value qcQubit0 = op.getQubit0In(); + Value qcQubit1 = op.getQubit1In(); + Value qcoQubit0 = lookupMappedQubit(state, operation, qcQubit0); + Value qcoQubit1 = lookupMappedQubit(state, operation, qcQubit1); // Create the QCO operation (consumes input, produces output) auto qcoOp = QCOOpType::create(rewriter, op.getLoc(), qcoQubit0, qcoQubit1, op.getParameter(0)); - // Update the state map - if (inNestedRegion == 0) { - qubitMap[qcQubit0] = qcoOp.getQubit0Out(); - qubitMap[qcQubit1] = qcoOp.getQubit1Out(); - } else { - state.targetsIn.erase(inNestedRegion); - const SmallVector targetsOut( - {qcoOp.getQubit0Out(), qcoOp.getQubit1Out()}); - state.targetsOut.try_emplace(inNestedRegion, targetsOut); - } + assignMappedQubit(state, operation, qcQubit0, qcoOp.getQubit0Out()); + assignMappedQubit(state, operation, qcQubit1, qcoOp.getQubit1Out()); rewriter.eraseOp(op); @@ -841,39 +858,18 @@ struct ConvertQCTwoTargetTwoParameterToQCO final matchAndRewrite(QCOpType op, QCOpType::Adaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = this->getState(); - auto& qubitMap = state.qubitMap[op->getParentRegion()]; - const auto inNestedRegion = state.inNestedRegion; - - // Get the latest QCO qubits - auto qcQubit0 = op.getQubit0In(); - auto qcQubit1 = op.getQubit1In(); - Value qcoQubit0; - Value qcoQubit1; - if (inNestedRegion == 0) { - qcoQubit0 = lookupMappedQubit(qubitMap, qcQubit0); - qcoQubit1 = lookupMappedQubit(qubitMap, qcQubit1); - } else { - assert(state.targetsIn[inNestedRegion].size() == 2 && - "Invalid number of input targets"); - const auto& targetsIn = state.targetsIn[inNestedRegion]; - qcoQubit0 = targetsIn[0]; - qcoQubit1 = targetsIn[1]; - } + auto* operation = op.getOperation(); + Value qcQubit0 = op.getQubit0In(); + Value qcQubit1 = op.getQubit1In(); + Value qcoQubit0 = lookupMappedQubit(state, operation, qcQubit0); + Value qcoQubit1 = lookupMappedQubit(state, operation, qcQubit1); // Create the QCO operation (consumes input, produces output) auto qcoOp = QCOOpType::create(rewriter, op.getLoc(), qcoQubit0, qcoQubit1, op.getParameter(0), op.getParameter(1)); - // Update the state map - if (inNestedRegion == 0) { - qubitMap[qcQubit0] = qcoOp.getQubit0Out(); - qubitMap[qcQubit1] = qcoOp.getQubit1Out(); - } else { - state.targetsIn.erase(inNestedRegion); - const SmallVector targetsOut( - {qcoOp.getQubit0Out(), qcoOp.getQubit1Out()}); - state.targetsOut.try_emplace(inNestedRegion, targetsOut); - } + assignMappedQubit(state, operation, qcQubit0, qcoOp.getQubit0Out()); + assignMappedQubit(state, operation, qcQubit1, qcoOp.getQubit1Out()); rewriter.eraseOp(op); @@ -901,24 +897,14 @@ struct ConvertQCBarrierOp final : StatefulOpConversionPattern { matchAndRewrite(qc::BarrierOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = getState(); - auto& qubitMap = state.qubitMap[op->getParentRegion()]; - - // Get QCO qubits from state map - auto qcQubits = op.getQubits(); - SmallVector qcoQubits; - qcoQubits.reserve(qcQubits.size()); - for (const auto& qcQubit : qcQubits) { - qcoQubits.emplace_back(lookupMappedQubit(qubitMap, qcQubit)); - } + auto* operation = op.getOperation(); + const auto qcQubits = llvm::to_vector(op.getQubits()); + auto qcoQubits = resolveMappedQubits(state, operation, qcQubits); // Create qco.barrier auto qcoOp = qco::BarrierOp::create(rewriter, op.getLoc(), qcoQubits); - // Update the state map - for (const auto& [qcQubit, qcoQubitOut] : - llvm::zip_equal(qcQubits, qcoOp.getQubitsOut())) { - qubitMap[qcQubit] = qcoQubitOut; - } + assignMappedQubits(state, operation, qcQubits, qcoOp.getQubitsOut()); rewriter.eraseOp(op); return success(); @@ -949,46 +935,18 @@ struct ConvertQCCtrlOp final : StatefulOpConversionPattern { matchAndRewrite(qc::CtrlOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = getState(); - auto& qubitMap = state.qubitMap[op->getParentRegion()]; - - // Get QCO controls from state map - auto qcControls = op.getControls(); - SmallVector qcoControls; - qcoControls.reserve(qcControls.size()); - for (const auto& qcControl : qcControls) { - qcoControls.emplace_back(lookupMappedQubit(qubitMap, qcControl)); - } - - // Get QCO targets from state map - const auto numTargets = op.getNumTargets(); - SmallVector qcoTargets; - qcoTargets.reserve(numTargets); - for (size_t i = 0; i < numTargets; ++i) { - Value qcTarget = op.getTarget(i); - Value qcoTarget = lookupMappedQubit(qubitMap, qcTarget); - qcoTargets.emplace_back(qcoTarget); - } + auto* operation = op.getOperation(); + const auto qcControls = llvm::to_vector(op.getControls()); + const auto qcTargets = collectTargets(op); + auto qcoControls = resolveMappedQubits(state, operation, qcControls); + auto qcoTargets = resolveMappedQubits(state, operation, qcTargets); // Create qco.ctrl auto qcoOp = qco::CtrlOp::create(rewriter, op.getLoc(), qcoControls, qcoTargets); - // Update the state map if this is a top-level CtrlOp - // Nested CtrlOps are managed via the targetsIn and targetsOut maps - if (state.inNestedRegion == 0) { - for (const auto& [qcControl, qcoControl] : - llvm::zip_equal(qcControls, qcoOp.getControlsOut())) { - qubitMap[qcControl] = qcoControl; - } - auto qcoTargetsOut = qcoOp.getTargetsOut(); - for (size_t i = 0; i < numTargets; ++i) { - auto qcTarget = op.getTarget(i); - qubitMap[qcTarget] = qcoTargetsOut[i]; - } - } - - // Update modifier information - state.inNestedRegion++; + assignMappedQubits(state, operation, qcControls, qcoOp.getControlsOut()); + assignMappedQubits(state, operation, qcTargets, qcoOp.getTargetsOut()); // Clone body region from QC to QCO auto& dstRegion = qcoOp.getRegion(); @@ -998,16 +956,8 @@ struct ConvertQCCtrlOp final : StatefulOpConversionPattern { auto& entryBlock = dstRegion.front(); assert(entryBlock.getNumArguments() == 0 && "QC ctrl region unexpectedly has entry block arguments"); - SmallVector qcoTargetAliases; - qcoTargetAliases.reserve(numTargets); - const auto opLoc = op.getLoc(); - rewriter.modifyOpInPlace(qcoOp, [&] { - for (Value qcoTarget : qcoTargets) { - qcoTargetAliases.emplace_back( - entryBlock.addArgument(qcoTarget.getType(), opLoc)); - } - }); - state.targetsIn[state.inNestedRegion] = std::move(qcoTargetAliases); + auto qcoTargetAliases = addModifierAliases(qcoOp, qcoTargets, rewriter); + pushModifierFrame(state, qcTargets, qcoTargetAliases); rewriter.eraseOp(op); return success(); @@ -1038,63 +988,25 @@ struct ConvertQCInvOp final : StatefulOpConversionPattern { matchAndRewrite(qc::InvOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = getState(); - auto& qubitMap = state.qubitMap[op->getParentRegion()]; - auto& inNestedRegion = state.inNestedRegion; - auto& targetsIn = state.targetsIn; - auto& targetsOut = state.targetsOut; - - // Get QCO targets from state map - const auto numTargets = op.getNumTargets(); - SmallVector qcoTargets; - if (inNestedRegion == 0) { - qcoTargets.reserve(numTargets); - for (size_t i = 0; i < numTargets; ++i) { - auto qcTarget = op.getTarget(i); - qcoTargets.emplace_back(lookupMappedQubit(qubitMap, qcTarget)); - } - } else { - assert(targetsIn[inNestedRegion].size() == numTargets && - "Invalid number of input targets"); - qcoTargets = targetsIn[inNestedRegion]; - } + auto* operation = op.getOperation(); + const auto qcTargets = collectTargets(op); + auto qcoTargets = resolveMappedQubits(state, operation, qcTargets); // Create qco.inv auto qcoOp = qco::InvOp::create(rewriter, op.getLoc(), qcoTargets); - // Update state map - if (inNestedRegion == 0) { - const auto qubitsOut = qcoOp.getQubitsOut(); - for (size_t i = 0; i < numTargets; ++i) { - auto qcTarget = op.getTarget(i); - qubitMap[qcTarget] = qubitsOut[i]; - } - } else { - targetsIn.erase(inNestedRegion); - targetsOut.try_emplace(inNestedRegion, qcoOp.getQubitsOut()); - } - - // Update modifier information - inNestedRegion++; + assignMappedQubits(state, operation, qcTargets, qcoOp.getQubitsOut()); // 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`. + // Create block arguments for target qubits and seed the nested frame. auto& entryBlock = dstRegion.front(); assert(entryBlock.getNumArguments() == 0 && "QC inv region unexpectedly has entry block arguments"); - SmallVector qcoTargetAliases; - qcoTargetAliases.reserve(numTargets); - const auto opLoc = op.getLoc(); - rewriter.modifyOpInPlace(qcoOp, [&] { - for (Value qcoTarget : qcoTargets) { - qcoTargetAliases.emplace_back( - entryBlock.addArgument(qcoTarget.getType(), opLoc)); - } - }); - targetsIn[inNestedRegion] = std::move(qcoTargetAliases); + auto qcoTargetAliases = addModifierAliases(qcoOp, qcoTargets, rewriter); + pushModifierFrame(state, qcTargets, qcoTargetAliases); rewriter.eraseOp(op); return success(); @@ -1120,10 +1032,11 @@ struct ConvertQCYieldOp final : StatefulOpConversionPattern { matchAndRewrite(qc::YieldOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { auto& state = getState(); - const auto& targets = state.targetsOut[state.inNestedRegion]; + auto* operation = op.getOperation(); + auto& frame = currentModifierFrame(state); + auto targets = resolveMappedQubits(state, operation, frame.yieldOrder); rewriter.replaceOpWithNewOp(op, targets); - state.targetsOut.erase(state.inNestedRegion); - state.inNestedRegion--; + popModifierFrame(state); return success(); } }; From 2b6f8081c015a6f90a31c5b0dd0e4522fd9d3524 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sat, 21 Mar 2026 00:16:30 +0100 Subject: [PATCH 33/57] =?UTF-8?q?=F0=9F=9A=A8=20Fix=20clang-tidy=20warning?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/lib/Conversion/QCOToQC/QCOToQC.cpp | 1 - mlir/lib/Conversion/QCToQCO/QCToQCO.cpp | 48 +++++++++++++------------ 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp index 5fe548a446..0f74f99045 100644 --- a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -26,7 +26,6 @@ #include #include -#include #include namespace mlir { diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index 6ce04cd98b..1999197563 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -43,7 +44,6 @@ using namespace qc; #include "mlir/Conversion/QCToQCO/QCToQCO.h.inc" namespace { - /** * @brief State object for tracking qubit value flow during conversion * @@ -123,6 +123,7 @@ class StatefulOpConversionPattern : public OpConversionPattern { private: LoweringState* state_; }; +} // namespace /** * @brief Helper function to look up the latest QCO qubit value for a given QC @@ -134,27 +135,27 @@ class StatefulOpConversionPattern : public OpConversionPattern { * @return The latest QCO qubit value corresponding to the given QC qubit * reference */ -[[nodiscard]] Value lookupMappedQubit(llvm::DenseMap& qubitMap, - Value qcQubit) { +[[nodiscard]] static Value +lookupMappedQubit(llvm::DenseMap& qubitMap, Value qcQubit) { auto it = qubitMap.find(qcQubit); assert(it != qubitMap.end() && "QC qubit not found"); return it->second; } /** @brief Returns whether lowering currently processes a modifier body. */ -[[nodiscard]] bool isInsideModifier(const LoweringState& state) { +[[nodiscard]] static bool isInsideModifier(const LoweringState& state) { return !state.modifierFrames.empty(); } /** @brief Returns the active modifier frame. */ -[[nodiscard]] LoweringState::ModifierFrame& +[[nodiscard]] static LoweringState::ModifierFrame& currentModifierFrame(LoweringState& state) { assert(isInsideModifier(state) && "expected active modifier frame"); return state.modifierFrames.back(); } /** @brief Finds the nearest region-local qubit map containing @p qcQubit. */ -[[nodiscard]] llvm::DenseMap* +[[nodiscard]] static llvm::DenseMap* findMappedQubitMap(LoweringState& state, Operation* anchor, Value qcQubit) { for (Region* current = anchor->getParentRegion(); current != nullptr; current = current->getParentRegion()) { @@ -167,8 +168,8 @@ findMappedQubitMap(LoweringState& state, Operation* anchor, Value qcQubit) { } /** @brief Resolves the latest QCO SSA value for a QC qubit reference. */ -[[nodiscard]] Value lookupMappedQubit(LoweringState& state, Operation* anchor, - Value qcQubit) { +[[nodiscard]] static Value lookupMappedQubit(LoweringState& state, + Operation* anchor, Value qcQubit) { if (isInsideModifier(state)) { auto& frame = currentModifierFrame(state); if (auto it = frame.currentQubits.find(qcQubit); @@ -183,8 +184,8 @@ findMappedQubitMap(LoweringState& state, Operation* anchor, Value qcQubit) { } /** @brief Updates the latest QCO SSA value for a QC qubit reference. */ -void assignMappedQubit(LoweringState& state, Operation* anchor, Value qcQubit, - Value qcoQubit) { +static void assignMappedQubit(LoweringState& state, Operation* anchor, + Value qcQubit, Value qcoQubit) { if (isInsideModifier(state)) { auto& frame = currentModifierFrame(state); if (auto it = frame.currentQubits.find(qcQubit); @@ -204,9 +205,9 @@ void assignMappedQubit(LoweringState& state, Operation* anchor, Value qcQubit, /** @brief Resolves a range of QC qubits to their latest QCO values. */ template -[[nodiscard]] SmallVector resolveMappedQubits(LoweringState& state, - Operation* anchor, - const Range& qcQubits) { +[[nodiscard]] static SmallVector +resolveMappedQubits(LoweringState& state, Operation* anchor, + const Range& qcQubits) { return llvm::to_vector(llvm::map_range(qcQubits, [&](Value qcQubit) { return lookupMappedQubit(state, anchor, qcQubit); })); @@ -214,8 +215,9 @@ template /** @brief Updates mappings for matching QC and QCO qubit ranges. */ template -void assignMappedQubits(LoweringState& state, Operation* anchor, - const QcRange& qcQubits, const QcoRange& qcoQubits) { +static void assignMappedQubits(LoweringState& state, Operation* anchor, + const QcRange& qcQubits, + const QcoRange& qcoQubits) { for (auto [qcQubit, qcoQubit] : llvm::zip_equal(qcQubits, qcoQubits)) { assignMappedQubit(state, anchor, qcQubit, qcoQubit); } @@ -223,7 +225,7 @@ void assignMappedQubits(LoweringState& state, Operation* anchor, /** @brief Collects the target qubits of a variadic QC unitary op. */ template -[[nodiscard]] SmallVector collectTargets(OpType op) { +[[nodiscard]] static SmallVector collectTargets(OpType op) { SmallVector targets; targets.reserve(op.getNumTargets()); for (size_t i = 0; i < op.getNumTargets(); ++i) { @@ -233,8 +235,8 @@ template } /** @brief Pushes a new modifier frame seeded with aliased target values. */ -void pushModifierFrame(LoweringState& state, ValueRange qcTargets, - ValueRange qcoTargets) { +static void pushModifierFrame(LoweringState& state, ValueRange qcTargets, + ValueRange qcoTargets) { auto& [yieldOrder, currentQubits] = state.modifierFrames.emplace_back(); llvm::append_range(yieldOrder, qcTargets); for (auto [qcTarget, qcoTarget] : llvm::zip_equal(qcTargets, qcoTargets)) { @@ -243,16 +245,16 @@ void pushModifierFrame(LoweringState& state, ValueRange qcTargets, } /** @brief Pops the active modifier frame after lowering its yield. */ -void popModifierFrame(LoweringState& state) { +static void popModifierFrame(LoweringState& state) { assert(isInsideModifier(state) && "expected active modifier frame"); state.modifierFrames.pop_back(); } /** @brief Adds entry block aliases for modifier target values. */ template -[[nodiscard]] SmallVector addModifierAliases(OpType op, - ValueRange qcoTargets, - PatternRewriter& rewriter) { +[[nodiscard]] static SmallVector +addModifierAliases(OpType op, ValueRange qcoTargets, + PatternRewriter& rewriter) { auto& entryBlock = op.getRegion().front(); SmallVector aliases; aliases.reserve(qcoTargets.size()); @@ -265,6 +267,8 @@ template return aliases; } +namespace { + /** * @brief Converts func.return and sinks remaining live qubits. * From 20a7e6a475effbb6f70c1f00a191839428744359 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sat, 21 Mar 2026 01:02:48 +0100 Subject: [PATCH 34/57] =?UTF-8?q?=F0=9F=90=9B=20Fix=20the=20logic=20in=20`?= =?UTF-8?q?synchronizeMappedQubitTypes`=20and=20add=20dedicated=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../QCO/Transforms/Mapping/Mapping.cpp | 50 +++--- .../QCO/Transforms/Mapping/CMakeLists.txt | 1 + .../QCO/Transforms/Mapping/test_mapping.cpp | 147 +++++++++++++++++- 3 files changed, 171 insertions(+), 27 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index a06d58475b..778898784c 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -67,46 +67,46 @@ namespace mlir::qco { */ static void synchronizeMappedQubitTypes(Region& region) { region.walk([](Operation* op) { + const auto synchronizeQubitTypes = [](Value input, Value output) { + const auto qIn = dyn_cast(input.getType()); + const auto qOut = dyn_cast(output.getType()); + if (qIn && qOut && qIn != qOut) { + output.setType(qIn); + } + }; + // Region entry arguments are fixed at build time; operand Value types - // update after alloc→static placement but block argument types do not. + // update after alloc→static placement, but block argument types do not. if (auto ctrl = dyn_cast(op)) { Block& body = *ctrl.getBody(); for (unsigned i = 0; i < ctrl.getNumTargets(); ++i) { - const Type expected = ctrl.getTargetsIn()[i].getType(); - if (body.getArgument(i).getType() != expected) { - body.getArgument(i).setType(expected); - } + body.getArgument(i).setType(ctrl.getTargetsIn()[i].getType()); } } else if (auto inv = dyn_cast(op)) { Block& body = *inv.getBody(); for (unsigned i = 0; i < inv.getNumTargets(); ++i) { - const Type expected = inv.getQubitsIn()[i].getType(); - if (body.getArgument(i).getType() != expected) { - body.getArgument(i).setType(expected); - } + body.getArgument(i).setType(inv.getQubitsIn()[i].getType()); } } - const unsigned numOp = op->getNumOperands(); - const unsigned numRes = op->getNumResults(); - if (numOp == numRes) { - for (unsigned i = 0; i < numOp; ++i) { - const auto qIn = dyn_cast(op->getOperand(i).getType()); - const auto qOut = dyn_cast(op->getResult(i).getType()); - if (qIn && qOut && qIn != qOut) { - op->getResult(i).setType(qIn); - } + if (auto unitary = dyn_cast(op)) { + for (auto [input, output] : llvm::zip_equal(unitary.getInputQubits(), + unitary.getOutputQubits())) { + synchronizeQubitTypes(input, output); } return WalkResult::advance(); } - // e.g. qco.measure: qubit in, (qubit_out, i1) - if (numOp >= 1 && numRes >= 1) { - const auto qIn = dyn_cast(op->getOperand(0).getType()); - const auto qOut = dyn_cast(op->getResult(0).getType()); - if (qIn && qOut && qIn != qOut) { - op->getResult(0).setType(qIn); - } + + if (auto measure = dyn_cast(op)) { + synchronizeQubitTypes(measure.getQubitIn(), measure.getQubitOut()); + return WalkResult::advance(); } + + if (auto reset = dyn_cast(op)) { + synchronizeQubitTypes(reset.getQubitIn(), reset.getQubitOut()); + return WalkResult::advance(); + } + return WalkResult::advance(); }); } diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/Mapping/CMakeLists.txt index 3ea628104a..1ced00dc34 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/CMakeLists.txt @@ -14,6 +14,7 @@ target_link_libraries( PRIVATE MLIRParser GTest::gtest_main MLIRQCProgramBuilder + MLIRQCOProgramBuilder MLIRQCOUtils MLIRQCToQCO MLIRQCOToQC diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp index 8b65bf57f6..5e465716ab 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -14,7 +14,9 @@ #include "mlir/Dialect/QC/IR/QCDialect.h" #include "mlir/Dialect/QC/IR/QCInterfaces.h" #include "mlir/Dialect/QC/IR/QCOps.h" +#include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" #include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/IR/QCOOps.h" #include "mlir/Dialect/QCO/Transforms/Mapping/Architecture.h" #include "mlir/Dialect/QCO/Transforms/Passes.h" @@ -27,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -45,8 +48,7 @@ struct ArchitectureParam { Architecture (*factory)(); }; -class MappingPassTest : public testing::Test, - public testing::WithParamInterface { +class MappingPassTestBase : public testing::Test { public: /** * @brief Walks the IR and validates if each two-qubit op is executable on the @@ -115,10 +117,151 @@ class MappingPassTest : public testing::Test, ASSERT_TRUE(succeeded(res)); } + static void runQCOMapping(OwningOpRef& moduleOp) { + PassManager pm(moduleOp->getContext()); + pm.addPass(qco::createMappingPass(qco::MappingPassOptions{.nlookahead = 5, + .alpha = 1, + .lambda = 0.85, + .niterations = 2, + .ntrials = 8, + .seed = 1337})); + auto res = pm.run(*moduleOp); + ASSERT_TRUE(succeeded(res)); + ASSERT_TRUE(succeeded(verify(*moduleOp))); + } + + static void expectSameStaticQubitType(Value input, Value output) { + const auto qIn = dyn_cast(input.getType()); + const auto qOut = dyn_cast(output.getType()); + ASSERT_TRUE(qIn); + ASSERT_TRUE(qOut); + EXPECT_EQ(qIn, qOut); + EXPECT_TRUE(qOut.getIsStatic()); + } + std::unique_ptr context; }; + +class MappingPassTest : public MappingPassTestBase, + public testing::WithParamInterface { +}; }; // namespace +TEST_F(MappingPassTestBase, SynchronizesParameterizedGateAndMeasureTypes) { + qco::QCOProgramBuilder builder(context.get()); + builder.initialize(); + + auto q0 = builder.allocQubit(); + auto q1 = builder.allocQubit(); + + q0 = builder.rx(0.25, q0); + std::tie(q0, q1) = builder.rxx(0.5, q0, q1); + std::tie(q0, q1) = builder.xx_plus_yy(0.75, 1.25, q0, q1); + const auto [measuredQubit, measuredBit] = builder.measure(q0); + (void)measuredBit; + q0 = measuredQubit; + + builder.dealloc(q0); + builder.dealloc(q1); + + auto moduleOp = builder.finalize(); + runQCOMapping(moduleOp); + + auto entry = *moduleOp->getOps().begin(); + qco::RXOp rxOp; + qco::RXXOp rxxOp; + qco::XXPlusYYOp xxPlusYYOp; + qco::MeasureOp measureOp; + entry.walk([&](Operation* op) { + if (auto rx = dyn_cast(op)) { + rxOp = rx; + } else if (auto rxx = dyn_cast(op)) { + rxxOp = rxx; + } else if (auto xxPlusYY = dyn_cast(op)) { + xxPlusYYOp = xxPlusYY; + } else if (auto measure = dyn_cast(op)) { + measureOp = measure; + } + }); + + ASSERT_TRUE(rxOp); + ASSERT_TRUE(rxxOp); + ASSERT_TRUE(xxPlusYYOp); + ASSERT_TRUE(measureOp); + + expectSameStaticQubitType(rxOp.getQubitIn(), rxOp.getQubitOut()); + expectSameStaticQubitType(rxxOp.getQubit0In(), rxxOp.getQubit0Out()); + expectSameStaticQubitType(rxxOp.getQubit1In(), rxxOp.getQubit1Out()); + expectSameStaticQubitType(xxPlusYYOp.getQubit0In(), + xxPlusYYOp.getQubit0Out()); + expectSameStaticQubitType(xxPlusYYOp.getQubit1In(), + xxPlusYYOp.getQubit1Out()); + expectSameStaticQubitType(measureOp.getQubitIn(), measureOp.getQubitOut()); +} + +TEST_F(MappingPassTestBase, SynchronizesModifierRegionArguments) { + qco::QCOProgramBuilder builder(context.get()); + builder.initialize(); + + auto q0 = builder.allocQubit(); + auto q1 = builder.allocQubit(); + + auto [controlsOut, targetsOut] = builder.ctrl( + {q0}, {q1}, [&](ValueRange targets) -> llvm::SmallVector { + return {builder.rx(0.5, targets[0])}; + }); + q0 = controlsOut.front(); + q1 = targetsOut.front(); + + auto invOut = + builder.inv({q1}, [&](ValueRange qubits) -> llvm::SmallVector { + return {builder.rx(0.25, qubits[0])}; + }); + q1 = invOut.front(); + + builder.dealloc(q0); + builder.dealloc(q1); + + auto moduleOp = builder.finalize(); + runQCOMapping(moduleOp); + + auto entry = *moduleOp->getOps().begin(); + qco::CtrlOp ctrlOp; + qco::InvOp invOp; + qco::RXOp ctrlBodyRxOp; + qco::RXOp invBodyRxOp; + entry.walk([&](Operation* op) { + if (auto ctrl = dyn_cast(op)) { + ctrlOp = ctrl; + } else if (auto inv = dyn_cast(op)) { + invOp = inv; + } else if (auto rx = dyn_cast(op)) { + if (rx->getParentOfType()) { + ctrlBodyRxOp = rx; + } else if (rx->getParentOfType()) { + invBodyRxOp = rx; + } + } + }); + + ASSERT_TRUE(ctrlOp); + ASSERT_TRUE(invOp); + ASSERT_TRUE(ctrlBodyRxOp); + ASSERT_TRUE(invBodyRxOp); + + ASSERT_EQ(ctrlOp.getBody()->getNumArguments(), ctrlOp.getNumTargets()); + expectSameStaticQubitType(ctrlOp.getTargetsIn()[0], + ctrlOp.getBody()->getArgument(0)); + expectSameStaticQubitType(ctrlBodyRxOp.getQubitIn(), + ctrlBodyRxOp.getQubitOut()); + + ASSERT_EQ(invOp.getBody()->getNumArguments(), invOp.getNumTargets()); + expectSameStaticQubitType(invOp.getQubitsIn()[0], + invOp.getBody()->getArgument(0)); + expectSameStaticQubitType(invBodyRxOp.getQubitIn(), + invBodyRxOp.getQubitOut()); +} + TEST_P(MappingPassTest, GHZ) { auto arch = GetParam().factory(); From 3166aa0907af54ecb234fda1694f653c0ce86aee Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Mon, 23 Mar 2026 14:04:47 +0100 Subject: [PATCH 35/57] =?UTF-8?q?=E2=9C=A8=20Update=20mapping=20pass=20to?= =?UTF-8?q?=20use=20`IRRewriter`.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../QCO/Transforms/Mapping/Mapping.cpp | 251 +++++++++++++++--- 1 file changed, 216 insertions(+), 35 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 778898784c..eb4410e7d3 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -25,8 +25,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -57,56 +59,235 @@ namespace mlir::qco { #define GEN_PASS_DEF_MAPPINGPASS #include "mlir/Dialect/QCO/Transforms/Passes.h.inc" +//===----------------------------------------------------------------------===// +// After alloc→static placement, operand qubit types match the mapped static +// type but many op results (and ctrl/inv region args) may still be typed as +// plain !qco.qubit. IR is recreated with IRRewriter. +//===----------------------------------------------------------------------===// + +/** True if any declared qubit result type disagrees with the paired operand. */ +[[nodiscard]] static bool +qubitOperandAndResultTypesDiffer(UnitaryOpInterface unitary) { + for (auto [input, output] : + llvm::zip_equal(unitary.getInputQubits(), unitary.getOutputQubits())) { + const auto qIn = dyn_cast(input.getType()); + const auto qOut = dyn_cast(output.getType()); + if (qIn && qOut && qIn != qOut) { + return true; + } + } + return false; +} + +/** Region entry args vs target operands, for ctrl/inv verifier alignment. */ +[[nodiscard]] static bool +modifierBodyArgsMismatchTargetOperands(Block& body, + OperandRange targetOperands) { + for (auto [arg, target] : + llvm::zip_equal(body.getArguments(), targetOperands)) { + if (arg.getType() != target.getType()) { + return true; + } + } + return false; +} + +[[nodiscard]] static bool ctrlNeedsQubitTypeResync(CtrlOp ctrl) { + return modifierBodyArgsMismatchTargetOperands(*ctrl.getBody(), + ctrl.getTargetsIn()) || + qubitOperandAndResultTypesDiffer(ctrl); +} + +[[nodiscard]] static bool invNeedsQubitTypeResync(InvOp inv) { + return modifierBodyArgsMismatchTargetOperands(*inv.getBody(), + inv.getQubitsIn()) || + qubitOperandAndResultTypesDiffer(inv); +} + +/** @brief Copies op results into `SmallVector` (OpResult → Value). */ +[[nodiscard]] static llvm::SmallVector opResultsAsValues(Operation* op) { + llvm::SmallVector vals; + llvm::append_range(vals, op->getResults()); + return vals; +} + +/** + * @brief Duplicate @p op with new result types; clone attached regions. + * @param rewriter Insertion point is set to @p op before create. + */ +static Operation* cloneOpWithNewResultTypes(Operation* op, + ArrayRef newResultTypes, + IRRewriter& rewriter) { + assert(op->getNumResults() == newResultTypes.size() && + "result type count must match the operation"); + rewriter.setInsertionPoint(op); + OperationState state(op->getLoc(), op->getName()); + state.addOperands(op->getOperands()); + state.addTypes(newResultTypes); + state.addAttributes(op->getAttrs()); + state.addSuccessors(op->getSuccessors()); + for (Region& region : op->getRegions()) { + Region* newRegion = state.addRegion(); + IRMapping mapper; + rewriter.cloneRegionBefore(region, *newRegion, newRegion->end(), mapper); + } + return rewriter.create(state); +} + +[[nodiscard]] static llvm::SmallVector +resyncClonedUnitaryAndGetResults(Operation* cloned, IRRewriter& rewriter); + /** - * @brief Align declared qubit result types with operand types after placement. + * @brief Rebuild a leaf unitary so each qubit result type matches its operand. * - * @details Replacing `qco.alloc` with `qco.static` reroutes SSA uses, so - * operand types become `!qco.qubit` while many gates still declare - * `!qco.qubit` results from before placement. Ops with `TypesMatchWith` / - * same-type traits then fail verification until results are refreshed. + * Skips ops with nested regions (handled via @ref + * resyncClonedUnitaryAndGetResults). */ -static void synchronizeMappedQubitTypes(Region& region) { - region.walk([](Operation* op) { - const auto synchronizeQubitTypes = [](Value input, Value output) { - const auto qIn = dyn_cast(input.getType()); - const auto qOut = dyn_cast(output.getType()); - if (qIn && qOut && qIn != qOut) { - output.setType(qIn); - } - }; +[[nodiscard]] static llvm::SmallVector +replaceLeafUnitaryWithAlignedQubitTypes(UnitaryOpInterface unitary, + IRRewriter& rewriter) { + Operation* op = unitary.getOperation(); + if (op->getNumRegions() != 0 || !qubitOperandAndResultTypesDiffer(unitary)) { + return opResultsAsValues(op); + } + SmallVector newTypes(op->getResultTypes()); + for (auto [input, output] : + llvm::zip_equal(unitary.getInputQubits(), unitary.getOutputQubits())) { + const auto qIn = dyn_cast(input.getType()); + const auto qOut = dyn_cast(output.getType()); + if (qIn && qOut && qIn != qOut) { + newTypes[llvm::cast(output).getResultNumber()] = qIn; + } + } + Operation* newOp = cloneOpWithNewResultTypes(op, newTypes, rewriter); + rewriter.replaceOp(op, newOp->getResults()); + return opResultsAsValues(newOp); +} + +/** + * @brief Clone all ops in the modifier body before `qco.yield`, then align the + * body unitary’s qubit types. + * + * The body may contain `arith.constant` (or similar) before the single + * `UnitaryOpInterface`; cloning only that unitary would leave uses pointing at + * the old region, which is destroyed when the modifier is replaced. + */ +[[nodiscard]] static llvm::SmallVector +cloneModifierBodyAndResyncUnitary(Block& oldBody, ValueRange newTargetArgs, + IRRewriter& rewriter) { + IRMapping mapping; + for (auto [oldArg, newArg] : + llvm::zip_equal(oldBody.getArguments(), newTargetArgs)) { + mapping.map(oldArg, newArg); + } + Operation* clonedUnitary = nullptr; + for (Operation& op : oldBody) { + if (llvm::isa(op)) { + break; + } + Operation* cloned = rewriter.clone(op, mapping); + for (auto [oldRes, newRes] : + llvm::zip_equal(op.getResults(), cloned->getResults())) { + mapping.map(oldRes, newRes); + } + if (llvm::isa(cloned)) { + clonedUnitary = cloned; + } + } + assert(clonedUnitary != nullptr && + "modifier body must contain a unitary before yield"); + return resyncClonedUnitaryAndGetResults(clonedUnitary, rewriter); +} + +[[nodiscard]] static CtrlOp replaceCtrlPreservingBody(CtrlOp ctrl, + IRRewriter& rewriter) { + return rewriter.replaceOpWithNewOp( + ctrl, ctrl.getControlsIn(), ctrl.getTargetsIn(), + [&](ValueRange newArgs) -> llvm::SmallVector { + return cloneModifierBodyAndResyncUnitary(*ctrl.getBody(), newArgs, + rewriter); + }); +} + +[[nodiscard]] static InvOp replaceInvPreservingBody(InvOp inv, + IRRewriter& rewriter) { + rewriter.setInsertionPoint(inv); + InvOp newInv = + InvOp::create(rewriter, inv.getLoc(), inv.getQubitsIn(), + [&](ValueRange newArgs) -> llvm::SmallVector { + return cloneModifierBodyAndResyncUnitary( + *inv.getBody(), newArgs, rewriter); + }); + rewriter.replaceOp(inv, newInv.getResults()); + return newInv; +} + +/** + * @brief If @p cloned is ctrl/inv, rebuild it when qubit types are stale; else + * align a leaf unitary. Returns values that replace @p cloned's results. + */ +[[nodiscard]] static llvm::SmallVector +resyncClonedUnitaryAndGetResults(Operation* cloned, IRRewriter& rewriter) { + if (auto ctrl = dyn_cast(cloned)) { + if (!ctrlNeedsQubitTypeResync(ctrl)) { + return opResultsAsValues(ctrl.getOperation()); + } + return opResultsAsValues( + replaceCtrlPreservingBody(ctrl, rewriter).getOperation()); + } + if (auto inv = dyn_cast(cloned)) { + if (!invNeedsQubitTypeResync(inv)) { + return opResultsAsValues(inv.getOperation()); + } + return opResultsAsValues( + replaceInvPreservingBody(inv, rewriter).getOperation()); + } + return replaceLeafUnitaryWithAlignedQubitTypes( + llvm::cast(cloned), rewriter); +} - // Region entry arguments are fixed at build time; operand Value types - // update after alloc→static placement, but block argument types do not. +/** + * @brief Walk the function body and fix qubit SSA types after mapping. + * + * @details Post-order allows erasing/replacing the visited op safely. Ctrl and + * Inv are handled before the generic `UnitaryOpInterface` case so nested + * modifiers are not double-processed as leaf unitaries. + */ +static void synchronizeMappedQubitTypes(Region& region, IRRewriter& rewriter) { + region.walk([&](Operation* op) { + rewriter.setInsertionPoint(op); if (auto ctrl = dyn_cast(op)) { - Block& body = *ctrl.getBody(); - for (unsigned i = 0; i < ctrl.getNumTargets(); ++i) { - body.getArgument(i).setType(ctrl.getTargetsIn()[i].getType()); - } - } else if (auto inv = dyn_cast(op)) { - Block& body = *inv.getBody(); - for (unsigned i = 0; i < inv.getNumTargets(); ++i) { - body.getArgument(i).setType(inv.getQubitsIn()[i].getType()); + if (ctrlNeedsQubitTypeResync(ctrl)) { + (void)replaceCtrlPreservingBody(ctrl, rewriter); } + return WalkResult::advance(); } - - if (auto unitary = dyn_cast(op)) { - for (auto [input, output] : llvm::zip_equal(unitary.getInputQubits(), - unitary.getOutputQubits())) { - synchronizeQubitTypes(input, output); + if (auto inv = dyn_cast(op)) { + if (invNeedsQubitTypeResync(inv)) { + (void)replaceInvPreservingBody(inv, rewriter); } return WalkResult::advance(); } - if (auto measure = dyn_cast(op)) { - synchronizeQubitTypes(measure.getQubitIn(), measure.getQubitOut()); + if (measure.getQubitOut().getType() != measure.getQubitIn().getType()) { + SmallVector newTypes(measure.getResultTypes()); + newTypes[0] = measure.getQubitIn().getType(); + Operation* newOp = + cloneOpWithNewResultTypes(measure, newTypes, rewriter); + rewriter.replaceOp(measure, newOp->getResults()); + } return WalkResult::advance(); } - if (auto reset = dyn_cast(op)) { - synchronizeQubitTypes(reset.getQubitIn(), reset.getQubitOut()); + if (reset.getQubitOut().getType() != reset.getQubitIn().getType()) { + rewriter.replaceOpWithNewOp(reset, reset.getQubitIn()); + } + return WalkResult::advance(); + } + if (auto unitary = dyn_cast(op)) { + (void)replaceLeafUnitaryWithAlignedQubitTypes(unitary, rewriter); return WalkResult::advance(); } - return WalkResult::advance(); }); } @@ -433,7 +614,7 @@ struct MappingPass : impl::MappingPassBase { } commitTrial(*best, dyn, func.getFunctionBody(), rewriter); - synchronizeMappedQubitTypes(func.getFunctionBody()); + synchronizeMappedQubitTypes(func.getFunctionBody(), rewriter); } } From 01e9861909eaed1d803842625c2b740c4d79ed56 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Mon, 23 Mar 2026 14:39:23 +0100 Subject: [PATCH 36/57] =?UTF-8?q?=E2=9C=A8=20clang-tidy=20and=20coderabbit?= =?UTF-8?q?=20comments.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../QCO/Transforms/Mapping/Mapping.cpp | 81 +++++++++++++++---- 1 file changed, 65 insertions(+), 16 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index eb4410e7d3..10ad23cd55 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -68,36 +68,59 @@ namespace mlir::qco { /** True if any declared qubit result type disagrees with the paired operand. */ [[nodiscard]] static bool qubitOperandAndResultTypesDiffer(UnitaryOpInterface unitary) { - for (auto [input, output] : - llvm::zip_equal(unitary.getInputQubits(), unitary.getOutputQubits())) { - const auto qIn = dyn_cast(input.getType()); - const auto qOut = dyn_cast(output.getType()); - if (qIn && qOut && qIn != qOut) { - return true; - } - } - return false; + return llvm::any_of( + llvm::zip_equal(unitary.getInputQubits(), unitary.getOutputQubits()), + [](const auto& io) { + const auto& [input, output] = io; + const auto qIn = dyn_cast(input.getType()); + const auto qOut = dyn_cast(output.getType()); + return qIn && qOut && qIn != qOut; + }); } /** Region entry args vs target operands, for ctrl/inv verifier alignment. */ [[nodiscard]] static bool modifierBodyArgsMismatchTargetOperands(Block& body, OperandRange targetOperands) { - for (auto [arg, target] : - llvm::zip_equal(body.getArguments(), targetOperands)) { - if (arg.getType() != target.getType()) { - return true; - } - } - return false; + return llvm::any_of(llvm::zip_equal(body.getArguments(), targetOperands), + [](const auto& pair) { + const auto& [arg, target] = pair; + return arg.getType() != target.getType(); + }); } +/** + * @brief True if @p ctrl must be rebuilt so qubit types match after placement. + * + * @param ctrl Operation examined: sole region (targets + body args), operands + * (`getControlsIn()`, `getTargetsIn()`), and qubit results + * (`getOutputQubits()`). + * @return True when either (1) a body block argument type differs from the + * matching `getTargetsIn()` operand, or (2) any qubit result type + * differs from the paired input qubit type (via + * @ref modifierBodyArgsMismatchTargetOperands and + * @ref qubitOperandAndResultTypesDiffer). + * + * @details Pure predicate: no IR changes. Callers use this to skip redundant + * `replaceOpWithNewOp` work. + */ [[nodiscard]] static bool ctrlNeedsQubitTypeResync(CtrlOp ctrl) { return modifierBodyArgsMismatchTargetOperands(*ctrl.getBody(), ctrl.getTargetsIn()) || qubitOperandAndResultTypesDiffer(ctrl); } +/** + * @brief True if @p inv must be rebuilt so qubit types match after placement. + * + * @param inv Operation examined: sole region (`getBody()` args vs + * `getQubitsIn()` operands) and paired qubit results. + * @return True when body argument types disagree with `getQubitsIn()`, or qubit + * result types disagree with operands (same predicates as for @ref + * ctrlNeedsQubitTypeResync). + * + * @details Pure predicate: no IR changes. + */ [[nodiscard]] static bool invNeedsQubitTypeResync(InvOp inv) { return modifierBodyArgsMismatchTargetOperands(*inv.getBody(), inv.getQubitsIn()) || @@ -199,6 +222,20 @@ cloneModifierBodyAndResyncUnitary(Block& oldBody, ValueRange newTargetArgs, return resyncClonedUnitaryAndGetResults(clonedUnitary, rewriter); } +/** + * @brief Replace @p ctrl with a new `qco.ctrl` that keeps the same controls, + * targets, and logical body (ops before `qco.yield` are cloned). + * + * @param ctrl Modifier to replace. Must satisfy the usual `qco.ctrl` verifier + * (single unitary before yield, etc.). + * @param rewriter Drives insertion and erasure; insertion point is updated by + * `replaceOpWithNewOp`. + * @return The new `CtrlOp` whose region holds cloned ops and refreshed types. + * + * @note Side effects: erases @p ctrl and rewires uses to the new op's results; + * runs @ref cloneModifierBodyAndResyncUnitary in the builder callback + * (may recurse for nested ctrl/inv in the body). + */ [[nodiscard]] static CtrlOp replaceCtrlPreservingBody(CtrlOp ctrl, IRRewriter& rewriter) { return rewriter.replaceOpWithNewOp( @@ -209,6 +246,18 @@ cloneModifierBodyAndResyncUnitary(Block& oldBody, ValueRange newTargetArgs, }); } +/** + * @brief Replace @p inv with a new `qco.inv` that preserves operands and body + * structure (clone ops before `qco.yield`, then align qubit types). + * + * @param inv Modifier to replace; must verify as `qco.inv`. + * @param rewriter Drives insertion and erasure. + * @return The new `InvOp` after `replaceOp` has rewired uses away from @p inv. + * + * @note Side effects: erases @p inv; uses @ref + * cloneModifierBodyAndResyncUnitary in the body callback (may recurse for + * nested modifiers). + */ [[nodiscard]] static InvOp replaceInvPreservingBody(InvOp inv, IRRewriter& rewriter) { rewriter.setInsertionPoint(inv); From a202cd7eb545e7108b5423f679bb2b49ad6d15cd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 07:56:08 +0000 Subject: [PATCH 37/57] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 1453c47258..28273e06d7 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -19,9 +19,9 @@ #include #include #include +#include #include #include -#include #include #include #include From 4fdd34f0b9f3d49f71e9bc201e66e68a3194cf16 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 31 Mar 2026 10:17:32 +0200 Subject: [PATCH 38/57] =?UTF-8?q?=F0=9F=94=80=20Resolve=20changes=20after?= =?UTF-8?q?=20merge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/include/mlir/Dialect/QCO/Utils/Drivers.h | 9 +++-- .../QCO/Transforms/Mapping/Mapping.cpp | 33 ------------------- 2 files changed, 6 insertions(+), 36 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Utils/Drivers.h b/mlir/include/mlir/Dialect/QCO/Utils/Drivers.h index cce4590efc..c014e0ef0a 100644 --- a/mlir/include/mlir/Dialect/QCO/Utils/Drivers.h +++ b/mlir/include/mlir/Dialect/QCO/Utils/Drivers.h @@ -87,9 +87,12 @@ template void walkUnit(Region& region, Fn&& fn) { }; TypeSwitch(&curr) - .template Case( - [&](StaticOp op) { qubits.add(op.getQubit(), op.getIndex()); }) - .template Case([&](AllocOp op) { qubits.add(op.getResult()); }) + .template Case([&](StaticOp op) { + qubits.add(cast>(op.getQubit()), op.getIndex()); + }) + .template Case([&](AllocOp op) { + qubits.add(cast>(op.getResult())); + }) .template Case([&](UnitaryOpInterface op) { for (const auto& [prevV, nextV] : llvm::zip(op.getInputQubits(), op.getOutputQubits())) { diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 28273e06d7..c285ed4ff0 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -788,39 +788,6 @@ struct MappingPass : impl::MappingPassBase { return std::make_pair(ltr, rtl); } - /** - * @brief Perform placement. - * @details Replaces dynamic with static qubits. Extends the computation with - * as many static qubits the architecture supports. - * @returns vector of SSA values produced by qco.static operations, ordered by - * the static index s.t. [i] = qco.static i. - */ - [[nodiscard]] static SmallVector - place(ArrayRef dynQubits, const Layout& layout, Region& funcBody, - IRRewriter& rewriter) { - SmallVector statics(layout.nqubits()); - - // 1. Replace existing dynamic allocations with mapped static ones. - for (const auto [p, q] : enumerate(dynQubits)) { - const auto hw = layout.getHardwareIndex(p); - rewriter.setInsertionPoint(q.getDefiningOp()); - auto op = rewriter.replaceOpWithNewOp(q.getDefiningOp(), hw); - statics[hw] = llvm::cast>(op.getQubit()); - } - - // 2. Create static qubits for the remaining (unused) hardware indices. - for (std::size_t p = dynQubits.size(); p < layout.nqubits(); ++p) { - rewriter.setInsertionPointToStart(&funcBody.front()); - const auto hw = layout.getHardwareIndex(p); - auto op = StaticOp::create(rewriter, funcBody.getLoc(), hw); - rewriter.setInsertionPoint(funcBody.back().getTerminator()); - DeallocOp::create(rewriter, funcBody.getLoc(), op.getQubit()); - statics[hw] = llvm::cast>(op.getQubit()); - } - - return statics; - } - /** * @brief Perform A* search to find a sequence of SWAPs that makes the * two-qubit operations inside the first layer (the front) executable. From 5b5251168fba4fbe581ffc41093f2970d2dee3c0 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 31 Mar 2026 11:12:24 +0200 Subject: [PATCH 39/57] =?UTF-8?q?=F0=9F=94=84=20Rename=20`dealloc`=20to=20?= =?UTF-8?q?`sink`=20in=20QCO=20dialect=20for=20qubit=20management?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Dialect/QCO/Builder/QCOProgramBuilder.h | 12 +++--- mlir/include/mlir/Dialect/QCO/IR/QCOOps.td | 17 ++++---- mlir/include/mlir/Dialect/QCO/Utils/Drivers.h | 4 +- mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp | 18 ++++---- mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp | 11 +++-- mlir/lib/Conversion/QCOToQC/QCOToQC.cpp | 12 +++--- mlir/lib/Conversion/QCToQCO/QCToQCO.cpp | 16 +++---- .../Dialect/QCO/Builder/QCOProgramBuilder.cpp | 6 +-- .../{DeallocOp.cpp => SinkOp.cpp} | 15 +++---- .../QCO/Transforms/Mapping/Mapping.cpp | 42 +++++++++---------- mlir/lib/Dialect/QCO/Utils/WireIterator.cpp | 13 +++--- mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp | 9 ++-- .../QCO/Transforms/Mapping/test_mapping.cpp | 8 ++-- .../Dialect/QCO/Utils/test_drivers.cpp | 8 ++-- .../Dialect/QCO/Utils/test_wireiterator.cpp | 8 ++-- mlir/unittests/programs/qco_programs.cpp | 4 +- mlir/unittests/programs/qco_programs.h | 4 +- 17 files changed, 103 insertions(+), 104 deletions(-) rename mlir/lib/Dialect/QCO/IR/QubitManagement/{DeallocOp.cpp => SinkOp.cpp} (61%) diff --git a/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h b/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h index f86737ba5a..6520d83702 100644 --- a/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h @@ -1228,24 +1228,24 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { //===--------------------------------------------------------------------===// /** - * @brief Explicitly deallocate a qubit + * @brief Consume a qubit value (end of lifetime) * * @details * Validates and removes the qubit from tracking. Optional; `finalize()` - * automatically deallocates all remaining qubits. + * automatically sinks all remaining qubits. * - * @param qubit Qubit to deallocate (must be valid/unconsumed) + * @param qubit Qubit to sink (must be valid/unconsumed) * @return Reference to this builder for method chaining * * @par Example: * ```c++ - * builder.dealloc(q); + * builder.sink(q); * ``` * ```mlir - * qco.dealloc %q : !qco.qubit + * qco.sink %q : !qco.qubit * ``` */ - QCOProgramBuilder& dealloc(Value qubit); + QCOProgramBuilder& sink(Value qubit); //===--------------------------------------------------------------------===// // SCF operations diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td index dcdeb1bcbb..031901ccb3 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td @@ -93,19 +93,20 @@ def AllocOp : QCOOp<"alloc", [MemoryEffects<[MemAlloc]>]> { let hasVerifier = 1; } -def DeallocOp : QCOOp<"dealloc", [MemoryEffects<[MemFree]>]> { - let summary = "Deallocate a qubit"; +def SinkOp : QCOOp<"sink", [MemoryEffects<[MemFree]>]> { + let summary = "Consume a qubit value (end of lifetime)"; let description = [{ - Deallocates a qubit. + Consumes a qubit SSA value and marks the end of its lifetime. - In QCO's value/linear semantics, this operation also serves as the sink - that consumes the qubit SSA value (ensuring every qubit value is used - exactly once). When converting back to QC (reference semantics), deallocs - corresponding to static qubits may be erased. + This operation is the canonical "sink" for QCO's linear/value semantics: + every qubit value must be consumed exactly once on all paths. + + When converting back to QC (reference semantics), sinks corresponding to + static qubits may be erased. Example: ```mlir - qco.dealloc %q : !qco.qubit + qco.sink %q : !qco.qubit ``` }]; diff --git a/mlir/include/mlir/Dialect/QCO/Utils/Drivers.h b/mlir/include/mlir/Dialect/QCO/Utils/Drivers.h index c014e0ef0a..2b4b0553f9 100644 --- a/mlir/include/mlir/Dialect/QCO/Utils/Drivers.h +++ b/mlir/include/mlir/Dialect/QCO/Utils/Drivers.h @@ -107,8 +107,8 @@ template void walkUnit(Region& region, Fn&& fn) { .template Case([&](MeasureOp op) { qubits.remap(op.getQubitIn(), op.getQubitOut()); }) - .template Case( - [&](DeallocOp op) { qubits.remove(op.getQubit()); }); + .template Case( + [&](SinkOp op) { qubits.remove(op.getQubit()); }); } } } // namespace mlir::qco diff --git a/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp b/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp index 5599753f96..24e467b99c 100644 --- a/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp +++ b/mlir/lib/Conversion/JeffToQCO/JeffToQCO.cpp @@ -406,7 +406,7 @@ struct ConvertJeffQubitAllocOpToQCO final }; /** - * @brief Converts jeff.qubit_free to qco.reset + qco.dealloc + * @brief Converts jeff.qubit_free to qco.reset + qco.sink * * @par Example: * ```mlir @@ -415,7 +415,7 @@ struct ConvertJeffQubitAllocOpToQCO final * is converted to * ```mlir * %q_out = qco.reset %q_in : !qco.qubit - * qco.dealloc %q_out : !qco.qubit + * qco.sink %q_out : !qco.qubit * ``` */ struct ConvertJeffQubitFreeOpToQCO final @@ -427,13 +427,13 @@ struct ConvertJeffQubitFreeOpToQCO final ConversionPatternRewriter& rewriter) const override { auto resetOp = qco::ResetOp::create(rewriter, op.getLoc(), adaptor.getInQubit()); - rewriter.replaceOpWithNewOp(op, resetOp.getQubitOut()); + rewriter.replaceOpWithNewOp(op, resetOp.getQubitOut()); return success(); } }; /** - * @brief Converts jeff.qubit_free_zero to qco.dealloc + * @brief Converts jeff.qubit_free_zero to qco.sink * * @par Example: * ```mlir @@ -441,7 +441,7 @@ struct ConvertJeffQubitFreeOpToQCO final * ``` * is converted to * ```mlir - * qco.dealloc %q : !qco.qubit + * qco.sink %q : !qco.qubit * ``` */ struct ConvertJeffQubitFreeZeroOpToQCO final @@ -451,13 +451,13 @@ struct ConvertJeffQubitFreeZeroOpToQCO final LogicalResult matchAndRewrite(jeff::QubitFreeZeroOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { - rewriter.replaceOpWithNewOp(op, adaptor.getInQubit()); + rewriter.replaceOpWithNewOp(op, adaptor.getInQubit()); return success(); } }; /** - * @brief Converts jeff.qubit_measure to qco.measure + qco.dealloc + * @brief Converts jeff.qubit_measure to qco.measure + qco.sink * * @par Example: * ```mlir @@ -466,7 +466,7 @@ struct ConvertJeffQubitFreeZeroOpToQCO final * is converted to * ```mlir * %q_out, %result = qco.measure %q_in : !qco.qubit - * qco.dealloc %q_out : !qco.qubit + * qco.sink %q_out : !qco.qubit * ``` */ struct ConvertJeffQubitMeasureOpToQCO final @@ -479,7 +479,7 @@ struct ConvertJeffQubitMeasureOpToQCO final auto loc = op.getLoc(); auto measureOp = qco::MeasureOp::create(rewriter, loc, adaptor.getInQubit()); - qco::DeallocOp::create(rewriter, loc, measureOp.getQubitOut()); + qco::SinkOp::create(rewriter, loc, measureOp.getQubitOut()); rewriter.replaceOp(op, measureOp.getResult()); return success(); } diff --git a/mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp b/mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp index 124c095060..01ed0f82a8 100644 --- a/mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp +++ b/mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp @@ -269,23 +269,22 @@ struct ConvertQCOAllocOpToJeff final }; /** - * @brief Converts qco.dealloc to jeff.qubit_free_zero + * @brief Converts qco.sink to jeff.qubit_free_zero * * @par Example: * ```mlir - * qco.dealloc %q : !qco.qubit + * qco.sink %q : !qco.qubit * ``` * is converted to * ```mlir * jeff.qubit_free_zero %q : !jeff.qubit * ``` */ -struct ConvertQCODeallocOpToJeff final - : StatefulOpConversionPattern { +struct ConvertQCOSinkOpToJeff final : StatefulOpConversionPattern { using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(qco::DeallocOp op, OpAdaptor adaptor, + matchAndRewrite(qco::SinkOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { rewriter.replaceOpWithNewOp(op, adaptor.getQubit()); return success(); @@ -1364,7 +1363,7 @@ struct QCOToJeff final : impl::QCOToJeffBase { // Register operation conversion patterns jeff::populateNativeToJeffConversionPatterns(patterns); patterns.add< - ConvertQCOAllocOpToJeff, ConvertQCODeallocOpToJeff, + ConvertQCOAllocOpToJeff, ConvertQCOSinkOpToJeff, ConvertQCOMeasureOpToJeff, ConvertQCOResetOpToJeff, ConvertQCOGPhaseOpToJeff, ConvertQCOOneTargetZeroParameterToJeff, diff --git a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp index 0f74f99045..5f454ca3f0 100644 --- a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -96,7 +96,7 @@ struct ConvertQCOAllocOp final : OpConversionPattern { }; /** - * @brief Converts qco.dealloc to qc.dealloc (dynamic) or erases it (static). + * @brief Converts qco.sink to qc.dealloc (dynamic) or erases it (static). * * @details * For dynamic qubits (`!qco.qubit`), converts to `qc.dealloc`. @@ -105,23 +105,23 @@ struct ConvertQCOAllocOp final : OpConversionPattern { * * Example transformation (dynamic): * ```mlir - * qco.dealloc %q_qco : !qco.qubit + * qco.sink %q_qco : !qco.qubit * // becomes: * qc.dealloc %q_qc : !qc.qubit * ``` * * Example transformation (static): * ```mlir - * qco.dealloc %q_qco : !qco.qubit + * qco.sink %q_qco : !qco.qubit * // becomes: * (erased) * ``` */ -struct ConvertQCODeallocOp final : OpConversionPattern { +struct ConvertQCOSinkOp final : OpConversionPattern { using OpConversionPattern::OpConversionPattern; LogicalResult - matchAndRewrite(qco::DeallocOp op, OpAdaptor adaptor, + matchAndRewrite(qco::SinkOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { if (op.getQubit().getType().getIsStatic()) { rewriter.eraseOp(op); @@ -769,7 +769,7 @@ struct QCOToQC final : impl::QCOToQCBase { // Register operation conversion patterns // Note: No state tracking needed - OpAdaptors handle type conversion patterns.add< - ConvertQCOAllocOp, ConvertQCODeallocOp, ConvertQCOStaticOp, + ConvertQCOAllocOp, ConvertQCOSinkOp, ConvertQCOStaticOp, ConvertQCOMeasureOp, ConvertQCOResetOp, ConvertQCOZeroTargetOneParameterToQC, ConvertQCOOneTargetZeroParameterToQC, diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index 1999197563..8d7c5ea5c2 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -276,7 +276,7 @@ namespace { * QC uses reference semantics and does not enforce linear typing for qubits. * After conversion, QCO requires that every qubit SSA value is consumed * exactly once. For allocations (including static qubits), the sink is - * `qco.dealloc`. This pattern inserts `qco.dealloc` operations for all + * `qco.sink`. This pattern inserts `qco.sink` operations for all * still-live qubits tracked in the lowering state right before the return. */ struct ConvertFuncReturnOp final : StatefulOpConversionPattern { @@ -319,7 +319,7 @@ struct ConvertFuncReturnOp final : StatefulOpConversionPattern { llvm::sort(liveQubitsSorted, SSAOrder{}); for (Value qubit : liveQubitsSorted) { - qco::DeallocOp::create(rewriter, op.getLoc(), qubit); + qco::SinkOp::create(rewriter, op.getLoc(), qubit); } state.qubitMap.erase(funcRegion); @@ -397,18 +397,18 @@ struct ConvertQCAllocOp final : StatefulOpConversionPattern { }; /** - * @brief Converts qc.dealloc to qco.dealloc + * @brief Converts qc.dealloc to qco.sink * * @details * Deallocates a qubit by looking up its latest QCO value and creating - * a corresponding qco.dealloc operation. The mapping is removed from + * a corresponding qco.sink operation. The mapping is removed from * the state as the qubit is no longer in use. * * Example transformation: * ```mlir * qc.dealloc %q : !qc.qubit * // becomes (where %q maps to %q_final): - * qco.dealloc %q_final : !qco.qubit + * qco.sink %q_final : !qco.qubit * ``` */ struct ConvertQCDeallocOp final : StatefulOpConversionPattern { @@ -424,8 +424,8 @@ struct ConvertQCDeallocOp final : StatefulOpConversionPattern { Value qcQubit = op.getQubit(); Value qcoQubit = lookupMappedQubit(state, operation, qcQubit); - // Create the dealloc operation - rewriter.replaceOpWithNewOp(op, qcoQubit); + // Create the sink operation + rewriter.replaceOpWithNewOp(op, qcoQubit); // Remove from state as qubit is no longer in use qubitMap.erase(qcQubit); @@ -1130,7 +1130,7 @@ struct QCToQCO final : impl::QCToQCOBase { // Conversion of qc types in func.return // // Note: `func.return` may already be type-legal even though we still need - // to insert sink operations (`qco.dealloc`) for remaining live + // to insert sink operations (`qco.sink`) for remaining live // qubits. Therefore, we make it dynamically illegal unless the lowering // state has no remaining qubits. patterns.add(typeConverter, context, &state); diff --git a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp index 327266b804..633b46bf1a 100644 --- a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp +++ b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp @@ -810,13 +810,13 @@ ValueRange QCOProgramBuilder::inv( // Deallocation //===----------------------------------------------------------------------===// -QCOProgramBuilder& QCOProgramBuilder::dealloc(Value qubit) { +QCOProgramBuilder& QCOProgramBuilder::sink(Value qubit) { checkFinalized(); validateQubitValue(qubit); validQubits.erase(qubit); - DeallocOp::create(*this, qubit); + SinkOp::create(*this, qubit); return *this; } @@ -924,7 +924,7 @@ OwningOpRef QCOProgramBuilder::finalize() { llvm::sort(sortedQubits, SSAOrder{}); for (auto qubit : sortedQubits) { - DeallocOp::create(*this, qubit); + SinkOp::create(*this, qubit); } // Automatically deallocate all still-allocated tensors diff --git a/mlir/lib/Dialect/QCO/IR/QubitManagement/DeallocOp.cpp b/mlir/lib/Dialect/QCO/IR/QubitManagement/SinkOp.cpp similarity index 61% rename from mlir/lib/Dialect/QCO/IR/QubitManagement/DeallocOp.cpp rename to mlir/lib/Dialect/QCO/IR/QubitManagement/SinkOp.cpp index 0de300cb7d..62ce1a384b 100644 --- a/mlir/lib/Dialect/QCO/IR/QubitManagement/DeallocOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/QubitManagement/SinkOp.cpp @@ -12,7 +12,6 @@ #include #include -#include #include #include @@ -22,21 +21,19 @@ using namespace mlir::qco; namespace { /** - * @brief Remove matching allocation and deallocation pairs without operations + * @brief Remove matching allocation/static and sink pairs without operations * between them. */ -struct RemoveAllocDeallocPair final : OpRewritePattern { +struct RemoveAllocSinkPair final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; - LogicalResult matchAndRewrite(DeallocOp op, + LogicalResult matchAndRewrite(SinkOp op, PatternRewriter& rewriter) const override { - // Check if the predecessor is an AllocOp or a StaticOp auto* defOp = op.getQubit().getDefiningOp(); if (!llvm::isa(defOp)) { return failure(); } - // Remove the AllocOp/StaticOp and the DeallocOp rewriter.eraseOp(op); rewriter.eraseOp(defOp); return success(); @@ -45,7 +42,7 @@ struct RemoveAllocDeallocPair final : OpRewritePattern { } // namespace -void DeallocOp::getCanonicalizationPatterns(RewritePatternSet& results, - MLIRContext* context) { - results.add(context); +void SinkOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); } diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index c285ed4ff0..15d0990126 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -130,13 +129,6 @@ modifierBodyArgsMismatchTargetOperands(Block& body, qubitOperandAndResultTypesDiffer(inv); } -/** @brief Copies op results into `SmallVector` (OpResult → Value). */ -[[nodiscard]] static llvm::SmallVector opResultsAsValues(Operation* op) { - llvm::SmallVector vals; - llvm::append_range(vals, op->getResults()); - return vals; -} - /** * @brief Duplicate @p op with new result types; clone attached regions. * @param rewriter Insertion point is set to @p op before create. @@ -174,7 +166,8 @@ replaceLeafUnitaryWithAlignedQubitTypes(UnitaryOpInterface unitary, IRRewriter& rewriter) { Operation* op = unitary.getOperation(); if (op->getNumRegions() != 0 || !qubitOperandAndResultTypesDiffer(unitary)) { - return opResultsAsValues(op); + return llvm::to_vector(llvm::map_range( + op->getResults(), [](OpResult res) -> Value { return res; })); } SmallVector newTypes(op->getResultTypes()); for (auto [input, output] : @@ -187,7 +180,8 @@ replaceLeafUnitaryWithAlignedQubitTypes(UnitaryOpInterface unitary, } Operation* newOp = cloneOpWithNewResultTypes(op, newTypes, rewriter); rewriter.replaceOp(op, newOp->getResults()); - return opResultsAsValues(newOp); + return llvm::to_vector(llvm::map_range( + newOp->getResults(), [](OpResult res) -> Value { return res; })); } /** @@ -282,17 +276,21 @@ cloneModifierBodyAndResyncUnitary(Block& oldBody, ValueRange newTargetArgs, resyncClonedUnitaryAndGetResults(Operation* cloned, IRRewriter& rewriter) { if (auto ctrl = dyn_cast(cloned)) { if (!ctrlNeedsQubitTypeResync(ctrl)) { - return opResultsAsValues(ctrl.getOperation()); + return llvm::to_vector(llvm::map_range( + ctrl->getResults(), [](OpResult res) -> Value { return res; })); } - return opResultsAsValues( - replaceCtrlPreservingBody(ctrl, rewriter).getOperation()); + return llvm::to_vector( + llvm::map_range(replaceCtrlPreservingBody(ctrl, rewriter)->getResults(), + [](OpResult res) -> Value { return res; })); } if (auto inv = dyn_cast(cloned)) { if (!invNeedsQubitTypeResync(inv)) { - return opResultsAsValues(inv.getOperation()); + return llvm::to_vector(llvm::map_range( + inv->getResults(), [](OpResult res) -> Value { return res; })); } - return opResultsAsValues( - replaceInvPreservingBody(inv, rewriter).getOperation()); + return llvm::to_vector( + llvm::map_range(replaceInvPreservingBody(inv, rewriter)->getResults(), + [](OpResult res) -> Value { return res; })); } return replaceLeafUnitaryWithAlignedQubitTypes( llvm::cast(cloned), rewriter); @@ -1004,11 +1002,11 @@ struct MappingPass : impl::MappingPassBase { return WalkResult::interrupt(); }) - .template Case([&](auto) { - std::ranges::advance(it, step); - return WalkResult::advance(); - }) + .template Case( + [&](auto) { + std::ranges::advance(it, step); + return WalkResult::advance(); + }) .Default([&](Operation* op) { report_fatal_error("unknown op in wireiterator use: " + op->getName().getStringRef()); @@ -1086,7 +1084,7 @@ struct MappingPass : impl::MappingPassBase { const auto hw = layout.getHardwareIndex(p); auto op = StaticOp::create(rewriter, rewriter.getUnknownLoc(), hw); rewriter.setInsertionPoint(funcBody.back().getTerminator()); - DeallocOp::create(rewriter, rewriter.getUnknownLoc(), op.getQubit()); + SinkOp::create(rewriter, rewriter.getUnknownLoc(), op.getQubit()); } } diff --git a/mlir/lib/Dialect/QCO/Utils/WireIterator.cpp b/mlir/lib/Dialect/QCO/Utils/WireIterator.cpp index 7d91cd88c3..c9f60b1eb3 100644 --- a/mlir/lib/Dialect/QCO/Utils/WireIterator.cpp +++ b/mlir/lib/Dialect/QCO/Utils/WireIterator.cpp @@ -24,8 +24,8 @@ namespace mlir::qco { mlir::Value WireIterator::qubit() const { - // A deallocation doesn't have an OpResult. - if (op_ != nullptr && mlir::isa(op_)) { + // A sink/deallocation doesn't have an OpResult. + if (op_ != nullptr && mlir::isa(op_)) { return nullptr; } return qubit_; @@ -41,8 +41,8 @@ void WireIterator::forward() { assert(qubit_.getNumUses() == 1 && "expected linear typing"); op_ = *(qubit_.getUsers().begin()); - // A deallocation op defines the end of the qubit wire (dynamic and static). - if (mlir::isa(op_)) { + // A sink op defines the end of the qubit wire (dynamic and static). + if (mlir::isa(op_)) { isSentinel_ = true; return; } @@ -69,8 +69,9 @@ void WireIterator::backward() { return; } - // For deallocations, qubit_ is an OpOperand. Hence, only get the def-op. - if (mlir::isa(op_)) { + // For sinks/deallocations, qubit_ is an OpOperand. Hence, only get the + // def-op. + if (mlir::isa(op_)) { op_ = qubit_.getDefiningOp(); return; } diff --git a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp index aad6ebe783..be31cd95cb 100644 --- a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp +++ b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp @@ -99,7 +99,10 @@ TEST_F(QCOTest, DirectIfBuilder) { // Test If construction directly qco::QCOProgramBuilder builder(context.get()); builder.initialize(); - auto q0 = AllocOp::create(builder); + // Match `allocQubitRegister(1)` defaults ("q", size=1, index=0). + auto q0 = AllocOp::create(builder, builder.getStringAttr("q"), + builder.getI64IntegerAttr(1), + builder.getI64IntegerAttr(0)); auto q1 = HOp::create(builder, q0); auto measureOp = MeasureOp::create(builder, q1); auto ifOp = @@ -108,7 +111,7 @@ TEST_F(QCOTest, DirectIfBuilder) { auto innerQubit = XOp::create(builder, qubits[0]); return llvm::SmallVector{innerQubit}; }); - DeallocOp::create(builder, ifOp.getResult(0)); + SinkOp::create(builder, ifOp.getResult(0)); auto directBuilder = builder.finalize(); ASSERT_TRUE(directBuilder); @@ -1078,7 +1081,7 @@ INSTANTIATE_TEST_SUITE_P( QCOTestCase{"MixedStaticDynamicQubits", MQT_NAMED_BUILDER(mixedStaticDynamicQubits), MQT_NAMED_BUILDER(mixedStaticDynamicQubits)}, - QCOTestCase{"AllocDeallocPair", MQT_NAMED_BUILDER(allocDeallocPair), + QCOTestCase{"AllocSinkPair", MQT_NAMED_BUILDER(allocSinkPair), MQT_NAMED_BUILDER(emptyQCO)})); /// @} diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp index 366bde71ad..e2aaca20f4 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -165,8 +165,8 @@ TEST_F(MappingPassTestBase, SynchronizesParameterizedGateAndMeasureTypes) { (void)measuredBit; q0 = measuredQubit; - builder.dealloc(q0); - builder.dealloc(q1); + builder.sink(q0); + builder.sink(q1); auto moduleOp = builder.finalize(); runQCOMapping(moduleOp); @@ -223,8 +223,8 @@ TEST_F(MappingPassTestBase, SynchronizesModifierRegionArguments) { }); q1 = invOut.front(); - builder.dealloc(q0); - builder.dealloc(q1); + builder.sink(q0); + builder.sink(q1); auto moduleOp = builder.finalize(); runQCOMapping(moduleOp); diff --git a/mlir/unittests/Dialect/QCO/Utils/test_drivers.cpp b/mlir/unittests/Dialect/QCO/Utils/test_drivers.cpp index 39b16525bd..80622812d0 100644 --- a/mlir/unittests/Dialect/QCO/Utils/test_drivers.cpp +++ b/mlir/unittests/Dialect/QCO/Utils/test_drivers.cpp @@ -57,10 +57,10 @@ TEST_F(DriversTest, FullWalk) { const auto [q22, c2] = builder.measure(q21); const auto [q32, c3] = builder.measure(q31); - builder.dealloc(q03); - builder.dealloc(q12); - builder.dealloc(q22); - builder.dealloc(q32); + builder.sink(q03); + builder.sink(q12); + builder.sink(q22); + builder.sink(q32); auto module = builder.finalize(); auto func = *(module->getOps().begin()); diff --git a/mlir/unittests/Dialect/QCO/Utils/test_wireiterator.cpp b/mlir/unittests/Dialect/QCO/Utils/test_wireiterator.cpp index fdc2b758da..7eb59db509 100644 --- a/mlir/unittests/Dialect/QCO/Utils/test_wireiterator.cpp +++ b/mlir/unittests/Dialect/QCO/Utils/test_wireiterator.cpp @@ -52,8 +52,8 @@ TEST_P(WireIteratorTest, MixedUse) { const auto [q02, q11] = builder.cx(q01, q10); const auto [q03, c0] = builder.measure(q02); const auto q04 = builder.reset(q03); - builder.dealloc(q04); - builder.dealloc(q11); + builder.sink(q04); + builder.sink(q11); [[maybe_unused]] auto module = builder.finalize(); // Setup WireIterator. @@ -83,7 +83,7 @@ TEST_P(WireIteratorTest, MixedUse) { ASSERT_EQ(it.qubit(), q04); ++it; - ASSERT_EQ(it.operation(), *(q04.getUsers().begin())); // qco.dealloc + ASSERT_EQ(it.operation(), *(q04.getUsers().begin())); // qco.sink ASSERT_EQ(it.qubit(), nullptr); ++it; @@ -97,7 +97,7 @@ TEST_P(WireIteratorTest, MixedUse) { // --it; - ASSERT_EQ(it.operation(), *(q04.getUsers().begin())); // qco.dealloc + ASSERT_EQ(it.operation(), *(q04.getUsers().begin())); // qco.sink ASSERT_EQ(it.qubit(), nullptr); --it; diff --git a/mlir/unittests/programs/qco_programs.cpp b/mlir/unittests/programs/qco_programs.cpp index 5c2995f34a..598e18c295 100644 --- a/mlir/unittests/programs/qco_programs.cpp +++ b/mlir/unittests/programs/qco_programs.cpp @@ -79,9 +79,9 @@ void mixedStaticDynamicQubits(QCOProgramBuilder& b) { q1 = b.h(q1); } -void allocDeallocPair(QCOProgramBuilder& b) { +void allocSinkPair(QCOProgramBuilder& b) { auto q = b.allocQubit(); - b.dealloc(q); + b.sink(q); } void singleMeasurementToSingleBit(QCOProgramBuilder& b) { diff --git a/mlir/unittests/programs/qco_programs.h b/mlir/unittests/programs/qco_programs.h index 29768bb731..026fbf13c2 100644 --- a/mlir/unittests/programs/qco_programs.h +++ b/mlir/unittests/programs/qco_programs.h @@ -51,8 +51,8 @@ void staticQubitsWithInv(QCOProgramBuilder& b); /// Allocates one static and one dynamic qubit and applies mixed operations. void mixedStaticDynamicQubits(QCOProgramBuilder& b); -/// Allocates and explicitly deallocates a single qubit. -void allocDeallocPair(QCOProgramBuilder& b); +/// Allocates and explicitly sinks a single qubit. +void allocSinkPair(QCOProgramBuilder& b); // --- MeasureOp ------------------------------------------------------------ // From 6fe5d43b0c1971d67c02bd7dad0efa49b922c8b7 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 31 Mar 2026 13:51:26 +0200 Subject: [PATCH 40/57] =?UTF-8?q?=F0=9F=94=A7=20Refactor=20QubitType=20han?= =?UTF-8?q?dling=20in=20QC=20and=20QCO=20dialects?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/include/mlir/Dialect/QC/IR/QCOps.td | 34 ++------ mlir/include/mlir/Dialect/QC/IR/QCTypes.td | 8 -- mlir/include/mlir/Dialect/QCO/IR/QCOOps.td | 30 ++----- mlir/include/mlir/Dialect/QCO/IR/QCOTypes.td | 8 -- mlir/lib/Conversion/QCOToQC/QCOToQC.cpp | 83 ++++++++++++------- mlir/lib/Conversion/QCToQCO/QCToQCO.cpp | 22 ++++- mlir/lib/Dialect/QC/IR/QCOps.cpp | 18 ---- mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp | 4 +- mlir/lib/Dialect/QCO/IR/QCOOps.cpp | 32 +------ .../Dialect/QTensor/IR/Operations/AllocOp.cpp | 12 +-- .../Compiler/test_compiler_pipeline.cpp | 5 -- .../Conversion/QCOToQC/test_qco_to_qc.cpp | 22 ++++- .../Conversion/QCToQCO/test_qc_to_qco.cpp | 22 ++++- .../Conversion/QCToQIR/test_qc_to_qir.cpp | 3 - .../QCO/Transforms/Mapping/test_mapping.cpp | 2 +- 15 files changed, 138 insertions(+), 167 deletions(-) diff --git a/mlir/include/mlir/Dialect/QC/IR/QCOps.td b/mlir/include/mlir/Dialect/QC/IR/QCOps.td index 1272bc0f24..0255b54a2f 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCOps.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCOps.td @@ -26,21 +26,6 @@ include "mlir/Interfaces/SideEffectInterfaces.td" class QCOp traits = []> : Op; -//===----------------------------------------------------------------------===// -// Type Constraints -//===----------------------------------------------------------------------===// - -def DynamicQubit - : Type($_self)">, - CPred<"!::mlir::cast<::mlir::qc::QubitType>($_self)." - "getIsStatic()">]>, - "dynamic qubit type (!qc.qubit)">; - -def StaticQubit : Type($_self)">, - CPred<"::mlir::cast<::mlir::qc::QubitType>($_self)." - "getIsStatic()">]>, - "static qubit type (!qc.qubit)">; - //===----------------------------------------------------------------------===// // Resource Operations //===----------------------------------------------------------------------===// @@ -72,20 +57,20 @@ def AllocOp : QCOp<"alloc", [MemoryEffects<[MemAlloc]>]> { let arguments = (ins OptionalAttr:$register_name, OptionalAttr>:$register_size, OptionalAttr>:$register_index); - let results = (outs DynamicQubit:$result); + let results = (outs QubitType:$result); let assemblyFormat = [{ (`(` $register_name^ `,` $register_size `,` $register_index `)`)? attr-dict `:` type($result) }]; let builders = [OpBuilder<(ins), [{ - build($_builder, $_state, QubitType::get($_builder.getContext(), /*isStatic=*/false), nullptr, nullptr, nullptr); + build($_builder, $_state, QubitType::get($_builder.getContext()), nullptr, nullptr, nullptr); }]>, OpBuilder<(ins "::mlir::StringAttr":$register_name, "::mlir::IntegerAttr":$register_size, "::mlir::IntegerAttr":$register_index), [{ - build($_builder, $_state, QubitType::get($_builder.getContext(), /*isStatic=*/false), + build($_builder, $_state, QubitType::get($_builder.getContext()), register_name, register_size, register_index); }]>]; @@ -93,10 +78,9 @@ def AllocOp : QCOp<"alloc", [MemoryEffects<[MemAlloc]>]> { } def DeallocOp : QCOp<"dealloc", [MemoryEffects<[MemFree]>]> { - let summary = "Deallocate a dynamically allocated qubit"; + let summary = "Deallocate a qubit"; let description = [{ - Deallocates a dynamically allocated qubit, releasing its resources. - Static qubits (`!qc.qubit`) cannot be deallocated. + Deallocates a qubit, releasing its resources. Example: ```mlir @@ -104,7 +88,7 @@ def DeallocOp : QCOp<"dealloc", [MemoryEffects<[MemFree]>]> { ``` }]; - let arguments = (ins DynamicQubit:$qubit); + let arguments = (ins QubitType:$qubit); let assemblyFormat = "$qubit attr-dict `:` type($qubit)"; let hasCanonicalizer = 1; @@ -119,16 +103,16 @@ def StaticOp : QCOp<"static", [Pure]> { Example: ```mlir - %q = qc.static 0 : !qc.qubit + %q = qc.static 0 : !qc.qubit ``` }]; let arguments = (ins ConfinedAttr:$index); - let results = (outs StaticQubit:$qubit); + let results = (outs QubitType:$qubit); let assemblyFormat = "$index attr-dict `:` type($qubit)"; let builders = [OpBuilder<(ins "uint64_t":$index), [{ - build($_builder, $_state, QubitType::get($_builder.getContext(), /*isStatic=*/true), index); + build($_builder, $_state, QubitType::get($_builder.getContext()), index); }]>]; } diff --git a/mlir/include/mlir/Dialect/QC/IR/QCTypes.td b/mlir/include/mlir/Dialect/QC/IR/QCTypes.td index bc4ab8852c..85d36311fa 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCTypes.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCTypes.td @@ -25,15 +25,7 @@ def QubitType : QCType<"Qubit", "qubit"> { QC dialect. Operations using this type modify qubits in place using reference semantics, similar to how classical imperative languages handle mutable references. - - `!qc.qubit` (default) denotes a dynamically allocated qubit. - `!qc.qubit` denotes a qubit with a statically known identifier. }]; - let parameters = (ins DefaultValuedParameter<"bool", "false">:$isStatic); - let builders = [TypeBuilder<(ins), [{ - return $_get($_ctxt, /*isStatic=*/false); - }]>]; - let hasCustomAssemblyFormat = 1; } #endif // MLIR_DIALECT_QC_IR_QCTYPES_TD diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td index 031901ccb3..b310bcadef 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td @@ -26,22 +26,6 @@ include "mlir/Interfaces/SideEffectInterfaces.td" class QCOOp traits = []> : Op; -//===----------------------------------------------------------------------===// -// Type Constraints -//===----------------------------------------------------------------------===// - -def DynamicQubit - : Type($_self)">, - CPred<"!::mlir::cast<::mlir::qco::QubitType>($_self)." - "getIsStatic()">]>, - "dynamic qubit type (!qco.qubit)">; - -def StaticQubit - : Type($_self)">, - CPred<"::mlir::cast<::mlir::qco::QubitType>($_self)." - "getIsStatic()">]>, - "static qubit type (!qco.qubit)">; - //===----------------------------------------------------------------------===// // Resource Operations //===----------------------------------------------------------------------===// @@ -73,20 +57,20 @@ def AllocOp : QCOOp<"alloc", [MemoryEffects<[MemAlloc]>]> { let arguments = (ins OptionalAttr:$register_name, OptionalAttr>:$register_size, OptionalAttr>:$register_index); - let results = (outs DynamicQubit:$result); + let results = (outs QubitType:$result); let assemblyFormat = [{ (`(` $register_name^ `,` $register_size `,` $register_index `)`)? attr-dict `:` type($result) }]; let builders = [OpBuilder<(ins), [{ - build($_builder, $_state, QubitType::get($_builder.getContext(), /*isStatic=*/false), nullptr, nullptr, nullptr); + build($_builder, $_state, QubitType::get($_builder.getContext()), nullptr, nullptr, nullptr); }]>, OpBuilder<(ins "::mlir::StringAttr":$register_name, "::mlir::IntegerAttr":$register_size, "::mlir::IntegerAttr":$register_index), [{ - build($_builder, $_state, QubitType::get($_builder.getContext(), /*isStatic=*/false), + build($_builder, $_state, QubitType::get($_builder.getContext()), register_name, register_size, register_index); }]>]; @@ -124,16 +108,16 @@ def StaticOp : QCOOp<"static", [Pure]> { Example: ```mlir - %q = qco.static 0 : !qco.qubit + %q = qco.static 0 : !qco.qubit ``` }]; let arguments = (ins ConfinedAttr:$index); - let results = (outs StaticQubit:$qubit); + let results = (outs QubitType:$qubit); let assemblyFormat = "$index attr-dict `:` type($qubit)"; let builders = [OpBuilder<(ins "uint64_t":$index), [{ - build($_builder, $_state, QubitType::get($_builder.getContext(), /*isStatic=*/true), index); + build($_builder, $_state, QubitType::get($_builder.getContext()), index); }]>]; } @@ -1140,7 +1124,7 @@ def BarrierOp : QCOOp<"barrier", traits = [UnitaryOpInterface]> { let description = [{ Applies a barrier gate to a set of qubits and returns the transformed qubits. Each output qubit type must match its corresponding input type (pairwise - type preservation, e.g., disallows !qco.qubit -> !qco.qubit). + type preservation. Example: ```mlir diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOTypes.td b/mlir/include/mlir/Dialect/QCO/IR/QCOTypes.td index 358afffc8d..2438b98d6c 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOTypes.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOTypes.td @@ -26,9 +26,6 @@ def QubitType : QCOType<"Qubit", "qubit"> { and produce new output qubits following value semantics and the SSA paradigm, enabling powerful dataflow analysis and optimization. - `!qco.qubit` (default) denotes a dynamically allocated qubit. - `!qco.qubit` denotes a qubit with a statically known identifier. - Example: ```mlir %q0 = qco.alloc : !qco.qubit @@ -36,11 +33,6 @@ def QubitType : QCOType<"Qubit", "qubit"> { %q2 = qco.x %q1 : !qco.qubit -> !qco.qubit ``` }]; - let parameters = (ins DefaultValuedParameter<"bool", "false">:$isStatic); - let builders = [TypeBuilder<(ins), [{ - return $_get($_ctxt, /*isStatic=*/false); - }]>]; - let hasCustomAssemblyFormat = 1; } #endif // MLIR_DIALECT_QCO_IR_QCOTYPES_TD diff --git a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp index 5f454ca3f0..e66e0e8007 100644 --- a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -26,6 +26,7 @@ #include #include +#include #include namespace mlir { @@ -55,9 +56,9 @@ class QCOToQCTypeConverter final : public TypeConverter { // Identity conversion for all types by default addConversion([](Type type) { return type; }); - // Convert QCO qubit values to QC qubit references, preserving isStatic - addConversion([ctx](qco::QubitType type) -> Type { - return qc::QubitType::get(ctx, type.getIsStatic()); + // Convert QCO qubit values to QC qubit references + addConversion([ctx](qco::QubitType /*type*/) -> Type { + return qc::QubitType::get(ctx); }); } }; @@ -95,42 +96,54 @@ struct ConvertQCOAllocOp final : OpConversionPattern { } }; +enum class QubitMode : std::uint8_t { Unknown, StaticOnly, DynamicOnly, Mixed }; + +[[nodiscard]] static QubitMode inferQubitMode(func::FuncOp func) { + bool sawStatic = false; + bool sawAlloc = false; + + func.walk([&](Operation* op) { + sawStatic |= isa(op); + sawAlloc |= isa(op); + }); + + if (sawStatic && sawAlloc) { + return QubitMode::Mixed; + } + if (sawStatic) { + return QubitMode::StaticOnly; + } + if (sawAlloc) { + return QubitMode::DynamicOnly; + } + return QubitMode::Unknown; +} + /** - * @brief Converts qco.sink to qc.dealloc (dynamic) or erases it (static). - * - * @details - * For dynamic qubits (`!qco.qubit`), converts to `qc.dealloc`. - * For static qubits (`!qco.qubit`), erases the op since QC does not - * require explicit deallocation of static qubits. - * - * Example transformation (dynamic): - * ```mlir - * qco.sink %q_qco : !qco.qubit - * // becomes: - * qc.dealloc %q_qc : !qc.qubit - * ``` - * - * Example transformation (static): - * ```mlir - * qco.sink %q_qco : !qco.qubit - * // becomes: - * (erased) - * ``` + * @brief Converts qco.sink to qc.dealloc. */ struct ConvertQCOSinkOp final : OpConversionPattern { - using OpConversionPattern::OpConversionPattern; + explicit ConvertQCOSinkOp(TypeConverter& typeConverter, MLIRContext* context, + const DenseMap* modeMap) + : OpConversionPattern(typeConverter, context), modeMap(modeMap) {} LogicalResult matchAndRewrite(qco::SinkOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { - if (op.getQubit().getType().getIsStatic()) { + auto func = op->getParentOfType(); + auto it = modeMap->find(func); + const auto mode = (it != modeMap->end()) ? it->second : QubitMode::Unknown; + + if (mode == QubitMode::StaticOnly) { rewriter.eraseOp(op); return success(); } - rewriter.replaceOpWithNewOp(op, adaptor.getQubit()); return success(); } + +private: + const DenseMap* modeMap; }; /** @@ -762,6 +775,19 @@ struct QCOToQC final : impl::QCOToQCBase { RewritePatternSet patterns(context); QCOToQCTypeConverter typeConverter(context); + DenseMap modeMap; + for (auto func : cast(module).getOps()) { + const auto mode = inferQubitMode(func); + if (mode == QubitMode::Mixed) { + func.emitError( + "mixing static and dynamic qubits is forbidden (saw both " + "qco.static and qco.alloc)"); + signalPassFailure(); + return; + } + modeMap.try_emplace(func, mode); + } + // Configure conversion target: QCO illegal, QC legal target.addIllegalDialect(); target.addLegalDialect(); @@ -769,8 +795,8 @@ struct QCOToQC final : impl::QCOToQCBase { // Register operation conversion patterns // Note: No state tracking needed - OpAdaptors handle type conversion patterns.add< - ConvertQCOAllocOp, ConvertQCOSinkOp, ConvertQCOStaticOp, - ConvertQCOMeasureOp, ConvertQCOResetOp, + ConvertQCOAllocOp, ConvertQCOStaticOp, ConvertQCOMeasureOp, + ConvertQCOResetOp, ConvertQCOZeroTargetOneParameterToQC, ConvertQCOOneTargetZeroParameterToQC, ConvertQCOOneTargetZeroParameterToQC, @@ -801,6 +827,7 @@ struct QCOToQC final : impl::QCOToQCBase { ConvertQCOTwoTargetTwoParameterToQC, ConvertQCOBarrierOp, ConvertQCOCtrlOp, ConvertQCOInvOp, ConvertQCOYieldOp>(typeConverter, context); + patterns.add(typeConverter, context, &modeMap); // Conversion of qco types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index 8d7c5ea5c2..4731fa6b9c 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -346,9 +346,9 @@ class QCToQCOTypeConverter final : public TypeConverter { // Identity conversion for all types by default addConversion([](Type type) { return type; }); - // Convert QC qubit references to QCO qubit values, preserving isStatic - addConversion([ctx](qc::QubitType type) -> Type { - return qco::QubitType::get(ctx, type.getIsStatic()); + // Convert QC qubit references to QCO qubit values + addConversion([ctx](qc::QubitType /*type*/) -> Type { + return qco::QubitType::get(ctx); }); } }; @@ -1071,6 +1071,22 @@ struct QCToQCO final : impl::QCToQCOBase { MLIRContext* context = &getContext(); auto* module = getOperation(); + for (auto func : cast(module).getOps()) { + bool sawStatic = false; + bool sawAlloc = false; + func.walk([&](Operation* op) { + sawStatic |= isa(op); + sawAlloc |= isa(op); + }); + if (sawStatic && sawAlloc) { + func.emitError( + "mixing static and dynamic qubits is forbidden (saw both " + "qc.static and qc.alloc)"); + signalPassFailure(); + return; + } + } + // Create state object to track qubit value flow LoweringState state; diff --git a/mlir/lib/Dialect/QC/IR/QCOps.cpp b/mlir/lib/Dialect/QC/IR/QCOps.cpp index 0b573461f0..a0fbef5b33 100644 --- a/mlir/lib/Dialect/QC/IR/QCOps.cpp +++ b/mlir/lib/Dialect/QC/IR/QCOps.cpp @@ -48,24 +48,6 @@ void QCDialect::initialize() { // Types //===----------------------------------------------------------------------===// -/// Print `!qc.qubit` (dynamic, default) or `!qc.qubit`. -void QubitType::print(AsmPrinter& printer) const { - if (getIsStatic()) { - printer << ""; - } -} - -/// Parse `!qc.qubit` or `!qc.qubit`. -Type QubitType::parse(AsmParser& parser) { - if (succeeded(parser.parseOptionalLess())) { - if (parser.parseKeyword("static") || parser.parseGreater()) { - return {}; - } - return get(parser.getContext(), /*isStatic=*/true); - } - return get(parser.getContext(), /*isStatic=*/false); -} - #define GET_TYPEDEF_CLASSES #include "mlir/Dialect/QC/IR/QCOpsTypes.cpp.inc" diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp index 91db9e582a..8969ef4d49 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp @@ -256,8 +256,8 @@ void CtrlOp::build( } LogicalResult CtrlOp::verify() { - // Allows !qco.qubit and !qco.qubit to differ between controls and - // targets, but requires pairwise equality within each group. + // Controls and targets may differ in type (e.g., qubits vs tensors), but + // require pairwise equality within each group. if (!llvm::equal(getControlsIn().getTypes(), getControlsOut().getTypes())) { return emitOpError("qco.ctrl control qubit input types must match output " "types pairwise"); diff --git a/mlir/lib/Dialect/QCO/IR/QCOOps.cpp b/mlir/lib/Dialect/QCO/IR/QCOOps.cpp index 02105cdc8b..cc9fe874fe 100644 --- a/mlir/lib/Dialect/QCO/IR/QCOOps.cpp +++ b/mlir/lib/Dialect/QCO/IR/QCOOps.cpp @@ -70,17 +70,15 @@ parseTargetAliasing(OpAsmParser& parser, Region& region, } operands.push_back(oldOperand); - // Parse optional inline type to preserve isStatic; when absent, default - // to dynamic to avoid double-binding type($targets_in) in the assembly - // format. + // Parse optional inline type; when absent, use !qco.qubit to avoid + // double-binding type($targets_in) in the assembly format. Type operandType; if (succeeded(parser.parseOptionalColon())) { if (parser.parseType(operandType)) { return failure(); } } else { - operandType = QubitType::get(parser.getBuilder().getContext(), - /*isStatic=*/false); + operandType = QubitType::get(parser.getBuilder().getContext()); } newArg.type = operandType; blockArgs.push_back(newArg); @@ -120,12 +118,6 @@ static void printTargetAliasing(OpAsmPrinter& printer, Operation* /*op*/, printer.printOperand(entryBlock.getArgument(i)); printer << " = "; printer.printOperand(targetsIn[i]); - // Print inline type when static to preserve isStatic on round-trip - if (auto qubitType = llvm::dyn_cast(targetsIn[i].getType()); - qubitType && qubitType.getIsStatic()) { - printer << " : "; - printer.printType(qubitType); - } } printer << ") "; @@ -219,24 +211,6 @@ void QCODialect::initialize() { // Types //===----------------------------------------------------------------------===// -/// Print `!qco.qubit` (dynamic, default) or `!qco.qubit`. -void QubitType::print(AsmPrinter& printer) const { - if (getIsStatic()) { - printer << ""; - } -} - -/// Parse `!qco.qubit` or `!qco.qubit`. -Type QubitType::parse(AsmParser& parser) { - if (succeeded(parser.parseOptionalLess())) { - if (parser.parseKeyword("static") || parser.parseGreater()) { - return {}; - } - return get(parser.getContext(), /*isStatic=*/true); - } - return get(parser.getContext(), /*isStatic=*/false); -} - #define GET_TYPEDEF_CLASSES #include "mlir/Dialect/QCO/IR/QCOOpsTypes.cpp.inc" diff --git a/mlir/lib/Dialect/QTensor/IR/Operations/AllocOp.cpp b/mlir/lib/Dialect/QTensor/IR/Operations/AllocOp.cpp index 335b0e3620..c0d58b09c7 100644 --- a/mlir/lib/Dialect/QTensor/IR/Operations/AllocOp.cpp +++ b/mlir/lib/Dialect/QTensor/IR/Operations/AllocOp.cpp @@ -31,9 +31,9 @@ void AllocOp::build(OpBuilder& builder, OperationState& result, Value size) { assert(*sizeValue > 0 && "qtensor.alloc size must be positive"); } - auto resultType = RankedTensorType::get( - {sizeValue ? *sizeValue : ShapedType::kDynamic}, - qco::QubitType::get(builder.getContext(), /*isStatic=*/false)); + auto resultType = + RankedTensorType::get({sizeValue ? *sizeValue : ShapedType::kDynamic}, + qco::QubitType::get(builder.getContext())); build(builder, result, resultType, size); } @@ -58,11 +58,7 @@ LogicalResult AllocOp::verify() { } auto elementType = resultType.getElementType(); - if (auto qubitType = dyn_cast(elementType); - qubitType && qubitType.getIsStatic()) { - return emitOpError("qtensor.alloc cannot allocate static qubits; element " - "type must be a dynamic qubit type (!qco.qubit)"); - } + (void)elementType; return success(); } diff --git a/mlir/unittests/Compiler/test_compiler_pipeline.cpp b/mlir/unittests/Compiler/test_compiler_pipeline.cpp index 5f648fe113..184bb7d677 100644 --- a/mlir/unittests/Compiler/test_compiler_pipeline.cpp +++ b/mlir/unittests/Compiler/test_compiler_pipeline.cpp @@ -218,11 +218,6 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithInv), MQT_NAMED_BUILDER(mlir::qc::staticQubitsWithInv), MQT_NAMED_BUILDER(mlir::qir::staticQubitsWithInv), false}, - CompilerPipelineTestCase{ - "MixedStaticDynamicQubits", nullptr, - MQT_NAMED_BUILDER(mlir::qc::mixedStaticDynamicQubits), - MQT_NAMED_BUILDER(mlir::qc::mixedStaticDynamicQubits), - MQT_NAMED_BUILDER(mlir::qir::mixedStaticDynamicQubits), false}, CompilerPipelineTestCase{"AllocQubit", MQT_NAMED_BUILDER(qc::allocQubit), nullptr, MQT_NAMED_BUILDER(mlir::qc::allocQubit), diff --git a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp index 3112362ac9..31036d2acb 100644 --- a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp +++ b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp @@ -76,6 +76,22 @@ static LogicalResult runQCOToQCConversion(ModuleOp module) { return pm.run(module); } +TEST(QCOToQCMixedStaticDynamicQubitsTest, FailsConversion) { + DialectRegistry registry; + registry.insert(); + auto context = std::make_unique(); + context->appendDialectRegistry(registry); + context->loadAllAvailableDialects(); + + auto program = qco::QCOProgramBuilder::build( + context.get(), MQT_NAMED_BUILDER(qco::mixedStaticDynamicQubits).fn); + ASSERT_TRUE(program); + EXPECT_TRUE(verify(*program).succeeded()); + + EXPECT_TRUE(failed(runQCOToQCConversion(program.get()))); +} + TEST_P(QCOToQCTest, ProgramEquivalence) { const auto& [nameStr, programBuilder, referenceBuilder] = GetParam(); const auto name = " (" + nameStr + ")"; @@ -135,9 +151,9 @@ INSTANTIATE_TEST_SUITE_P( QCOToQCTestCase{"StaticQubitsWithInv", MQT_NAMED_BUILDER(qco::staticQubitsWithInv), MQT_NAMED_BUILDER(qc::staticQubitsWithInv)}, - QCOToQCTestCase{"MixedStaticDynamicQubits", - MQT_NAMED_BUILDER(qco::mixedStaticDynamicQubits), - MQT_NAMED_BUILDER(qc::mixedStaticDynamicQubits)})); + QCOToQCTestCase{"AllocDeallocPair", + MQT_NAMED_BUILDER(qco::allocSinkPair), + MQT_NAMED_BUILDER(qc::allocDeallocPair)})); /// @} /// \name QCOToQC/Modifiers/InvOp.cpp diff --git a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp index ba43c30b98..5954479e94 100644 --- a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp +++ b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp @@ -76,6 +76,22 @@ static LogicalResult runQCToQCOConversion(ModuleOp module) { return pm.run(module); } +TEST(QCToQCOMixedStaticDynamicQubitsTest, FailsConversion) { + DialectRegistry registry; + registry.insert(); + auto context = std::make_unique(); + context->appendDialectRegistry(registry); + context->loadAllAvailableDialects(); + + auto program = qc::QCProgramBuilder::build( + context.get(), MQT_NAMED_BUILDER(qc::mixedStaticDynamicQubits).fn); + ASSERT_TRUE(program); + EXPECT_TRUE(verify(*program).succeeded()); + + EXPECT_TRUE(failed(runQCToQCOConversion(program.get()))); +} + TEST_P(QCToQCOTest, ProgramEquivalence) { const auto& [_, programBuilder, referenceBuilder] = GetParam(); const auto name = " (" + GetParam().name + ")"; @@ -134,9 +150,9 @@ INSTANTIATE_TEST_SUITE_P( QCToQCOTestCase{"StaticQubitsWithInv", MQT_NAMED_BUILDER(qc::staticQubitsWithInv), MQT_NAMED_BUILDER(qco::staticQubitsWithInv)}, - QCToQCOTestCase{"MixedStaticDynamicQubits", - MQT_NAMED_BUILDER(qc::mixedStaticDynamicQubits), - MQT_NAMED_BUILDER(qco::mixedStaticDynamicQubits)})); + QCToQCOTestCase{"AllocDeallocPair", + MQT_NAMED_BUILDER(qc::allocDeallocPair), + MQT_NAMED_BUILDER(qco::allocSinkPair)})); /// @} /// \name QCToQCO/Modifiers/InvOp.cpp diff --git a/mlir/unittests/Conversion/QCToQIR/test_qc_to_qir.cpp b/mlir/unittests/Conversion/QCToQIR/test_qc_to_qir.cpp index 58e832305b..ef938ce4e7 100644 --- a/mlir/unittests/Conversion/QCToQIR/test_qc_to_qir.cpp +++ b/mlir/unittests/Conversion/QCToQIR/test_qc_to_qir.cpp @@ -639,9 +639,6 @@ INSTANTIATE_TEST_SUITE_P( QCToQIRTestCase{"StaticQubitsWithInv", MQT_NAMED_BUILDER(qc::staticQubitsWithInv), MQT_NAMED_BUILDER(qir::staticQubitsWithInv)}, - QCToQIRTestCase{"MixedStaticDynamicQubits", - MQT_NAMED_BUILDER(qc::mixedStaticDynamicQubits), - MQT_NAMED_BUILDER(qir::mixedStaticDynamicQubits)}, QCToQIRTestCase{"AllocDeallocPair", MQT_NAMED_BUILDER(qc::allocDeallocPair), MQT_NAMED_BUILDER(qir::emptyQIR)})); diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp index e2aaca20f4..fb188715e3 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -140,7 +140,7 @@ class MappingPassTestBase : public testing::Test { ASSERT_TRUE(qIn); ASSERT_TRUE(qOut); EXPECT_EQ(qIn, qOut); - EXPECT_TRUE(qOut.getIsStatic()); + (void)qOut; } std::unique_ptr context; From 22791f47afd8d7b0eb32f0a7c1ab1a5e8d245951 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 31 Mar 2026 14:25:33 +0200 Subject: [PATCH 41/57] =?UTF-8?q?=F0=9F=94=A7=20Refactor=20QCO=20operation?= =?UTF-8?q?s=20to=20simplify=20type=20handling=20and=20remove=20redundant?= =?UTF-8?q?=20type=20checks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/include/mlir/Dialect/QCO/IR/QCOOps.td | 187 +++-------- mlir/lib/Dialect/QC/IR/QCOps.cpp | 2 - mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp | 22 +- .../IR/Operations/StandardGates/BarrierOp.cpp | 8 - mlir/lib/Dialect/QCO/IR/QCOOps.cpp | 17 +- .../QCO/Transforms/Mapping/Mapping.cpp | 293 +----------------- .../Dialect/QTensor/IR/Operations/AllocOp.cpp | 3 - .../QCO/Transforms/Mapping/test_mapping.cpp | 147 +-------- 8 files changed, 48 insertions(+), 631 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td index b310bcadef..efee9623e5 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td @@ -115,19 +115,13 @@ def StaticOp : QCOOp<"static", [Pure]> { let arguments = (ins ConfinedAttr:$index); let results = (outs QubitType:$qubit); let assemblyFormat = "$index attr-dict `:` type($qubit)"; - - let builders = [OpBuilder<(ins "uint64_t":$index), [{ - build($_builder, $_state, QubitType::get($_builder.getContext()), index); - }]>]; } //===----------------------------------------------------------------------===// // Measurement and Reset Operations //===----------------------------------------------------------------------===// -def MeasureOp - : QCOOp<"measure", [TypesMatchWith<"qubit output type matches input", - "qubit_in", "qubit_out", "$_self">]> { +def MeasureOp : QCOOp<"measure"> { let summary = "Measure a qubit in the computational basis"; let description = [{ Measures a qubit in the computational (Z) basis, collapsing the state @@ -162,7 +156,7 @@ def MeasureOp }]; let builders = [OpBuilder<(ins "Value":$qubit_in), [{ - build($_builder, $_state, qubit_in.getType(), $_builder.getI1Type(), + build($_builder, $_state, QubitType::get($_builder.getContext()), $_builder.getI1Type(), qubit_in, nullptr, nullptr, nullptr); }]>]; @@ -238,10 +232,7 @@ def GPhaseOp let hasCanonicalizer = 1; } -def IdOp : QCOOp<"id", - traits = [UnitaryOpInterface, OneTargetZeroParameter, - TypesMatchWith<"result type matches input", - "qubit_in", "qubit_out", "$_self">]> { +def IdOp : QCOOp<"id", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply an Id gate to a qubit"; let description = [{ Applies an Id gate to a qubit and returns the transformed qubit. @@ -265,10 +256,7 @@ def IdOp : QCOOp<"id", let hasCanonicalizer = 1; } -def XOp - : QCOOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter, - TypesMatchWith<"result type matches input", - "qubit_in", "qubit_out", "$_self">]> { +def XOp : QCOOp<"x", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply an X gate to a qubit"; let description = [{ Applies an X gate to a qubit and returns the transformed qubit. @@ -292,10 +280,7 @@ def XOp let hasCanonicalizer = 1; } -def YOp - : QCOOp<"y", traits = [UnitaryOpInterface, OneTargetZeroParameter, - TypesMatchWith<"result type matches input", - "qubit_in", "qubit_out", "$_self">]> { +def YOp : QCOOp<"y", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply a Y gate to a qubit"; let description = [{ Applies a Y gate to a qubit and returns the transformed qubit. @@ -319,10 +304,7 @@ def YOp let hasCanonicalizer = 1; } -def ZOp - : QCOOp<"z", traits = [UnitaryOpInterface, OneTargetZeroParameter, - TypesMatchWith<"result type matches input", - "qubit_in", "qubit_out", "$_self">]> { +def ZOp : QCOOp<"z", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply a Z gate to a qubit"; let description = [{ Applies a Z gate to a qubit and returns the transformed qubit. @@ -346,10 +328,7 @@ def ZOp let hasCanonicalizer = 1; } -def HOp - : QCOOp<"h", traits = [UnitaryOpInterface, OneTargetZeroParameter, - TypesMatchWith<"result type matches input", - "qubit_in", "qubit_out", "$_self">]> { +def HOp : QCOOp<"h", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply a H gate to a qubit"; let description = [{ Applies a H gate to a qubit and returns the transformed qubit. @@ -373,10 +352,7 @@ def HOp let hasCanonicalizer = 1; } -def SOp - : QCOOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter, - TypesMatchWith<"result type matches input", - "qubit_in", "qubit_out", "$_self">]> { +def SOp : QCOOp<"s", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply an S gate to a qubit"; let description = [{ Applies an S gate to a qubit and returns the transformed qubit. @@ -401,10 +377,7 @@ def SOp } def SdgOp - : QCOOp<"sdg", - traits = [UnitaryOpInterface, OneTargetZeroParameter, - TypesMatchWith<"result type matches input", "qubit_in", - "qubit_out", "$_self">]> { + : QCOOp<"sdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply an Sdg gate to a qubit"; let description = [{ Applies an Sdg gate to a qubit and returns the transformed qubit. @@ -428,10 +401,7 @@ def SdgOp let hasCanonicalizer = 1; } -def TOp - : QCOOp<"t", traits = [UnitaryOpInterface, OneTargetZeroParameter, - TypesMatchWith<"result type matches input", - "qubit_in", "qubit_out", "$_self">]> { +def TOp : QCOOp<"t", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply a T gate to a qubit"; let description = [{ Applies a T gate to a qubit and returns the transformed qubit. @@ -456,10 +426,7 @@ def TOp } def TdgOp - : QCOOp<"tdg", - traits = [UnitaryOpInterface, OneTargetZeroParameter, - TypesMatchWith<"result type matches input", "qubit_in", - "qubit_out", "$_self">]> { + : QCOOp<"tdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply a Tdg gate to a qubit"; let description = [{ Applies a Tdg gate to a qubit and returns the transformed qubit. @@ -483,10 +450,7 @@ def TdgOp let hasCanonicalizer = 1; } -def SXOp : QCOOp<"sx", - traits = [UnitaryOpInterface, OneTargetZeroParameter, - TypesMatchWith<"result type matches input", - "qubit_in", "qubit_out", "$_self">]> { +def SXOp : QCOOp<"sx", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply an SX gate to a qubit"; let description = [{ Applies an SX gate to a qubit and returns the transformed qubit. @@ -511,10 +475,7 @@ def SXOp : QCOOp<"sx", } def SXdgOp - : QCOOp<"sxdg", - traits = [UnitaryOpInterface, OneTargetZeroParameter, - TypesMatchWith<"result type matches input", "qubit_in", - "qubit_out", "$_self">]> { + : QCOOp<"sxdg", traits = [UnitaryOpInterface, OneTargetZeroParameter]> { let summary = "Apply an SXdg gate to a qubit"; let description = [{ Applies an SXdg gate to a qubit and returns the transformed qubit. @@ -538,10 +499,7 @@ def SXdgOp let hasCanonicalizer = 1; } -def RXOp : QCOOp<"rx", - traits = [UnitaryOpInterface, OneTargetOneParameter, - TypesMatchWith<"result type matches input", - "qubit_in", "qubit_out", "$_self">]> { +def RXOp : QCOOp<"rx", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let summary = "Apply an RX gate to a qubit"; let description = [{ Applies an RX gate to a qubit and returns the transformed qubit. @@ -569,10 +527,7 @@ def RXOp : QCOOp<"rx", let hasCanonicalizer = 1; } -def RYOp : QCOOp<"ry", - traits = [UnitaryOpInterface, OneTargetOneParameter, - TypesMatchWith<"result type matches input", - "qubit_in", "qubit_out", "$_self">]> { +def RYOp : QCOOp<"ry", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let summary = "Apply an RY gate to a qubit"; let description = [{ Applies an RY gate to a qubit and returns the transformed qubit. @@ -600,10 +555,7 @@ def RYOp : QCOOp<"ry", let hasCanonicalizer = 1; } -def RZOp : QCOOp<"rz", - traits = [UnitaryOpInterface, OneTargetOneParameter, - TypesMatchWith<"result type matches input", - "qubit_in", "qubit_out", "$_self">]> { +def RZOp : QCOOp<"rz", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let summary = "Apply an RZ gate to a qubit"; let description = [{ Applies an RZ gate to a qubit and returns the transformed qubit. @@ -631,10 +583,7 @@ def RZOp : QCOOp<"rz", let hasCanonicalizer = 1; } -def POp - : QCOOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter, - TypesMatchWith<"result type matches input", - "qubit_in", "qubit_out", "$_self">]> { +def POp : QCOOp<"p", traits = [UnitaryOpInterface, OneTargetOneParameter]> { let summary = "Apply a P gate to a qubit"; let description = [{ Applies a P gate to a qubit and returns the transformed qubit. @@ -662,10 +611,7 @@ def POp let hasCanonicalizer = 1; } -def ROp - : QCOOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter, - TypesMatchWith<"result type matches input", - "qubit_in", "qubit_out", "$_self">]> { +def ROp : QCOOp<"r", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { let summary = "Apply an R gate to a qubit"; let description = [{ Applies an R gate to a qubit and returns the transformed qubit. @@ -695,10 +641,7 @@ def ROp let hasCanonicalizer = 1; } -def U2Op : QCOOp<"u2", - traits = [UnitaryOpInterface, OneTargetTwoParameter, - TypesMatchWith<"result type matches input", - "qubit_in", "qubit_out", "$_self">]> { +def U2Op : QCOOp<"u2", traits = [UnitaryOpInterface, OneTargetTwoParameter]> { let summary = "Apply a U2 gate to a qubit"; let description = [{ Applies a U2 gate to a qubit and returns the transformed qubit. @@ -728,10 +671,7 @@ def U2Op : QCOOp<"u2", let hasCanonicalizer = 1; } -def UOp - : QCOOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter, - TypesMatchWith<"result type matches input", - "qubit_in", "qubit_out", "$_self">]> { +def UOp : QCOOp<"u", traits = [UnitaryOpInterface, OneTargetThreeParameter]> { let summary = "Apply a U gate to a qubit"; let description = [{ Applies a U gate to a qubit and returns the transformed qubit. @@ -764,12 +704,7 @@ def UOp } def SWAPOp - : QCOOp<"swap", - traits = [UnitaryOpInterface, TwoTargetZeroParameter, - TypesMatchWith<"result type matches input", "qubit0_in", - "qubit0_out", "$_self">, - TypesMatchWith<"result type matches input", "qubit1_in", - "qubit1_out", "$_self">]> { + : QCOOp<"swap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { let summary = "Apply a SWAP gate to two qubits"; let description = [{ Applies a SWAP gate to two qubits and returns the transformed qubits. @@ -797,12 +732,7 @@ def SWAPOp } def iSWAPOp - : QCOOp<"iswap", - traits = [UnitaryOpInterface, TwoTargetZeroParameter, - TypesMatchWith<"result type matches input", "qubit0_in", - "qubit0_out", "$_self">, - TypesMatchWith<"result type matches input", "qubit1_in", - "qubit1_out", "$_self">]> { + : QCOOp<"iswap", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { let summary = "Apply a iSWAP gate to two qubits"; let description = [{ Applies a iSWAP gate to two qubits and returns the transformed qubits. @@ -828,12 +758,7 @@ def iSWAPOp } def DCXOp - : QCOOp<"dcx", - traits = [UnitaryOpInterface, TwoTargetZeroParameter, - TypesMatchWith<"result type matches input", "qubit0_in", - "qubit0_out", "$_self">, - TypesMatchWith<"result type matches input", "qubit1_in", - "qubit1_out", "$_self">]> { + : QCOOp<"dcx", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { let summary = "Apply a DCX gate to two qubits"; let description = [{ Applies a DCX gate to two qubits and returns the transformed qubits. @@ -861,12 +786,7 @@ def DCXOp } def ECROp - : QCOOp<"ecr", - traits = [UnitaryOpInterface, TwoTargetZeroParameter, - TypesMatchWith<"result type matches input", "qubit0_in", - "qubit0_out", "$_self">, - TypesMatchWith<"result type matches input", "qubit1_in", - "qubit1_out", "$_self">]> { + : QCOOp<"ecr", traits = [UnitaryOpInterface, TwoTargetZeroParameter]> { let summary = "Apply an ECR gate to two qubits"; let description = [{ Applies an ECR gate to two qubits and returns the transformed qubits. @@ -893,13 +813,7 @@ def ECROp let hasCanonicalizer = 1; } -def RXXOp - : QCOOp<"rxx", - traits = [UnitaryOpInterface, TwoTargetOneParameter, - TypesMatchWith<"result type matches input", "qubit0_in", - "qubit0_out", "$_self">, - TypesMatchWith<"result type matches input", "qubit1_in", - "qubit1_out", "$_self">]> { +def RXXOp : QCOOp<"rxx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let summary = "Apply an RXX gate to two qubits"; let description = [{ Applies an RXX gate to two qubits and returns the transformed qubits. @@ -930,13 +844,7 @@ def RXXOp let hasCanonicalizer = 1; } -def RYYOp - : QCOOp<"ryy", - traits = [UnitaryOpInterface, TwoTargetOneParameter, - TypesMatchWith<"result type matches input", "qubit0_in", - "qubit0_out", "$_self">, - TypesMatchWith<"result type matches input", "qubit1_in", - "qubit1_out", "$_self">]> { +def RYYOp : QCOOp<"ryy", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let summary = "Apply an RYY gate to two qubits"; let description = [{ Applies an RYY gate to two qubits and returns the transformed qubits. @@ -967,13 +875,7 @@ def RYYOp let hasCanonicalizer = 1; } -def RZXOp - : QCOOp<"rzx", - traits = [UnitaryOpInterface, TwoTargetOneParameter, - TypesMatchWith<"result type matches input", "qubit0_in", - "qubit0_out", "$_self">, - TypesMatchWith<"result type matches input", "qubit1_in", - "qubit1_out", "$_self">]> { +def RZXOp : QCOOp<"rzx", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let summary = "Apply an RZX gate to two qubits"; let description = [{ Applies an RZX gate to two qubits and returns the transformed qubits. @@ -1004,13 +906,7 @@ def RZXOp let hasCanonicalizer = 1; } -def RZZOp - : QCOOp<"rzz", - traits = [UnitaryOpInterface, TwoTargetOneParameter, - TypesMatchWith<"result type matches input", "qubit0_in", - "qubit0_out", "$_self">, - TypesMatchWith<"result type matches input", "qubit1_in", - "qubit1_out", "$_self">]> { +def RZZOp : QCOOp<"rzz", traits = [UnitaryOpInterface, TwoTargetOneParameter]> { let summary = "Apply an RZZ gate to two qubits"; let description = [{ Applies an RZZ gate to two qubits and returns the transformed qubits. @@ -1041,13 +937,8 @@ def RZZOp let hasCanonicalizer = 1; } -def XXPlusYYOp - : QCOOp<"xx_plus_yy", - traits = [UnitaryOpInterface, TwoTargetTwoParameter, - TypesMatchWith<"result type matches input", "qubit0_in", - "qubit0_out", "$_self">, - TypesMatchWith<"result type matches input", "qubit1_in", - "qubit1_out", "$_self">]> { +def XXPlusYYOp : QCOOp<"xx_plus_yy", + traits = [UnitaryOpInterface, TwoTargetTwoParameter]> { let summary = "Apply an XX+YY gate to two qubits"; let description = [{ Applies an XX+YY gate to two qubits and returns the transformed qubits. @@ -1080,13 +971,8 @@ def XXPlusYYOp let hasCanonicalizer = 1; } -def XXMinusYYOp - : QCOOp<"xx_minus_yy", - traits = [UnitaryOpInterface, TwoTargetTwoParameter, - TypesMatchWith<"result type matches input", "qubit0_in", - "qubit0_out", "$_self">, - TypesMatchWith<"result type matches input", "qubit1_in", - "qubit1_out", "$_self">]> { +def XXMinusYYOp : QCOOp<"xx_minus_yy", + traits = [UnitaryOpInterface, TwoTargetTwoParameter]> { let summary = "Apply an XX-YY gate to two qubits"; let description = [{ Applies an XX-YY gate to two qubits and returns the transformed qubits. @@ -1123,8 +1009,6 @@ def BarrierOp : QCOOp<"barrier", traits = [UnitaryOpInterface]> { let summary = "Apply a barrier gate to a set of qubits"; let description = [{ Applies a barrier gate to a set of qubits and returns the transformed qubits. - Each output qubit type must match its corresponding input type (pairwise - type preservation. Example: ```mlir @@ -1138,8 +1022,6 @@ def BarrierOp : QCOOp<"barrier", traits = [UnitaryOpInterface]> { let assemblyFormat = "$qubits_in attr-dict `:` type($qubits_in) `->` type($qubits_out)"; - let hasVerifier = 1; - let extraClassDeclaration = [{ size_t getNumQubits() { return getNumTargets(); } size_t getNumTargets() { return getQubitsIn().size(); } @@ -1184,13 +1066,14 @@ def YieldOp : QCOOp<"yield", traits = [Terminator, ReturnLike]> { }]; let arguments = (ins Variadic:$targets); - let assemblyFormat = "$targets attr-dict (`:` type($targets)^)?"; + let assemblyFormat = "$targets attr-dict"; } def CtrlOp : QCOOp<"ctrl", traits = [UnitaryOpInterface, AttrSizedOperandSegments, - AttrSizedResultSegments, + AttrSizedResultSegments, SameOperandsAndResultType, + SameOperandsAndResultShape, SingleBlockImplicitTerminator<"::mlir::qco::YieldOp">, RecursiveMemoryEffects]> { let summary = "Add control qubits to a unitary operation"; diff --git a/mlir/lib/Dialect/QC/IR/QCOps.cpp b/mlir/lib/Dialect/QC/IR/QCOps.cpp index a0fbef5b33..5b93c2ebaa 100644 --- a/mlir/lib/Dialect/QC/IR/QCOps.cpp +++ b/mlir/lib/Dialect/QC/IR/QCOps.cpp @@ -16,8 +16,6 @@ // IWYU pragma: begin_keep #include #include -#include -#include // IWYU pragma: end_keep using namespace mlir; diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp index 8969ef4d49..bf67c21e36 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp @@ -8,6 +8,7 @@ * Licensed under the MIT License */ +#include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" #include @@ -114,10 +115,6 @@ struct ReduceCtrl final : OpRewritePattern { return success(); } - // Capture the promoted control's type before adjusting segments; after - // setAttr, getControlsIn().back() would point to a different control. - const auto promotedControlType = op.getControlsIn().back().getType(); - // Adjust the segment sizes of the control and target operands const auto opSegmentsAttrName = CtrlOp::getOperandSegmentSizeAttr(); auto segmentsAttr = @@ -128,9 +125,9 @@ struct ReduceCtrl final : OpRewritePattern { const auto opResultSegmentsAttrName = CtrlOp::getResultSegmentSizeAttr(); op->setAttr(opResultSegmentsAttrName, newSegments); - // Add a block argument for the promoted target qubit, preserving the - // control's type (including isStatic) - auto arg = op.getBody()->addArgument(promotedControlType, op.getLoc()); + // Add a block argument for the target qubit + auto arg = op.getBody()->addArgument(QubitType::get(rewriter.getContext()), + op.getLoc()); // Replace the current GPhaseOp with a PhaseOp const OpBuilder::InsertionGuard guard(rewriter); @@ -256,17 +253,6 @@ void CtrlOp::build( } LogicalResult CtrlOp::verify() { - // Controls and targets may differ in type (e.g., qubits vs tensors), but - // require pairwise equality within each group. - if (!llvm::equal(getControlsIn().getTypes(), getControlsOut().getTypes())) { - return emitOpError("qco.ctrl control qubit input types must match output " - "types pairwise"); - } - if (!llvm::equal(getTargetsIn().getTypes(), getTargetsOut().getTypes())) { - return emitOpError("qco.ctrl target qubit input types must match output " - "types pairwise"); - } - auto& block = *getBody(); if (block.getOperations().size() < 2) { return emitOpError("body region must have at least two operations"); diff --git a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/BarrierOp.cpp b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/BarrierOp.cpp index c7488fd6b0..d168624987 100644 --- a/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/BarrierOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Operations/StandardGates/BarrierOp.cpp @@ -77,14 +77,6 @@ struct MergeSubsequentBarrier final : OpRewritePattern { } // namespace -LogicalResult BarrierOp::verify() { - if (!llvm::equal(getQubitsIn().getTypes(), getQubitsOut().getTypes())) { - return emitOpError("qco.barrier qubit input types must match output types " - "pairwise"); - } - return success(); -} - Value BarrierOp::getInputTarget(const size_t i) { if (i < getNumTargets()) { return getQubitsIn()[i]; diff --git a/mlir/lib/Dialect/QCO/IR/QCOOps.cpp b/mlir/lib/Dialect/QCO/IR/QCOOps.cpp index cc9fe874fe..36aa66ad54 100644 --- a/mlir/lib/Dialect/QCO/IR/QCOOps.cpp +++ b/mlir/lib/Dialect/QCO/IR/QCOOps.cpp @@ -14,12 +14,10 @@ #include #include -#include #include #include #include #include -#include #include #include #include @@ -70,17 +68,10 @@ parseTargetAliasing(OpAsmParser& parser, Region& region, } operands.push_back(oldOperand); - // Parse optional inline type; when absent, use !qco.qubit to avoid - // double-binding type($targets_in) in the assembly format. - Type operandType; - if (succeeded(parser.parseOptionalColon())) { - if (parser.parseType(operandType)) { - return failure(); - } - } else { - operandType = QubitType::get(parser.getBuilder().getContext()); - } - newArg.type = operandType; + // 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. + newArg.type = QubitType::get(parser.getBuilder().getContext()); blockArgs.push_back(newArg); } while (succeeded(parser.parseOptionalComma())); diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 15d0990126..0682b1dd87 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -20,20 +20,16 @@ #include #include #include -#include #include #include #include #include #include -#include #include #include -#include #include #include #include -#include #include #include @@ -61,287 +57,6 @@ namespace mlir::qco { #define GEN_PASS_DEF_MAPPINGPASS #include "mlir/Dialect/QCO/Transforms/Passes.h.inc" -//===----------------------------------------------------------------------===// -// After alloc→static placement, operand qubit types match the mapped static -// type but many op results (and ctrl/inv region args) may still be typed as -// plain !qco.qubit. IR is recreated with IRRewriter. -//===----------------------------------------------------------------------===// - -/** True if any declared qubit result type disagrees with the paired operand. */ -[[nodiscard]] static bool -qubitOperandAndResultTypesDiffer(UnitaryOpInterface unitary) { - return llvm::any_of( - llvm::zip_equal(unitary.getInputQubits(), unitary.getOutputQubits()), - [](const auto& io) { - const auto& [input, output] = io; - const auto qIn = dyn_cast(input.getType()); - const auto qOut = dyn_cast(output.getType()); - return qIn && qOut && qIn != qOut; - }); -} - -/** Region entry args vs target operands, for ctrl/inv verifier alignment. */ -[[nodiscard]] static bool -modifierBodyArgsMismatchTargetOperands(Block& body, - OperandRange targetOperands) { - return llvm::any_of(llvm::zip_equal(body.getArguments(), targetOperands), - [](const auto& pair) { - const auto& [arg, target] = pair; - return arg.getType() != target.getType(); - }); -} - -/** - * @brief True if @p ctrl must be rebuilt so qubit types match after placement. - * - * @param ctrl Operation examined: sole region (targets + body args), operands - * (`getControlsIn()`, `getTargetsIn()`), and qubit results - * (`getOutputQubits()`). - * @return True when either (1) a body block argument type differs from the - * matching `getTargetsIn()` operand, or (2) any qubit result type - * differs from the paired input qubit type (via - * @ref modifierBodyArgsMismatchTargetOperands and - * @ref qubitOperandAndResultTypesDiffer). - * - * @details Pure predicate: no IR changes. Callers use this to skip redundant - * `replaceOpWithNewOp` work. - */ -[[nodiscard]] static bool ctrlNeedsQubitTypeResync(CtrlOp ctrl) { - return modifierBodyArgsMismatchTargetOperands(*ctrl.getBody(), - ctrl.getTargetsIn()) || - qubitOperandAndResultTypesDiffer(ctrl); -} - -/** - * @brief True if @p inv must be rebuilt so qubit types match after placement. - * - * @param inv Operation examined: sole region (`getBody()` args vs - * `getQubitsIn()` operands) and paired qubit results. - * @return True when body argument types disagree with `getQubitsIn()`, or qubit - * result types disagree with operands (same predicates as for @ref - * ctrlNeedsQubitTypeResync). - * - * @details Pure predicate: no IR changes. - */ -[[nodiscard]] static bool invNeedsQubitTypeResync(InvOp inv) { - return modifierBodyArgsMismatchTargetOperands(*inv.getBody(), - inv.getQubitsIn()) || - qubitOperandAndResultTypesDiffer(inv); -} - -/** - * @brief Duplicate @p op with new result types; clone attached regions. - * @param rewriter Insertion point is set to @p op before create. - */ -static Operation* cloneOpWithNewResultTypes(Operation* op, - ArrayRef newResultTypes, - IRRewriter& rewriter) { - assert(op->getNumResults() == newResultTypes.size() && - "result type count must match the operation"); - rewriter.setInsertionPoint(op); - OperationState state(op->getLoc(), op->getName()); - state.addOperands(op->getOperands()); - state.addTypes(newResultTypes); - state.addAttributes(op->getAttrs()); - state.addSuccessors(op->getSuccessors()); - for (Region& region : op->getRegions()) { - Region* newRegion = state.addRegion(); - IRMapping mapper; - rewriter.cloneRegionBefore(region, *newRegion, newRegion->end(), mapper); - } - return rewriter.create(state); -} - -[[nodiscard]] static llvm::SmallVector -resyncClonedUnitaryAndGetResults(Operation* cloned, IRRewriter& rewriter); - -/** - * @brief Rebuild a leaf unitary so each qubit result type matches its operand. - * - * Skips ops with nested regions (handled via @ref - * resyncClonedUnitaryAndGetResults). - */ -[[nodiscard]] static llvm::SmallVector -replaceLeafUnitaryWithAlignedQubitTypes(UnitaryOpInterface unitary, - IRRewriter& rewriter) { - Operation* op = unitary.getOperation(); - if (op->getNumRegions() != 0 || !qubitOperandAndResultTypesDiffer(unitary)) { - return llvm::to_vector(llvm::map_range( - op->getResults(), [](OpResult res) -> Value { return res; })); - } - SmallVector newTypes(op->getResultTypes()); - for (auto [input, output] : - llvm::zip_equal(unitary.getInputQubits(), unitary.getOutputQubits())) { - const auto qIn = dyn_cast(input.getType()); - const auto qOut = dyn_cast(output.getType()); - if (qIn && qOut && qIn != qOut) { - newTypes[llvm::cast(output).getResultNumber()] = qIn; - } - } - Operation* newOp = cloneOpWithNewResultTypes(op, newTypes, rewriter); - rewriter.replaceOp(op, newOp->getResults()); - return llvm::to_vector(llvm::map_range( - newOp->getResults(), [](OpResult res) -> Value { return res; })); -} - -/** - * @brief Clone all ops in the modifier body before `qco.yield`, then align the - * body unitary’s qubit types. - * - * The body may contain `arith.constant` (or similar) before the single - * `UnitaryOpInterface`; cloning only that unitary would leave uses pointing at - * the old region, which is destroyed when the modifier is replaced. - */ -[[nodiscard]] static llvm::SmallVector -cloneModifierBodyAndResyncUnitary(Block& oldBody, ValueRange newTargetArgs, - IRRewriter& rewriter) { - IRMapping mapping; - for (auto [oldArg, newArg] : - llvm::zip_equal(oldBody.getArguments(), newTargetArgs)) { - mapping.map(oldArg, newArg); - } - Operation* clonedUnitary = nullptr; - for (Operation& op : oldBody) { - if (llvm::isa(op)) { - break; - } - Operation* cloned = rewriter.clone(op, mapping); - for (auto [oldRes, newRes] : - llvm::zip_equal(op.getResults(), cloned->getResults())) { - mapping.map(oldRes, newRes); - } - if (llvm::isa(cloned)) { - clonedUnitary = cloned; - } - } - assert(clonedUnitary != nullptr && - "modifier body must contain a unitary before yield"); - return resyncClonedUnitaryAndGetResults(clonedUnitary, rewriter); -} - -/** - * @brief Replace @p ctrl with a new `qco.ctrl` that keeps the same controls, - * targets, and logical body (ops before `qco.yield` are cloned). - * - * @param ctrl Modifier to replace. Must satisfy the usual `qco.ctrl` verifier - * (single unitary before yield, etc.). - * @param rewriter Drives insertion and erasure; insertion point is updated by - * `replaceOpWithNewOp`. - * @return The new `CtrlOp` whose region holds cloned ops and refreshed types. - * - * @note Side effects: erases @p ctrl and rewires uses to the new op's results; - * runs @ref cloneModifierBodyAndResyncUnitary in the builder callback - * (may recurse for nested ctrl/inv in the body). - */ -[[nodiscard]] static CtrlOp replaceCtrlPreservingBody(CtrlOp ctrl, - IRRewriter& rewriter) { - return rewriter.replaceOpWithNewOp( - ctrl, ctrl.getControlsIn(), ctrl.getTargetsIn(), - [&](ValueRange newArgs) -> llvm::SmallVector { - return cloneModifierBodyAndResyncUnitary(*ctrl.getBody(), newArgs, - rewriter); - }); -} - -/** - * @brief Replace @p inv with a new `qco.inv` that preserves operands and body - * structure (clone ops before `qco.yield`, then align qubit types). - * - * @param inv Modifier to replace; must verify as `qco.inv`. - * @param rewriter Drives insertion and erasure. - * @return The new `InvOp` after `replaceOp` has rewired uses away from @p inv. - * - * @note Side effects: erases @p inv; uses @ref - * cloneModifierBodyAndResyncUnitary in the body callback (may recurse for - * nested modifiers). - */ -[[nodiscard]] static InvOp replaceInvPreservingBody(InvOp inv, - IRRewriter& rewriter) { - rewriter.setInsertionPoint(inv); - InvOp newInv = - InvOp::create(rewriter, inv.getLoc(), inv.getQubitsIn(), - [&](ValueRange newArgs) -> llvm::SmallVector { - return cloneModifierBodyAndResyncUnitary( - *inv.getBody(), newArgs, rewriter); - }); - rewriter.replaceOp(inv, newInv.getResults()); - return newInv; -} - -/** - * @brief If @p cloned is ctrl/inv, rebuild it when qubit types are stale; else - * align a leaf unitary. Returns values that replace @p cloned's results. - */ -[[nodiscard]] static llvm::SmallVector -resyncClonedUnitaryAndGetResults(Operation* cloned, IRRewriter& rewriter) { - if (auto ctrl = dyn_cast(cloned)) { - if (!ctrlNeedsQubitTypeResync(ctrl)) { - return llvm::to_vector(llvm::map_range( - ctrl->getResults(), [](OpResult res) -> Value { return res; })); - } - return llvm::to_vector( - llvm::map_range(replaceCtrlPreservingBody(ctrl, rewriter)->getResults(), - [](OpResult res) -> Value { return res; })); - } - if (auto inv = dyn_cast(cloned)) { - if (!invNeedsQubitTypeResync(inv)) { - return llvm::to_vector(llvm::map_range( - inv->getResults(), [](OpResult res) -> Value { return res; })); - } - return llvm::to_vector( - llvm::map_range(replaceInvPreservingBody(inv, rewriter)->getResults(), - [](OpResult res) -> Value { return res; })); - } - return replaceLeafUnitaryWithAlignedQubitTypes( - llvm::cast(cloned), rewriter); -} - -/** - * @brief Walk the function body and fix qubit SSA types after mapping. - * - * @details Post-order allows erasing/replacing the visited op safely. Ctrl and - * Inv are handled before the generic `UnitaryOpInterface` case so nested - * modifiers are not double-processed as leaf unitaries. - */ -static void synchronizeMappedQubitTypes(Region& region, IRRewriter& rewriter) { - region.walk([&](Operation* op) { - rewriter.setInsertionPoint(op); - if (auto ctrl = dyn_cast(op)) { - if (ctrlNeedsQubitTypeResync(ctrl)) { - (void)replaceCtrlPreservingBody(ctrl, rewriter); - } - return WalkResult::advance(); - } - if (auto inv = dyn_cast(op)) { - if (invNeedsQubitTypeResync(inv)) { - (void)replaceInvPreservingBody(inv, rewriter); - } - return WalkResult::advance(); - } - if (auto measure = dyn_cast(op)) { - if (measure.getQubitOut().getType() != measure.getQubitIn().getType()) { - SmallVector newTypes(measure.getResultTypes()); - newTypes[0] = measure.getQubitIn().getType(); - Operation* newOp = - cloneOpWithNewResultTypes(measure, newTypes, rewriter); - rewriter.replaceOp(measure, newOp->getResults()); - } - return WalkResult::advance(); - } - if (auto reset = dyn_cast(op)) { - if (reset.getQubitOut().getType() != reset.getQubitIn().getType()) { - rewriter.replaceOpWithNewOp(reset, reset.getQubitIn()); - } - return WalkResult::advance(); - } - if (auto unitary = dyn_cast(op)) { - (void)replaceLeafUnitaryWithAlignedQubitTypes(unitary, rewriter); - return WalkResult::advance(); - } - return WalkResult::advance(); - }); -} - namespace { struct MappingPass : impl::MappingPassBase { @@ -349,6 +64,7 @@ struct MappingPass : impl::MappingPassBase { using QubitValue = TypedValue; using IndexType = std::size_t; using IndexGate = std::pair; + using IndexGateSet = DenseSet; using Layer = DenseSet; /** @@ -702,7 +418,6 @@ struct MappingPass : impl::MappingPassBase { place(dynQubits, best->layout, func.getFunctionBody(), rewriter); commit(best->swaps, ltr.anchors, func.getFunctionBody(), rewriter); - synchronizeMappedQubitTypes(func.getFunctionBody(), rewriter); } } @@ -768,10 +483,8 @@ struct MappingPass : impl::MappingPassBase { */ [[nodiscard]] static SmallVector collectDynamicQubits(Region& funcBody) { - return SmallVector( - map_range(funcBody.getOps(), [](AllocOp op) { - return llvm::cast>(op.getResult()); - })); + return SmallVector(map_range( + funcBody.getOps(), [](AllocOp op) { return op.getResult(); })); } /** diff --git a/mlir/lib/Dialect/QTensor/IR/Operations/AllocOp.cpp b/mlir/lib/Dialect/QTensor/IR/Operations/AllocOp.cpp index c0d58b09c7..898b8b6412 100644 --- a/mlir/lib/Dialect/QTensor/IR/Operations/AllocOp.cpp +++ b/mlir/lib/Dialect/QTensor/IR/Operations/AllocOp.cpp @@ -57,8 +57,5 @@ LogicalResult AllocOp::verify() { << resultSize << ")"; } - auto elementType = resultType.getElementType(); - (void)elementType; - return success(); } diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp index fb188715e3..70a943c536 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -14,9 +14,7 @@ #include "mlir/Dialect/QC/IR/QCDialect.h" #include "mlir/Dialect/QC/IR/QCInterfaces.h" #include "mlir/Dialect/QC/IR/QCOps.h" -#include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" #include "mlir/Dialect/QCO/IR/QCODialect.h" -#include "mlir/Dialect/QCO/IR/QCOOps.h" #include "mlir/Dialect/QCO/Transforms/Mapping/Architecture.h" #include "mlir/Dialect/QCO/Transforms/Passes.h" @@ -29,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -49,7 +46,8 @@ struct ArchitectureParam { Architecture (*factory)(); }; -class MappingPassTestBase : public testing::Test { +class MappingPassTest : public testing::Test, + public testing::WithParamInterface { public: /** * @brief Walks the IR and validates if each two-qubit op is executable on the @@ -121,151 +119,10 @@ class MappingPassTestBase : public testing::Test { ASSERT_TRUE(succeeded(res)); } - static void runQCOMapping(OwningOpRef& moduleOp) { - PassManager pm(moduleOp->getContext()); - pm.addPass(qco::createMappingPass(qco::MappingPassOptions{.nlookahead = 5, - .alpha = 1, - .lambda = 0.85, - .niterations = 2, - .ntrials = 8, - .seed = 1337})); - auto res = pm.run(*moduleOp); - ASSERT_TRUE(succeeded(res)); - ASSERT_TRUE(succeeded(verify(*moduleOp))); - } - - static void expectSameStaticQubitType(Value input, Value output) { - const auto qIn = dyn_cast(input.getType()); - const auto qOut = dyn_cast(output.getType()); - ASSERT_TRUE(qIn); - ASSERT_TRUE(qOut); - EXPECT_EQ(qIn, qOut); - (void)qOut; - } - std::unique_ptr context; }; - -class MappingPassTest : public MappingPassTestBase, - public testing::WithParamInterface { -}; }; // namespace -TEST_F(MappingPassTestBase, SynchronizesParameterizedGateAndMeasureTypes) { - qco::QCOProgramBuilder builder(context.get()); - builder.initialize(); - - auto q0 = builder.allocQubit(); - auto q1 = builder.allocQubit(); - - q0 = builder.rx(0.25, q0); - std::tie(q0, q1) = builder.rxx(0.5, q0, q1); - std::tie(q0, q1) = builder.xx_plus_yy(0.75, 1.25, q0, q1); - const auto [measuredQubit, measuredBit] = builder.measure(q0); - (void)measuredBit; - q0 = measuredQubit; - - builder.sink(q0); - builder.sink(q1); - - auto moduleOp = builder.finalize(); - runQCOMapping(moduleOp); - - auto entry = *moduleOp->getOps().begin(); - qco::RXOp rxOp; - qco::RXXOp rxxOp; - qco::XXPlusYYOp xxPlusYYOp; - qco::MeasureOp measureOp; - entry.walk([&](Operation* op) { - if (auto rx = dyn_cast(op)) { - rxOp = rx; - } else if (auto rxx = dyn_cast(op)) { - rxxOp = rxx; - } else if (auto xxPlusYY = dyn_cast(op)) { - xxPlusYYOp = xxPlusYY; - } else if (auto measure = dyn_cast(op)) { - measureOp = measure; - } - }); - - ASSERT_TRUE(rxOp); - ASSERT_TRUE(rxxOp); - ASSERT_TRUE(xxPlusYYOp); - ASSERT_TRUE(measureOp); - - expectSameStaticQubitType(rxOp.getQubitIn(), rxOp.getQubitOut()); - expectSameStaticQubitType(rxxOp.getQubit0In(), rxxOp.getQubit0Out()); - expectSameStaticQubitType(rxxOp.getQubit1In(), rxxOp.getQubit1Out()); - expectSameStaticQubitType(xxPlusYYOp.getQubit0In(), - xxPlusYYOp.getQubit0Out()); - expectSameStaticQubitType(xxPlusYYOp.getQubit1In(), - xxPlusYYOp.getQubit1Out()); - expectSameStaticQubitType(measureOp.getQubitIn(), measureOp.getQubitOut()); -} - -TEST_F(MappingPassTestBase, SynchronizesModifierRegionArguments) { - qco::QCOProgramBuilder builder(context.get()); - builder.initialize(); - - auto q0 = builder.allocQubit(); - auto q1 = builder.allocQubit(); - - auto [controlsOut, targetsOut] = builder.ctrl( - {q0}, {q1}, [&](ValueRange targets) -> llvm::SmallVector { - return {builder.rx(0.5, targets[0])}; - }); - q0 = controlsOut.front(); - q1 = targetsOut.front(); - - auto invOut = - builder.inv({q1}, [&](ValueRange qubits) -> llvm::SmallVector { - return {builder.rx(0.25, qubits[0])}; - }); - q1 = invOut.front(); - - builder.sink(q0); - builder.sink(q1); - - auto moduleOp = builder.finalize(); - runQCOMapping(moduleOp); - - auto entry = *moduleOp->getOps().begin(); - qco::CtrlOp ctrlOp; - qco::InvOp invOp; - qco::RXOp ctrlBodyRxOp; - qco::RXOp invBodyRxOp; - entry.walk([&](Operation* op) { - if (auto ctrl = dyn_cast(op)) { - ctrlOp = ctrl; - } else if (auto inv = dyn_cast(op)) { - invOp = inv; - } else if (auto rx = dyn_cast(op)) { - if (rx->getParentOfType()) { - ctrlBodyRxOp = rx; - } else if (rx->getParentOfType()) { - invBodyRxOp = rx; - } - } - }); - - ASSERT_TRUE(ctrlOp); - ASSERT_TRUE(invOp); - ASSERT_TRUE(ctrlBodyRxOp); - ASSERT_TRUE(invBodyRxOp); - - ASSERT_EQ(ctrlOp.getBody()->getNumArguments(), ctrlOp.getNumTargets()); - expectSameStaticQubitType(ctrlOp.getTargetsIn()[0], - ctrlOp.getBody()->getArgument(0)); - expectSameStaticQubitType(ctrlBodyRxOp.getQubitIn(), - ctrlBodyRxOp.getQubitOut()); - - ASSERT_EQ(invOp.getBody()->getNumArguments(), invOp.getNumTargets()); - expectSameStaticQubitType(invOp.getQubitsIn()[0], - invOp.getBody()->getArgument(0)); - expectSameStaticQubitType(invBodyRxOp.getQubitIn(), - invBodyRxOp.getQubitOut()); -} - TEST_P(MappingPassTest, GHZ) { auto arch = GetParam().factory(); From ead5c13c9dc461a897ce1e22ce10570af4793d3d Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 31 Mar 2026 16:18:50 +0200 Subject: [PATCH 42/57] =?UTF-8?q?=F0=9F=94=A7=20Move=20`QubitMode`=20enum?= =?UTF-8?q?=20and=20`inferQubitMode`=20function=20to=20a=20more=20appropri?= =?UTF-8?q?ate=20location.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/lib/Conversion/QCOToQC/QCOToQC.cpp | 46 ++++++++++++------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp index e66e0e8007..273342b489 100644 --- a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -34,6 +34,29 @@ namespace mlir { using namespace qco; using namespace qc; +enum class QubitMode : std::uint8_t { Unknown, StaticOnly, DynamicOnly, Mixed }; + +[[nodiscard]] QubitMode inferQubitMode(func::FuncOp func) { + bool sawStatic = false; + bool sawAlloc = false; + + func.walk([&](Operation* op) { + sawStatic |= isa(op); + sawAlloc |= isa(op); + }); + + if (sawStatic && sawAlloc) { + return QubitMode::Mixed; + } + if (sawStatic) { + return QubitMode::StaticOnly; + } + if (sawAlloc) { + return QubitMode::DynamicOnly; + } + return QubitMode::Unknown; +} + #define GEN_PASS_DEF_QCOTOQC #include "mlir/Conversion/QCOToQC/QCOToQC.h.inc" @@ -96,29 +119,6 @@ struct ConvertQCOAllocOp final : OpConversionPattern { } }; -enum class QubitMode : std::uint8_t { Unknown, StaticOnly, DynamicOnly, Mixed }; - -[[nodiscard]] static QubitMode inferQubitMode(func::FuncOp func) { - bool sawStatic = false; - bool sawAlloc = false; - - func.walk([&](Operation* op) { - sawStatic |= isa(op); - sawAlloc |= isa(op); - }); - - if (sawStatic && sawAlloc) { - return QubitMode::Mixed; - } - if (sawStatic) { - return QubitMode::StaticOnly; - } - if (sawAlloc) { - return QubitMode::DynamicOnly; - } - return QubitMode::Unknown; -} - /** * @brief Converts qco.sink to qc.dealloc. */ From 45800ccb7f3d48330a1caf66c207208f7014c468 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 31 Mar 2026 17:14:06 +0200 Subject: [PATCH 43/57] =?UTF-8?q?=F0=9F=90=87=20Address=20rabbit's=20comme?= =?UTF-8?q?nts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/lib/Conversion/QCOToQC/QCOToQC.cpp | 8 ++++++++ .../lib/Dialect/QCO/IR/QubitManagement/SinkOp.cpp | 2 +- mlir/lib/Dialect/QTensor/IR/CMakeLists.txt | 1 - .../Conversion/QCOToQC/test_qco_to_qc.cpp | 15 ++++++++++++++- .../Conversion/QCToQCO/test_qc_to_qco.cpp | 15 ++++++++++++++- 5 files changed, 37 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp index 273342b489..ac4966c954 100644 --- a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -34,8 +34,16 @@ namespace mlir { using namespace qco; using namespace qc; +namespace { +/** + * @brief Function-local qubit mode classification. + */ enum class QubitMode : std::uint8_t { Unknown, StaticOnly, DynamicOnly, Mixed }; +} // namespace +/** + * @brief Infer whether a function uses static, dynamic, or mixed qubits. + */ [[nodiscard]] QubitMode inferQubitMode(func::FuncOp func) { bool sawStatic = false; bool sawAlloc = false; diff --git a/mlir/lib/Dialect/QCO/IR/QubitManagement/SinkOp.cpp b/mlir/lib/Dialect/QCO/IR/QubitManagement/SinkOp.cpp index 62ce1a384b..4f452f03af 100644 --- a/mlir/lib/Dialect/QCO/IR/QubitManagement/SinkOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/QubitManagement/SinkOp.cpp @@ -30,7 +30,7 @@ struct RemoveAllocSinkPair final : OpRewritePattern { LogicalResult matchAndRewrite(SinkOp op, PatternRewriter& rewriter) const override { auto* defOp = op.getQubit().getDefiningOp(); - if (!llvm::isa(defOp)) { + if (!llvm::isa_and_nonnull(defOp)) { return failure(); } diff --git a/mlir/lib/Dialect/QTensor/IR/CMakeLists.txt b/mlir/lib/Dialect/QTensor/IR/CMakeLists.txt index b4b416dac9..f19fa71111 100644 --- a/mlir/lib/Dialect/QTensor/IR/CMakeLists.txt +++ b/mlir/lib/Dialect/QTensor/IR/CMakeLists.txt @@ -21,7 +21,6 @@ add_mlir_dialect_library( MLIRIR MLIRDialectUtils MLIRArithDialect - MLIRDialectUtils MLIRInferTypeOpInterface MLIRSideEffectInterfaces PUBLIC diff --git a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp index 31036d2acb..09b7b9aa53 100644 --- a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp +++ b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp @@ -20,8 +20,10 @@ #include "qco_programs.h" #include +#include #include #include +#include #include #include #include @@ -89,7 +91,18 @@ TEST(QCOToQCMixedStaticDynamicQubitsTest, FailsConversion) { ASSERT_TRUE(program); EXPECT_TRUE(verify(*program).succeeded()); - EXPECT_TRUE(failed(runQCOToQCConversion(program.get()))); + std::string diagnostics; + ScopedDiagnosticHandler diagHandler(context.get(), [&](Diagnostic& diag) { + llvm::raw_string_ostream os(diagnostics); + diag.print(os); + os << '\n'; + return success(); + }); + + const auto result = runQCOToQCConversion(program.get()); + EXPECT_TRUE(failed(result)); + EXPECT_NE(diagnostics.find("mixing static and dynamic qubits"), + std::string::npos); } TEST_P(QCOToQCTest, ProgramEquivalence) { diff --git a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp index 5954479e94..5351126638 100644 --- a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp +++ b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp @@ -20,8 +20,10 @@ #include "qco_programs.h" #include +#include #include #include +#include #include #include #include @@ -89,7 +91,18 @@ TEST(QCToQCOMixedStaticDynamicQubitsTest, FailsConversion) { ASSERT_TRUE(program); EXPECT_TRUE(verify(*program).succeeded()); - EXPECT_TRUE(failed(runQCToQCOConversion(program.get()))); + std::string diagnostics; + ScopedDiagnosticHandler diagHandler(context.get(), [&](Diagnostic& diag) { + llvm::raw_string_ostream os(diagnostics); + diag.print(os); + os << '\n'; + return success(); + }); + + const auto result = runQCToQCOConversion(program.get()); + EXPECT_TRUE(failed(result)); + EXPECT_NE(diagnostics.find("mixing static and dynamic qubits"), + std::string::npos); } TEST_P(QCToQCOTest, ProgramEquivalence) { From 0ceb41d51a3a233c0efc17ffe2363f2c88e8ffa0 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 31 Mar 2026 17:24:44 +0200 Subject: [PATCH 44/57] =?UTF-8?q?=F0=9F=94=A7=20Mark=20`inferQubitMode`=20?= =?UTF-8?q?as=20static=20to=20limit=20its=20linkage=20scope?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/lib/Conversion/QCOToQC/QCOToQC.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp index ac4966c954..3decb01d27 100644 --- a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -44,7 +44,7 @@ enum class QubitMode : std::uint8_t { Unknown, StaticOnly, DynamicOnly, Mixed }; /** * @brief Infer whether a function uses static, dynamic, or mixed qubits. */ -[[nodiscard]] QubitMode inferQubitMode(func::FuncOp func) { +[[nodiscard]] static QubitMode inferQubitMode(func::FuncOp func) { bool sawStatic = false; bool sawAlloc = false; From 8c100e7650deeab331d1fdda6c0a6e9aba6070d9 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 31 Mar 2026 17:40:20 +0200 Subject: [PATCH 45/57] =?UTF-8?q?=F0=9F=93=9D=20Enhance=20documentation=20?= =?UTF-8?q?for=20`ConvertQCOSinkOp`=20to=20clarify=20transformation=20from?= =?UTF-8?q?=20`qco.sink`=20to=20`qc.dealloc`=20and=20explain=20qubit=20sem?= =?UTF-8?q?antics=20in=20QCO=20and=20QC=20dialects.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/lib/Conversion/QCOToQC/QCOToQC.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp index 3decb01d27..c361bf8986 100644 --- a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -129,6 +129,23 @@ struct ConvertQCOAllocOp final : OpConversionPattern { /** * @brief Converts qco.sink to qc.dealloc. + * + * @details + * In QCO, qubits have value/linear semantics and must be consumed explicitly + * (via `qco.sink`). In QC, qubits have reference semantics; for dynamic qubits + * we materialize this end-of-lifetime as `qc.dealloc`. For static-only + * functions, sinks are erased. + * + * The OpAdaptor automatically provides the type-converted qubit operand + * (`!qc.qubit` instead of `!qco.qubit`), so we simply pass it through to the + * new operation when needed. + * + * Example transformation: + * ```mlir + * qco.sink %q_qco : !qco.qubit + * // becomes: + * qc.dealloc %q_qc : !qc.qubit + * ``` */ struct ConvertQCOSinkOp final : OpConversionPattern { explicit ConvertQCOSinkOp(TypeConverter& typeConverter, MLIRContext* context, From 888c53efbc528be28bf6272f4cf10426d416f8e6 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 1 Apr 2026 10:53:50 +0200 Subject: [PATCH 46/57] =?UTF-8?q?=E2=8F=AA=20Revert=20adding=20cast?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/include/mlir/Dialect/QCO/Utils/Drivers.h | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Utils/Drivers.h b/mlir/include/mlir/Dialect/QCO/Utils/Drivers.h index 2b4b0553f9..1a901882dc 100644 --- a/mlir/include/mlir/Dialect/QCO/Utils/Drivers.h +++ b/mlir/include/mlir/Dialect/QCO/Utils/Drivers.h @@ -87,12 +87,9 @@ template void walkUnit(Region& region, Fn&& fn) { }; TypeSwitch(&curr) - .template Case([&](StaticOp op) { - qubits.add(cast>(op.getQubit()), op.getIndex()); - }) - .template Case([&](AllocOp op) { - qubits.add(cast>(op.getResult())); - }) + .template Case( + [&](StaticOp op) { qubits.add(op.getQubit(), op.getIndex()); }) + .template Case([&](AllocOp op) { qubits.add(op.getResult()); }) .template Case([&](UnitaryOpInterface op) { for (const auto& [prevV, nextV] : llvm::zip(op.getInputQubits(), op.getOutputQubits())) { From d8dfec5581a984a9ea23d14472779f0e131c77de Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 1 Apr 2026 10:57:33 +0200 Subject: [PATCH 47/57] =?UTF-8?q?=F0=9F=8E=A8=20Simplify=20QCO=20to=20QC?= =?UTF-8?q?=20conversion=20by=20avoiding=20walking=20the=20entire=20progra?= =?UTF-8?q?m?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/lib/Conversion/QCOToQC/QCOToQC.cpp | 133 ++++++++++-------- .../Conversion/QCOToQC/test_qco_to_qc.cpp | 27 ---- 2 files changed, 74 insertions(+), 86 deletions(-) diff --git a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp index c361bf8986..a9b02ee528 100644 --- a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -35,35 +35,50 @@ using namespace qco; using namespace qc; namespace { +/** @brief Qubit addressing mode */ +enum class QubitAddressingMode : std::uint8_t { + Unknown, //!< The addressing mode is not known. + Static, //!< The module uses static qubit allocation. + Dynamic //!< The module uses dynamic qubit allocation. +}; + /** - * @brief Function-local qubit mode classification. + * @brief State object for tracking qubit addressing mode. + * + * @details + * Used to track whether a function uses static or dynamic qubit allocation. + * This is used to determine whether to convert `qco.sink` to `qc.dealloc` (for + * dynamic qubits) or simply erase it (for static qubits). This is also used to + * catch cases of mixed addressing being used, which is not supported. */ -enum class QubitMode : std::uint8_t { Unknown, StaticOnly, DynamicOnly, Mixed }; -} // namespace +struct LoweringState { + /// The qubit addressing mode used in the module + QubitAddressingMode mode = QubitAddressingMode::Unknown; +}; /** - * @brief Infer whether a function uses static, dynamic, or mixed qubits. + * @brief Base class for conversion patterns that need access to lowering state + * + * @details + * Extends OpConversionPattern to provide access to a shared LoweringState + * object, which is used to track the addressing mode of the module. + * @tparam OpType The QCO operation type to be converted. */ -[[nodiscard]] static QubitMode inferQubitMode(func::FuncOp func) { - bool sawStatic = false; - bool sawAlloc = false; +template +class StatefulOpConversionPattern : public OpConversionPattern { - func.walk([&](Operation* op) { - sawStatic |= isa(op); - sawAlloc |= isa(op); - }); +public: + StatefulOpConversionPattern(TypeConverter& typeConverter, + MLIRContext* context, LoweringState* state) + : OpConversionPattern(typeConverter, context), state_(state) {} - if (sawStatic && sawAlloc) { - return QubitMode::Mixed; - } - if (sawStatic) { - return QubitMode::StaticOnly; - } - if (sawAlloc) { - return QubitMode::DynamicOnly; - } - return QubitMode::Unknown; -} + /// Returns the shared lowering state object + [[nodiscard]] LoweringState& getState() const { return *state_; } + +private: + LoweringState* state_; +}; +} // namespace #define GEN_PASS_DEF_QCOTOQC #include "mlir/Conversion/QCOToQC/QCOToQC.h.inc" @@ -112,12 +127,19 @@ class QCOToQCTypeConverter final : public TypeConverter { * %q = qc.alloc("q", 3, 0) : !qc.qubit * ``` */ -struct ConvertQCOAllocOp final : OpConversionPattern { - using OpConversionPattern::OpConversionPattern; +struct ConvertQCOAllocOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult matchAndRewrite(qco::AllocOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { + auto& qubitMode = getState().mode; + if (qubitMode == QubitAddressingMode::Unknown) { + qubitMode = QubitAddressingMode::Dynamic; + } + assert(qubitMode != QubitAddressingMode::Static && + "Static qubits cannot be mixed with dynamic qubits"); + // Create qc.alloc with preserved register metadata rewriter.replaceOpWithNewOp(op, op.getRegisterNameAttr(), op.getRegisterSizeAttr(), @@ -133,8 +155,8 @@ struct ConvertQCOAllocOp final : OpConversionPattern { * @details * In QCO, qubits have value/linear semantics and must be consumed explicitly * (via `qco.sink`). In QC, qubits have reference semantics; for dynamic qubits - * we materialize this end-of-lifetime as `qc.dealloc`. For static-only - * functions, sinks are erased. + * we materialize this end-of-lifetime as `qc.dealloc`. Static qubits do not + * need explicit deallocation, so we simply erase the `qco.sink` operation. * * The OpAdaptor automatically provides the type-converted qubit operand * (`!qc.qubit` instead of `!qco.qubit`), so we simply pass it through to the @@ -147,28 +169,23 @@ struct ConvertQCOAllocOp final : OpConversionPattern { * qc.dealloc %q_qc : !qc.qubit * ``` */ -struct ConvertQCOSinkOp final : OpConversionPattern { - explicit ConvertQCOSinkOp(TypeConverter& typeConverter, MLIRContext* context, - const DenseMap* modeMap) - : OpConversionPattern(typeConverter, context), modeMap(modeMap) {} +struct ConvertQCOSinkOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult - matchAndRewrite(qco::SinkOp op, OpAdaptor adaptor, + matchAndRewrite(SinkOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { - auto func = op->getParentOfType(); - auto it = modeMap->find(func); - const auto mode = (it != modeMap->end()) ? it->second : QubitMode::Unknown; + const auto mode = getState().mode; + assert(mode != QubitAddressingMode::Unknown && + "Sinks cannot exist without allocations"); - if (mode == QubitMode::StaticOnly) { + if (mode == QubitAddressingMode::Static) { rewriter.eraseOp(op); return success(); } - rewriter.replaceOpWithNewOp(op, adaptor.getQubit()); + rewriter.replaceOpWithNewOp(op, adaptor.getQubit()); return success(); } - -private: - const DenseMap* modeMap; }; /** @@ -186,12 +203,19 @@ struct ConvertQCOSinkOp final : OpConversionPattern { * %q = qc.static 0 : !qc.qubit * ``` */ -struct ConvertQCOStaticOp final : OpConversionPattern { - using OpConversionPattern::OpConversionPattern; +struct ConvertQCOStaticOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; LogicalResult matchAndRewrite(qco::StaticOp op, OpAdaptor /*adaptor*/, ConversionPatternRewriter& rewriter) const override { + auto& qubitMode = getState().mode; + if (qubitMode == QubitAddressingMode::Unknown) { + qubitMode = QubitAddressingMode::Static; + } + assert(qubitMode != QubitAddressingMode::Dynamic && + "Dynamic qubits cannot be mixed with static qubits"); + // Create qc.static with the same index rewriter.replaceOpWithNewOp(op, op.getIndex()); return success(); @@ -796,32 +820,20 @@ struct QCOToQC final : impl::QCOToQCBase { MLIRContext* context = &getContext(); auto* module = getOperation(); + // Create state object to track the qubit addressing mode + LoweringState state; + ConversionTarget target(*context); RewritePatternSet patterns(context); QCOToQCTypeConverter typeConverter(context); - DenseMap modeMap; - for (auto func : cast(module).getOps()) { - const auto mode = inferQubitMode(func); - if (mode == QubitMode::Mixed) { - func.emitError( - "mixing static and dynamic qubits is forbidden (saw both " - "qco.static and qco.alloc)"); - signalPassFailure(); - return; - } - modeMap.try_emplace(func, mode); - } - // Configure conversion target: QCO illegal, QC legal target.addIllegalDialect(); target.addLegalDialect(); - // Register operation conversion patterns - // Note: No state tracking needed - OpAdaptors handle type conversion + // Register operation conversion patterns that do not need state tracking patterns.add< - ConvertQCOAllocOp, ConvertQCOStaticOp, ConvertQCOMeasureOp, - ConvertQCOResetOp, + ConvertQCOMeasureOp, ConvertQCOResetOp, ConvertQCOZeroTargetOneParameterToQC, ConvertQCOOneTargetZeroParameterToQC, ConvertQCOOneTargetZeroParameterToQC, @@ -852,7 +864,10 @@ struct QCOToQC final : impl::QCOToQCBase { ConvertQCOTwoTargetTwoParameterToQC, ConvertQCOBarrierOp, ConvertQCOCtrlOp, ConvertQCOInvOp, ConvertQCOYieldOp>(typeConverter, context); - patterns.add(typeConverter, context, &modeMap); + + // Register operation conversion patterns that need state tracking + patterns.add( + typeConverter, context, &state); // Conversion of qco types in func.func signatures // Note: This currently has limitations with signature changes diff --git a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp index 09b7b9aa53..b3e0d997a7 100644 --- a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp +++ b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp @@ -78,33 +78,6 @@ static LogicalResult runQCOToQCConversion(ModuleOp module) { return pm.run(module); } -TEST(QCOToQCMixedStaticDynamicQubitsTest, FailsConversion) { - DialectRegistry registry; - registry.insert(); - auto context = std::make_unique(); - context->appendDialectRegistry(registry); - context->loadAllAvailableDialects(); - - auto program = qco::QCOProgramBuilder::build( - context.get(), MQT_NAMED_BUILDER(qco::mixedStaticDynamicQubits).fn); - ASSERT_TRUE(program); - EXPECT_TRUE(verify(*program).succeeded()); - - std::string diagnostics; - ScopedDiagnosticHandler diagHandler(context.get(), [&](Diagnostic& diag) { - llvm::raw_string_ostream os(diagnostics); - diag.print(os); - os << '\n'; - return success(); - }); - - const auto result = runQCOToQCConversion(program.get()); - EXPECT_TRUE(failed(result)); - EXPECT_NE(diagnostics.find("mixing static and dynamic qubits"), - std::string::npos); -} - TEST_P(QCOToQCTest, ProgramEquivalence) { const auto& [nameStr, programBuilder, referenceBuilder] = GetParam(); const auto name = " (" + nameStr + ")"; From 50c79fad23389057ca01387aee7eab805003ec4c Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 1 Apr 2026 12:50:33 +0200 Subject: [PATCH 48/57] =?UTF-8?q?=F0=9F=9A=B8=20Add=20convenience=20functi?= =?UTF-8?q?ons=20for=20getting=20control=20and=20target=20ranges=20through?= =?UTF-8?q?=20the=20QC=20UnitaryOpInterface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/include/mlir/Dialect/QC/IR/QCDialect.h | 4 ++++ mlir/include/mlir/Dialect/QC/IR/QCInterfaces.td | 4 ++++ mlir/include/mlir/Dialect/QC/IR/QCOps.td | 5 +++++ 3 files changed, 13 insertions(+) diff --git a/mlir/include/mlir/Dialect/QC/IR/QCDialect.h b/mlir/include/mlir/Dialect/QC/IR/QCDialect.h index 137c052b77..f6ff1cc093 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCDialect.h +++ b/mlir/include/mlir/Dialect/QC/IR/QCDialect.h @@ -52,6 +52,7 @@ template class TargetAndParameterArityTrait { static size_t getNumQubits() { return T; } static size_t getNumTargets() { return T; } static size_t getNumControls() { return 0; } + static ValueRange getControls() { return {}; } Value getQubit(size_t i) { if constexpr (T == 0) { @@ -71,6 +72,9 @@ template class TargetAndParameterArityTrait { } return this->getOperation()->getOperand(i); } + ValueRange getTargets() { + return this->getOperation()->getOperands().slice(0, T); + } static Value getControl([[maybe_unused]] size_t i) { llvm::reportFatalUsageError("Operation does not have controls"); diff --git a/mlir/include/mlir/Dialect/QC/IR/QCInterfaces.td b/mlir/include/mlir/Dialect/QC/IR/QCInterfaces.td index 6f77839a65..e0b86f1ca0 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCInterfaces.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCInterfaces.td @@ -44,6 +44,10 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { (ins "size_t":$i)>, InterfaceMethod<"Returns the i-th control qubit.", "Value", "getControl", (ins "size_t":$i)>, + InterfaceMethod<"Returns a range of all target qubits.", "ValueRange", + "getTargets", (ins)>, + InterfaceMethod<"Returns a range of all control qubits.", "ValueRange", + "getControls", (ins)>, // Parameter handling InterfaceMethod<"Returns the number of parameters.", "size_t", diff --git a/mlir/include/mlir/Dialect/QC/IR/QCOps.td b/mlir/include/mlir/Dialect/QC/IR/QCOps.td index 0255b54a2f..5f22b03171 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCOps.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCOps.td @@ -914,8 +914,10 @@ def BarrierOp : QCOp<"barrier", traits = [UnitaryOpInterface]> { size_t getNumQubits() { return getNumTargets(); } size_t getNumTargets() { return getQubits().size(); } static size_t getNumControls() { return 0; } + static ValueRange getControls() { return {}; } Value getQubit(size_t i) { return getTarget(i); } Value getTarget(size_t i); + ValueRange getTargets() { return getQubits(); } static Value getControl(size_t i) { llvm::reportFatalUsageError("BarrierOp cannot be controlled"); } static size_t getNumParams() { return 0; } static Value getParameter(size_t i) { llvm::reportFatalUsageError("BarrierOp does not have parameters"); } @@ -973,6 +975,7 @@ def CtrlOp size_t getNumControls() { return getControls().size(); } Value getQubit(size_t i); Value getTarget(size_t i) { return getBodyUnitary().getTarget(i); } + ValueRange getTargets() { return getBodyUnitary().getTargets(); } Value getControl(size_t i); size_t getNumParams() { return getBodyUnitary().getNumParams(); } Value getParameter(size_t i) { return getBodyUnitary().getParameter(i); } @@ -1011,8 +1014,10 @@ def InvOp : QCOp<"inv", size_t getNumQubits() { return getBodyUnitary().getNumQubits(); } size_t getNumTargets() { return getNumQubits(); } static size_t getNumControls() { return 0; } + static ValueRange getControls() { return {}; } Value getQubit(size_t i) { return getBodyUnitary().getQubit(i); } Value getTarget(size_t i) { return getQubit(i); } + ValueRange getTargets() { return getBodyUnitary().getTargets(); } static Value getControl(size_t i) { llvm::reportFatalUsageError("Operation does not have controls"); } size_t getNumParams() { return getBodyUnitary().getNumParams(); } Value getParameter(size_t i) { return getBodyUnitary().getParameter(i); } From 798899359fde00519d157459d5af09066cfa820c Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 1 Apr 2026 12:53:30 +0200 Subject: [PATCH 49/57] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Simplify=20QC=20to?= =?UTF-8?q?=20QCO=20conversion=20and=20drop=20sorting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Our equivalence checker can handle non-deterministic sequences of these operations just fine so there is no need to incur the cost of a costly sort on every conversion. Signed-off-by: burgholzer --- .../mlir/Dialect/QCO/Utils/ValueOrdering.h | 35 ------- mlir/lib/Conversion/QCToQCO/QCToQCO.cpp | 98 ++++++------------- .../Dialect/QCO/Builder/QCOProgramBuilder.cpp | 18 +--- 3 files changed, 32 insertions(+), 119 deletions(-) delete mode 100644 mlir/include/mlir/Dialect/QCO/Utils/ValueOrdering.h diff --git a/mlir/include/mlir/Dialect/QCO/Utils/ValueOrdering.h b/mlir/include/mlir/Dialect/QCO/Utils/ValueOrdering.h deleted file mode 100644 index e111820e06..0000000000 --- a/mlir/include/mlir/Dialect/QCO/Utils/ValueOrdering.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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 -#include - -namespace mlir::qco { - -/** - * @brief Deterministic order for SSA values. - * - * @details Uses block order when both defining ops are in the same block; - * otherwise fall back to opaque pointer order for a deterministic total order. - */ -struct SSAOrder { - bool operator()(Value a, Value b) const { - auto* opA = a.getDefiningOp(); - auto* opB = b.getDefiningOp(); - if (!opA || !opB || opA->getBlock() != opB->getBlock()) { - return a.getAsOpaquePointer() < b.getAsOpaquePointer(); - } - return opA->isBeforeInBlock(opB); - } -}; - -} // namespace mlir::qco diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index 4731fa6b9c..7b34de45a3 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -14,7 +14,6 @@ #include "mlir/Dialect/QC/IR/QCOps.h" #include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" -#include "mlir/Dialect/QCO/Utils/ValueOrdering.h" #include #include @@ -85,9 +84,7 @@ struct LoweringState { /// Per-region map from QC qubit references to latest QCO SSA values. /// /// @details Keys are `Operation::getParentRegion()` for ops being converted - /// (typically a `func.func` body, or a `qc.ctrl` / `qc.inv` region). This - /// avoids clearing state at the first `func.return` while later functions - /// still convert. + /// (typically a `func.func` body, or a `qc.ctrl` / `qc.inv` region). llvm::DenseMap> qubitMap; /// Stack of active modifier regions (`qc.ctrl` / `qc.inv`). @@ -223,17 +220,6 @@ static void assignMappedQubits(LoweringState& state, Operation* anchor, } } -/** @brief Collects the target qubits of a variadic QC unitary op. */ -template -[[nodiscard]] static SmallVector collectTargets(OpType op) { - SmallVector targets; - targets.reserve(op.getNumTargets()); - for (size_t i = 0; i < op.getNumTargets(); ++i) { - targets.emplace_back(op.getTarget(i)); - } - return targets; -} - /** @brief Pushes a new modifier frame seeded with aliased target values. */ static void pushModifierFrame(LoweringState& state, ValueRange qcTargets, ValueRange qcoTargets) { @@ -252,19 +238,18 @@ static void popModifierFrame(LoweringState& state) { /** @brief Adds entry block aliases for modifier target values. */ template -[[nodiscard]] static SmallVector -addModifierAliases(OpType op, ValueRange qcoTargets, - PatternRewriter& rewriter) { +[[nodiscard]] static ValueRange addModifierAliases(OpType op, + const size_t numTargets, + PatternRewriter& rewriter) { auto& entryBlock = op.getRegion().front(); - SmallVector aliases; - aliases.reserve(qcoTargets.size()); const auto opLoc = op.getLoc(); + const auto qubitType = qco::QubitType::get(op.getContext()); rewriter.modifyOpInPlace(op, [&] { - for (Value qcoTarget : qcoTargets) { - aliases.emplace_back(entryBlock.addArgument(qcoTarget.getType(), opLoc)); + for (size_t i = 0; i < numTargets; ++i) { + entryBlock.addArgument(qubitType, opLoc); } }); - return aliases; + return entryBlock.getArguments().take_back(numTargets); } namespace { @@ -289,39 +274,29 @@ struct ConvertFuncReturnOp final : StatefulOpConversionPattern { Region* funcRegion = op->getParentRegion(); auto& map = state.qubitMap[funcRegion]; - // Build return values from qubitMap (adaptor.getOperands() may carry stale - // root values because gate patterns use eraseOp instead of replaceOp). + // Build return values from qubitMap and collect live qubit information. + // A qubit from the current scope is considered alive if it is returned from + // the function. Otherwise, it is considered dead. llvm::SmallVector returnValues; - llvm::DenseSet escapedQubits; returnValues.reserve(op.getNumOperands()); + llvm::DenseSet liveQubits; for (auto [qcOperand, adaptorOperand] : llvm::zip_equal(op.getOperands(), adaptor.getOperands())) { - if (map.contains(qcOperand)) { - auto latest = map[qcOperand]; + if (auto it = map.find(qcOperand); it != map.end()) { + auto latest = it->second; returnValues.emplace_back(latest); - escapedQubits.insert(latest); + liveQubits.insert(latest); } else { returnValues.emplace_back(adaptorOperand); } } - // Collect non-escaped live qubits for deallocation. - llvm::DenseSet liveQubits; + // Deallocate dead qubit values for (Value qcoQubit : llvm::make_second_range(map)) { - if (!escapedQubits.contains(qcoQubit)) { - liveQubits.insert(qcoQubit); + if (!liveQubits.contains(qcoQubit)) { + SinkOp::create(rewriter, op.getLoc(), qcoQubit); } } - // Copy to a vector before sorting: DenseSet iterators are not - // random-access. - llvm::SmallVector liveQubitsSorted(liveQubits.begin(), - liveQubits.end()); - llvm::sort(liveQubitsSorted, SSAOrder{}); - - for (Value qubit : liveQubitsSorted) { - qco::SinkOp::create(rewriter, op.getLoc(), qubit); - } - state.qubitMap.erase(funcRegion); rewriter.replaceOpWithNewOp(op, returnValues); @@ -940,8 +915,9 @@ struct ConvertQCCtrlOp final : StatefulOpConversionPattern { ConversionPatternRewriter& rewriter) const override { auto& state = getState(); auto* operation = op.getOperation(); - const auto qcControls = llvm::to_vector(op.getControls()); - const auto qcTargets = collectTargets(op); + const auto numTargets = op.getNumTargets(); + const auto qcControls = op.getControls(); + const auto qcTargets = op.getTargets(); auto qcoControls = resolveMappedQubits(state, operation, qcControls); auto qcoTargets = resolveMappedQubits(state, operation, qcTargets); @@ -960,8 +936,8 @@ struct ConvertQCCtrlOp final : StatefulOpConversionPattern { auto& entryBlock = dstRegion.front(); assert(entryBlock.getNumArguments() == 0 && "QC ctrl region unexpectedly has entry block arguments"); - auto qcoTargetAliases = addModifierAliases(qcoOp, qcoTargets, rewriter); - pushModifierFrame(state, qcTargets, qcoTargetAliases); + pushModifierFrame(state, qcTargets, + addModifierAliases(qcoOp, numTargets, rewriter)); rewriter.eraseOp(op); return success(); @@ -993,7 +969,8 @@ struct ConvertQCInvOp final : StatefulOpConversionPattern { ConversionPatternRewriter& rewriter) const override { auto& state = getState(); auto* operation = op.getOperation(); - const auto qcTargets = collectTargets(op); + const auto numTargets = op.getNumTargets(); + const auto qcTargets = op.getTargets(); auto qcoTargets = resolveMappedQubits(state, operation, qcTargets); // Create qco.inv @@ -1009,8 +986,8 @@ struct ConvertQCInvOp final : StatefulOpConversionPattern { auto& entryBlock = dstRegion.front(); assert(entryBlock.getNumArguments() == 0 && "QC inv region unexpectedly has entry block arguments"); - auto qcoTargetAliases = addModifierAliases(qcoOp, qcoTargets, rewriter); - pushModifierFrame(state, qcTargets, qcoTargetAliases); + pushModifierFrame(state, qcTargets, + addModifierAliases(qcoOp, numTargets, rewriter)); rewriter.eraseOp(op); return success(); @@ -1071,22 +1048,6 @@ struct QCToQCO final : impl::QCToQCOBase { MLIRContext* context = &getContext(); auto* module = getOperation(); - for (auto func : cast(module).getOps()) { - bool sawStatic = false; - bool sawAlloc = false; - func.walk([&](Operation* op) { - sawStatic |= isa(op); - sawAlloc |= isa(op); - }); - if (sawStatic && sawAlloc) { - func.emitError( - "mixing static and dynamic qubits is forbidden (saw both " - "qc.static and qc.alloc)"); - signalPassFailure(); - return; - } - } - // Create state object to track qubit value flow LoweringState state; @@ -1146,9 +1107,8 @@ struct QCToQCO final : impl::QCToQCOBase { // Conversion of qc types in func.return // // Note: `func.return` may already be type-legal even though we still need - // to insert sink operations (`qco.sink`) for remaining live - // qubits. Therefore, we make it dynamically illegal unless the lowering - // state has no remaining qubits. + // to insert sink operations (`qco.sink`) for dead qubit values. Therefore, + // we mark it illegal as long as the qubit map of the region is not empty. patterns.add(typeConverter, context, &state); target.addDynamicallyLegalOp([&](const func::ReturnOp op) { if (!typeConverter.isLegal(op)) { diff --git a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp index 633b46bf1a..4d0868fc95 100644 --- a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp +++ b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp @@ -12,7 +12,6 @@ #include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" -#include "mlir/Dialect/QCO/Utils/ValueOrdering.h" #include "mlir/Dialect/QTensor/IR/QTensorDialect.h" #include "mlir/Dialect/QTensor/IR/QTensorOps.h" #include "mlir/Dialect/Utils/Utils.h" @@ -919,25 +918,14 @@ OwningOpRef QCOProgramBuilder::finalize() { } // Automatically deallocate all still-allocated qubits - // Sort qubits for deterministic output - llvm::SmallVector sortedQubits(validQubits.begin(), validQubits.end()); - llvm::sort(sortedQubits, SSAOrder{}); - - for (auto qubit : sortedQubits) { + for (auto qubit : validQubits) { SinkOp::create(*this, qubit); } + validQubits.clear(); - // Automatically deallocate all still-allocated tensors - // Sort tensors for deterministic output - llvm::SmallVector sortedTensors(validTensors.begin(), - validTensors.end()); - llvm::sort(sortedTensors, SSAOrder{}); - - for (auto tensor : sortedTensors) { + for (auto tensor : validTensors) { qtensor::DeallocOp::create(*this, tensor); } - - validQubits.clear(); validTensors.clear(); // Create constant 0 for successful exit code From cd18772707c8bc72ff1260dcaa44511ccc6397c3 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 1 Apr 2026 12:54:32 +0200 Subject: [PATCH 50/57] =?UTF-8?q?=F0=9F=94=A5=20Drop=20the=20programs=20fo?= =?UTF-8?q?r=20mixing=20dynamic=20and=20static=20qubits?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These may be added back when tackling https://github.com/munich-quantum-toolkit/core/issues/1601 Signed-off-by: burgholzer --- .../Conversion/QCToQCO/test_qc_to_qco.cpp | 27 ------------------- mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp | 3 --- mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp | 3 --- mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp | 5 +--- mlir/unittests/programs/qc_programs.cpp | 8 ------ mlir/unittests/programs/qc_programs.h | 3 --- mlir/unittests/programs/qco_programs.cpp | 8 ------ mlir/unittests/programs/qco_programs.h | 3 --- mlir/unittests/programs/qir_programs.cpp | 9 ------- mlir/unittests/programs/qir_programs.h | 3 --- 10 files changed, 1 insertion(+), 71 deletions(-) diff --git a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp index 5351126638..e71c4a566d 100644 --- a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp +++ b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp @@ -78,33 +78,6 @@ static LogicalResult runQCToQCOConversion(ModuleOp module) { return pm.run(module); } -TEST(QCToQCOMixedStaticDynamicQubitsTest, FailsConversion) { - DialectRegistry registry; - registry.insert(); - auto context = std::make_unique(); - context->appendDialectRegistry(registry); - context->loadAllAvailableDialects(); - - auto program = qc::QCProgramBuilder::build( - context.get(), MQT_NAMED_BUILDER(qc::mixedStaticDynamicQubits).fn); - ASSERT_TRUE(program); - EXPECT_TRUE(verify(*program).succeeded()); - - std::string diagnostics; - ScopedDiagnosticHandler diagHandler(context.get(), [&](Diagnostic& diag) { - llvm::raw_string_ostream os(diagnostics); - diag.print(os); - os << '\n'; - return success(); - }); - - const auto result = runQCToQCOConversion(program.get()); - EXPECT_TRUE(failed(result)); - EXPECT_NE(diagnostics.find("mixing static and dynamic qubits"), - std::string::npos); -} - TEST_P(QCToQCOTest, ProgramEquivalence) { const auto& [_, programBuilder, referenceBuilder] = GetParam(); const auto name = " (" + GetParam().name + ")"; diff --git a/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp b/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp index 33b8f0ac75..2a58f028e2 100644 --- a/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp +++ b/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp @@ -914,9 +914,6 @@ INSTANTIATE_TEST_SUITE_P( QCTestCase{"StaticQubitsWithInv", MQT_NAMED_BUILDER(staticQubitsWithInv), MQT_NAMED_BUILDER(staticQubitsWithInv)}, - QCTestCase{"MixedStaticDynamicQubits", - MQT_NAMED_BUILDER(mixedStaticDynamicQubits), - MQT_NAMED_BUILDER(mixedStaticDynamicQubits)}, QCTestCase{"AllocDeallocPair", MQT_NAMED_BUILDER(allocDeallocPair), MQT_NAMED_BUILDER(emptyQC)})); /// @} diff --git a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp index be31cd95cb..3e17c94d24 100644 --- a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp +++ b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp @@ -1078,9 +1078,6 @@ INSTANTIATE_TEST_SUITE_P( QCOTestCase{"StaticQubitsWithInv", MQT_NAMED_BUILDER(staticQubitsWithInv), MQT_NAMED_BUILDER(staticQubitsWithInv)}, - QCOTestCase{"MixedStaticDynamicQubits", - MQT_NAMED_BUILDER(mixedStaticDynamicQubits), - MQT_NAMED_BUILDER(mixedStaticDynamicQubits)}, QCOTestCase{"AllocSinkPair", MQT_NAMED_BUILDER(allocSinkPair), MQT_NAMED_BUILDER(emptyQCO)})); /// @} diff --git a/mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp b/mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp index 157fb5cabc..66f063b069 100644 --- a/mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp +++ b/mlir/unittests/Dialect/QIR/IR/test_qir_ir.cpp @@ -548,8 +548,5 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(staticQubitsWithCtrl)}, QIRTestCase{"StaticQubitsWithInv", MQT_NAMED_BUILDER(staticQubitsWithInv), - MQT_NAMED_BUILDER(staticQubitsWithInv)}, - QIRTestCase{"MixedStaticDynamicQubits", - MQT_NAMED_BUILDER(mixedStaticDynamicQubits), - MQT_NAMED_BUILDER(mixedStaticDynamicQubits)})); + MQT_NAMED_BUILDER(staticQubitsWithInv)})); /// @} diff --git a/mlir/unittests/programs/qc_programs.cpp b/mlir/unittests/programs/qc_programs.cpp index c8c5d7d9c4..7e6acc8b90 100644 --- a/mlir/unittests/programs/qc_programs.cpp +++ b/mlir/unittests/programs/qc_programs.cpp @@ -65,14 +65,6 @@ void staticQubitsWithInv(QCProgramBuilder& b) { b.inv([&]() { b.t(q0); }); } -void mixedStaticDynamicQubits(QCProgramBuilder& b) { - auto q0 = b.staticQubit(0); - auto q1 = b.allocQubit(); - b.rzz(0.123, q0, q1); - b.h(q0); - b.h(q1); -} - void allocDeallocPair(QCProgramBuilder& b) { auto q = b.allocQubit(); b.dealloc(q); diff --git a/mlir/unittests/programs/qc_programs.h b/mlir/unittests/programs/qc_programs.h index a0c379197c..e48c1c4404 100644 --- a/mlir/unittests/programs/qc_programs.h +++ b/mlir/unittests/programs/qc_programs.h @@ -48,9 +48,6 @@ void staticQubitsWithCtrl(QCProgramBuilder& b); /// Allocates a static qubit and applies an inverse modifier. void staticQubitsWithInv(QCProgramBuilder& b); -/// Allocates one static and one dynamic qubit and applies mixed operations. -void mixedStaticDynamicQubits(QCProgramBuilder& b); - /// Allocates and explicitly deallocates a single qubit. void allocDeallocPair(QCProgramBuilder& b); diff --git a/mlir/unittests/programs/qco_programs.cpp b/mlir/unittests/programs/qco_programs.cpp index 598e18c295..e7695e3cac 100644 --- a/mlir/unittests/programs/qco_programs.cpp +++ b/mlir/unittests/programs/qco_programs.cpp @@ -71,14 +71,6 @@ void staticQubitsWithInv(QCOProgramBuilder& b) { })[0]; } -void mixedStaticDynamicQubits(QCOProgramBuilder& b) { - auto q0 = b.staticQubit(0); - auto q1 = b.allocQubit(); - std::tie(q0, q1) = b.rzz(0.123, q0, q1); - q0 = b.h(q0); - q1 = b.h(q1); -} - void allocSinkPair(QCOProgramBuilder& b) { auto q = b.allocQubit(); b.sink(q); diff --git a/mlir/unittests/programs/qco_programs.h b/mlir/unittests/programs/qco_programs.h index 026fbf13c2..a14100102c 100644 --- a/mlir/unittests/programs/qco_programs.h +++ b/mlir/unittests/programs/qco_programs.h @@ -48,9 +48,6 @@ void staticQubitsWithCtrl(QCOProgramBuilder& b); /// Allocates a static qubit and applies an inverse modifier. void staticQubitsWithInv(QCOProgramBuilder& b); -/// Allocates one static and one dynamic qubit and applies mixed operations. -void mixedStaticDynamicQubits(QCOProgramBuilder& b); - /// Allocates and explicitly sinks a single qubit. void allocSinkPair(QCOProgramBuilder& b); diff --git a/mlir/unittests/programs/qir_programs.cpp b/mlir/unittests/programs/qir_programs.cpp index fa1102d04d..8674715ca7 100644 --- a/mlir/unittests/programs/qir_programs.cpp +++ b/mlir/unittests/programs/qir_programs.cpp @@ -65,15 +65,6 @@ void staticQubitsWithInv(QIRProgramBuilder& b) { b.tdg(q0); } -void mixedStaticDynamicQubits(QIRProgramBuilder& b) { - auto q0 = b.staticQubit(0); - auto qDyn = b.allocQubitRegister(1); - auto q1 = qDyn[0]; - b.rzz(0.123, q0, q1); - b.h(q0); - b.h(q1); -} - void singleMeasurementToSingleBit(QIRProgramBuilder& b) { auto q = b.allocQubitRegister(1); const auto c = b.allocClassicalBitRegister(1); diff --git a/mlir/unittests/programs/qir_programs.h b/mlir/unittests/programs/qir_programs.h index d14a5d57dc..927a935286 100644 --- a/mlir/unittests/programs/qir_programs.h +++ b/mlir/unittests/programs/qir_programs.h @@ -48,9 +48,6 @@ void staticQubitsWithCtrl(QIRProgramBuilder& b); /// Allocates a static qubit and applies the inverse of a T gate (Tdg). void staticQubitsWithInv(QIRProgramBuilder& b); -/// Allocates one static and one dynamic qubit and applies mixed operations. -void mixedStaticDynamicQubits(QIRProgramBuilder& b); - // --- MeasureOp ------------------------------------------------------------ // /// Measures a single qubit into a single classical bit. From 810322b4a56f61cd42d30ed2622402d2a99fe743 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 1 Apr 2026 12:59:00 +0200 Subject: [PATCH 51/57] =?UTF-8?q?=F0=9F=8E=A8=20streamline=20QCO=20program?= =?UTF-8?q?=20builder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp index 4d0868fc95..372498cb0c 100644 --- a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp +++ b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp @@ -742,8 +742,9 @@ std::pair QCOProgramBuilder::ctrl( auto ctrlOp = CtrlOp::create(*this, controls, targets); auto& block = ctrlOp.getBodyRegion().emplaceBlock(); + const auto qubitType = QubitType::get(getContext()); for (const auto target : targets) { - const auto arg = block.addArgument(target.getType(), getLoc()); + const auto arg = block.addArgument(qubitType, getLoc()); updateQubitTracking(target, arg); } const InsertionGuard guard(*this); @@ -758,12 +759,13 @@ std::pair QCOProgramBuilder::ctrl( // Update tracking const auto& controlsOut = ctrlOp.getControlsOut(); - for (const auto& [control, controlOut] : llvm::zip(controls, controlsOut)) { + for (const auto& [control, controlOut] : + llvm::zip_equal(controls, controlsOut)) { updateQubitTracking(control, controlOut); } const auto& targetsOut = ctrlOp.getTargetsOut(); for (const auto& [target, targetOut] : - llvm::zip(innerTargetsOut, targetsOut)) { + llvm::zip_equal(innerTargetsOut, targetsOut)) { updateQubitTracking(target, targetOut); } @@ -779,8 +781,9 @@ ValueRange QCOProgramBuilder::inv( // Add block arguments for all qubits auto& block = invOp.getBodyRegion().emplaceBlock(); + const auto qubitType = QubitType::get(getContext()); for (const auto qubit : qubits) { - const auto arg = block.addArgument(qubit.getType(), getLoc()); + const auto arg = block.addArgument(qubitType, getLoc()); updateQubitTracking(qubit, arg); } @@ -798,7 +801,7 @@ ValueRange QCOProgramBuilder::inv( // Update tracking const auto& targetsOut = invOp.getQubitsOut(); for (const auto& [target, targetOut] : - llvm::zip(innerTargetsOut, targetsOut)) { + llvm::zip_equal(innerTargetsOut, targetsOut)) { updateQubitTracking(target, targetOut); } From da8c30e9adbdb44f7f0410a6ad8ab82f754e1f37 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 1 Apr 2026 13:02:45 +0200 Subject: [PATCH 52/57] =?UTF-8?q?=E2=8F=AA=20Revert=20changes=20based=20on?= =?UTF-8?q?=20different=20type=20assumption?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp | 8 +++++--- mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp | 9 ++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp index bf67c21e36..96c811eed8 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp @@ -242,8 +242,9 @@ void CtrlOp::build( build(odsBuilder, odsState, controls, targets); auto& block = odsState.regions.front()->emplaceBlock(); - for (auto target : targets) { - block.addArgument(target.getType(), odsState.location); + const auto qubitType = QubitType::get(odsBuilder.getContext()); + for (size_t i = 0; i < targets.size(); ++i) { + block.addArgument(qubitType, odsState.location); } const OpBuilder::InsertionGuard guard(odsBuilder); @@ -262,8 +263,9 @@ LogicalResult CtrlOp::verify() { return emitOpError( "number of block arguments must match the number of targets"); } + const auto qubitType = QubitType::get(getContext()); for (size_t i = 0; i < numTargets; ++i) { - if (block.getArgument(i).getType() != getTargetsIn()[i].getType()) { + if (block.getArgument(i).getType() != qubitType) { return emitOpError("block argument type at index ") << i << " does not match target type"; } diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp index dd97a43687..18cc331c5b 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp @@ -8,6 +8,7 @@ * Licensed under the MIT License */ +#include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" #include @@ -325,8 +326,9 @@ void InvOp::build( build(odsBuilder, odsState, qubits); auto& block = odsState.regions.front()->emplaceBlock(); - for (auto qubit : qubits) { - block.addArgument(qubit.getType(), odsState.location); + const auto qubitType = QubitType::get(odsBuilder.getContext()); + for (size_t i = 0; i < qubits.size(); ++i) { + block.addArgument(qubitType, odsState.location); } const OpBuilder::InsertionGuard guard(odsBuilder); @@ -345,8 +347,9 @@ LogicalResult InvOp::verify() { return emitOpError( "number of block arguments must match the number of targets"); } + const auto qubitType = QubitType::get(getContext()); for (size_t i = 0; i < numTargets; ++i) { - if (block.getArgument(i).getType() != getQubitsIn()[i].getType()) { + if (block.getArgument(i).getType() != qubitType) { return emitOpError("block argument type at index ") << i << " does not match target type"; } From 4e92611fb9b18c509310308f4884e27d26b75d3f Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 1 Apr 2026 13:03:01 +0200 Subject: [PATCH 53/57] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Adjust=20docstring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/lib/Dialect/QCO/IR/QubitManagement/SinkOp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/IR/QubitManagement/SinkOp.cpp b/mlir/lib/Dialect/QCO/IR/QubitManagement/SinkOp.cpp index 4f452f03af..de6ee6edc7 100644 --- a/mlir/lib/Dialect/QCO/IR/QubitManagement/SinkOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/QubitManagement/SinkOp.cpp @@ -21,7 +21,7 @@ using namespace mlir::qco; namespace { /** - * @brief Remove matching allocation/static and sink pairs without operations + * @brief Remove matching alloc/static and sink pairs without operations * between them. */ struct RemoveAllocSinkPair final : OpRewritePattern { From b7be1cbd121a59c1754b04159b745a036ba9b9e2 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 1 Apr 2026 13:08:06 +0200 Subject: [PATCH 54/57] =?UTF-8?q?=E2=8F=AA=20Cleanup=20PR=20diff?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp | 2 -- mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp | 2 -- mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp | 5 +---- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp index b3e0d997a7..86133967cb 100644 --- a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp +++ b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp @@ -20,10 +20,8 @@ #include "qco_programs.h" #include -#include #include #include -#include #include #include #include diff --git a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp index e71c4a566d..ce017e956f 100644 --- a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp +++ b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp @@ -20,10 +20,8 @@ #include "qco_programs.h" #include -#include #include #include -#include #include #include #include diff --git a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp index 3e17c94d24..8c0c54d2ed 100644 --- a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp +++ b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp @@ -99,10 +99,7 @@ TEST_F(QCOTest, DirectIfBuilder) { // Test If construction directly qco::QCOProgramBuilder builder(context.get()); builder.initialize(); - // Match `allocQubitRegister(1)` defaults ("q", size=1, index=0). - auto q0 = AllocOp::create(builder, builder.getStringAttr("q"), - builder.getI64IntegerAttr(1), - builder.getI64IntegerAttr(0)); + auto q0 = AllocOp::create(builder); auto q1 = HOp::create(builder, q0); auto measureOp = MeasureOp::create(builder, q1); auto ifOp = From fa20138625481fa009f4358ab3afd92514ea4010 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 1 Apr 2026 13:29:53 +0200 Subject: [PATCH 55/57] =?UTF-8?q?=F0=9F=9A=A8=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/lib/Conversion/QCOToQC/QCOToQC.cpp | 1 + mlir/lib/Conversion/QCToQCO/QCToQCO.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp index a9b02ee528..7c1dc2baca 100644 --- a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index 7b34de45a3..23976d6760 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -400,7 +400,7 @@ struct ConvertQCDeallocOp final : StatefulOpConversionPattern { Value qcoQubit = lookupMappedQubit(state, operation, qcQubit); // Create the sink operation - rewriter.replaceOpWithNewOp(op, qcoQubit); + rewriter.replaceOpWithNewOp(op, qcoQubit); // Remove from state as qubit is no longer in use qubitMap.erase(qcQubit); From 701e5e8e5a722cbcf314b42ea897fe786c428f31 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 1 Apr 2026 13:33:42 +0200 Subject: [PATCH 56/57] =?UTF-8?q?=F0=9F=9A=B8=20Add=20convenience=20functi?= =?UTF-8?q?ons=20for=20getting=20parameter=20ranges=20through=20the=20QC?= =?UTF-8?q?=20UnitaryOpInterface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a preparation for reducing the code duplication in the various conversion routines around unitary gates. Signed-off-by: burgholzer --- mlir/include/mlir/Dialect/QC/IR/QCDialect.h | 4 ++++ mlir/include/mlir/Dialect/QC/IR/QCInterfaces.td | 2 ++ mlir/include/mlir/Dialect/QC/IR/QCOps.td | 3 +++ 3 files changed, 9 insertions(+) diff --git a/mlir/include/mlir/Dialect/QC/IR/QCDialect.h b/mlir/include/mlir/Dialect/QC/IR/QCDialect.h index f6ff1cc093..69d11e8471 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCDialect.h +++ b/mlir/include/mlir/Dialect/QC/IR/QCDialect.h @@ -88,6 +88,10 @@ template class TargetAndParameterArityTrait { } return this->getOperation()->getOperand(T + i); } + + ValueRange getParameters() { + return this->getOperation()->getOperands().slice(T, P); + } }; }; diff --git a/mlir/include/mlir/Dialect/QC/IR/QCInterfaces.td b/mlir/include/mlir/Dialect/QC/IR/QCInterfaces.td index e0b86f1ca0..9b2399bdff 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCInterfaces.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCInterfaces.td @@ -54,6 +54,8 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> { "getNumParams", (ins)>, InterfaceMethod<"Returns the i-th parameter.", "Value", "getParameter", (ins "size_t":$i)>, + InterfaceMethod<"Returns a range of all parameters.", "ValueRange", + "getParameters", (ins)>, // Convenience methods InterfaceMethod<"Returns true if the operation has any control qubits, " diff --git a/mlir/include/mlir/Dialect/QC/IR/QCOps.td b/mlir/include/mlir/Dialect/QC/IR/QCOps.td index 5f22b03171..b08c2c998a 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCOps.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCOps.td @@ -921,6 +921,7 @@ def BarrierOp : QCOp<"barrier", traits = [UnitaryOpInterface]> { static Value getControl(size_t i) { llvm::reportFatalUsageError("BarrierOp cannot be controlled"); } static size_t getNumParams() { return 0; } static Value getParameter(size_t i) { llvm::reportFatalUsageError("BarrierOp does not have parameters"); } + static ValueRange getParameters() { return {}; } static StringRef getBaseSymbol() { return "barrier"; } }]; } @@ -979,6 +980,7 @@ def CtrlOp Value getControl(size_t i); size_t getNumParams() { return getBodyUnitary().getNumParams(); } Value getParameter(size_t i) { return getBodyUnitary().getParameter(i); } + ValueRange getParameters() { return getBodyUnitary().getParameters(); } static StringRef getBaseSymbol() { return "ctrl"; } }]; @@ -1021,6 +1023,7 @@ def InvOp : QCOp<"inv", static Value getControl(size_t i) { llvm::reportFatalUsageError("Operation does not have controls"); } size_t getNumParams() { return getBodyUnitary().getNumParams(); } Value getParameter(size_t i) { return getBodyUnitary().getParameter(i); } + ValueRange getParameters() { return getBodyUnitary().getParameters(); } static StringRef getBaseSymbol() { return "inv"; } }]; From ac68ee47d29cdae3f6f6d760beed394bae83b75e Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 1 Apr 2026 15:12:06 +0200 Subject: [PATCH 57/57] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Adjust=20semantics?= =?UTF-8?q?=20of=20QC=20`InvOp`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/include/mlir/Dialect/QC/IR/QCOps.td | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mlir/include/mlir/Dialect/QC/IR/QCOps.td b/mlir/include/mlir/Dialect/QC/IR/QCOps.td index b08c2c998a..e42965e721 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCOps.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCOps.td @@ -1014,13 +1014,13 @@ def InvOp : QCOp<"inv", let extraClassDeclaration = [{ [[nodiscard]] UnitaryOpInterface getBodyUnitary(); size_t getNumQubits() { return getBodyUnitary().getNumQubits(); } - size_t getNumTargets() { return getNumQubits(); } - static size_t getNumControls() { return 0; } - static ValueRange getControls() { return {}; } + size_t getNumTargets() { return getBodyUnitary().getNumTargets(); } + size_t getNumControls() { return getBodyUnitary().getNumControls(); } Value getQubit(size_t i) { return getBodyUnitary().getQubit(i); } - Value getTarget(size_t i) { return getQubit(i); } + Value getTarget(size_t i) { return getBodyUnitary().getTarget(i); } ValueRange getTargets() { return getBodyUnitary().getTargets(); } - static Value getControl(size_t i) { llvm::reportFatalUsageError("Operation does not have controls"); } + Value getControl(size_t i) { return getBodyUnitary().getControl(i); } + ValueRange getControls() { return getBodyUnitary().getControls(); } size_t getNumParams() { return getBodyUnitary().getNumParams(); } Value getParameter(size_t i) { return getBodyUnitary().getParameter(i); } ValueRange getParameters() { return getBodyUnitary().getParameters(); }