From 09443570bf4a9f4ea8f0d15102fe7bcc2427f9a2 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 27 May 2026 12:43:59 +0200 Subject: [PATCH 1/7] Lift one tensor assumption --- .../Dialect/QTensor/Utils/TensorIterator.h | 7 +- mlir/lib/Compiler/CompilerPipeline.cpp | 2 + .../lib/Dialect/QCO/Transforms/CMakeLists.txt | 1 + .../QCO/Transforms/Mapping/Mapping.cpp | 123 +++++++----------- .../Dialect/QTensor/Utils/TensorIterator.cpp | 9 +- .../QCO/Transforms/Mapping/test_mapping.cpp | 61 +++------ 6 files changed, 79 insertions(+), 124 deletions(-) diff --git a/mlir/include/mlir/Dialect/QTensor/Utils/TensorIterator.h b/mlir/include/mlir/Dialect/QTensor/Utils/TensorIterator.h index 75acfdbe1c..5824a38e8a 100644 --- a/mlir/include/mlir/Dialect/QTensor/Utils/TensorIterator.h +++ b/mlir/include/mlir/Dialect/QTensor/Utils/TensorIterator.h @@ -28,9 +28,11 @@ class [[nodiscard]] TensorIterator { using difference_type = std::ptrdiff_t; using value_type = Operation*; - TensorIterator() : op_(nullptr), tensor_(nullptr), isSentinel_(false) {} + TensorIterator() + : op_(nullptr), tensor_(nullptr), isFinal_(false), isSentinel_(false) {} explicit TensorIterator(TypedValue tensor) - : op_(tensor.getDefiningOp()), tensor_(tensor), isSentinel_(false) {} + : op_(tensor.getDefiningOp()), tensor_(tensor), isFinal_(false), + isSentinel_(false) {} /// @returns the operation the iterator points to. [[nodiscard]] Operation* operation() const { return op_; } @@ -81,6 +83,7 @@ class [[nodiscard]] TensorIterator { Operation* op_; TypedValue tensor_; + bool isFinal_; bool isSentinel_; }; } // namespace mlir::qtensor diff --git a/mlir/lib/Compiler/CompilerPipeline.cpp b/mlir/lib/Compiler/CompilerPipeline.cpp index 311152b41a..b86a5c2db3 100644 --- a/mlir/lib/Compiler/CompilerPipeline.cpp +++ b/mlir/lib/Compiler/CompilerPipeline.cpp @@ -68,6 +68,8 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, return failure(); } + module->dumpPretty(); + auto runStage = [&](auto&& populatePasses) -> LogicalResult { PassManager pm(module.getContext()); configurePassManager(pm); diff --git a/mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt b/mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt index 2b1e33d326..3564b55dc5 100644 --- a/mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt +++ b/mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt @@ -15,6 +15,7 @@ add_mlir_library( PRIVATE MLIRQCODialect MLIRQCOUtils + MLIRQTensorUtils MLIRArithDialect MLIRMathDialect MLIRSCFUtils diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 29f80e74ab..26112c7e1e 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -16,12 +16,14 @@ #include "mlir/Dialect/QCO/Utils/Drivers.h" #include "mlir/Dialect/QCO/Utils/WireIterator.h" #include "mlir/Dialect/QTensor/IR/QTensorOps.h" +#include "mlir/Dialect/QTensor/Utils/TensorIterator.h" #include #include #include #include #include +#include #include #include #include @@ -453,9 +455,8 @@ struct MappingPass : impl::MappingPassBase { /** * @brief Collect wires of the quantum computation before placement. * @details - * The mapping pass currently assumes that the quantum computation consists of - * a single quantum tensor. The required qubits are extracted and inserted "in - * one go" at the beginning and the end of the function, respectively. + * The mapping pass currently assumes that the quantum computations consists + * The required qubits of each tensor are extracted and inserted "in one go". * * @returns a vector of wire iterator, or failure() if any of the above * assumptions are violated. @@ -467,44 +468,26 @@ struct MappingPass : impl::MappingPassBase { return failure(); } - const auto tensors = func.getOps(); - if (range_size(tensors) != 1) { - func.emitError() << "must contain a single qtensor.alloc operation"; - return failure(); - } - - bool inExtractPhase = true; SmallVector wires; - Value tensor = (*tensors.begin()).getResult(); - - while (true) { - assert(tensor.hasOneUse() && "getComputation: expected linear typing"); - Operation* curr = *(tensor.user_begin()); - - if (isa(curr)) { - break; - } - - if (auto extractOp = dyn_cast(curr)) { - if (!inExtractPhase) { - func.emitError() << "must extract and insert all qubits at once."; - return failure(); + for (auto tensor : func.getOps()) { + bool isInitPhase = true; + TensorIterator it(tensor.getResult()); + for (; it != std::default_sentinel; ++it) { + if (auto extract = dyn_cast(it.operation())) { + if (!isInitPhase) { + func.emitError() << "must extract and insert all qubits at once."; + return failure(); + } + wires.emplace_back(extract.getResult()); + continue; } - tensor = extractOp.getOutTensor(); - wires.emplace_back(extractOp.getResult()); - continue; - } - if (auto insertOp = dyn_cast(curr)) { - inExtractPhase = false; - tensor = insertOp.getResult(); - continue; + if (isa(it.operation())) { + isInitPhase = false; + continue; + } } - - report_fatal_error("unknown op in def-use chain: " + - curr->getName().getStringRef()); } - return wires; } @@ -533,50 +516,38 @@ struct MappingPass : impl::MappingPassBase { // Replace extract ops and collect in program-qubit order. SmallVector placedWires(layout.nqubits()); - const auto tensors = func.getOps(); - assert(range_size(tensors) == 1 && "place: expected exactly one tensor"); - - qtensor::AllocOp alloc = *(tensors.begin()); - const Value tensor = alloc.getResult(); - assert(tensor.hasOneUse() && "place: expected linear typing"); - - size_t prog = 0; - while (true) { - Operation* curr = *(tensor.user_begin()); - if (isa(curr)) { - rewriter.eraseOp(curr); - break; + size_t prog = 0UL; + for (auto alloc : make_early_inc_range(func.getOps())) { + TensorIterator it(alloc.getResult()); + while (it != std::default_sentinel) { + // Get the operation and early increment to avoid issues after erasure. + Operation* curr = it.operation(); + ++it; + + TypeSwitch(curr) + .Case([&](auto op) { + const auto hw = layout.getHardwareIndex(prog); + const auto qubit = staticOps[hw].getQubit(); + + rewriter.replaceAllUsesWith(op.getResult(), qubit); + rewriter.replaceAllUsesWith(op.getOutTensor(), op.getTensor()); + rewriter.eraseOp(op); + + placedWires[prog] = WireIterator(qubit); + ++prog; + }) + .Case([&](auto op) { + rewriter.setInsertionPointAfter(op); + SinkOp::create(rewriter, op.getLoc(), op.getScalar()); + rewriter.replaceAllUsesWith(op.getResult(), op.getDest()); + rewriter.eraseOp(op); + }) + .Case([&](auto op) { rewriter.eraseOp(op); }); } - TypeSwitch(curr) - .Case([&](ExtractOp op) { - const auto hw = layout.getHardwareIndex(prog); - const auto qubit = staticOps[hw].getQubit(); - - placedWires[prog] = WireIterator(qubit); - - rewriter.replaceAllUsesWith(op.getResult(), qubit); - rewriter.replaceAllUsesWith(op.getOutTensor(), tensor); - rewriter.eraseOp(op); - - ++prog; - }) - .Case([&](InsertOp op) { - rewriter.setInsertionPointAfter(op); - - SinkOp::create(rewriter, op.getLoc(), op.getScalar()); - - rewriter.replaceAllUsesWith(op.getResult(), tensor); - rewriter.eraseOp(op); - }) - .Default([&](Operation* op) { - report_fatal_error("unknown op in def-use chain: " + - op->getName().getStringRef()); - }); + rewriter.eraseOp(alloc); } - rewriter.eraseOp(alloc); - // Create sinks for remaining, unused, static qubits. rewriter.setInsertionPoint(func.getFunctionBody().back().getTerminator()); for (; prog < layout.nqubits(); ++prog) { diff --git a/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp b/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp index 7452ddcd7a..46b87a7bb1 100644 --- a/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp +++ b/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp @@ -44,13 +44,19 @@ void TensorIterator::forward() { return; } + // After the final operation comes the sentinel. + if (isFinal_) { + isSentinel_ = true; + return; + } + // Find the user-operation of the tensor SSA value. assert(tensor_.hasOneUse() && "expected linear typing"); op_ = *(tensor_.user_begin()); // The following operations define the end of the tensor's life-chain. if (isa(op_)) { - isSentinel_ = true; + isFinal_ = true; return; } @@ -80,6 +86,7 @@ void TensorIterator::backward() { // If the iterator is a sentinel, reactivate the iterator. if (isSentinel_) { isSentinel_ = false; + isFinal_ = true; return; } diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp index fdab758e68..f209d23716 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -135,37 +135,6 @@ TEST_P(MappingPassTest, NoQubitAllocations) { ASSERT_TRUE(res.failed()); } -TEST_P(MappingPassTest, NoTwoTensors) { - const auto& device = GetParam(); - - QCOProgramBuilder builder(context.get()); - builder.initialize(); - - Value tensor0 = builder.qtensorAlloc(1); - Value tensor1 = builder.qtensorAlloc(1); - - Value q0; - std::tie(tensor0, q0) = builder.qtensorExtract(tensor0, 0); - Value q1; - std::tie(tensor1, q1) = builder.qtensorExtract(tensor1, 0); - - q0 = builder.h(q0); - q1 = builder.h(q1); - - std::tie(q0, q1) = builder.cx(q0, q1); - - tensor0 = builder.qtensorInsert(q0, tensor0, 0); - tensor1 = builder.qtensorInsert(q1, tensor1, 0); - - builder.qtensorDealloc(tensor0); - builder.qtensorDealloc(tensor1); - - auto m = builder.finalize(); - auto res = runPass(m.get(), device, MappingPassOptions{}); - - ASSERT_TRUE(res.failed()); -} - TEST_P(MappingPassTest, NoExtractAfterInsert) { const auto& device = GetParam(); @@ -259,25 +228,26 @@ TEST_P(MappingPassTest, Sabre) { QCOProgramBuilder builder(context.get()); builder.initialize(); - Value tensor = builder.qtensorAlloc(6); + Value tensorUp = builder.qtensorAlloc(4); + Value tensorDown = builder.qtensorAlloc(2); Value q0; - std::tie(tensor, q0) = builder.qtensorExtract(tensor, 0); + std::tie(tensorUp, q0) = builder.qtensorExtract(tensorUp, 0); Value q1; - std::tie(tensor, q1) = builder.qtensorExtract(tensor, 1); + std::tie(tensorUp, q1) = builder.qtensorExtract(tensorUp, 1); Value q2; - std::tie(tensor, q2) = builder.qtensorExtract(tensor, 2); + std::tie(tensorUp, q2) = builder.qtensorExtract(tensorUp, 2); Value q3; - std::tie(tensor, q3) = builder.qtensorExtract(tensor, 3); + std::tie(tensorUp, q3) = builder.qtensorExtract(tensorUp, 3); Value q4; - std::tie(tensor, q4) = builder.qtensorExtract(tensor, 4); + std::tie(tensorDown, q4) = builder.qtensorExtract(tensorDown, 0); Value q5; - std::tie(tensor, q5) = builder.qtensorExtract(tensor, 5); + std::tie(tensorDown, q5) = builder.qtensorExtract(tensorDown, 1); q0 = builder.h(q0); q1 = builder.h(q1); @@ -329,13 +299,14 @@ TEST_P(MappingPassTest, Sabre) { std::tie(q4, c4) = builder.measure(q4); std::tie(q5, c5) = builder.measure(q5); - tensor = builder.qtensorInsert(q0, tensor, 0); - tensor = builder.qtensorInsert(q1, tensor, 1); - tensor = builder.qtensorInsert(q2, tensor, 2); - tensor = builder.qtensorInsert(q3, tensor, 3); - tensor = builder.qtensorInsert(q4, tensor, 4); - tensor = builder.qtensorInsert(q5, tensor, 5); - builder.qtensorDealloc(tensor); + tensorUp = builder.qtensorInsert(q0, tensorUp, 0); + tensorUp = builder.qtensorInsert(q1, tensorUp, 1); + tensorUp = builder.qtensorInsert(q2, tensorUp, 2); + tensorUp = builder.qtensorInsert(q3, tensorUp, 3); + tensorDown = builder.qtensorInsert(q4, tensorDown, 0); + tensorDown = builder.qtensorInsert(q5, tensorDown, 1); + builder.qtensorDealloc(tensorUp); + builder.qtensorDealloc(tensorDown); auto m = builder.finalize(); auto res = runPass(m.get(), device, MappingPassOptions{}); From cf658ff8f13bd9b420aab6c78ffadc2c6ae6edbf Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 27 May 2026 12:50:14 +0200 Subject: [PATCH 2/7] Revert CompilerPipeline.cpp --- mlir/lib/Compiler/CompilerPipeline.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/mlir/lib/Compiler/CompilerPipeline.cpp b/mlir/lib/Compiler/CompilerPipeline.cpp index b86a5c2db3..311152b41a 100644 --- a/mlir/lib/Compiler/CompilerPipeline.cpp +++ b/mlir/lib/Compiler/CompilerPipeline.cpp @@ -68,8 +68,6 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, return failure(); } - module->dumpPretty(); - auto runStage = [&](auto&& populatePasses) -> LogicalResult { PassManager pm(module.getContext()); configurePassManager(pm); From 67cac352521dd7303d8aed26acb1feef609175a6 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 27 May 2026 12:53:58 +0200 Subject: [PATCH 3/7] Remove unused header --- mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 26112c7e1e..e801ee0a5a 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include From c862449d15edcc96cb2a9aa7a0ae8adcd9cfcc54 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 27 May 2026 12:55:25 +0200 Subject: [PATCH 4/7] Fix tensor iterator unit-test --- .../unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp b/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp index ee85dc897e..358f095cd2 100644 --- a/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp +++ b/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp @@ -144,6 +144,8 @@ TEST_F(TensorIteratorTest, Traversal) { ++it; ASSERT_EQ(it.operation(), *(tensor8.user_begin())); // qtensor.dealloc ASSERT_EQ(it.tensor(), nullptr); + + ++it; ASSERT_EQ(it, std::default_sentinel); ++it; @@ -211,13 +213,15 @@ TEST_F(TensorIteratorTest, Traversal) { ASSERT_EQ(recIt.tensor(), tensorElse2); ++recIt; - ASSERT_EQ(recIt, std::default_sentinel); ASSERT_EQ(recIt.operation(), *(tensorElse2.user_begin())); // qco.yield ASSERT_EQ(recIt.tensor(), nullptr); ++recIt; ASSERT_EQ(recIt, std::default_sentinel); + ++recIt; + ASSERT_EQ(recIt, std::default_sentinel); + --recIt; ASSERT_EQ(recIt.operation(), *(tensorElse2.user_begin())); // qco.yield ASSERT_EQ(recIt.tensor(), nullptr); From a72a5a8dc3d29aafa13f9772b80ff2c92b8f5b14 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 27 May 2026 13:03:00 +0200 Subject: [PATCH 5/7] Update TensorIterator.cpp --- mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp b/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp index 46b87a7bb1..064cdbd624 100644 --- a/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp +++ b/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp @@ -99,6 +99,7 @@ void TensorIterator::backward() { // For these operations, tensor_ is an OpOperand. Hence, only get the def-op. if (isa(op_)) { op_ = tensor_.getDefiningOp(); + isFinal_ = false; return; } @@ -143,6 +144,7 @@ void TensorIterator::backward() { // If the current tensor SSA value is a BlockArgument (no defining op), the // operation will be a nullptr. op_ = tensor_.getDefiningOp(); + isFinal_ = false; } static_assert(std::bidirectional_iterator); From c9bd2741789930564e85b5612f6a19ecae83e1b7 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 27 May 2026 13:35:29 +0200 Subject: [PATCH 6/7] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c3b5c34c8..f05799f028 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel - ✨ Add a `hadamard-lifting` pass for lifting Hadamard gates above Pauli gates ([#1605]) ([**@lirem101**], [**@burgholzer**]) - ✨ Add a `merge-single-qubit-rotation-gates` pass for merging consecutive rotation gates using quaternions ([#1407], [#1674]) ([**@J4MMlE**], [**@denialhaag**], [**@MatthiasReumann**]) - ✨ Add conversions between `jeff` and QCO ([#1479], [#1548], [#1565], [#1637], [#1676], [#1706]) ([**@denialhaag**], [**@burgholzer**]) -- ✨ Add a `place-and-route` pass for mapping circuits to architectures with restricted topologies ([#1537], [#1547], [#1568], [#1581], [#1583], [#1588], [#1600], [#1664], [#1709], [#1716]) ([**@MatthiasReumann**], [**@burgholzer**]) +- ✨ Add a `place-and-route` pass for mapping circuits to architectures with restricted topologies ([#1537], [#1547], [#1568], [#1581], [#1583], [#1588], [#1600], [#1664], [#1709], [#1716], [#1748]) ([**@MatthiasReumann**], [**@burgholzer**]) - ✨ 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], [#1567], [#1569], [#1570], [#1572], [#1573], [#1580], [#1602], [#1620], [#1623], [#1624], [#1626], [#1627], [#1635], [#1638], [#1673], [#1675], [#1700], [#1717], [#1730]) ([**@burgholzer**], [**@denialhaag**], [**@taminob**], [**@DRovara**], [**@li-mingbao**], [**@Ectras**], [**@MatthiasReumann**], [**@simon1hofmann**]) @@ -402,6 +402,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool +[#1748]: https://github.com/munich-quantum-toolkit/core/pull/1748 [#1737]: https://github.com/munich-quantum-toolkit/core/pull/1737 [#1730]: https://github.com/munich-quantum-toolkit/core/pull/1730 [#1720]: https://github.com/munich-quantum-toolkit/core/pull/1720 From 227789eb39126ca72d8e9f09a6a11de0e90a9bff Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 28 May 2026 09:13:55 +0200 Subject: [PATCH 7/7] Fix function description --- mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index e801ee0a5a..74a6d9e6db 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -454,8 +454,17 @@ struct MappingPass : impl::MappingPassBase { /** * @brief Collect wires of the quantum computation before placement. * @details - * The mapping pass currently assumes that the quantum computations consists - * The required qubits of each tensor are extracted and inserted "in one go". + * The mapping pass currently assumes that the quantum computation allocates + * all tensors at the start of the function. The required qubits are extracted + * from these tensors and used for the computation. Finally, the qubits are + * inserted back into the tensors at the end of the function. + * Thus, a valid program has the following structure: + * + * T ⨉ [qtensor::AllocOp] + * → N ⨉ [qtensor::ExtractOp] + * → (Computation) + * → N ⨉ [qtensor::InsertOp] + * → T ⨉ [qtensor::DeallocOp] * * @returns a vector of wire iterator, or failure() if any of the above * assumptions are violated.