From 70c6bf7547ce5affcb64564da0ac9fa3ef4fd8df Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 4 Aug 2026 16:15:22 +0200 Subject: [PATCH 01/12] =?UTF-8?q?=F0=9F=90=9B=20Replay=20cold-preview=20SW?= =?UTF-8?q?AP=20plan=20during=20hot=20routing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../QCO/Transforms/Mapping/Mapping.cpp | 123 +++++++++++++++--- .../QCO/Transforms/Mapping/test_mapping.cpp | 80 +++++++++++- 2 files changed, 183 insertions(+), 20 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index a47f7fd46c..a3c93177b7 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -52,6 +52,7 @@ #include #include #include +#include #include #include #include @@ -154,6 +155,9 @@ struct MappingPass : impl::MappingPassBase { DenseSet touchedPrograms; }; + /// Whether A* SWAP batches are recorded (cold preview) or replayed (hot). + enum class SwapPlanMode : uint8_t { Off, Record, Replay }; + /// Parameters influencing the behavior of the A* search algorithm. struct Parameters { float alpha; @@ -369,9 +373,16 @@ struct MappingPass : impl::MappingPassBase { SmallVector materializedPrograms(wires.size()); std::iota(materializedPrograms.begin(), materializedPrograms.end(), 0); if (comp->hasTwoQubitOperations) { + // Cold preview records the A* SWAP plan that Hot must reproduce after + // sparse workspace materialization. + swapPlan.clear(); + swapPlanCursor = 0; + swapPlanMode = SwapPlanMode::Record; + RoutingBundle preview{.wires = wires, .infos = infos, .layout = *layout}; Statistics previewStats; if (failed(route(preview, previewStats))) { + swapPlanMode = SwapPlanMode::Off; func.emitError() << "failed to plan target routing"; signalPassFailure(); return; @@ -392,13 +403,27 @@ struct MappingPass : impl::MappingPassBase { .infos = std::move(infos), .layout = std::move(*layout)}; + if (comp->hasTwoQubitOperations) { + swapPlanCursor = 0; + swapPlanMode = SwapPlanMode::Replay; + } + const auto res = route( bundle, stats, &rewriter); - if (res.failed()) { + const bool planExhausted = + !comp->hasTwoQubitOperations || swapPlanCursor == swapPlan.size(); + swapPlanMode = SwapPlanMode::Off; + if (failed(res)) { func.emitError() << "failed to map the function"; signalPassFailure(); return; } + if (!planExhausted) { + func.emitError() + << "hot routing did not consume the full cold-preview SWAP plan"; + signalPassFailure(); + return; + } // Collect statistics. numSwaps += stats.nswaps; @@ -1192,17 +1217,30 @@ struct MappingPass : impl::MappingPassBase { /// (`RoutingMode::Cold`) or into the IR (`RoutingMode::Hot`). The function /// expects that each wire points at the correct insertion point. template - static void insertSWAPs(ArrayRef swaps, RoutingBundle& bundle, - Statistics& stats, IRRewriter* rewriter) { + static LogicalResult insertSWAPs(ArrayRef swaps, + RoutingBundle& bundle, Statistics& stats, + IRRewriter* rewriter) { auto& [wires, infos, layout] = bundle; + + // Hot: validate the full batch against a layout probe before mutating IR, + // so a missing workspace program cannot leave a partially applied batch. + if constexpr (Mode == RoutingMode::Hot) { + Layout probe = layout; + for (const auto& [hw0, hw1] : swaps) { + const auto [prog0, prog1] = probe.getProgramIndices(hw0, hw1); + if (!infos.containsProgram(prog0) || !infos.containsProgram(prog1)) { + return failure(); + } + probe.swap(hw0, hw1); + } + } + for (const auto& [hw0, hw1] : swaps) { const auto [prog0, prog1] = layout.getProgramIndices(hw0, hw1); stats.touchedPrograms.insert(prog0); stats.touchedPrograms.insert(prog1); if constexpr (Mode == RoutingMode::Hot) { - assert(infos.containsProgram(prog0) && infos.containsProgram(prog1) && - "expected the routing preview to materialize SWAP operands"); const auto i0 = infos.lookupIndex(prog0); const auto i1 = infos.lookupIndex(prog1); @@ -1231,6 +1269,7 @@ struct MappingPass : impl::MappingPassBase { } stats.nswaps += swaps.size(); + return success(); } /// Advance past all executable gates and return operations with nested @@ -1468,16 +1507,23 @@ struct MappingPass : impl::MappingPassBase { // using the restore (scf::ForOp, scf::While), converge (IfOp), and vote // and restore (IndexSwitchOp) strategies. + LogicalResult epilogueStatus = success(); const Layout exit = TypeSwitch(op) .Case([&](scf::ForOp) { const auto swaps = restore(children[0].layout, parent.layout); - insertSWAPs(swaps, children[0], stats, rewriter); + if (failed( + insertSWAPs(swaps, children[0], stats, rewriter))) { + epilogueStatus = failure(); + } return parent.layout; }) .template Case([&](scf::WhileOp) { const auto swaps = restore(children[1].layout, parent.layout); - insertSWAPs(swaps, children[1], stats, rewriter); + if (failed( + insertSWAPs(swaps, children[1], stats, rewriter))) { + epilogueStatus = failure(); + } // The scf::YieldOp is the terminator in the before region and // thus determines the final output layout. return children[0].layout; @@ -1485,8 +1531,12 @@ struct MappingPass : impl::MappingPassBase { .template Case([&](IfOp) { const auto [convergedLayout, fst, snd] = converge(children[0].layout, children[1].layout); - insertSWAPs(fst, children[0], stats, rewriter); - insertSWAPs(snd, children[1], stats, rewriter); + if (failed( + insertSWAPs(fst, children[0], stats, rewriter)) || + failed( + insertSWAPs(snd, children[1], stats, rewriter))) { + epilogueStatus = failure(); + } return convergedLayout; }) .template Case([&](IndexSwitchOp) { @@ -1496,10 +1546,16 @@ struct MappingPass : impl::MappingPassBase { })); for (RoutingBundle& child : children) { const auto swaps = restore(child.layout, winner); - insertSWAPs(swaps, child, stats, rewriter); + if (failed(insertSWAPs(swaps, child, stats, rewriter))) { + epilogueStatus = failure(); + break; + } } return winner; }); + if (failed(epilogueStatus)) { + return failure(); + } if constexpr (Mode == RoutingMode::Hot) { // Realign terminator values to ensure that i-th input qubit and the @@ -1565,11 +1621,11 @@ struct MappingPass : impl::MappingPassBase { return success(); } - /// Iterates over a dynamically computed window of layers and uses A* search - /// to find a SWAP sequence that makes each layer executable. Depending on - /// the template parameter, this function only updates the layout or also - /// inserts the SWAPs into the IR. The function returns `failure` if A* is - /// unable to find a solution. + /// Iterates over a dynamically computed window of layers and obtains a SWAP + /// sequence (via A* search, or by replaying the cold-preview plan in Hot + /// mode) that makes each layer executable. Depending on the template + /// parameter, this function only updates the layout or also inserts the + /// SWAPs into the IR. Returns `failure` if no solution is available. template requires(Mode != RoutingMode::Hot || Direction == WireDirection::Forward) LogicalResult route(RoutingBundle& bundle, Statistics& stats, @@ -1596,7 +1652,7 @@ struct MappingPass : impl::MappingPassBase { break; } - const auto swaps = search(window, layout); + const auto swaps = obtainSwaps(window, layout); if (failed(swaps)) { return failure(); } @@ -1614,7 +1670,18 @@ struct MappingPass : impl::MappingPassBase { } } - insertSWAPs(*swaps, bundle, stats, rewriter); + if (failed(insertSWAPs(*swaps, bundle, stats, rewriter))) { + return failure(); + } + + // After replay (or search), the front layer must be hardware-adjacent. + if (swapPlanMode == SwapPlanMode::Replay) { + const auto [prog0, prog1] = window.front(); + const auto [hw0, hw1] = layout.getHardwareIndices(prog0, prog1); + if (!target->areAdjacent(hw0, hw1)) { + return failure(); + } + } if constexpr (Mode == RoutingMode::Hot) { @@ -1633,9 +1700,29 @@ struct MappingPass : impl::MappingPassBase { return success(); } + /// Resolve the next A* SWAP batch: search (and optionally record), or replay + /// the cold-preview plan during hot routing. + FailureOr> obtainSwaps(const Window& window, + const Layout& layout) { + if (swapPlanMode == SwapPlanMode::Replay) { + if (swapPlanCursor >= swapPlan.size()) { + return failure(); + } + return swapPlan[swapPlanCursor++]; + } + + auto swaps = search(window, layout); + if (succeeded(swaps) && swapPlanMode == SwapPlanMode::Record) { + swapPlan.push_back(*swaps); + } + return swaps; + } + + SwapPlanMode swapPlanMode{SwapPlanMode::Off}; + SmallVector, 0> swapPlan; + size_t swapPlanCursor{0}; std::optional target; }; - } // namespace std::unique_ptr createMappingPass(const CompilerTarget& target, diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp index fa7d68f75d..dc4d658954 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -274,8 +274,7 @@ static void loopGHZ(QCOProgramBuilder& builder, Value& tensor, namespace { -class MappingPassTest : public testing::Test, - public testing::WithParamInterface { +class MappingPassFixture : public testing::Test { protected: void SetUp() override { DialectRegistry registry; @@ -296,6 +295,9 @@ class MappingPassTest : public testing::Test, std::unique_ptr context; }; +class MappingPassTest : public MappingPassFixture, + public testing::WithParamInterface {}; + }; // namespace TEST_P(MappingPassTest, FailNoEntryPoint) { @@ -1569,5 +1571,79 @@ TEST_P(MappingPassTest, MapIndexSwitchUsesVotedLayout) { EXPECT_EQ(numSwaps, 4UL); } +namespace { + +CompilerTarget getFourByFourSquareGrid() { + constexpr size_t side = 4; + constexpr size_t numTarget = side * side; + std::vector couplings; + couplings.reserve(2 * side * (side - 1)); + for (size_t r = 0; r < side; ++r) { + for (size_t c = 0; c < side; ++c) { + const auto i = static_cast(r * side + c); + if (c + 1 < side) { + couplings.emplace_back(i, i + 1); + } + if (r + 1 < side) { + couplings.emplace_back(i, i + static_cast(side)); + } + } + } + return CompilerTarget(numTarget, std::move(couplings)); +} + +/// Build an 11-qubit CX/CZ circuit used with a larger square target. +OwningOpRef buildPaddedSquareRoutingModule(MLIRContext* context) { + QCOProgramBuilder builder(context); + builder.initialize(); + constexpr size_t nprog = 11; + SmallVector qs; + qs.reserve(nprog); + for (size_t i = 0; i < nprog; ++i) { + qs.push_back(builder.allocQubit()); + } + for (size_t i = 0; i + 1 < nprog; ++i) { + std::tie(qs[i], qs[i + 1]) = builder.cx(qs[i], qs[i + 1]); + } + for (size_t i = 0; i + 2 < nprog; ++i) { + std::tie(qs[i], qs[i + 2]) = builder.cz(qs[i], qs[i + 2]); + } + for (Value q : qs) { + builder.sink(q); + } + return builder.finalize(); +} + +} // namespace + +/** + * @brief Hot routing replays the cold-preview SWAP plan on padded targets. + * + * On targets with more sites than program qubits, cold preview materializes + * only vacant layout indices touched by its plan. Hot must replay that plan + * (not re-run A*) so every SWAP operand has a wire. Sweep a few seeds and + * assert each result is executable with sparse workspace. + */ +TEST_F(MappingPassFixture, HotRouteRespectsColdPreviewWorkspace) { + const CompilerTarget target = getFourByFourSquareGrid(); + + for (size_t seed = 0; seed < 16; ++seed) { + auto module = buildPaddedSquareRoutingModule(context.get()); + ASSERT_TRUE(runPass(module.get(), target, + MappingPassOptions{ + .niterations = 1, .ntrials = 1, .seed = seed}) + .succeeded()) + << "seed " << seed; + ASSERT_TRUE(succeeded(verify(*module))) << "seed " << seed; + EXPECT_TRUE(isExecutable(getEntryPoint(module.get()), target)) + << "seed " << seed; + + size_t numStatics = 0; + module->walk([&](StaticOp) { ++numStatics; }); + EXPECT_GE(numStatics, 11U) << "seed " << seed; + EXPECT_LT(numStatics, target.numQubits()) << "seed " << seed; + } +} + INSTANTIATE_TEST_SUITE_P(NineQubitSquareGrid, MappingPassTest, testing::Values(getNineQubitSquareGrid())); From 99b331e743cd78a1ac79d807275aa43022660957 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 4 Aug 2026 16:47:29 +0200 Subject: [PATCH 02/12] =?UTF-8?q?=F0=9F=9A=A8=20Fix=20linter=20warnings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Dialect/QCO/Transforms/Mapping/test_mapping.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp index dc4d658954..b606483818 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -1571,16 +1571,14 @@ TEST_P(MappingPassTest, MapIndexSwitchUsesVotedLayout) { EXPECT_EQ(numSwaps, 4UL); } -namespace { - -CompilerTarget getFourByFourSquareGrid() { +static CompilerTarget getFourByFourSquareGrid() { constexpr size_t side = 4; constexpr size_t numTarget = side * side; std::vector couplings; couplings.reserve(2 * side * (side - 1)); for (size_t r = 0; r < side; ++r) { for (size_t c = 0; c < side; ++c) { - const auto i = static_cast(r * side + c); + const auto i = static_cast((r * side) + c); if (c + 1 < side) { couplings.emplace_back(i, i + 1); } @@ -1593,7 +1591,8 @@ CompilerTarget getFourByFourSquareGrid() { } /// Build an 11-qubit CX/CZ circuit used with a larger square target. -OwningOpRef buildPaddedSquareRoutingModule(MLIRContext* context) { +static OwningOpRef +buildPaddedSquareRoutingModule(MLIRContext* context) { QCOProgramBuilder builder(context); builder.initialize(); constexpr size_t nprog = 11; @@ -1614,8 +1613,6 @@ OwningOpRef buildPaddedSquareRoutingModule(MLIRContext* context) { return builder.finalize(); } -} // namespace - /** * @brief Hot routing replays the cold-preview SWAP plan on padded targets. * From e5177f83c17a8bd7532bc31375c8be7434f381ca Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 5 Aug 2026 09:04:02 +0200 Subject: [PATCH 03/12] Simplify placement and remove touchedPrograms --- .../QCO/Transforms/Mapping/Mapping.cpp | 209 +++------- .../QCO/Transforms/Mapping/test_mapping.cpp | 362 +++++++++--------- 2 files changed, 236 insertions(+), 335 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index a3c93177b7..20e048a6d4 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -52,7 +53,6 @@ #include #include #include -#include #include #include #include @@ -152,12 +152,8 @@ struct MappingPass : impl::MappingPassBase { /// Statistics collected while routing. struct Statistics { size_t nswaps{0}; - DenseSet touchedPrograms; }; - /// Whether A* SWAP batches are recorded (cold preview) or replayed (hot). - enum class SwapPlanMode : uint8_t { Off, Record, Replay }; - /// Parameters influencing the behavior of the A* search algorithm. struct Parameters { float alpha; @@ -370,60 +366,20 @@ struct MappingPass : impl::MappingPassBase { return; } - SmallVector materializedPrograms(wires.size()); - std::iota(materializedPrograms.begin(), materializedPrograms.end(), 0); - if (comp->hasTwoQubitOperations) { - // Cold preview records the A* SWAP plan that Hot must reproduce after - // sparse workspace materialization. - swapPlan.clear(); - swapPlanCursor = 0; - swapPlanMode = SwapPlanMode::Record; - - RoutingBundle preview{.wires = wires, .infos = infos, .layout = *layout}; - Statistics previewStats; - if (failed(route(preview, previewStats))) { - swapPlanMode = SwapPlanMode::Off; - func.emitError() << "failed to plan target routing"; - signalPassFailure(); - return; - } - for (const auto prog : previewStats.touchedPrograms) { - if (prog >= wires.size()) { - materializedPrograms.emplace_back(prog); - } - } - std::ranges::sort(materializedPrograms); - } - - std::tie(wires, infos) = std::move( - place(body, *layout, *target, materializedPrograms, *comp, rewriter)); + std::tie(wires, infos) = std::move(place(body, *layout, *comp, rewriter)); Statistics stats; RoutingBundle bundle{.wires = std::move(wires), .infos = std::move(infos), .layout = std::move(*layout)}; - if (comp->hasTwoQubitOperations) { - swapPlanCursor = 0; - swapPlanMode = SwapPlanMode::Replay; - } - const auto res = route( bundle, stats, &rewriter); - const bool planExhausted = - !comp->hasTwoQubitOperations || swapPlanCursor == swapPlan.size(); - swapPlanMode = SwapPlanMode::Off; - if (failed(res)) { + if (res.failed()) { func.emitError() << "failed to map the function"; signalPassFailure(); return; } - if (!planExhausted) { - func.emitError() - << "hot routing did not consume the full cold-preview SWAP plan"; - signalPassFailure(); - return; - } // Collect statistics. numSwaps += stats.nswaps; @@ -625,6 +581,7 @@ struct MappingPass : impl::MappingPassBase { "function body"; return WalkResult::interrupt(); }); + if (discovery.wasInterrupted()) { return failure(); } @@ -672,19 +629,18 @@ struct MappingPass : impl::MappingPassBase { /// /// Analogously to the discoverComputation function, the i-th extract /// operation defines the i-th program qubit. - static std::pair - place(Region& body, const Layout& layout, - const CompilerTarget& compilerTarget, - const ArrayRef materializedPrograms, Computation& computation, - IRRewriter& rewriter) { - SmallVector staticQubits(layout.nqubits()); + std::pair place(Region& body, const Layout& layout, + Computation& computation, + IRRewriter& rewriter) { + SmallVector staticQubits; + staticQubits.reserve(target->numQubits()); + // Create and save static qubit operations. rewriter.setInsertionPointToStart(&body.front()); - for (const auto prog : materializedPrograms) { - const auto hw = layout.getHardwareIndex(prog); - const auto site = compilerTarget.siteForVertex(hw); + for (size_t hw = 0; hw < layout.nqubits(); ++hw) { + const auto site = target->siteForVertex(hw); auto op = StaticOp::create(rewriter, body.getLoc(), site); - staticQubits[prog] = op.getQubit(); + staticQubits.emplace_back(op.getQubit()); rewriter.setInsertionPointAfter(op); } @@ -693,8 +649,8 @@ struct MappingPass : impl::MappingPassBase { for (auto alloc : computation.scalarAllocations) { const auto prog = wires.size(); - const auto qubit = staticQubits[prog]; - assert(qubit && "expected program qubit to be materialized"); + const auto hw = layout.getHardwareIndex(prog); + const auto qubit = staticQubits[hw]; rewriter.replaceAllUsesWith(alloc.getResult(), qubit); rewriter.eraseOp(alloc); @@ -708,8 +664,8 @@ struct MappingPass : impl::MappingPassBase { TypeSwitch(operation) .Case([&](auto op) { const auto prog = wires.size(); - const auto qubit = staticQubits[prog]; - assert(qubit && "expected program qubit to be materialized"); + const auto hw = layout.getHardwareIndex(prog); + const auto qubit = staticQubits[hw]; rewriter.replaceAllUsesWith(op.getResult(), qubit); rewriter.replaceAllUsesWith(op.getOutTensor(), op.getTensor()); @@ -730,20 +686,24 @@ struct MappingPass : impl::MappingPassBase { rewriter.eraseOp(tensor.allocation); } - const auto numProgramQubits = wires.size(); + // Create sinks for remaining, unused, static qubits. + rewriter.setInsertionPoint(body.back().getTerminator()); - for (const auto prog : materializedPrograms) { - if (prog < numProgramQubits) { - continue; - } - const auto qubit = staticQubits[prog]; + for (size_t prog = wires.size(); prog < layout.nqubits(); ++prog) { + const auto hw = layout.getHardwareIndex(prog); + const auto site = target->siteForVertex(hw); + const auto qubit = staticQubits[site]; wires.emplace_back(qubit); - infos.insertOrUpdate(wires.size() - 1, prog); + infos.insertOrUpdate(prog, prog); SinkOp::create(rewriter, body.getLoc(), qubit); } + // Finally, update the SCF operations such that they take all static qubits + // as input. To handle recursively nested SCF operations, use a stack of + // (region, mapping) pairs. + SmallVector>> stack; stack.emplace_back(body, DenseSet{}); @@ -761,7 +721,7 @@ struct MappingPass : impl::MappingPassBase { } }) .Case([&](scf::ForOp forOp) { - assert(qubits.size() == materializedPrograms.size()); + assert(qubits.size() == layout.nqubits()); llvm::for_each(getQubitValues(forOp.getInits()), [&](Value v) { qubits.erase(v); }); @@ -782,7 +742,7 @@ struct MappingPass : impl::MappingPassBase { DenseSet(regionQubits.begin(), regionQubits.end())); }) .Case([&](scf::WhileOp whileOp) { - assert(qubits.size() == materializedPrograms.size()); + assert(qubits.size() == layout.nqubits()); llvm::for_each(getQubitValues(whileOp.getInits()), [&](Value v) { qubits.erase(v); }); @@ -808,7 +768,7 @@ struct MappingPass : impl::MappingPassBase { DenseSet(afterArgs.begin(), afterArgs.end())); }) .Case([&](IfOp ifOp) { - assert(qubits.size() == materializedPrograms.size()); + assert(qubits.size() == layout.nqubits()); llvm::for_each(ifOp.getQubits(), [&](Value v) { qubits.erase(v); }); @@ -831,7 +791,7 @@ struct MappingPass : impl::MappingPassBase { DenseSet(elseArgs.begin(), elseArgs.end())); }) .Case([&](IndexSwitchOp switchOp) { - assert(qubits.size() == materializedPrograms.size()); + assert(qubits.size() == layout.nqubits()); llvm::for_each(switchOp.getTargets(), [&](Value value) { qubits.erase(value); }); @@ -872,6 +832,11 @@ struct MappingPass : impl::MappingPassBase { /// finally find the trial with the fewest SWAPs on the final backwards pass /// and return the respective layout. FailureOr generateLayout(const Wires& wires, const WireInfos& infos) { + if (!target->hasExplicitTopology()) { + return Layout::fromMapping( + llvm::to_vector(llvm::seq(target->numQubits()))); + } + std::mt19937_64 rng{seed}; struct Trial { @@ -1217,30 +1182,15 @@ struct MappingPass : impl::MappingPassBase { /// (`RoutingMode::Cold`) or into the IR (`RoutingMode::Hot`). The function /// expects that each wire points at the correct insertion point. template - static LogicalResult insertSWAPs(ArrayRef swaps, - RoutingBundle& bundle, Statistics& stats, - IRRewriter* rewriter) { + static void insertSWAPs(ArrayRef swaps, RoutingBundle& bundle, + Statistics& stats, IRRewriter* rewriter) { auto& [wires, infos, layout] = bundle; - - // Hot: validate the full batch against a layout probe before mutating IR, - // so a missing workspace program cannot leave a partially applied batch. - if constexpr (Mode == RoutingMode::Hot) { - Layout probe = layout; - for (const auto& [hw0, hw1] : swaps) { - const auto [prog0, prog1] = probe.getProgramIndices(hw0, hw1); - if (!infos.containsProgram(prog0) || !infos.containsProgram(prog1)) { - return failure(); - } - probe.swap(hw0, hw1); - } - } - for (const auto& [hw0, hw1] : swaps) { const auto [prog0, prog1] = layout.getProgramIndices(hw0, hw1); - stats.touchedPrograms.insert(prog0); - stats.touchedPrograms.insert(prog1); if constexpr (Mode == RoutingMode::Hot) { + assert(infos.containsProgram(prog0) && infos.containsProgram(prog1) && + "expected the routing preview to materialize SWAP operands"); const auto i0 = infos.lookupIndex(prog0); const auto i1 = infos.lookupIndex(prog1); @@ -1269,7 +1219,6 @@ struct MappingPass : impl::MappingPassBase { } stats.nswaps += swaps.size(); - return success(); } /// Advance past all executable gates and return operations with nested @@ -1507,23 +1456,16 @@ struct MappingPass : impl::MappingPassBase { // using the restore (scf::ForOp, scf::While), converge (IfOp), and vote // and restore (IndexSwitchOp) strategies. - LogicalResult epilogueStatus = success(); const Layout exit = TypeSwitch(op) .Case([&](scf::ForOp) { const auto swaps = restore(children[0].layout, parent.layout); - if (failed( - insertSWAPs(swaps, children[0], stats, rewriter))) { - epilogueStatus = failure(); - } + insertSWAPs(swaps, children[0], stats, rewriter); return parent.layout; }) .template Case([&](scf::WhileOp) { const auto swaps = restore(children[1].layout, parent.layout); - if (failed( - insertSWAPs(swaps, children[1], stats, rewriter))) { - epilogueStatus = failure(); - } + insertSWAPs(swaps, children[1], stats, rewriter); // The scf::YieldOp is the terminator in the before region and // thus determines the final output layout. return children[0].layout; @@ -1531,12 +1473,8 @@ struct MappingPass : impl::MappingPassBase { .template Case([&](IfOp) { const auto [convergedLayout, fst, snd] = converge(children[0].layout, children[1].layout); - if (failed( - insertSWAPs(fst, children[0], stats, rewriter)) || - failed( - insertSWAPs(snd, children[1], stats, rewriter))) { - epilogueStatus = failure(); - } + insertSWAPs(fst, children[0], stats, rewriter); + insertSWAPs(snd, children[1], stats, rewriter); return convergedLayout; }) .template Case([&](IndexSwitchOp) { @@ -1546,16 +1484,10 @@ struct MappingPass : impl::MappingPassBase { })); for (RoutingBundle& child : children) { const auto swaps = restore(child.layout, winner); - if (failed(insertSWAPs(swaps, child, stats, rewriter))) { - epilogueStatus = failure(); - break; - } + insertSWAPs(swaps, child, stats, rewriter); } return winner; }); - if (failed(epilogueStatus)) { - return failure(); - } if constexpr (Mode == RoutingMode::Hot) { // Realign terminator values to ensure that i-th input qubit and the @@ -1621,11 +1553,11 @@ struct MappingPass : impl::MappingPassBase { return success(); } - /// Iterates over a dynamically computed window of layers and obtains a SWAP - /// sequence (via A* search, or by replaying the cold-preview plan in Hot - /// mode) that makes each layer executable. Depending on the template - /// parameter, this function only updates the layout or also inserts the - /// SWAPs into the IR. Returns `failure` if no solution is available. + /// Iterates over a dynamically computed window of layers and uses A* search + /// to find a SWAP sequence that makes each layer executable. Depending on + /// the template parameter, this function only updates the layout or also + /// inserts the SWAPs into the IR. The function returns `failure` if A* is + /// unable to find a solution. template requires(Mode != RoutingMode::Hot || Direction == WireDirection::Forward) LogicalResult route(RoutingBundle& bundle, Statistics& stats, @@ -1652,7 +1584,7 @@ struct MappingPass : impl::MappingPassBase { break; } - const auto swaps = obtainSwaps(window, layout); + const auto swaps = search(window, layout); if (failed(swaps)) { return failure(); } @@ -1670,18 +1602,7 @@ struct MappingPass : impl::MappingPassBase { } } - if (failed(insertSWAPs(*swaps, bundle, stats, rewriter))) { - return failure(); - } - - // After replay (or search), the front layer must be hardware-adjacent. - if (swapPlanMode == SwapPlanMode::Replay) { - const auto [prog0, prog1] = window.front(); - const auto [hw0, hw1] = layout.getHardwareIndices(prog0, prog1); - if (!target->areAdjacent(hw0, hw1)) { - return failure(); - } - } + insertSWAPs(*swaps, bundle, stats, rewriter); if constexpr (Mode == RoutingMode::Hot) { @@ -1700,29 +1621,9 @@ struct MappingPass : impl::MappingPassBase { return success(); } - /// Resolve the next A* SWAP batch: search (and optionally record), or replay - /// the cold-preview plan during hot routing. - FailureOr> obtainSwaps(const Window& window, - const Layout& layout) { - if (swapPlanMode == SwapPlanMode::Replay) { - if (swapPlanCursor >= swapPlan.size()) { - return failure(); - } - return swapPlan[swapPlanCursor++]; - } - - auto swaps = search(window, layout); - if (succeeded(swaps) && swapPlanMode == SwapPlanMode::Record) { - swapPlan.push_back(*swaps); - } - return swaps; - } - - SwapPlanMode swapPlanMode{SwapPlanMode::Off}; - SmallVector, 0> swapPlan; - size_t swapPlanCursor{0}; std::optional target; }; + } // namespace std::unique_ptr createMappingPass(const CompilerTarget& target, @@ -1730,4 +1631,4 @@ std::unique_ptr createMappingPass(const CompilerTarget& target, return std::make_unique(target, options); } -} // namespace mlir::qco +} // namespace mlir::qco \ No newline at end of file diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp index b606483818..8023c35992 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -215,20 +215,26 @@ static bool isExecutable(func::FuncOp entry, const CompilerTarget& target) { return isExecutable(entry.getFunctionBody(), m, target); } -/// Return a 3x3 square-grid compiler target. -static CompilerTarget getNineQubitSquareGrid() { - return CompilerTarget(9, std::vector{{0, 1}, - {0, 3}, - {1, 2}, - {1, 4}, - {2, 5}, - {3, 4}, - {3, 6}, - {4, 5}, - {4, 7}, - {5, 8}, - {6, 7}, - {7, 8}}); +/// Return a nxn square-grid compiler target. +static CompilerTarget getSquareGridTarget(const size_t n) { + const auto numTarget = n * n; + + std::vector couplings; + couplings.reserve(n * n); + + for (auto r = 0; r < n; ++r) { + for (auto c = 0; c < n; ++c) { + const auto i = (r * n) + c; + if (c + 1 < n) { + couplings.emplace_back(i, i + 1); + } + if (r + 1 < n) { + couplings.emplace_back(i, i + n); + } + } + } + + return CompilerTarget(numTarget, std::move(couplings)); } /// Creates an N-qubit GHZ state, where N = `qubits.size()` using @@ -272,6 +278,16 @@ static void loopGHZ(QCOProgramBuilder& builder, Value& tensor, .front(); } +/// Creates an N-qubit CX/CZ circuit. +static void cxcz(QCOProgramBuilder& builder, SmallVector& qubits) { + for (size_t i = 0; i + 1 < qubits.size(); ++i) { + std::tie(qubits[i], qubits[i + 1]) = builder.cx(qubits[i], qubits[i + 1]); + } + for (size_t i = 0; i + 2 < qubits.size(); ++i) { + std::tie(qubits[i], qubits[i + 2]) = builder.cz(qubits[i], qubits[i + 2]); + } +} + namespace { class MappingPassFixture : public testing::Test { @@ -289,6 +305,7 @@ class MappingPassFixture : public testing::Test { const MappingPassOptions& options) { PassManager pm(m->getContext()); pm.addPass(createMappingPass(target, options)); + pm.addPass(createCanonicalizerPass()); return pm.run(m); } @@ -359,16 +376,16 @@ TEST_P(MappingPassTest, MapMixedScalarAndTensorAllocations) { tensor = builder.qtensorInsert(tensorQubit1, tensor, 1); builder.qtensorDealloc(tensor); - auto module = builder.finalize(); - ASSERT_TRUE(runPass(module.get(), target, MappingPassOptions{.ntrials = 1}) - .succeeded()); - ASSERT_TRUE(succeeded(verify(*module))); - EXPECT_TRUE(isExecutable(getEntryPoint(module.get()), target)); + auto m = builder.finalize(); + ASSERT_TRUE( + runPass(m.get(), target, MappingPassOptions{.ntrials = 1}).succeeded()); + ASSERT_TRUE(succeeded(verify(*m))); + EXPECT_TRUE(isExecutable(getEntryPoint(m.get()), target)); size_t numScalarAllocations = 0; size_t numTensorAllocations = 0; - module->walk([&](AllocOp) { ++numScalarAllocations; }); - module->walk([&](qtensor::AllocOp) { ++numTensorAllocations; }); + m->walk([&](AllocOp) { ++numScalarAllocations; }); + m->walk([&](qtensor::AllocOp) { ++numTensorAllocations; }); EXPECT_EQ(numScalarAllocations, 0); EXPECT_EQ(numTensorAllocations, 0); } @@ -391,19 +408,20 @@ TEST_P(MappingPassTest, MapProgramAfterQubitReuse) { std::tie(q1, bit1) = builder.measure(q1); builder.sink(q1); - auto module = builder.finalize({bit0, bit1}); + auto m = builder.finalize({bit0, bit1}); PassManager pm(context.get()); pm.addPass(createReuseQubits()); pm.addPass(createCanonicalizerPass()); pm.addPass(createMappingPass(target, MappingPassOptions{.ntrials = 1})); - ASSERT_TRUE(pm.run(module.get()).succeeded()); - ASSERT_TRUE(succeeded(verify(*module))); - EXPECT_TRUE(isExecutable(getEntryPoint(module.get()), target)); + pm.addPass(createCanonicalizerPass()); + ASSERT_TRUE(pm.run(m.get()).succeeded()); + ASSERT_TRUE(succeeded(verify(*m))); + EXPECT_TRUE(isExecutable(getEntryPoint(m.get()), target)); size_t numStatics = 0; size_t numResets = 0; - module->walk([&](StaticOp) { ++numStatics; }); - module->walk([&](ResetOp) { ++numResets; }); + m->walk([&](StaticOp) { ++numStatics; }); + m->walk([&](ResetOp) { ++numResets; }); EXPECT_EQ(numStatics, 1); EXPECT_EQ(numResets, 1); } @@ -428,16 +446,16 @@ TEST_P(MappingPassTest, FailNestedScalarAllocation) { } )mlir"; - auto module = parseSourceString(source, context.get()); - ASSERT_TRUE(module); - ASSERT_TRUE(succeeded(verify(*module))); + auto m = parseSourceString(source, context.get()); + ASSERT_TRUE(m); + ASSERT_TRUE(succeeded(verify(*m))); std::string diagnostics; ScopedDiagnosticHandler handler(context.get(), [&](Diagnostic& diagnostic) { diagnostics += diagnostic.str(); return success(); }); - EXPECT_TRUE(failed(runPass(module.get(), target, MappingPassOptions{}))); + EXPECT_TRUE(failed(runPass(m.get(), target, MappingPassOptions{}))); EXPECT_TRUE( StringRef(diagnostics) .contains( @@ -467,16 +485,16 @@ TEST_P(MappingPassTest, FailNestedTensorAllocation) { } )mlir"; - auto module = parseSourceString(source, context.get()); - ASSERT_TRUE(module); - ASSERT_TRUE(succeeded(verify(*module))); + auto m = parseSourceString(source, context.get()); + ASSERT_TRUE(m); + ASSERT_TRUE(succeeded(verify(*m))); std::string diagnostics; ScopedDiagnosticHandler handler(context.get(), [&](Diagnostic& diagnostic) { diagnostics += diagnostic.str(); return success(); }); - EXPECT_TRUE(failed(runPass(module.get(), target, MappingPassOptions{}))); + EXPECT_TRUE(failed(runPass(m.get(), target, MappingPassOptions{}))); EXPECT_TRUE( StringRef(diagnostics) .contains( @@ -503,13 +521,13 @@ TEST_P(MappingPassTest, FailNestedHigherArityUnitary) { builder.sink(qubit); } - auto module = builder.finalize(); + auto m = builder.finalize(); std::string diagnostics; ScopedDiagnosticHandler handler(context.get(), [&](Diagnostic& diagnostic) { diagnostics += diagnostic.str(); return success(); }); - EXPECT_TRUE(failed(runPass(module.get(), target, MappingPassOptions{}))); + EXPECT_TRUE(failed(runPass(m.get(), target, MappingPassOptions{}))); EXPECT_TRUE( StringRef(diagnostics) .contains("decompose it to one- and two-qubit operations first")) @@ -517,8 +535,8 @@ TEST_P(MappingPassTest, FailNestedHigherArityUnitary) { size_t numAllocations = 0; size_t numStatics = 0; - module->walk([&](AllocOp) { ++numAllocations; }); - module->walk([&](StaticOp) { ++numStatics; }); + m->walk([&](AllocOp) { ++numAllocations; }); + m->walk([&](StaticOp) { ++numStatics; }); EXPECT_EQ(numAllocations, 3); EXPECT_EQ(numStatics, 0); } @@ -581,64 +599,83 @@ TEST_P(MappingPassTest, FailTooManyQubitsForArch) { } TEST_P(MappingPassTest, MapTopologyOnlyWithEmptyOperationSet) { + constexpr int64_t size = 3; + const CompilerTarget target( 3, std::vector{{0, 1}, {1, 2}}, std::vector{}); QCOProgramBuilder builder(context.get()); - builder.initialize(); - SmallVector qubits{builder.allocQubit(), builder.allocQubit(), - builder.allocQubit()}; + builder.initialize(SmallVector(size, builder.getI1Type())); + + SmallVector qubits(size); + SmallVector bits(size); + + for (int64_t i = 0; i < size; ++i) { + qubits[i] = builder.allocQubit(); + } qubits[0] = builder.x(qubits[0]); std::tie(qubits[0], qubits[1]) = builder.rxx(0.25, qubits[0], qubits[1]); std::tie(qubits[1], qubits[2]) = builder.rzx(0.5, qubits[1], qubits[2]); std::tie(qubits[0], qubits[2]) = builder.cx(qubits[0], qubits[2]); - for (const auto qubit : qubits) { - builder.sink(qubit); + + for (int64_t i = 0; i < qubits.size(); ++i) { + std::tie(qubits[i], bits[i]) = builder.measure(qubits[i]); + builder.sink(qubits[i]); } - auto module = builder.finalize(); - ASSERT_TRUE(runPass(module.get(), target, MappingPassOptions{.ntrials = 1}) - .succeeded()); - ASSERT_TRUE(succeeded(verify(*module))); - EXPECT_TRUE(isExecutable(getEntryPoint(module.get()), target)); + auto m = builder.finalize(bits); + ASSERT_TRUE( + runPass(m.get(), target, MappingPassOptions{.ntrials = 1}).succeeded()); + ASSERT_TRUE(succeeded(verify(*m))); + EXPECT_TRUE(isExecutable(getEntryPoint(m.get()), target)); size_t numSwaps = 0; - module->walk([&](SWAPOp) { ++numSwaps; }); + m->walk([&](SWAPOp) { ++numSwaps; }); EXPECT_GT(numSwaps, 0); } TEST_P(MappingPassTest, PreserveNoncontiguousTargetSiteIds) { + constexpr int64_t size = 3; + std::vector sites; sites.emplace_back(7); sites.emplace_back(19); sites.emplace_back(42); + const CompilerTarget target( std::move(sites), std::vector{{7, 19}, {19, 42}}, std::vector{}); QCOProgramBuilder builder(context.get()); - builder.initialize(); - SmallVector qubits{builder.allocQubit(), builder.allocQubit(), - builder.allocQubit()}; + builder.initialize(SmallVector(size, builder.getI1Type())); + + SmallVector qubits(size); + SmallVector bits(size); + + for (int64_t i = 0; i < size; ++i) { + qubits[i] = builder.allocQubit(); + } + std::tie(qubits[0], qubits[1]) = builder.cx(qubits[0], qubits[1]); std::tie(qubits[1], qubits[2]) = builder.cz(qubits[1], qubits[2]); std::tie(qubits[0], qubits[2]) = builder.cx(qubits[0], qubits[2]); - for (const auto qubit : qubits) { - builder.sink(qubit); + for (int64_t i = 0; i < qubits.size(); ++i) { + std::tie(qubits[i], bits[i]) = builder.measure(qubits[i]); + builder.sink(qubits[i]); } - auto module = builder.finalize(); - ASSERT_TRUE(runPass(module.get(), target, MappingPassOptions{.ntrials = 1}) - .succeeded()); - ASSERT_TRUE(succeeded(verify(*module))); - EXPECT_TRUE(isExecutable(getEntryPoint(module.get()), target)); + auto m = builder.finalize(bits); + ASSERT_TRUE( + runPass(m.get(), target, MappingPassOptions{.ntrials = 1}).succeeded()); + ASSERT_TRUE(succeeded(verify(*m))); + EXPECT_TRUE(isExecutable(getEntryPoint(m.get()), target)); const DenseSet expectedSites{7, 19, 42}; size_t numStatics = 0; - module->walk([&](StaticOp op) { + m->walk([&](StaticOp op) { ++numStatics; EXPECT_TRUE(expectedSites.contains(op.getIndex())); }); @@ -652,27 +689,32 @@ TEST_P(MappingPassTest, KeepWorkspaceSparseOnLargeTarget) { for (size_t site = 1; site < numTargetQubits; ++site) { couplings.emplace_back(0, static_cast(site)); } + const CompilerTarget target(numTargetQubits, std::move(couplings)); QCOProgramBuilder builder(context.get()); - builder.initialize(); + builder.initialize(SmallVector(2, builder.getI1Type())); + + SmallVector bits(2); Value q0 = builder.allocQubit(); Value q1 = builder.allocQubit(); std::tie(q0, q1) = builder.cx(q0, q1); + std::tie(q0, bits[0]) = builder.measure(q0); + std::tie(q1, bits[1]) = builder.measure(q1); builder.sink(q0); builder.sink(q1); - auto module = builder.finalize(); - ASSERT_TRUE(runPass(module.get(), target, + auto m = builder.finalize(bits); + ASSERT_TRUE(runPass(m.get(), target, MappingPassOptions{.niterations = 1, .ntrials = 1}) .succeeded()); - ASSERT_TRUE(succeeded(verify(*module))); - EXPECT_TRUE(isExecutable(getEntryPoint(module.get()), target)); + ASSERT_TRUE(succeeded(verify(*m))); + EXPECT_TRUE(isExecutable(getEntryPoint(m.get()), target)); size_t numStatics = 0; size_t numSinks = 0; - module->walk([&](StaticOp) { ++numStatics; }); - module->walk([&](SinkOp) { ++numSinks; }); + m->walk([&](StaticOp) { ++numStatics; }); + m->walk([&](SinkOp) { ++numSinks; }); EXPECT_GE(numStatics, 2); EXPECT_LE(numStatics, 3); EXPECT_LT(numStatics, numTargetQubits); @@ -771,7 +813,7 @@ TEST_P(MappingPassTest, MapGroverLike) { pm.addPass(createMappingPass(target, MappingPassOptions{})); QCOProgramBuilder builder(context.get()); - builder.initialize(SmallVector(5, builder.getI1Type())); + builder.initialize(SmallVector(size, builder.getI1Type())); Value tensor = builder.qtensorAlloc(4); Value flagTensor = builder.qtensorAlloc(1); @@ -940,14 +982,13 @@ TEST_P(MappingPassTest, MapForWithClassicalIterArg) { } )mlir"; - auto module = parseSourceString(source, context.get()); - ASSERT_TRUE(module); - ASSERT_TRUE(verify(*module).succeeded()); - - ASSERT_TRUE(runPass(module.get(), target, MappingPassOptions{.ntrials = 1}) - .succeeded()); - EXPECT_TRUE(verify(*module).succeeded()); - EXPECT_TRUE(isExecutable(getEntryPoint(module.get()), target)); + auto m = parseSourceString(source, context.get()); + ASSERT_TRUE(m); + ASSERT_TRUE(verify(*m).succeeded()); + ASSERT_TRUE( + runPass(m.get(), target, MappingPassOptions{.ntrials = 1}).succeeded()); + EXPECT_TRUE(verify(*m).succeeded()); + EXPECT_TRUE(isExecutable(getEntryPoint(m.get()), target)); } TEST_P(MappingPassTest, MapTypeChangingWhileWithClassicalState) { @@ -991,14 +1032,14 @@ TEST_P(MappingPassTest, MapTypeChangingWhileWithClassicalState) { } )mlir"; - auto module = parseSourceString(source, context.get()); - ASSERT_TRUE(module); - ASSERT_TRUE(verify(*module).succeeded()); + auto m = parseSourceString(source, context.get()); + ASSERT_TRUE(m); + ASSERT_TRUE(verify(*m).succeeded()); - ASSERT_TRUE(runPass(module.get(), target, MappingPassOptions{.ntrials = 1}) - .succeeded()); - EXPECT_TRUE(verify(*module).succeeded()); - EXPECT_TRUE(isExecutable(getEntryPoint(module.get()), target)); + ASSERT_TRUE( + runPass(m.get(), target, MappingPassOptions{.ntrials = 1}).succeeded()); + EXPECT_TRUE(verify(*m).succeeded()); + EXPECT_TRUE(isExecutable(getEntryPoint(m.get()), target)); } TEST_P(MappingPassTest, MapIfWithClassicalResult) { @@ -1039,17 +1080,17 @@ TEST_P(MappingPassTest, MapIfWithClassicalResult) { } )mlir"; - auto module = parseSourceString(source, context.get()); - ASSERT_TRUE(module); - ASSERT_TRUE(succeeded(verify(*module))); + auto m = parseSourceString(source, context.get()); + ASSERT_TRUE(m); + ASSERT_TRUE(succeeded(verify(*m))); - ASSERT_TRUE(runPass(module.get(), target, MappingPassOptions{.ntrials = 1}) - .succeeded()); - ASSERT_TRUE(succeeded(verify(*module))); - EXPECT_TRUE(isExecutable(getEntryPoint(module.get()), target)); + ASSERT_TRUE( + runPass(m.get(), target, MappingPassOptions{.ntrials = 1}).succeeded()); + ASSERT_TRUE(succeeded(verify(*m))); + EXPECT_TRUE(isExecutable(getEntryPoint(m.get()), target)); IfOp ifOp; - module->walk([&](IfOp candidate) { ifOp = candidate; }); + m->walk([&](IfOp candidate) { ifOp = candidate; }); ASSERT_TRUE(ifOp); ASSERT_EQ(ifOp.getClassicalResults().size(), 1); EXPECT_TRUE(ifOp.getClassicalResults().front().getType().isInteger(64)); @@ -1104,17 +1145,17 @@ TEST_P(MappingPassTest, MapIndexSwitchWithClassicalResult) { } )mlir"; - auto module = parseSourceString(source, context.get()); - ASSERT_TRUE(module); - ASSERT_TRUE(succeeded(verify(*module))); + auto m = parseSourceString(source, context.get()); + ASSERT_TRUE(m); + ASSERT_TRUE(succeeded(verify(*m))); - ASSERT_TRUE(runPass(module.get(), target, MappingPassOptions{.ntrials = 1}) - .succeeded()); - ASSERT_TRUE(succeeded(verify(*module))); - EXPECT_TRUE(isExecutable(getEntryPoint(module.get()), target)); + ASSERT_TRUE( + runPass(m.get(), target, MappingPassOptions{.ntrials = 1}).succeeded()); + ASSERT_TRUE(succeeded(verify(*m))); + EXPECT_TRUE(isExecutable(getEntryPoint(m.get()), target)); IndexSwitchOp switchOp; - module->walk([&](IndexSwitchOp candidate) { switchOp = candidate; }); + m->walk([&](IndexSwitchOp candidate) { switchOp = candidate; }); ASSERT_TRUE(switchOp); ASSERT_EQ(switchOp.getClassicalResults().size(), 1); EXPECT_TRUE(switchOp.getClassicalResults().front().getType().isInteger(64)); @@ -1174,16 +1215,16 @@ TEST_P(MappingPassTest, RouteIndexSwitchRegions) { } )mlir"; - auto module = parseSourceString(source, context.get()); - ASSERT_TRUE(module); - ASSERT_TRUE(succeeded(verify(*module))); + auto m = parseSourceString(source, context.get()); + ASSERT_TRUE(m); + ASSERT_TRUE(succeeded(verify(*m))); - ASSERT_TRUE(runPass(module.get(), target, MappingPassOptions{.ntrials = 1}) - .succeeded()); - ASSERT_TRUE(succeeded(verify(*module))); + ASSERT_TRUE( + runPass(m.get(), target, MappingPassOptions{.ntrials = 1}).succeeded()); + ASSERT_TRUE(succeeded(verify(*m))); size_t numSwaps = 0; - module->walk([&](SWAPOp) { ++numSwaps; }); + m->walk([&](SWAPOp) { ++numSwaps; }); EXPECT_GT(numSwaps, 3); } @@ -1235,16 +1276,16 @@ TEST_P(MappingPassTest, RouteNestedOperationOnceWhileIndependentWiresAdvance) { } )mlir"; - auto module = parseSourceString(source, context.get()); - ASSERT_TRUE(module); - ASSERT_TRUE(succeeded(verify(*module))); + auto m = parseSourceString(source, context.get()); + ASSERT_TRUE(m); + ASSERT_TRUE(succeeded(verify(*m))); - ASSERT_TRUE(runPass(module.get(), target, MappingPassOptions{.ntrials = 1}) - .succeeded()); - EXPECT_TRUE(succeeded(verify(*module))); + ASSERT_TRUE( + runPass(m.get(), target, MappingPassOptions{.ntrials = 1}).succeeded()); + EXPECT_TRUE(succeeded(verify(*m))); size_t numIndexSwitches = 0; - module->walk([&](IndexSwitchOp) { ++numIndexSwitches; }); + m->walk([&](IndexSwitchOp) { ++numIndexSwitches; }); EXPECT_EQ(numIndexSwitches, 1); } @@ -1571,76 +1612,35 @@ TEST_P(MappingPassTest, MapIndexSwitchUsesVotedLayout) { EXPECT_EQ(numSwaps, 4UL); } -static CompilerTarget getFourByFourSquareGrid() { - constexpr size_t side = 4; - constexpr size_t numTarget = side * side; - std::vector couplings; - couplings.reserve(2 * side * (side - 1)); - for (size_t r = 0; r < side; ++r) { - for (size_t c = 0; c < side; ++c) { - const auto i = static_cast((r * side) + c); - if (c + 1 < side) { - couplings.emplace_back(i, i + 1); - } - if (r + 1 < side) { - couplings.emplace_back(i, i + static_cast(side)); - } - } - } - return CompilerTarget(numTarget, std::move(couplings)); -} +TEST_P(MappingPassTest, MapPaddedCXCZGrid) { + const auto& target = GetParam(); + const auto size = (target.numQubits() + 1) / 2; -/// Build an 11-qubit CX/CZ circuit used with a larger square target. -static OwningOpRef -buildPaddedSquareRoutingModule(MLIRContext* context) { - QCOProgramBuilder builder(context); - builder.initialize(); - constexpr size_t nprog = 11; - SmallVector qs; - qs.reserve(nprog); - for (size_t i = 0; i < nprog; ++i) { - qs.push_back(builder.allocQubit()); - } - for (size_t i = 0; i + 1 < nprog; ++i) { - std::tie(qs[i], qs[i + 1]) = builder.cx(qs[i], qs[i + 1]); - } - for (size_t i = 0; i + 2 < nprog; ++i) { - std::tie(qs[i], qs[i + 2]) = builder.cz(qs[i], qs[i + 2]); + SmallVector qubits(size); + SmallVector bits(size); + + QCOProgramBuilder builder(context.get()); + builder.initialize(SmallVector(size, builder.getI1Type())); + + for (int64_t i = 0; i < size; ++i) { + qubits[i] = builder.allocQubit(); } - for (Value q : qs) { - builder.sink(q); + cxcz(builder, qubits); + for (int64_t i = 0; i < qubits.size(); ++i) { + std::tie(qubits[i], bits[i]) = builder.measure(qubits[i]); + builder.sink(qubits[i]); } - return builder.finalize(); -} -/** - * @brief Hot routing replays the cold-preview SWAP plan on padded targets. - * - * On targets with more sites than program qubits, cold preview materializes - * only vacant layout indices touched by its plan. Hot must replay that plan - * (not re-run A*) so every SWAP operand has a wire. Sweep a few seeds and - * assert each result is executable with sparse workspace. - */ -TEST_F(MappingPassFixture, HotRouteRespectsColdPreviewWorkspace) { - const CompilerTarget target = getFourByFourSquareGrid(); - - for (size_t seed = 0; seed < 16; ++seed) { - auto module = buildPaddedSquareRoutingModule(context.get()); - ASSERT_TRUE(runPass(module.get(), target, - MappingPassOptions{ - .niterations = 1, .ntrials = 1, .seed = seed}) - .succeeded()) - << "seed " << seed; - ASSERT_TRUE(succeeded(verify(*module))) << "seed " << seed; - EXPECT_TRUE(isExecutable(getEntryPoint(module.get()), target)) - << "seed " << seed; - - size_t numStatics = 0; - module->walk([&](StaticOp) { ++numStatics; }); - EXPECT_GE(numStatics, 11U) << "seed " << seed; - EXPECT_LT(numStatics, target.numQubits()) << "seed " << seed; - } + auto m = builder.finalize(bits); + ASSERT_TRUE( + runPass(m.get(), target, MappingPassOptions{.ntrials = 1}).succeeded()); + ASSERT_TRUE(succeeded(verify(*m))); + EXPECT_TRUE(isExecutable(getEntryPoint(m.get()), target)); } -INSTANTIATE_TEST_SUITE_P(NineQubitSquareGrid, MappingPassTest, - testing::Values(getNineQubitSquareGrid())); +INSTANTIATE_TEST_SUITE_P(ThreeByThreeSquareGrid, MappingPassTest, + testing::Values(getSquareGridTarget(3))); +INSTANTIATE_TEST_SUITE_P(FourByFourSquareGrid, MappingPassTest, + testing::Values(getSquareGridTarget(4))); +INSTANTIATE_TEST_SUITE_P(ThenByThenSquareGrid, MappingPassTest, + testing::Values(getSquareGridTarget(10))); From a2b9ed1fdf5f9a57f9f61eddfc36a6f5bf44e457 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 5 Aug 2026 09:06:50 +0200 Subject: [PATCH 04/12] Use fixtures for non-configurable target-tests --- .../QCO/Transforms/Mapping/test_mapping.cpp | 250 +++++++++--------- 1 file changed, 125 insertions(+), 125 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp index 8023c35992..e7e856ef3c 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -317,6 +317,129 @@ class MappingPassTest : public MappingPassFixture, }; // namespace +TEST_F(MappingPassFixture, MapTopologyOnlyWithEmptyOperationSet) { + constexpr int64_t size = 3; + + const CompilerTarget target( + 3, std::vector{{0, 1}, {1, 2}}, + std::vector{}); + + QCOProgramBuilder builder(context.get()); + builder.initialize(SmallVector(size, builder.getI1Type())); + + SmallVector qubits(size); + SmallVector bits(size); + + for (int64_t i = 0; i < size; ++i) { + qubits[i] = builder.allocQubit(); + } + + qubits[0] = builder.x(qubits[0]); + std::tie(qubits[0], qubits[1]) = builder.rxx(0.25, qubits[0], qubits[1]); + std::tie(qubits[1], qubits[2]) = builder.rzx(0.5, qubits[1], qubits[2]); + std::tie(qubits[0], qubits[2]) = builder.cx(qubits[0], qubits[2]); + + for (int64_t i = 0; i < qubits.size(); ++i) { + std::tie(qubits[i], bits[i]) = builder.measure(qubits[i]); + builder.sink(qubits[i]); + } + + auto m = builder.finalize(bits); + ASSERT_TRUE( + runPass(m.get(), target, MappingPassOptions{.ntrials = 1}).succeeded()); + ASSERT_TRUE(succeeded(verify(*m))); + EXPECT_TRUE(isExecutable(getEntryPoint(m.get()), target)); + + size_t numSwaps = 0; + m->walk([&](SWAPOp) { ++numSwaps; }); + EXPECT_GT(numSwaps, 0); +} + +TEST_F(MappingPassFixture, PreserveNoncontiguousTargetSiteIds) { + constexpr int64_t size = 3; + + std::vector sites; + sites.emplace_back(7); + sites.emplace_back(19); + sites.emplace_back(42); + + const CompilerTarget target( + std::move(sites), + std::vector{{7, 19}, {19, 42}}, + std::vector{}); + + QCOProgramBuilder builder(context.get()); + builder.initialize(SmallVector(size, builder.getI1Type())); + + SmallVector qubits(size); + SmallVector bits(size); + + for (int64_t i = 0; i < size; ++i) { + qubits[i] = builder.allocQubit(); + } + + std::tie(qubits[0], qubits[1]) = builder.cx(qubits[0], qubits[1]); + std::tie(qubits[1], qubits[2]) = builder.cz(qubits[1], qubits[2]); + std::tie(qubits[0], qubits[2]) = builder.cx(qubits[0], qubits[2]); + for (int64_t i = 0; i < qubits.size(); ++i) { + std::tie(qubits[i], bits[i]) = builder.measure(qubits[i]); + builder.sink(qubits[i]); + } + + auto m = builder.finalize(bits); + ASSERT_TRUE( + runPass(m.get(), target, MappingPassOptions{.ntrials = 1}).succeeded()); + ASSERT_TRUE(succeeded(verify(*m))); + EXPECT_TRUE(isExecutable(getEntryPoint(m.get()), target)); + + const DenseSet expectedSites{7, 19, 42}; + size_t numStatics = 0; + m->walk([&](StaticOp op) { + ++numStatics; + EXPECT_TRUE(expectedSites.contains(op.getIndex())); + }); + EXPECT_EQ(numStatics, 3); +} + +TEST_F(MappingPassFixture, KeepWorkspaceSparseOnLargeTarget) { + constexpr size_t numTargetQubits = 64; + std::vector couplings; + couplings.reserve(numTargetQubits - 1); + for (size_t site = 1; site < numTargetQubits; ++site) { + couplings.emplace_back(0, static_cast(site)); + } + + const CompilerTarget target(numTargetQubits, std::move(couplings)); + + QCOProgramBuilder builder(context.get()); + builder.initialize(SmallVector(2, builder.getI1Type())); + + SmallVector bits(2); + Value q0 = builder.allocQubit(); + Value q1 = builder.allocQubit(); + std::tie(q0, q1) = builder.cx(q0, q1); + std::tie(q0, bits[0]) = builder.measure(q0); + std::tie(q1, bits[1]) = builder.measure(q1); + builder.sink(q0); + builder.sink(q1); + + auto m = builder.finalize(bits); + ASSERT_TRUE(runPass(m.get(), target, + MappingPassOptions{.niterations = 1, .ntrials = 1}) + .succeeded()); + ASSERT_TRUE(succeeded(verify(*m))); + EXPECT_TRUE(isExecutable(getEntryPoint(m.get()), target)); + + size_t numStatics = 0; + size_t numSinks = 0; + m->walk([&](StaticOp) { ++numStatics; }); + m->walk([&](SinkOp) { ++numSinks; }); + EXPECT_GE(numStatics, 2); + EXPECT_LE(numStatics, 3); + EXPECT_LT(numStatics, numTargetQubits); + EXPECT_EQ(numSinks, numStatics); +} + TEST_P(MappingPassTest, FailNoEntryPoint) { const auto& target = GetParam(); @@ -598,129 +721,6 @@ TEST_P(MappingPassTest, FailTooManyQubitsForArch) { ASSERT_TRUE(res.failed()); } -TEST_P(MappingPassTest, MapTopologyOnlyWithEmptyOperationSet) { - constexpr int64_t size = 3; - - const CompilerTarget target( - 3, std::vector{{0, 1}, {1, 2}}, - std::vector{}); - - QCOProgramBuilder builder(context.get()); - builder.initialize(SmallVector(size, builder.getI1Type())); - - SmallVector qubits(size); - SmallVector bits(size); - - for (int64_t i = 0; i < size; ++i) { - qubits[i] = builder.allocQubit(); - } - - qubits[0] = builder.x(qubits[0]); - std::tie(qubits[0], qubits[1]) = builder.rxx(0.25, qubits[0], qubits[1]); - std::tie(qubits[1], qubits[2]) = builder.rzx(0.5, qubits[1], qubits[2]); - std::tie(qubits[0], qubits[2]) = builder.cx(qubits[0], qubits[2]); - - for (int64_t i = 0; i < qubits.size(); ++i) { - std::tie(qubits[i], bits[i]) = builder.measure(qubits[i]); - builder.sink(qubits[i]); - } - - auto m = builder.finalize(bits); - ASSERT_TRUE( - runPass(m.get(), target, MappingPassOptions{.ntrials = 1}).succeeded()); - ASSERT_TRUE(succeeded(verify(*m))); - EXPECT_TRUE(isExecutable(getEntryPoint(m.get()), target)); - - size_t numSwaps = 0; - m->walk([&](SWAPOp) { ++numSwaps; }); - EXPECT_GT(numSwaps, 0); -} - -TEST_P(MappingPassTest, PreserveNoncontiguousTargetSiteIds) { - constexpr int64_t size = 3; - - std::vector sites; - sites.emplace_back(7); - sites.emplace_back(19); - sites.emplace_back(42); - - const CompilerTarget target( - std::move(sites), - std::vector{{7, 19}, {19, 42}}, - std::vector{}); - - QCOProgramBuilder builder(context.get()); - builder.initialize(SmallVector(size, builder.getI1Type())); - - SmallVector qubits(size); - SmallVector bits(size); - - for (int64_t i = 0; i < size; ++i) { - qubits[i] = builder.allocQubit(); - } - - std::tie(qubits[0], qubits[1]) = builder.cx(qubits[0], qubits[1]); - std::tie(qubits[1], qubits[2]) = builder.cz(qubits[1], qubits[2]); - std::tie(qubits[0], qubits[2]) = builder.cx(qubits[0], qubits[2]); - for (int64_t i = 0; i < qubits.size(); ++i) { - std::tie(qubits[i], bits[i]) = builder.measure(qubits[i]); - builder.sink(qubits[i]); - } - - auto m = builder.finalize(bits); - ASSERT_TRUE( - runPass(m.get(), target, MappingPassOptions{.ntrials = 1}).succeeded()); - ASSERT_TRUE(succeeded(verify(*m))); - EXPECT_TRUE(isExecutable(getEntryPoint(m.get()), target)); - - const DenseSet expectedSites{7, 19, 42}; - size_t numStatics = 0; - m->walk([&](StaticOp op) { - ++numStatics; - EXPECT_TRUE(expectedSites.contains(op.getIndex())); - }); - EXPECT_EQ(numStatics, 3); -} - -TEST_P(MappingPassTest, KeepWorkspaceSparseOnLargeTarget) { - constexpr size_t numTargetQubits = 64; - std::vector couplings; - couplings.reserve(numTargetQubits - 1); - for (size_t site = 1; site < numTargetQubits; ++site) { - couplings.emplace_back(0, static_cast(site)); - } - - const CompilerTarget target(numTargetQubits, std::move(couplings)); - - QCOProgramBuilder builder(context.get()); - builder.initialize(SmallVector(2, builder.getI1Type())); - - SmallVector bits(2); - Value q0 = builder.allocQubit(); - Value q1 = builder.allocQubit(); - std::tie(q0, q1) = builder.cx(q0, q1); - std::tie(q0, bits[0]) = builder.measure(q0); - std::tie(q1, bits[1]) = builder.measure(q1); - builder.sink(q0); - builder.sink(q1); - - auto m = builder.finalize(bits); - ASSERT_TRUE(runPass(m.get(), target, - MappingPassOptions{.niterations = 1, .ntrials = 1}) - .succeeded()); - ASSERT_TRUE(succeeded(verify(*m))); - EXPECT_TRUE(isExecutable(getEntryPoint(m.get()), target)); - - size_t numStatics = 0; - size_t numSinks = 0; - m->walk([&](StaticOp) { ++numStatics; }); - m->walk([&](SinkOp) { ++numSinks; }); - EXPECT_GE(numStatics, 2); - EXPECT_LE(numStatics, 3); - EXPECT_LT(numStatics, numTargetQubits); - EXPECT_EQ(numSinks, numStatics); -} - TEST_P(MappingPassTest, MapFlatGHZ) { const auto& target = GetParam(); const int64_t size = 3; @@ -1166,7 +1166,7 @@ TEST_P(MappingPassTest, MapIndexSwitchWithClassicalResult) { } } -TEST_P(MappingPassTest, RouteIndexSwitchRegions) { +TEST_P(MappingPassTest, MapIndexSwitchRegions) { const auto& target = GetParam(); constexpr StringLiteral source = R"mlir( module { @@ -1228,7 +1228,7 @@ TEST_P(MappingPassTest, RouteIndexSwitchRegions) { EXPECT_GT(numSwaps, 3); } -TEST_P(MappingPassTest, RouteNestedOperationOnceWhileIndependentWiresAdvance) { +TEST_P(MappingPassTest, MapNestedOperationOnceWhileIndependentWiresAdvance) { const auto& target = GetParam(); constexpr StringLiteral source = R"mlir( module { From c306d511bb2e5a57e295a43023d52e60ccef72be Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 5 Aug 2026 07:07:28 +0000 Subject: [PATCH 05/12] =?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 20e048a6d4..0a1672f0d0 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -1631,4 +1631,4 @@ std::unique_ptr createMappingPass(const CompilerTarget& target, return std::make_unique(target, options); } -} // namespace mlir::qco \ No newline at end of file +} // namespace mlir::qco From de59e9027829e6c16e35c166bfec626ee2a291fc Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 5 Aug 2026 09:09:46 +0200 Subject: [PATCH 06/12] Canonicalize unit-test structure --- .../QCO/Transforms/Mapping/test_mapping.cpp | 77 ++++++++----------- 1 file changed, 31 insertions(+), 46 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp index e7e856ef3c..d0e84fb901 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -751,11 +751,10 @@ TEST_P(MappingPassTest, MapFlatGHZ) { builder.qtensorDealloc(tensor); auto m = builder.finalize(bits); - auto res = runPass(m.get(), target, MappingPassOptions{}); - auto entry = getEntryPoint(m.get()); - - ASSERT_TRUE(res.succeeded()); - EXPECT_TRUE(isExecutable(entry, target)); + ASSERT_TRUE( + runPass(m.get(), target, MappingPassOptions{.ntrials = 1}).succeeded()); + ASSERT_TRUE(succeeded(verify(*m))); + EXPECT_TRUE(isExecutable(getEntryPoint(m.get()), target)); } TEST_P(MappingPassTest, MapLoopBasedGHZByUnrolling) { @@ -795,11 +794,9 @@ TEST_P(MappingPassTest, MapLoopBasedGHZByUnrolling) { builder.qtensorDealloc(tensor); auto m = builder.finalize(bits); - auto res = pm.run(m.get()); - auto entry = getEntryPoint(m.get()); - - ASSERT_TRUE(res.succeeded()); - EXPECT_TRUE(isExecutable(entry, target)); + ASSERT_TRUE(pm.run(m.get()).succeeded()); + ASSERT_TRUE(succeeded(verify(*m))); + EXPECT_TRUE(isExecutable(getEntryPoint(m.get()), target)); } TEST_P(MappingPassTest, MapGroverLike) { @@ -809,9 +806,6 @@ TEST_P(MappingPassTest, MapGroverLike) { SmallVector qubits(size); SmallVector bits(size); - PassManager pm(context.get()); - pm.addPass(createMappingPass(target, MappingPassOptions{})); - QCOProgramBuilder builder(context.get()); builder.initialize(SmallVector(size, builder.getI1Type())); @@ -861,11 +855,10 @@ TEST_P(MappingPassTest, MapGroverLike) { builder.qtensorDealloc(flagTensor); auto m = builder.finalize(bits); - auto res = pm.run(m.get()); - auto entry = getEntryPoint(m.get()); - - ASSERT_TRUE(res.succeeded()); - EXPECT_TRUE(isExecutable(entry, target)); + ASSERT_TRUE( + runPass(m.get(), target, MappingPassOptions{.ntrials = 1}).succeeded()); + ASSERT_TRUE(succeeded(verify(*m))); + EXPECT_TRUE(isExecutable(getEntryPoint(m.get()), target)); } TEST_P(MappingPassTest, MapParallelLoops) { @@ -875,9 +868,6 @@ TEST_P(MappingPassTest, MapParallelLoops) { SmallVector qubits(size); SmallVector bits(size); - PassManager pm(context.get()); - pm.addPass(createMappingPass(target, MappingPassOptions{})); - QCOProgramBuilder builder(context.get()); builder.initialize(SmallVector(size, builder.getI1Type())); @@ -940,11 +930,10 @@ TEST_P(MappingPassTest, MapParallelLoops) { builder.qtensorDealloc(tensor); auto m = builder.finalize(bits); - auto res = pm.run(m.get()); - auto entry = getEntryPoint(m.get()); - - ASSERT_TRUE(res.succeeded()); - EXPECT_TRUE(isExecutable(entry, target)); + ASSERT_TRUE( + runPass(m.get(), target, MappingPassOptions{.ntrials = 1}).succeeded()); + ASSERT_TRUE(succeeded(verify(*m))); + EXPECT_TRUE(isExecutable(getEntryPoint(m.get()), target)); } TEST_P(MappingPassTest, MapForWithClassicalIterArg) { @@ -1354,11 +1343,10 @@ TEST_P(MappingPassTest, MapSABRECircuit) { builder.qtensorDealloc(tensorDown); auto m = builder.finalize(bits); - auto res = runPass(m.get(), target, MappingPassOptions{}); - auto entry = getEntryPoint(m.get()); - - ASSERT_TRUE(res.succeeded()); - EXPECT_TRUE(isExecutable(entry, target)); + ASSERT_TRUE( + runPass(m.get(), target, MappingPassOptions{.ntrials = 1}).succeeded()); + ASSERT_TRUE(succeeded(verify(*m))); + EXPECT_TRUE(isExecutable(getEntryPoint(m.get()), target)); } TEST_P(MappingPassTest, MapBranchingGHZ) { @@ -1407,11 +1395,10 @@ TEST_P(MappingPassTest, MapBranchingGHZ) { builder.qtensorDealloc(tensor); auto m = builder.finalize(bits); - auto res = runPass(m.get(), target, MappingPassOptions{.ntrials = 1}); - auto entry = getEntryPoint(m.get()); - - ASSERT_TRUE(res.succeeded()); - EXPECT_TRUE(isExecutable(entry, target)); + ASSERT_TRUE( + runPass(m.get(), target, MappingPassOptions{.ntrials = 1}).succeeded()); + ASSERT_TRUE(succeeded(verify(*m))); + EXPECT_TRUE(isExecutable(getEntryPoint(m.get()), target)); } TEST_P(MappingPassTest, MapDoUntil) { @@ -1469,11 +1456,10 @@ TEST_P(MappingPassTest, MapDoUntil) { builder.qtensorDealloc(tensor); auto m = builder.finalize(); - auto res = runPass(m.get(), target, MappingPassOptions{.ntrials = 1}); - auto entry = getEntryPoint(m.get()); - - ASSERT_TRUE(res.succeeded()); - EXPECT_TRUE(isExecutable(entry, target)); + ASSERT_TRUE( + runPass(m.get(), target, MappingPassOptions{.ntrials = 1}).succeeded()); + ASSERT_TRUE(succeeded(verify(*m))); + EXPECT_TRUE(isExecutable(getEntryPoint(m.get()), target)); } TEST_P(MappingPassTest, MapNestedForSwitch) { @@ -1563,11 +1549,10 @@ TEST_P(MappingPassTest, MapNestedForSwitch) { builder.qtensorDealloc(tensor); auto m = builder.finalize(); - auto res = runPass(m.get(), target, MappingPassOptions{}); - auto entry = getEntryPoint(m.get()); - - ASSERT_TRUE(res.succeeded()); - EXPECT_TRUE(isExecutable(entry, target)); + ASSERT_TRUE( + runPass(m.get(), target, MappingPassOptions{.ntrials = 1}).succeeded()); + ASSERT_TRUE(succeeded(verify(*m))); + EXPECT_TRUE(isExecutable(getEntryPoint(m.get()), target)); } TEST_P(MappingPassTest, MapIndexSwitchUsesVotedLayout) { From d371aae4ac45a01bc155db8c9e85d2a5034b8dfd Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 5 Aug 2026 09:10:00 +0200 Subject: [PATCH 07/12] Remove unused include --- 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 20e048a6d4..571d7da924 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -55,7 +55,6 @@ #include #include #include -#include #include #include #include From 31053561ce592b3e60b7efddb0b4aa38305506ba Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 5 Aug 2026 09:16:22 +0200 Subject: [PATCH 08/12] Fix lint --- mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp | 1 - .../unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 134ee9b17e..33dfb4c517 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -28,7 +28,6 @@ #include #include #include -#include #include #include #include diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp index d0e84fb901..719dc4202f 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -222,8 +222,8 @@ static CompilerTarget getSquareGridTarget(const size_t n) { std::vector couplings; couplings.reserve(n * n); - for (auto r = 0; r < n; ++r) { - for (auto c = 0; c < n; ++c) { + for (size_t r = 0; r < n; ++r) { + for (size_t c = 0; c < n; ++c) { const auto i = (r * n) + c; if (c + 1 < n) { couplings.emplace_back(i, i + 1); From b86c14f5c9ea25d1e4a5cb1e6f5781748c95bb9a Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 5 Aug 2026 09:33:06 +0200 Subject: [PATCH 09/12] Final lint fix --- mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp index 719dc4202f..a913eb06a0 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -1607,7 +1607,7 @@ TEST_P(MappingPassTest, MapPaddedCXCZGrid) { QCOProgramBuilder builder(context.get()); builder.initialize(SmallVector(size, builder.getI1Type())); - for (int64_t i = 0; i < size; ++i) { + for (size_t i = 0; i < size; ++i) { qubits[i] = builder.allocQubit(); } cxcz(builder, qubits); From 9d11f24d133ba0e8fcee7f3bd661b4ce7e8dd832 Mon Sep 17 00:00:00 2001 From: matthias Date: Wed, 5 Aug 2026 10:03:09 +0200 Subject: [PATCH 10/12] Update mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp Co-authored-by: Lukas Burgholzer Signed-off-by: matthias --- mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp index a913eb06a0..bcefbcd546 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -1627,5 +1627,5 @@ INSTANTIATE_TEST_SUITE_P(ThreeByThreeSquareGrid, MappingPassTest, testing::Values(getSquareGridTarget(3))); INSTANTIATE_TEST_SUITE_P(FourByFourSquareGrid, MappingPassTest, testing::Values(getSquareGridTarget(4))); -INSTANTIATE_TEST_SUITE_P(ThenByThenSquareGrid, MappingPassTest, +INSTANTIATE_TEST_SUITE_P(TenByTenSquareGrid, MappingPassTest, testing::Values(getSquareGridTarget(10))); From 59d5e35cba6bd0b1e389cd0babfc93b8c22987e0 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 5 Aug 2026 11:02:39 +0200 Subject: [PATCH 11/12] Use SinkOp patterns instead of full canonicalization suite --- .../Dialect/QCO/Transforms/Mapping/test_mapping.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp index a913eb06a0..d5a5c1df41 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include @@ -305,8 +306,13 @@ class MappingPassFixture : public testing::Test { const MappingPassOptions& options) { PassManager pm(m->getContext()); pm.addPass(createMappingPass(target, options)); - pm.addPass(createCanonicalizerPass()); - return pm.run(m); + if (failed(pm.run(m))) { + return failure(); + } + + RewritePatternSet patterns(m.getContext()); + SinkOp::getCanonicalizationPatterns(patterns, m.getContext()); + return applyPatternsGreedily(m, std::move(patterns)); } std::unique_ptr context; From 068c29babd03e12d9f31a776956f4b9453f89206 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 5 Aug 2026 11:09:26 +0200 Subject: [PATCH 12/12] Fix lint --- mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp index f18476fabd..347ae4a635 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include