From dce8b93d555036128cc10874bea98ef7af8c6dfc Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 6 Jul 2026 08:28:12 +0200 Subject: [PATCH 01/23] Add dispatch function --- .../QCO/Transforms/Mapping/Mapping.cpp | 103 ++++++++++-------- 1 file changed, 60 insertions(+), 43 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 5378094e0d..a247b113cf 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -73,6 +73,8 @@ struct MappingPass : impl::MappingPassBase { using IndexPairType = std::pair; using Window = SmallVector; using Wires = SmallVector; + using RecursiveRoutingStackItem = std::pair>; + using RecursiveRoutingStack = SmallVector; enum class RoutingMode : bool { Cold, Hot }; @@ -900,9 +902,9 @@ struct MappingPass : impl::MappingPassBase { /// gates are found. After the function returns, the wires point at the /// results of non-executable gates or operations with nested regions. template - SmallVector>> - advance(Wires& wires, const WireInfos& infos, const Layout& layout) { - SmallVector>> stack; + RecursiveRoutingStack advance(Wires& wires, const WireInfos& infos, + const Layout& layout) { + RecursiveRoutingStack stack; // Advance wires past all executable gates and push operations with // nested regions and the respective wire indices of their inputs onto the @@ -940,54 +942,35 @@ struct MappingPass : impl::MappingPassBase { return stack; } - /// 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. + /// TODO: template requires(Mode != RoutingMode::Hot || Direction == WireDirection::Forward) - LogicalResult route(RoutingBundle& bundle, Statistics& stats, - IRRewriter* rewriter = nullptr) { - using Traits = WireTraversalTraits; - - auto& [wires, infos, layout] = bundle; - - while (true) { - - while (true) { - const auto stack = advance(wires, infos, layout); - - if (stack.empty()) { - break; - } - - // Continue with processing the nested regions recursively. - - for (const auto& [op, indices] : stack) { - assert(isa(op)); - auto forOp = cast(op); - - RoutingBundle child{.layout = layout}; - - // Map parent (results) to child values (iter args). Going forwards, - // the recursive routing starts at block arguments, while the - // backwards go starts at the yielded values. + LogicalResult dispatch(const RecursiveRoutingStackItem& item, + RoutingBundle& parent, Statistics& stats, + IRRewriter* rewriter = nullptr) { + const auto& [op, indices] = item; + return TypeSwitch(op) + .Case([&](scf::ForOp forOp) { + RoutingBundle child{.layout = parent.layout}; + + // Map parent (results) to child values (iter args). Going + // forwards, the recursive routing starts at block + // arguments, while the backwards go starts at the yielded + // values. for (size_t i : indices) { - const auto prog = infos.lookupProgram(i); - const auto res = cast(wires[i].qubit()); + const auto prog = parent.infos.lookupProgram(i); + const auto res = cast(parent.wires[i].qubit()); const auto arg = forOp.getTiedLoopRegionIterArg(res); const auto index = child.wires.size(); if constexpr (Direction == WireDirection::Forward) { child.wires.emplace_back(arg); - child.infos.map(index, prog); } else { const auto yield = forOp.getTiedLoopYieldedValue(arg)->get(); child.wires.emplace_back(yield); - child.infos.map(index, prog); } + child.infos.map(index, prog); } const auto res = route(child, stats, rewriter); @@ -995,14 +978,15 @@ struct MappingPass : impl::MappingPassBase { return failure(); } - const auto swaps = restore(child.layout, layout); + const auto swaps = restore(child.layout, parent.layout); if constexpr (Mode == RoutingMode::Hot) { // After routing the loop body, all iterators point to - // std::default_sentinel. To move the iterators to the correct - // qubit SSA values for the epilogue SWAPs, decrement each - // twice: (sentinel → yield → unitary/block arg). + // std::default_sentinel. To move the iterators to the + // correct qubit SSA values for the epilogue SWAPs, + // decrement each twice: (sentinel → yield → + // unitary/block arg). llvm::for_each(child.wires, [](auto& it) { std::advance(it, -2); }); } @@ -1017,8 +1001,41 @@ struct MappingPass : impl::MappingPassBase { // incrementing the respective global wires. llvm::for_each(indices, [&](size_t i) { - std::advance(wires[i], Traits::stride()); + std::advance(parent.wires[i], + WireTraversalTraits::stride()); }); + + return success(); + }) + .Default([](Operation*) { return failure(); }); + } + + /// 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, + IRRewriter* rewriter = nullptr) { + using Traits = WireTraversalTraits; + + auto& [wires, infos, layout] = bundle; + + while (true) { + + while (true) { + const auto stack = advance(wires, infos, layout); + if (stack.empty()) { + break; + } + + for (const auto& item : stack) { + if (dispatch(item, bundle, stats, rewriter) + .failed()) { + return failure(); + } } } From 0b9930236c24b0b7bdd658fde4d0baf480a7f217 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 6 Jul 2026 10:09:20 +0200 Subject: [PATCH 02/23] Implement qco::ifOp extend operation --- .../QCO/Transforms/Mapping/Mapping.cpp | 171 ++++++++++++++---- 1 file changed, 139 insertions(+), 32 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index a247b113cf..bc46591e52 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -324,48 +324,83 @@ struct MappingPass : impl::MappingPassBase { /// Extend the init arguments of an `scf::ForOp` by adding a given range of /// additional SSA values. Replaces the existing operation and returns the /// newly created one. - static scf::ForOp extend(scf::ForOp loop, ValueRange addons, + static scf::ForOp extend(scf::ForOp forOp, ValueRange addons, IRRewriter& rewriter) { OpBuilder::InsertionGuard guard(rewriter); - rewriter.setInsertionPoint(loop); + rewriter.setInsertionPoint(forOp); + + const auto naddons = addons.size(); + const auto res = + forOp.replaceWithAdditionalIterOperands(rewriter, addons, true); + assert(succeeded(res)); + + auto newForOp = cast(*res); + for (const auto [before, after] : + llvm::zip_equal(addons, newForOp.getResults().take_back(naddons))) { + rewriter.replaceAllUsesExcept(before, after, newForOp); + } + return newForOp; + } + + /// Extend the qubit arguments of an `qco::IfOp` by adding a given range of + /// additional SSA values. Replaces the existing operation and returns the + /// newly created one. + static qco::IfOp extend(qco::IfOp ifOp, ValueRange addons, + IRRewriter& rewriter) { + OpBuilder::InsertionGuard guard(rewriter); + rewriter.setInsertionPoint(ifOp); const auto naddons = addons.size(); SmallVector inits; - llvm::append_range(inits, loop.getInits()); + llvm::append_range(inits, ifOp.getQubits()); llvm::append_range(inits, addons); - auto newLoop = rewriter.create( - loop.getLoc(), loop.getLowerBound(), loop.getUpperBound(), - loop.getStep(), inits); + auto newIfOp = + rewriter.create(ifOp->getLoc(), ifOp.getCondition(), inits); - Block* loopBody = loop.getBody(); - Block* newLoopBody = newLoop.getBody(); + // The qco::IfOp implements the SingleBlockImplicitTerminator trait. + assert(newIfOp.getThenRegion().hasOneBlock()); + assert(newIfOp.getElseRegion().hasOneBlock()); - rewriter.mergeBlocks( - loopBody, newLoopBody, - newLoopBody->getArguments().take_front(loopBody->getNumArguments())); + const std::array oldBlocks{ + &ifOp.getThenRegion().getBlocks().front(), + &ifOp.getElseRegion().getBlocks().front(), + }; - for (const auto [before, after] : - llvm::zip_first(loop.getResults(), newLoop.getResults())) { - rewriter.replaceAllUsesWith(before, after); + const std::array newBlocks{ + &newIfOp.getThenRegion().getBlocks().front(), + &newIfOp.getElseRegion().getBlocks().front(), + }; + + for (const auto [oldBlock, newBlock] : + llvm::zip_equal(oldBlocks, newBlocks)) { + rewriter.mergeBlocks( + oldBlock, newBlock, + newBlock->getArguments().take_front(oldBlock->getNumArguments())); + + auto yield = cast(newBlock->getTerminator()); + + SmallVector results; + llvm::append_range(results, yield.getResults()); + llvm::append_range(results, newBlock->getArguments().take_back(naddons)); + rewriter.setInsertionPoint(yield); + rewriter.replaceOpWithNewOp(yield, results); } - for (const auto [before, after] : - llvm::zip_equal(addons, newLoop.getResults().take_back(naddons))) { - rewriter.replaceAllUsesExcept(before, after, newLoop); + for (const auto [oldResult, newResult] : + llvm::zip_first(ifOp.getResults(), newIfOp.getResults())) { + rewriter.replaceAllUsesWith(oldResult, newResult); } - auto yield = cast(newLoopBody->getTerminator()); + for (const auto [oldUse, newResult] : + llvm::zip_equal(addons, newIfOp.getResults().take_back(naddons))) { + rewriter.replaceAllUsesExcept(oldUse, newResult, newIfOp); + } - SmallVector results; - llvm::append_range(results, yield.getResults()); - llvm::append_range(results, newLoop.getRegionIterArgs().take_back(naddons)); - rewriter.setInsertionPoint(yield); - rewriter.replaceOpWithNewOp(yield, results); + rewriter.eraseOp(ifOp); - rewriter.eraseOp(loop); - return newLoop; + return newIfOp; } /// Return the wires of a dynamic computation. @@ -512,12 +547,13 @@ struct MappingPass : impl::MappingPassBase { qubits.erase(pred); } }) - .Case([&](scf::ForOp loop) { + .Case([&](scf::ForOp forOp) { assert(qubits.size() == layout.nqubits()); DenseSet addons(qubits); - llvm::for_each(loop.getInits(), [&](auto v) { addons.erase(v); }); - auto newLoop = extend(loop, to_vector(addons), rewriter); + llvm::for_each(forOp.getInits(), + [&](auto v) { addons.erase(v); }); + auto newLoop = extend(forOp, to_vector(addons), rewriter); for (OpOperand& operand : newLoop.getInitsMutable()) { qubits.insert(newLoop.getTiedLoopResult(&operand)); @@ -928,8 +964,8 @@ struct MappingPass : impl::MappingPassBase { released.emplace_back(op); } }) - .template Case( - [&](scf::ForOp op) { stack.emplace_back(op, indices); }); + .template Case( + [&](auto op) { stack.emplace_back(op, indices); }); } if (released.empty()) { @@ -942,7 +978,7 @@ struct MappingPass : impl::MappingPassBase { return stack; } - /// TODO: + /// TODO: template requires(Mode != RoutingMode::Hot || Direction == WireDirection::Forward) LogicalResult dispatch(const RecursiveRoutingStackItem& item, @@ -950,7 +986,7 @@ struct MappingPass : impl::MappingPassBase { IRRewriter* rewriter = nullptr) { const auto& [op, indices] = item; return TypeSwitch(op) - .Case([&](scf::ForOp forOp) { + .template Case([&](scf::ForOp forOp) { RoutingBundle child{.layout = parent.layout}; // Map parent (results) to child values (iter args). Going @@ -1007,6 +1043,77 @@ struct MappingPass : impl::MappingPassBase { return success(); }) + .template Case([&](qco::IfOp ifOp) { + std::array children{ + RoutingBundle{.layout = parent.layout}, + RoutingBundle{.layout = parent.layout}}; + + for (size_t i : indices) { + const auto prog = parent.infos.lookupProgram(i); + const auto res = cast(parent.wires[i].qubit()); + const auto index = children[0].wires.size(); + + OpOperand* qubit = ifOp.getTiedQubit(res); + const std::array args{ifOp.getTiedThenBlockArgument(qubit), + ifOp.getTiedElseBlockArgument(qubit)}; + + if constexpr (Direction == WireDirection::Forward) { + for (size_t i = 0; i < children.size(); ++i) { + children[i].wires.emplace_back(args[i]); + children[i].infos.map(index, prog); + } + } else { + const std::array yields{ + ifOp.getTiedThenYieldedValue(args[0])->get(), + ifOp.getTiedElseYieldedValue(args[1])->get()}; + for (size_t i = 0; i < children.size(); ++i) { + children[i].wires.emplace_back(yields[i]); + children[i].infos.map(index, prog); + } + } + } + + for (auto& child : children) { + const auto res = route(child, stats, rewriter); + if (failed(res)) { + return failure(); + } + + const auto swaps = restore(child.layout, parent.layout); + + if constexpr (Mode == RoutingMode::Hot) { + + // After routing the loop body, all iterators point to + // std::default_sentinel. To move the iterators to the + // correct qubit SSA values for the epilogue SWAPs, + // decrement each twice: (sentinel → yield → + // unitary/block arg). + + llvm::for_each(child.wires, + [](auto& it) { std::advance(it, -2); }); + } + + insertSWAPs(swaps, child, stats, rewriter); + } + + if constexpr (Mode == RoutingMode::Hot) { + // llvm::for_each(ifOp.getRegions(), [](Region& region) { + // llvm::for_each(region.getBlocks(), + // [](Block& block) { sortTopologically(&block); + // }); + // }); + } + + // Finally, move past the operation with nested regions by + // incrementing the respective global wires. + + llvm::for_each(indices, [&](size_t i) { + std::advance(parent.wires[i], + WireTraversalTraits::stride()); + }); + + return success(); + }) .Default([](Operation*) { return failure(); }); } From c3d2b1be6607674ef1f1b5de856d81defaa37d61 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 6 Jul 2026 15:01:39 +0200 Subject: [PATCH 03/23] Implement simple test --- mlir/include/mlir/Dialect/QCO/Utils/Graph.h | 10 +- mlir/lib/Dialect/QCO/IR/SCF/IfOp.cpp | 4 +- .../QCO/Transforms/Mapping/Mapping.cpp | 138 +++++++++++------- mlir/lib/Dialect/QCO/Utils/Graph.cpp | 7 + .../QCO/Transforms/Mapping/test_mapping.cpp | 52 ++++++- 5 files changed, 150 insertions(+), 61 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Utils/Graph.h b/mlir/include/mlir/Dialect/QCO/Utils/Graph.h index 203a3590d7..4c91ea882f 100644 --- a/mlir/include/mlir/Dialect/QCO/Utils/Graph.h +++ b/mlir/include/mlir/Dialect/QCO/Utils/Graph.h @@ -56,6 +56,11 @@ class Graph { for_each(edges, [this](const auto& e) { addEdge(e.first, e.second); }); } + /// Construct graph from edge set. + explicit Graph(ArrayRef nodes) { + for_each(nodes, [this](const auto& u) { std::ignore = adj_[u]; }); + } + /// Add a directed edge to the internal representation of the graph. /// Implicitly adds nodes. void addEdge(size_t u, size_t v); @@ -79,7 +84,10 @@ class Graph { [[nodiscard]] bool empty() const { return adj_.empty(); } /// Clear the graph. - [[nodiscard]] void clear() { adj_.clear(); } + void clear() { adj_.clear(); } + + /// Remove all edges from the graph. Keep nodes. + void clearEdges(); /// Return the minimum distance matrix of the graph by implementing the /// Floyd-Warshall Algorithm diff --git a/mlir/lib/Dialect/QCO/IR/SCF/IfOp.cpp b/mlir/lib/Dialect/QCO/IR/SCF/IfOp.cpp index c377947153..f3849d8015 100644 --- a/mlir/lib/Dialect/QCO/IR/SCF/IfOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/SCF/IfOp.cpp @@ -313,14 +313,14 @@ BlockArgument IfOp::getTiedElseBlockArgument(OpOperand* qubit) { } OpOperand* IfOp::getTiedThenYieldedValue(BlockArgument bbArg) { - if (bbArg.getDefiningOp() != getOperation()) { + if (bbArg.getOwner()->getParentOp() != getOperation()) { return nullptr; } return &thenYield().getTargetsMutable()[bbArg.getArgNumber()]; } OpOperand* IfOp::getTiedElseYieldedValue(BlockArgument bbArg) { - if (bbArg.getDefiningOp() != getOperation()) { + if (bbArg.getOwner()->getParentOp() != getOperation()) { return nullptr; } return &elseYield().getTargetsMutable()[bbArg.getArgNumber()]; diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index bc46591e52..e27c6cdc61 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -360,17 +361,21 @@ struct MappingPass : impl::MappingPassBase { rewriter.create(ifOp->getLoc(), ifOp.getCondition(), inits); // The qco::IfOp implements the SingleBlockImplicitTerminator trait. - assert(newIfOp.getThenRegion().hasOneBlock()); - assert(newIfOp.getElseRegion().hasOneBlock()); + assert(ifOp.getThenRegion().hasOneBlock()); + assert(ifOp.getElseRegion().hasOneBlock()); const std::array oldBlocks{ &ifOp.getThenRegion().getBlocks().front(), &ifOp.getElseRegion().getBlocks().front(), }; + SmallVector argTypes(inits.size(), + QubitType::get(rewriter.getContext())); + SmallVector locs(inits.size(), newIfOp->getLoc()); + const std::array newBlocks{ - &newIfOp.getThenRegion().getBlocks().front(), - &newIfOp.getElseRegion().getBlocks().front(), + rewriter.createBlock(&newIfOp.getThenRegion(), {}, argTypes, locs), + rewriter.createBlock(&newIfOp.getElseRegion(), {}, argTypes, locs), }; for (const auto [oldBlock, newBlock] : @@ -379,13 +384,13 @@ struct MappingPass : impl::MappingPassBase { oldBlock, newBlock, newBlock->getArguments().take_front(oldBlock->getNumArguments())); - auto yield = cast(newBlock->getTerminator()); + auto yield = cast(newBlock->getTerminator()); SmallVector results; - llvm::append_range(results, yield.getResults()); + llvm::append_range(results, yield.getTargets()); llvm::append_range(results, newBlock->getArguments().take_back(naddons)); rewriter.setInsertionPoint(yield); - rewriter.replaceOpWithNewOp(yield, results); + rewriter.replaceOpWithNewOp(yield, results); } for (const auto [oldResult, newResult] : @@ -553,17 +558,39 @@ struct MappingPass : impl::MappingPassBase { DenseSet addons(qubits); llvm::for_each(forOp.getInits(), [&](auto v) { addons.erase(v); }); - auto newLoop = extend(forOp, to_vector(addons), rewriter); + auto newForOp = extend(forOp, to_vector(addons), rewriter); + + for (OpOperand& operand : newForOp.getInitsMutable()) { + qubits.insert(newForOp.getTiedLoopResult(&operand)); + qubits.erase(operand.get()); + } + + stack.emplace_back( + newForOp.getRegion(), + DenseSet(newForOp.getRegionIterArgs().begin(), + newForOp.getRegionIterArgs().end())); + }) + .Case([&](IfOp ifOp) { + assert(qubits.size() == layout.nqubits()); + + DenseSet addons(qubits); + llvm::for_each(ifOp.getQubits(), + [&](auto v) { addons.erase(v); }); + auto newIfOp = extend(ifOp, to_vector(addons), rewriter); - for (OpOperand& operand : newLoop.getInitsMutable()) { - qubits.insert(newLoop.getTiedLoopResult(&operand)); + for (OpOperand& operand : newIfOp.getQubitsMutable()) { + qubits.insert(newIfOp.getTiedResult(&operand)); qubits.erase(operand.get()); } + const auto thenArgs = newIfOp.getThenRegion().getArguments(); + const auto elseArgs = newIfOp.getElseRegion().getArguments(); stack.emplace_back( - newLoop.getRegion(), - DenseSet(newLoop.getRegionIterArgs().begin(), - newLoop.getRegionIterArgs().end())); + newIfOp.getThenRegion(), + DenseSet(thenArgs.begin(), thenArgs.end())); + stack.emplace_back( + newIfOp.getElseRegion(), + DenseSet(elseArgs.begin(), elseArgs.end())); }) .Case([&](auto op) { qubits.insert(op.getQubitOut()); @@ -731,10 +758,8 @@ struct MappingPass : impl::MappingPassBase { /// Return the sequence of SWAPs to move from one layout to another. /// Implements the 4-Approximation algorithm described in arXiv:1602.05150v3. SmallVector restore(const Layout& from, const Layout& to) { - static constexpr size_t MIN_CYCLE_LENGTH = 2; - - Graph f; Layout curr(from); + Graph f(device->qubits()); SmallVector swaps; const auto shouldAddEdge = [&](size_t u, size_t v) { @@ -744,13 +769,20 @@ struct MappingPass : impl::MappingPassBase { device->distanceBetween(u, hwGoal); }; + const auto printLayout = [](const Layout& l) { + for (const auto j : l.getProgramToHardware()) { + llvm::dbgs() << j << " "; + } + llvm::dbgs() << '\n'; + }; + while (true) { // Build F-graph: Add edges to F for each edge in the coupling graph. // Note that this assumes that the coupling graph is directed, but // symmetric (essentially: undirected). - f.clear(); + f.clearEdges(); for (const auto u : device->qubits()) { for (const auto v : device->neighboursOf(u)) { if (shouldAddEdge(u, v)) { @@ -759,21 +791,16 @@ struct MappingPass : impl::MappingPassBase { } } - if (f.empty()) { - break; - } - // Try to find a directed cycle in the F graph. If there is one, // we can apply a happy swap chain. Note that this happy swap chain // does not include the final back edge closing the cycle because the // first SWAP changes the token (the qubit) on the target, invalidating // the edge in F. - const auto cycle = f.findCycle(); - if (cycle && cycle->size() >= MIN_CYCLE_LENGTH) { - for (size_t i = 0; i + 1 < cycle->size(); ++i) { - curr.swap((*cycle)[i], (*cycle)[i + 1]); - swaps.emplace_back((*cycle)[i], (*cycle)[i + 1]); + if (const auto cycle = f.findCycle(); cycle) { + for (size_t i = cycle->size() - 1; i > 0; --i) { + curr.swap((*cycle)[i], (*cycle)[i - 1]); + swaps.emplace_back((*cycle)[i], (*cycle)[i - 1]); } continue; } @@ -785,14 +812,12 @@ struct MappingPass : impl::MappingPassBase { bool found{false}; for (const auto u : f.getNodes()) { - if (f.getDegree(u) != 0) { - for (const auto v : f.getNeighbours(u)) { - if (f.getDegree(v) == 0) { - curr.swap(u, v); - swaps.emplace_back(u, v); - found = true; - break; - } + for (const auto v : f.getNeighbours(u)) { + if (f.getDegree(v) == 0) { + curr.swap(u, v); + swaps.emplace_back(u, v); + found = true; + break; } } @@ -801,7 +826,12 @@ struct MappingPass : impl::MappingPassBase { } } - assert(found); + // If there are no happy or unhappy swaps anymore, + // the final placement of every token is reached. + + if (!found) { + break; + } } return swaps; @@ -1044,9 +1074,8 @@ struct MappingPass : impl::MappingPassBase { return success(); }) .template Case([&](qco::IfOp ifOp) { - std::array children{ - RoutingBundle{.layout = parent.layout}, - RoutingBundle{.layout = parent.layout}}; + std::array children{RoutingBundle{.layout = parent.layout}, + RoutingBundle{.layout = parent.layout}}; for (size_t i : indices) { const auto prog = parent.infos.lookupProgram(i); @@ -1081,27 +1110,29 @@ struct MappingPass : impl::MappingPassBase { const auto swaps = restore(child.layout, parent.layout); - if constexpr (Mode == RoutingMode::Hot) { + // if constexpr (Mode == RoutingMode::Hot) { - // After routing the loop body, all iterators point to - // std::default_sentinel. To move the iterators to the - // correct qubit SSA values for the epilogue SWAPs, - // decrement each twice: (sentinel → yield → - // unitary/block arg). + // // After routing the loop body, all iterators point to + // // std::default_sentinel. To move the iterators to the + // // correct qubit SSA values for the epilogue SWAPs, + // // decrement each twice: (sentinel → yield → + // // unitary/block arg). - llvm::for_each(child.wires, - [](auto& it) { std::advance(it, -2); }); - } + // llvm::for_each(child.wires, + // [](auto& it) { std::advance(it, -2); }); + // } - insertSWAPs(swaps, child, stats, rewriter); + // insertSWAPs(swaps, child, stats, rewriter); } if constexpr (Mode == RoutingMode::Hot) { - // llvm::for_each(ifOp.getRegions(), [](Region& region) { - // llvm::for_each(region.getBlocks(), - // [](Block& block) { sortTopologically(&block); - // }); - // }); + + // The qco::IfOp implements the SingleBlockImplicitTerminator trait. + assert(ifOp.getThenRegion().hasOneBlock()); + assert(ifOp.getElseRegion().hasOneBlock()); + + sortTopologically(&ifOp.getThenRegion().getBlocks().front()); + sortTopologically(&ifOp.getElseRegion().getBlocks().front()); } // Finally, move past the operation with nested regions by @@ -1137,7 +1168,6 @@ struct MappingPass : impl::MappingPassBase { if (stack.empty()) { break; } - for (const auto& item : stack) { if (dispatch(item, bundle, stats, rewriter) .failed()) { diff --git a/mlir/lib/Dialect/QCO/Utils/Graph.cpp b/mlir/lib/Dialect/QCO/Utils/Graph.cpp index 81913d67ea..02684b233f 100644 --- a/mlir/lib/Dialect/QCO/Utils/Graph.cpp +++ b/mlir/lib/Dialect/QCO/Utils/Graph.cpp @@ -29,6 +29,7 @@ void Graph::addEdge(size_t u, size_t v) { } ArrayRef Graph::getNeighbours(size_t id) const { return adj_.at(id); } + SmallVector Graph::getNodes() const { return to_vector(adj_.keys()); } size_t Graph::getMaxDegree() const { @@ -39,6 +40,12 @@ size_t Graph::getMaxDegree() const { return deg; } +void Graph::clearEdges() { + for (auto& item : adj_) { + item.second.clear(); + } +} + Graph::DistanceMatrix Graph::getDistMatrix() const { const auto n = getNumNodes(); diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp index 3888966a70..1421723018 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -115,10 +115,6 @@ isExecutable(Region& body, DenseMap& m, } } }) - .Case([&](scf::YieldOp op) { - assert(isa(op->getParentOp())); - auto forOp = cast(op->getParentOp()); - }) .Case([&](auto op) { const auto pred = op.getQubitIn(); const auto succ = op.getQubitOut(); @@ -594,5 +590,53 @@ TEST_P(MappingPassTest, Sabre) { EXPECT_TRUE(isExecutable(entry, device.couplingSet)); } +TEST_P(MappingPassTest, RandomGHZ) { + const auto& device = GetParam(); + + QCOProgramBuilder builder(context.get()); + builder.initialize(); + + Value tensor = builder.qtensorAlloc(9); + SmallVector qubits(9); + SmallVector cregs(9); + + for (int64_t i = 0; i < 9; ++i) { + std::tie(tensor, qubits[i]) = builder.qtensorExtract(tensor, i); + } + + qubits[0] = builder.h(qubits[0]); + std::tie(qubits[0], cregs[0]) = builder.measure(qubits[0]); + + qubits = builder.qcoIf(cregs[0], qubits, [&](ValueRange args) { + SmallVector values(args); + values[0] = builder.h(values[0]); + for (size_t i = 1; i < 9; ++i) { + std::tie(values[0], values[i]) = builder.cx(values[0], values[i]); + } + return values; + }); + + qubits = builder.barrier(qubits); + + for (int64_t i = 0; i < 9; ++i) { + std::tie(qubits[i], cregs[i]) = builder.measure(qubits[i]); + } + + for (int64_t i = 0; i < 9; ++i) { + tensor = builder.qtensorInsert(qubits[i], tensor, i); + } + + builder.qtensorDealloc(tensor); + + auto m = builder.finalize(); + auto res = + runPass(m.get(), device.couplingSet, MappingPassOptions{.ntrials = 1}); + auto entry = getEntryPoint(m.get()); + + m->dump(); + ASSERT_TRUE(res.succeeded()); + EXPECT_TRUE(isExecutable(entry, device.couplingSet)); +} + INSTANTIATE_TEST_SUITE_P(NineQubitSquareGrid, MappingPassTest, testing::Values(getNineQubitSquareGrid())); From 062b3e6fc3285064504a164fc2f0cb3a0942190d Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 6 Jul 2026 15:22:58 +0200 Subject: [PATCH 04/23] Implement restoration logic for qco::IfOp --- .../QCO/Transforms/Mapping/Mapping.cpp | 28 ++++----- .../QCO/Transforms/Mapping/test_mapping.cpp | 57 ++++++++++++++++--- 2 files changed, 60 insertions(+), 25 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index e27c6cdc61..018e2eb711 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -758,6 +758,7 @@ struct MappingPass : impl::MappingPassBase { /// Return the sequence of SWAPs to move from one layout to another. /// Implements the 4-Approximation algorithm described in arXiv:1602.05150v3. SmallVector restore(const Layout& from, const Layout& to) { + Layout curr(from); Graph f(device->qubits()); SmallVector swaps; @@ -769,13 +770,6 @@ struct MappingPass : impl::MappingPassBase { device->distanceBetween(u, hwGoal); }; - const auto printLayout = [](const Layout& l) { - for (const auto j : l.getProgramToHardware()) { - llvm::dbgs() << j << " "; - } - llvm::dbgs() << '\n'; - }; - while (true) { // Build F-graph: Add edges to F for each edge in the coupling graph. @@ -1110,19 +1104,19 @@ struct MappingPass : impl::MappingPassBase { const auto swaps = restore(child.layout, parent.layout); - // if constexpr (Mode == RoutingMode::Hot) { + if constexpr (Mode == RoutingMode::Hot) { - // // After routing the loop body, all iterators point to - // // std::default_sentinel. To move the iterators to the - // // correct qubit SSA values for the epilogue SWAPs, - // // decrement each twice: (sentinel → yield → - // // unitary/block arg). + // After routing the loop body, all iterators point to + // std::default_sentinel. To move the iterators to the + // correct qubit SSA values for the epilogue SWAPs, + // decrement each twice: (sentinel → yield → + // unitary/block arg). - // llvm::for_each(child.wires, - // [](auto& it) { std::advance(it, -2); }); - // } + llvm::for_each(child.wires, + [](auto& it) { std::advance(it, -2); }); + } - // insertSWAPs(swaps, child, stats, rewriter); + insertSWAPs(swaps, child, stats, rewriter); } if constexpr (Mode == RoutingMode::Hot) { diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp index 1421723018..af8f855259 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -86,35 +86,76 @@ isExecutable(Region& body, DenseMap& m, m.try_emplace(succ, hw); } }) - .Case([&](scf::ForOp op) { + .Case([&](scf::ForOp forOp) { DenseMap loopM; for (const auto [init, arg] : - llvm::zip_equal(op.getInits(), op.getRegionIterArgs())) { + llvm::zip_equal(forOp.getInits(), forOp.getRegionIterArgs())) { const auto hw = m.at(init); loopM.try_emplace(arg, hw); } - for (OpOperand& operand : op.getInitsMutable()) { + for (OpOperand& operand : forOp.getInitsMutable()) { const auto pred = operand.get(); - const auto succ = op.getTiedLoopResult(&operand); + const auto succ = forOp.getTiedLoopResult(&operand); const auto hw = m.at(pred); m.try_emplace(succ, hw); } - if (!isExecutable(op.getRegion(), loopM, couplingSet)) { + if (!isExecutable(forOp.getRegion(), loopM, couplingSet)) { executable = false; return; } - for (const auto& [arg, yielded] : - llvm::zip_equal(op.getRegionIterArgs(), op.getYieldedValues())) { + for (const auto& [arg, yielded] : llvm::zip_equal( + forOp.getRegionIterArgs(), forOp.getYieldedValues())) { if (loopM.at(arg) != loopM.at(yielded)) { - llvm::dbgs() << "for loop layout not restored!\n"; + llvm::dbgs() << "scf::forOp: layout not restored!\n"; executable = false; return; } } }) + .Case([&](qco::IfOp ifOp) { + std::array mappings{DenseMap{}, + DenseMap{}}; + + const std::array regions{&ifOp.getThenRegion(), + &ifOp.getElseRegion()}; + + for (size_t i = 0; i < 2; ++i) { + for (const auto [init, arg] : llvm::zip_equal( + ifOp.getQubits(), regions[i]->getArguments())) { + const auto hw = m.at(init); + mappings[i].try_emplace(arg, hw); + } + } + + for (OpOperand& operand : ifOp.getQubitsMutable()) { + const auto pred = operand.get(); + const auto succ = ifOp.getTiedResult(&operand); + const auto hw = m.at(pred); + m.try_emplace(succ, hw); + } + + for (size_t i = 0; i < 2; ++i) { + Region* body = regions[i]; + if (!isExecutable(*body, mappings[i], couplingSet)) { + executable = false; + return; + } + + Block& block = body->getBlocks().front(); + auto yield = cast(block.getTerminator()); + for (const auto& [arg, yielded] : + llvm::zip_equal(body->getArguments(), yield.getTargets())) { + if (mappings[i].at(arg) != mappings[i].at(yielded)) { + llvm::dbgs() << "qco::IfOp: layout not restored!\n"; + executable = false; + return; + } + } + } + }) .Case([&](auto op) { const auto pred = op.getQubitIn(); const auto succ = op.getQubitOut(); From e8cc389d2d0ae14fc702066e0799e91bd6949d45 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 6 Jul 2026 15:48:24 +0200 Subject: [PATCH 05/23] Implement converge logic --- .../QCO/Transforms/Mapping/Mapping.cpp | 123 ++++++++++++++++-- 1 file changed, 112 insertions(+), 11 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 018e2eb711..f1a3019ced 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -831,6 +831,107 @@ struct MappingPass : impl::MappingPassBase { return swaps; } + /// Return the sequence of SWAPs to move from one layout to another. + /// Implements the 4-Approximation algorithm described in arXiv:1602.05150v3. + std::pair, SmallVector> + converge(const Layout& from, const Layout& to) { + + Layout lhs(from); + Layout rhs(to); + + Graph lhsF(device->qubits()); + Graph rhsF(device->qubits()); + + SmallVector lhsSwaps; + SmallVector rhsSwaps; + + const auto shouldAddEdge = [&](size_t u, size_t v, const Layout& f, + const Layout& t) { + const auto prog = f.getProgramIndex(u); + const auto hwGoal = t.getHardwareIndex(prog); + return device->distanceBetween(v, hwGoal) < + device->distanceBetween(u, hwGoal); + }; + + while (true) { + + // Build F-graph: Add edges to F for each edge in the coupling graph. + // Note that this assumes that the coupling graph is directed, but + // symmetric (essentially: undirected). + + lhsF.clearEdges(); + rhsF.clearEdges(); + + for (const auto u : device->qubits()) { + for (const auto v : device->neighboursOf(u)) { + if (shouldAddEdge(u, v, lhs, rhs)) { + lhsF.addEdge(u, v); + } + } + } + + for (const auto u : device->qubits()) { + for (const auto v : device->neighboursOf(u)) { + if (shouldAddEdge(u, v, rhs, lhs)) { + rhsF.addEdge(u, v); + } + } + } + + // Try to find a directed cycle in the F graph. If there is one, + // we can apply a happy swap chain. Note that this happy swap chain + // does not include the final back edge closing the cycle because the + // first SWAP changes the token (the qubit) on the target, invalidating + // the edge in F. + + if (const auto cycle = lhsF.findCycle(); cycle) { + for (size_t i = cycle->size() - 1; i > 0; --i) { + lhs.swap((*cycle)[i], (*cycle)[i - 1]); + lhsSwaps.emplace_back((*cycle)[i], (*cycle)[i - 1]); + } + continue; + } + + if (const auto cycle = rhsF.findCycle(); cycle) { + for (size_t i = cycle->size() - 1; i > 0; --i) { + rhs.swap((*cycle)[i], (*cycle)[i - 1]); + rhsSwaps.emplace_back((*cycle)[i], (*cycle)[i - 1]); + } + continue; + } + + // Otherwise, search for an unhappy SWAP. That is, search for an edge (u, + // v), where exchanging u and v, reduces u's distance to its target + // location (by one) and increases v's distance from 0 (already at the + // correct location) to one. + + bool found{false}; + for (const auto u : lhsF.getNodes()) { + for (const auto v : lhsF.getNeighbours(u)) { + if (lhsF.getDegree(v) == 0) { + lhs.swap(u, v); + lhsSwaps.emplace_back(u, v); + found = true; + break; + } + } + + if (found) { + break; + } + } + + // If there are no happy or unhappy swaps anymore, + // the final placement of every token is reached. + + if (!found) { + break; + } + } + + return std::make_pair(lhsSwaps, rhsSwaps); + } + /// Skip to the end of the two-qubit block for both wire iterators, where /// initially both must point at the same two-qubit operation. template @@ -1102,21 +1203,21 @@ struct MappingPass : impl::MappingPassBase { return failure(); } - const auto swaps = restore(child.layout, parent.layout); + const auto swaps = converge(child.layout, parent.layout); - if constexpr (Mode == RoutingMode::Hot) { + // if constexpr (Mode == RoutingMode::Hot) { - // After routing the loop body, all iterators point to - // std::default_sentinel. To move the iterators to the - // correct qubit SSA values for the epilogue SWAPs, - // decrement each twice: (sentinel → yield → - // unitary/block arg). + // // After routing the loop body, all iterators point to + // // std::default_sentinel. To move the iterators to the + // // correct qubit SSA values for the epilogue SWAPs, + // // decrement each twice: (sentinel → yield → + // // unitary/block arg). - llvm::for_each(child.wires, - [](auto& it) { std::advance(it, -2); }); - } + // llvm::for_each(child.wires, + // [](auto& it) { std::advance(it, -2); }); + // } - insertSWAPs(swaps, child, stats, rewriter); + // insertSWAPs(swaps, child, stats, rewriter); } if constexpr (Mode == RoutingMode::Hot) { From a07875c62bb61c2a1ac20cd2b44f60dbe3daf8f3 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 6 Jul 2026 08:28:12 +0200 Subject: [PATCH 06/23] Add dispatch function --- .../QCO/Transforms/Mapping/Mapping.cpp | 103 ++++++++++-------- 1 file changed, 60 insertions(+), 43 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index dd0ab588ff..c50e062b92 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -73,6 +73,8 @@ struct MappingPass : impl::MappingPassBase { using IndexPairType = std::pair; using Window = SmallVector; using Wires = SmallVector; + using RecursiveRoutingStackItem = std::pair>; + using RecursiveRoutingStack = SmallVector; enum class RoutingMode : bool { Cold, Hot }; @@ -871,9 +873,9 @@ struct MappingPass : impl::MappingPassBase { /// gates are found. After the function returns, the wires point at the /// results of non-executable gates or operations with nested regions. template - SmallVector>> - advance(Wires& wires, const WireInfos& infos, const Layout& layout) { - SmallVector>> stack; + RecursiveRoutingStack advance(Wires& wires, const WireInfos& infos, + const Layout& layout) { + RecursiveRoutingStack stack; // Advance wires past all executable gates and push operations with // nested regions and the respective wire indices of their inputs onto the @@ -911,54 +913,35 @@ struct MappingPass : impl::MappingPassBase { return stack; } - /// 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. + /// TODO: template requires(Mode != RoutingMode::Hot || Direction == WireDirection::Forward) - LogicalResult route(RoutingBundle& bundle, Statistics& stats, - IRRewriter* rewriter = nullptr) { - using Traits = WireTraversalTraits; - - auto& [wires, infos, layout] = bundle; - - while (true) { - - while (true) { - const auto stack = advance(wires, infos, layout); - - if (stack.empty()) { - break; - } - - // Continue with processing the nested regions recursively. - - for (const auto& [op, indices] : stack) { - assert(isa(op)); - auto forOp = cast(op); - - RoutingBundle child{.layout = layout}; - - // Map parent (results) to child values (iter args). Going forwards, - // the recursive routing starts at block arguments, while the - // backwards go starts at the yielded values. + LogicalResult dispatch(const RecursiveRoutingStackItem& item, + RoutingBundle& parent, Statistics& stats, + IRRewriter* rewriter = nullptr) { + const auto& [op, indices] = item; + return TypeSwitch(op) + .Case([&](scf::ForOp forOp) { + RoutingBundle child{.layout = parent.layout}; + + // Map parent (results) to child values (iter args). Going + // forwards, the recursive routing starts at block + // arguments, while the backwards go starts at the yielded + // values. for (size_t i : indices) { - const auto prog = infos.lookupProgram(i); - const auto res = cast(wires[i].qubit()); + const auto prog = parent.infos.lookupProgram(i); + const auto res = cast(parent.wires[i].qubit()); const auto arg = forOp.getTiedLoopRegionIterArg(res); const auto index = child.wires.size(); if constexpr (Direction == WireDirection::Forward) { child.wires.emplace_back(arg); - child.infos.map(index, prog); } else { const auto yield = forOp.getTiedLoopYieldedValue(arg)->get(); child.wires.emplace_back(yield); - child.infos.map(index, prog); } + child.infos.map(index, prog); } const auto res = route(child, stats, rewriter); @@ -966,14 +949,15 @@ struct MappingPass : impl::MappingPassBase { return failure(); } - const auto swaps = restore(child.layout, layout); + const auto swaps = restore(child.layout, parent.layout); if constexpr (Mode == RoutingMode::Hot) { // After routing the loop body, all iterators point to - // std::default_sentinel. To move the iterators to the correct - // qubit SSA values for the epilogue SWAPs, decrement each - // twice: (sentinel → yield → unitary/block arg). + // std::default_sentinel. To move the iterators to the + // correct qubit SSA values for the epilogue SWAPs, + // decrement each twice: (sentinel → yield → + // unitary/block arg). llvm::for_each(child.wires, [](auto& it) { std::advance(it, -2); }); } @@ -988,8 +972,41 @@ struct MappingPass : impl::MappingPassBase { // incrementing the respective global wires. llvm::for_each(indices, [&](size_t i) { - std::advance(wires[i], Traits::stride()); + std::advance(parent.wires[i], + WireTraversalTraits::stride()); }); + + return success(); + }) + .Default([](Operation*) { return failure(); }); + } + + /// 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, + IRRewriter* rewriter = nullptr) { + using Traits = WireTraversalTraits; + + auto& [wires, infos, layout] = bundle; + + while (true) { + + while (true) { + const auto stack = advance(wires, infos, layout); + if (stack.empty()) { + break; + } + + for (const auto& item : stack) { + if (dispatch(item, bundle, stats, rewriter) + .failed()) { + return failure(); + } } } From 7c18fa4cd2522582cb722ea9c63d1e649fc15d12 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 6 Jul 2026 10:09:20 +0200 Subject: [PATCH 07/23] Implement qco::ifOp extend operation --- .../QCO/Transforms/Mapping/Mapping.cpp | 151 ++++++++++++++++-- 1 file changed, 142 insertions(+), 9 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index c50e062b92..46612dd718 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -335,13 +335,74 @@ struct MappingPass : impl::MappingPassBase { assert(succeeded(res)); auto newForOp = cast(*res); - for (const auto [oldUse, newResult] : + for (const auto [before, after] : llvm::zip_equal(addons, newForOp.getResults().take_back(naddons))) { - rewriter.replaceAllUsesExcept(oldUse, newResult, newForOp); + rewriter.replaceAllUsesExcept(before, after, newForOp); } return newForOp; } + /// Extend the qubit arguments of an `qco::IfOp` by adding a given range of + /// additional SSA values. Replaces the existing operation and returns the + /// newly created one. + static qco::IfOp extend(qco::IfOp ifOp, ValueRange addons, + IRRewriter& rewriter) { + OpBuilder::InsertionGuard guard(rewriter); + rewriter.setInsertionPoint(ifOp); + + const auto naddons = addons.size(); + + SmallVector inits; + llvm::append_range(inits, ifOp.getQubits()); + llvm::append_range(inits, addons); + + auto newIfOp = + rewriter.create(ifOp->getLoc(), ifOp.getCondition(), inits); + + // The qco::IfOp implements the SingleBlockImplicitTerminator trait. + assert(newIfOp.getThenRegion().hasOneBlock()); + assert(newIfOp.getElseRegion().hasOneBlock()); + + const std::array oldBlocks{ + &ifOp.getThenRegion().getBlocks().front(), + &ifOp.getElseRegion().getBlocks().front(), + }; + + const std::array newBlocks{ + &newIfOp.getThenRegion().getBlocks().front(), + &newIfOp.getElseRegion().getBlocks().front(), + }; + + for (const auto [oldBlock, newBlock] : + llvm::zip_equal(oldBlocks, newBlocks)) { + rewriter.mergeBlocks( + oldBlock, newBlock, + newBlock->getArguments().take_front(oldBlock->getNumArguments())); + + auto yield = cast(newBlock->getTerminator()); + + SmallVector results; + llvm::append_range(results, yield.getResults()); + llvm::append_range(results, newBlock->getArguments().take_back(naddons)); + rewriter.setInsertionPoint(yield); + rewriter.replaceOpWithNewOp(yield, results); + } + + for (const auto [oldResult, newResult] : + llvm::zip_first(ifOp.getResults(), newIfOp.getResults())) { + rewriter.replaceAllUsesWith(oldResult, newResult); + } + + for (const auto [oldUse, newResult] : + llvm::zip_equal(addons, newIfOp.getResults().take_back(naddons))) { + rewriter.replaceAllUsesExcept(oldUse, newResult, newIfOp); + } + + rewriter.eraseOp(ifOp); + + return newIfOp; + } + /// Return the wires of a dynamic computation. /// The mapping pass currently assumes that /// - there are no `qco.alloc` operation @@ -486,12 +547,13 @@ struct MappingPass : impl::MappingPassBase { qubits.erase(pred); } }) - .Case([&](scf::ForOp loop) { + .Case([&](scf::ForOp forOp) { assert(qubits.size() == layout.nqubits()); DenseSet addons(qubits); - llvm::for_each(loop.getInits(), [&](auto v) { addons.erase(v); }); - auto newLoop = extend(loop, to_vector(addons), rewriter); + llvm::for_each(forOp.getInits(), + [&](auto v) { addons.erase(v); }); + auto newLoop = extend(forOp, to_vector(addons), rewriter); for (OpOperand& operand : newLoop.getInitsMutable()) { qubits.insert(newLoop.getTiedLoopResult(&operand)); @@ -899,8 +961,8 @@ struct MappingPass : impl::MappingPassBase { released.emplace_back(op); } }) - .template Case( - [&](scf::ForOp op) { stack.emplace_back(op, indices); }); + .template Case( + [&](auto op) { stack.emplace_back(op, indices); }); } if (released.empty()) { @@ -913,7 +975,7 @@ struct MappingPass : impl::MappingPassBase { return stack; } - /// TODO: + /// TODO: template requires(Mode != RoutingMode::Hot || Direction == WireDirection::Forward) LogicalResult dispatch(const RecursiveRoutingStackItem& item, @@ -921,7 +983,7 @@ struct MappingPass : impl::MappingPassBase { IRRewriter* rewriter = nullptr) { const auto& [op, indices] = item; return TypeSwitch(op) - .Case([&](scf::ForOp forOp) { + .template Case([&](scf::ForOp forOp) { RoutingBundle child{.layout = parent.layout}; // Map parent (results) to child values (iter args). Going @@ -978,6 +1040,77 @@ struct MappingPass : impl::MappingPassBase { return success(); }) + .template Case([&](qco::IfOp ifOp) { + std::array children{ + RoutingBundle{.layout = parent.layout}, + RoutingBundle{.layout = parent.layout}}; + + for (size_t i : indices) { + const auto prog = parent.infos.lookupProgram(i); + const auto res = cast(parent.wires[i].qubit()); + const auto index = children[0].wires.size(); + + OpOperand* qubit = ifOp.getTiedQubit(res); + const std::array args{ifOp.getTiedThenBlockArgument(qubit), + ifOp.getTiedElseBlockArgument(qubit)}; + + if constexpr (Direction == WireDirection::Forward) { + for (size_t i = 0; i < children.size(); ++i) { + children[i].wires.emplace_back(args[i]); + children[i].infos.map(index, prog); + } + } else { + const std::array yields{ + ifOp.getTiedThenYieldedValue(args[0])->get(), + ifOp.getTiedElseYieldedValue(args[1])->get()}; + for (size_t i = 0; i < children.size(); ++i) { + children[i].wires.emplace_back(yields[i]); + children[i].infos.map(index, prog); + } + } + } + + for (auto& child : children) { + const auto res = route(child, stats, rewriter); + if (failed(res)) { + return failure(); + } + + const auto swaps = restore(child.layout, parent.layout); + + if constexpr (Mode == RoutingMode::Hot) { + + // After routing the loop body, all iterators point to + // std::default_sentinel. To move the iterators to the + // correct qubit SSA values for the epilogue SWAPs, + // decrement each twice: (sentinel → yield → + // unitary/block arg). + + llvm::for_each(child.wires, + [](auto& it) { std::advance(it, -2); }); + } + + insertSWAPs(swaps, child, stats, rewriter); + } + + if constexpr (Mode == RoutingMode::Hot) { + // llvm::for_each(ifOp.getRegions(), [](Region& region) { + // llvm::for_each(region.getBlocks(), + // [](Block& block) { sortTopologically(&block); + // }); + // }); + } + + // Finally, move past the operation with nested regions by + // incrementing the respective global wires. + + llvm::for_each(indices, [&](size_t i) { + std::advance(parent.wires[i], + WireTraversalTraits::stride()); + }); + + return success(); + }) .Default([](Operation*) { return failure(); }); } From 390ccb5bbf10153dabdb2f61660ff8c6ec2b46e5 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 6 Jul 2026 15:01:39 +0200 Subject: [PATCH 08/23] Implement simple test --- mlir/lib/Dialect/QCO/IR/SCF/IfOp.cpp | 4 +- .../QCO/Transforms/Mapping/Mapping.cpp | 99 ++++++++++++------- mlir/lib/Dialect/QCO/Utils/Graph.cpp | 1 + .../QCO/Transforms/Mapping/test_mapping.cpp | 48 +++++++++ 4 files changed, 117 insertions(+), 35 deletions(-) diff --git a/mlir/lib/Dialect/QCO/IR/SCF/IfOp.cpp b/mlir/lib/Dialect/QCO/IR/SCF/IfOp.cpp index c377947153..f3849d8015 100644 --- a/mlir/lib/Dialect/QCO/IR/SCF/IfOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/SCF/IfOp.cpp @@ -313,14 +313,14 @@ BlockArgument IfOp::getTiedElseBlockArgument(OpOperand* qubit) { } OpOperand* IfOp::getTiedThenYieldedValue(BlockArgument bbArg) { - if (bbArg.getDefiningOp() != getOperation()) { + if (bbArg.getOwner()->getParentOp() != getOperation()) { return nullptr; } return &thenYield().getTargetsMutable()[bbArg.getArgNumber()]; } OpOperand* IfOp::getTiedElseYieldedValue(BlockArgument bbArg) { - if (bbArg.getDefiningOp() != getOperation()) { + if (bbArg.getOwner()->getParentOp() != getOperation()) { return nullptr; } return &elseYield().getTargetsMutable()[bbArg.getArgNumber()]; diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 46612dd718..d3198a4c48 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -360,17 +361,21 @@ struct MappingPass : impl::MappingPassBase { rewriter.create(ifOp->getLoc(), ifOp.getCondition(), inits); // The qco::IfOp implements the SingleBlockImplicitTerminator trait. - assert(newIfOp.getThenRegion().hasOneBlock()); - assert(newIfOp.getElseRegion().hasOneBlock()); + assert(ifOp.getThenRegion().hasOneBlock()); + assert(ifOp.getElseRegion().hasOneBlock()); const std::array oldBlocks{ &ifOp.getThenRegion().getBlocks().front(), &ifOp.getElseRegion().getBlocks().front(), }; + SmallVector argTypes(inits.size(), + QubitType::get(rewriter.getContext())); + SmallVector locs(inits.size(), newIfOp->getLoc()); + const std::array newBlocks{ - &newIfOp.getThenRegion().getBlocks().front(), - &newIfOp.getElseRegion().getBlocks().front(), + rewriter.createBlock(&newIfOp.getThenRegion(), {}, argTypes, locs), + rewriter.createBlock(&newIfOp.getElseRegion(), {}, argTypes, locs), }; for (const auto [oldBlock, newBlock] : @@ -379,13 +384,13 @@ struct MappingPass : impl::MappingPassBase { oldBlock, newBlock, newBlock->getArguments().take_front(oldBlock->getNumArguments())); - auto yield = cast(newBlock->getTerminator()); + auto yield = cast(newBlock->getTerminator()); SmallVector results; - llvm::append_range(results, yield.getResults()); + llvm::append_range(results, yield.getTargets()); llvm::append_range(results, newBlock->getArguments().take_back(naddons)); rewriter.setInsertionPoint(yield); - rewriter.replaceOpWithNewOp(yield, results); + rewriter.replaceOpWithNewOp(yield, results); } for (const auto [oldResult, newResult] : @@ -553,17 +558,39 @@ struct MappingPass : impl::MappingPassBase { DenseSet addons(qubits); llvm::for_each(forOp.getInits(), [&](auto v) { addons.erase(v); }); - auto newLoop = extend(forOp, to_vector(addons), rewriter); + auto newForOp = extend(forOp, to_vector(addons), rewriter); + + for (OpOperand& operand : newForOp.getInitsMutable()) { + qubits.insert(newForOp.getTiedLoopResult(&operand)); + qubits.erase(operand.get()); + } + + stack.emplace_back( + newForOp.getRegion(), + DenseSet(newForOp.getRegionIterArgs().begin(), + newForOp.getRegionIterArgs().end())); + }) + .Case([&](IfOp ifOp) { + assert(qubits.size() == layout.nqubits()); + + DenseSet addons(qubits); + llvm::for_each(ifOp.getQubits(), + [&](auto v) { addons.erase(v); }); + auto newIfOp = extend(ifOp, to_vector(addons), rewriter); - for (OpOperand& operand : newLoop.getInitsMutable()) { - qubits.insert(newLoop.getTiedLoopResult(&operand)); + for (OpOperand& operand : newIfOp.getQubitsMutable()) { + qubits.insert(newIfOp.getTiedResult(&operand)); qubits.erase(operand.get()); } + const auto thenArgs = newIfOp.getThenRegion().getArguments(); + const auto elseArgs = newIfOp.getElseRegion().getArguments(); + stack.emplace_back( + newIfOp.getThenRegion(), + DenseSet(thenArgs.begin(), thenArgs.end())); stack.emplace_back( - newLoop.getRegion(), - DenseSet(newLoop.getRegionIterArgs().begin(), - newLoop.getRegionIterArgs().end())); + newIfOp.getElseRegion(), + DenseSet(elseArgs.begin(), elseArgs.end())); }) .Case([&](auto op) { qubits.insert(op.getQubitOut()); @@ -731,7 +758,6 @@ struct MappingPass : impl::MappingPassBase { /// Return the sequence of SWAPs to move from one layout to another. /// Implements the 4-Approximation algorithm described in arXiv:1602.05150v3. SmallVector restore(const Layout& from, const Layout& to) { - Layout curr(from); Graph f(device->qubits()); SmallVector swaps; @@ -743,6 +769,13 @@ struct MappingPass : impl::MappingPassBase { device->distanceBetween(u, hwGoal); }; + const auto printLayout = [](const Layout& l) { + for (const auto j : l.getProgramToHardware()) { + llvm::dbgs() << j << " "; + } + llvm::dbgs() << '\n'; + }; + while (true) { // Build F-graph: Add edges to F for each edge in the coupling graph. @@ -1041,9 +1074,8 @@ struct MappingPass : impl::MappingPassBase { return success(); }) .template Case([&](qco::IfOp ifOp) { - std::array children{ - RoutingBundle{.layout = parent.layout}, - RoutingBundle{.layout = parent.layout}}; + std::array children{RoutingBundle{.layout = parent.layout}, + RoutingBundle{.layout = parent.layout}}; for (size_t i : indices) { const auto prog = parent.infos.lookupProgram(i); @@ -1078,27 +1110,29 @@ struct MappingPass : impl::MappingPassBase { const auto swaps = restore(child.layout, parent.layout); - if constexpr (Mode == RoutingMode::Hot) { + // if constexpr (Mode == RoutingMode::Hot) { - // After routing the loop body, all iterators point to - // std::default_sentinel. To move the iterators to the - // correct qubit SSA values for the epilogue SWAPs, - // decrement each twice: (sentinel → yield → - // unitary/block arg). + // // After routing the loop body, all iterators point to + // // std::default_sentinel. To move the iterators to the + // // correct qubit SSA values for the epilogue SWAPs, + // // decrement each twice: (sentinel → yield → + // // unitary/block arg). - llvm::for_each(child.wires, - [](auto& it) { std::advance(it, -2); }); - } + // llvm::for_each(child.wires, + // [](auto& it) { std::advance(it, -2); }); + // } - insertSWAPs(swaps, child, stats, rewriter); + // insertSWAPs(swaps, child, stats, rewriter); } if constexpr (Mode == RoutingMode::Hot) { - // llvm::for_each(ifOp.getRegions(), [](Region& region) { - // llvm::for_each(region.getBlocks(), - // [](Block& block) { sortTopologically(&block); - // }); - // }); + + // The qco::IfOp implements the SingleBlockImplicitTerminator trait. + assert(ifOp.getThenRegion().hasOneBlock()); + assert(ifOp.getElseRegion().hasOneBlock()); + + sortTopologically(&ifOp.getThenRegion().getBlocks().front()); + sortTopologically(&ifOp.getElseRegion().getBlocks().front()); } // Finally, move past the operation with nested regions by @@ -1134,7 +1168,6 @@ struct MappingPass : impl::MappingPassBase { if (stack.empty()) { break; } - for (const auto& item : stack) { if (dispatch(item, bundle, stats, rewriter) .failed()) { diff --git a/mlir/lib/Dialect/QCO/Utils/Graph.cpp b/mlir/lib/Dialect/QCO/Utils/Graph.cpp index 0f48b53281..5e3de25add 100644 --- a/mlir/lib/Dialect/QCO/Utils/Graph.cpp +++ b/mlir/lib/Dialect/QCO/Utils/Graph.cpp @@ -30,6 +30,7 @@ void Graph::addEdge(size_t u, size_t v) { } ArrayRef Graph::getNeighbours(size_t id) const { return adj_.at(id); } + SmallVector Graph::getNodes() const { return to_vector(adj_.keys()); } size_t Graph::getMaxDegree() const { diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp index 98ea6538d9..1421723018 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -590,5 +590,53 @@ TEST_P(MappingPassTest, Sabre) { EXPECT_TRUE(isExecutable(entry, device.couplingSet)); } +TEST_P(MappingPassTest, RandomGHZ) { + const auto& device = GetParam(); + + QCOProgramBuilder builder(context.get()); + builder.initialize(); + + Value tensor = builder.qtensorAlloc(9); + SmallVector qubits(9); + SmallVector cregs(9); + + for (int64_t i = 0; i < 9; ++i) { + std::tie(tensor, qubits[i]) = builder.qtensorExtract(tensor, i); + } + + qubits[0] = builder.h(qubits[0]); + std::tie(qubits[0], cregs[0]) = builder.measure(qubits[0]); + + qubits = builder.qcoIf(cregs[0], qubits, [&](ValueRange args) { + SmallVector values(args); + values[0] = builder.h(values[0]); + for (size_t i = 1; i < 9; ++i) { + std::tie(values[0], values[i]) = builder.cx(values[0], values[i]); + } + return values; + }); + + qubits = builder.barrier(qubits); + + for (int64_t i = 0; i < 9; ++i) { + std::tie(qubits[i], cregs[i]) = builder.measure(qubits[i]); + } + + for (int64_t i = 0; i < 9; ++i) { + tensor = builder.qtensorInsert(qubits[i], tensor, i); + } + + builder.qtensorDealloc(tensor); + + auto m = builder.finalize(); + auto res = + runPass(m.get(), device.couplingSet, MappingPassOptions{.ntrials = 1}); + auto entry = getEntryPoint(m.get()); + + m->dump(); + ASSERT_TRUE(res.succeeded()); + EXPECT_TRUE(isExecutable(entry, device.couplingSet)); +} + INSTANTIATE_TEST_SUITE_P(NineQubitSquareGrid, MappingPassTest, testing::Values(getNineQubitSquareGrid())); From 7e1084b84c8a0337cdb27b9f74f12c6108397d6b Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 6 Jul 2026 15:22:58 +0200 Subject: [PATCH 09/23] Implement restoration logic for qco::IfOp --- .../QCO/Transforms/Mapping/Mapping.cpp | 28 ++++----- .../QCO/Transforms/Mapping/test_mapping.cpp | 57 ++++++++++++++++--- 2 files changed, 60 insertions(+), 25 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index d3198a4c48..08a8c17658 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -758,6 +758,7 @@ struct MappingPass : impl::MappingPassBase { /// Return the sequence of SWAPs to move from one layout to another. /// Implements the 4-Approximation algorithm described in arXiv:1602.05150v3. SmallVector restore(const Layout& from, const Layout& to) { + Layout curr(from); Graph f(device->qubits()); SmallVector swaps; @@ -769,13 +770,6 @@ struct MappingPass : impl::MappingPassBase { device->distanceBetween(u, hwGoal); }; - const auto printLayout = [](const Layout& l) { - for (const auto j : l.getProgramToHardware()) { - llvm::dbgs() << j << " "; - } - llvm::dbgs() << '\n'; - }; - while (true) { // Build F-graph: Add edges to F for each edge in the coupling graph. @@ -1110,19 +1104,19 @@ struct MappingPass : impl::MappingPassBase { const auto swaps = restore(child.layout, parent.layout); - // if constexpr (Mode == RoutingMode::Hot) { + if constexpr (Mode == RoutingMode::Hot) { - // // After routing the loop body, all iterators point to - // // std::default_sentinel. To move the iterators to the - // // correct qubit SSA values for the epilogue SWAPs, - // // decrement each twice: (sentinel → yield → - // // unitary/block arg). + // After routing the loop body, all iterators point to + // std::default_sentinel. To move the iterators to the + // correct qubit SSA values for the epilogue SWAPs, + // decrement each twice: (sentinel → yield → + // unitary/block arg). - // llvm::for_each(child.wires, - // [](auto& it) { std::advance(it, -2); }); - // } + llvm::for_each(child.wires, + [](auto& it) { std::advance(it, -2); }); + } - // insertSWAPs(swaps, child, stats, rewriter); + insertSWAPs(swaps, child, stats, rewriter); } if constexpr (Mode == RoutingMode::Hot) { diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp index 1421723018..af8f855259 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -86,35 +86,76 @@ isExecutable(Region& body, DenseMap& m, m.try_emplace(succ, hw); } }) - .Case([&](scf::ForOp op) { + .Case([&](scf::ForOp forOp) { DenseMap loopM; for (const auto [init, arg] : - llvm::zip_equal(op.getInits(), op.getRegionIterArgs())) { + llvm::zip_equal(forOp.getInits(), forOp.getRegionIterArgs())) { const auto hw = m.at(init); loopM.try_emplace(arg, hw); } - for (OpOperand& operand : op.getInitsMutable()) { + for (OpOperand& operand : forOp.getInitsMutable()) { const auto pred = operand.get(); - const auto succ = op.getTiedLoopResult(&operand); + const auto succ = forOp.getTiedLoopResult(&operand); const auto hw = m.at(pred); m.try_emplace(succ, hw); } - if (!isExecutable(op.getRegion(), loopM, couplingSet)) { + if (!isExecutable(forOp.getRegion(), loopM, couplingSet)) { executable = false; return; } - for (const auto& [arg, yielded] : - llvm::zip_equal(op.getRegionIterArgs(), op.getYieldedValues())) { + for (const auto& [arg, yielded] : llvm::zip_equal( + forOp.getRegionIterArgs(), forOp.getYieldedValues())) { if (loopM.at(arg) != loopM.at(yielded)) { - llvm::dbgs() << "for loop layout not restored!\n"; + llvm::dbgs() << "scf::forOp: layout not restored!\n"; executable = false; return; } } }) + .Case([&](qco::IfOp ifOp) { + std::array mappings{DenseMap{}, + DenseMap{}}; + + const std::array regions{&ifOp.getThenRegion(), + &ifOp.getElseRegion()}; + + for (size_t i = 0; i < 2; ++i) { + for (const auto [init, arg] : llvm::zip_equal( + ifOp.getQubits(), regions[i]->getArguments())) { + const auto hw = m.at(init); + mappings[i].try_emplace(arg, hw); + } + } + + for (OpOperand& operand : ifOp.getQubitsMutable()) { + const auto pred = operand.get(); + const auto succ = ifOp.getTiedResult(&operand); + const auto hw = m.at(pred); + m.try_emplace(succ, hw); + } + + for (size_t i = 0; i < 2; ++i) { + Region* body = regions[i]; + if (!isExecutable(*body, mappings[i], couplingSet)) { + executable = false; + return; + } + + Block& block = body->getBlocks().front(); + auto yield = cast(block.getTerminator()); + for (const auto& [arg, yielded] : + llvm::zip_equal(body->getArguments(), yield.getTargets())) { + if (mappings[i].at(arg) != mappings[i].at(yielded)) { + llvm::dbgs() << "qco::IfOp: layout not restored!\n"; + executable = false; + return; + } + } + } + }) .Case([&](auto op) { const auto pred = op.getQubitIn(); const auto succ = op.getQubitOut(); From 079e0dd38f97663e5fe805d64b4f3e74d257176b Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 6 Jul 2026 15:48:24 +0200 Subject: [PATCH 10/23] Implement converge logic --- .../QCO/Transforms/Mapping/Mapping.cpp | 123 ++++++++++++++++-- 1 file changed, 112 insertions(+), 11 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 08a8c17658..1c6449c1d3 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -831,6 +831,107 @@ struct MappingPass : impl::MappingPassBase { return swaps; } + /// Return the sequence of SWAPs to move from one layout to another. + /// Implements the 4-Approximation algorithm described in arXiv:1602.05150v3. + std::pair, SmallVector> + converge(const Layout& from, const Layout& to) { + + Layout lhs(from); + Layout rhs(to); + + Graph lhsF(device->qubits()); + Graph rhsF(device->qubits()); + + SmallVector lhsSwaps; + SmallVector rhsSwaps; + + const auto shouldAddEdge = [&](size_t u, size_t v, const Layout& f, + const Layout& t) { + const auto prog = f.getProgramIndex(u); + const auto hwGoal = t.getHardwareIndex(prog); + return device->distanceBetween(v, hwGoal) < + device->distanceBetween(u, hwGoal); + }; + + while (true) { + + // Build F-graph: Add edges to F for each edge in the coupling graph. + // Note that this assumes that the coupling graph is directed, but + // symmetric (essentially: undirected). + + lhsF.clearEdges(); + rhsF.clearEdges(); + + for (const auto u : device->qubits()) { + for (const auto v : device->neighboursOf(u)) { + if (shouldAddEdge(u, v, lhs, rhs)) { + lhsF.addEdge(u, v); + } + } + } + + for (const auto u : device->qubits()) { + for (const auto v : device->neighboursOf(u)) { + if (shouldAddEdge(u, v, rhs, lhs)) { + rhsF.addEdge(u, v); + } + } + } + + // Try to find a directed cycle in the F graph. If there is one, + // we can apply a happy swap chain. Note that this happy swap chain + // does not include the final back edge closing the cycle because the + // first SWAP changes the token (the qubit) on the target, invalidating + // the edge in F. + + if (const auto cycle = lhsF.findCycle(); cycle) { + for (size_t i = cycle->size() - 1; i > 0; --i) { + lhs.swap((*cycle)[i], (*cycle)[i - 1]); + lhsSwaps.emplace_back((*cycle)[i], (*cycle)[i - 1]); + } + continue; + } + + if (const auto cycle = rhsF.findCycle(); cycle) { + for (size_t i = cycle->size() - 1; i > 0; --i) { + rhs.swap((*cycle)[i], (*cycle)[i - 1]); + rhsSwaps.emplace_back((*cycle)[i], (*cycle)[i - 1]); + } + continue; + } + + // Otherwise, search for an unhappy SWAP. That is, search for an edge (u, + // v), where exchanging u and v, reduces u's distance to its target + // location (by one) and increases v's distance from 0 (already at the + // correct location) to one. + + bool found{false}; + for (const auto u : lhsF.getNodes()) { + for (const auto v : lhsF.getNeighbours(u)) { + if (lhsF.getDegree(v) == 0) { + lhs.swap(u, v); + lhsSwaps.emplace_back(u, v); + found = true; + break; + } + } + + if (found) { + break; + } + } + + // If there are no happy or unhappy swaps anymore, + // the final placement of every token is reached. + + if (!found) { + break; + } + } + + return std::make_pair(lhsSwaps, rhsSwaps); + } + /// Skip to the end of the two-qubit block for both wire iterators, where /// initially both must point at the same two-qubit operation. template @@ -1102,21 +1203,21 @@ struct MappingPass : impl::MappingPassBase { return failure(); } - const auto swaps = restore(child.layout, parent.layout); + const auto swaps = converge(child.layout, parent.layout); - if constexpr (Mode == RoutingMode::Hot) { + // if constexpr (Mode == RoutingMode::Hot) { - // After routing the loop body, all iterators point to - // std::default_sentinel. To move the iterators to the - // correct qubit SSA values for the epilogue SWAPs, - // decrement each twice: (sentinel → yield → - // unitary/block arg). + // // After routing the loop body, all iterators point to + // // std::default_sentinel. To move the iterators to the + // // correct qubit SSA values for the epilogue SWAPs, + // // decrement each twice: (sentinel → yield → + // // unitary/block arg). - llvm::for_each(child.wires, - [](auto& it) { std::advance(it, -2); }); - } + // llvm::for_each(child.wires, + // [](auto& it) { std::advance(it, -2); }); + // } - insertSWAPs(swaps, child, stats, rewriter); + // insertSWAPs(swaps, child, stats, rewriter); } if constexpr (Mode == RoutingMode::Hot) { From 684ec1f3003c7e648a96413c27d99676d3c24bf6 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 8 Jul 2026 12:34:46 +0200 Subject: [PATCH 11/23] Add FGraph datastructure --- .../QCO/Transforms/Mapping/Mapping.cpp | 293 ++++++++---------- 1 file changed, 137 insertions(+), 156 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 1c6449c1d3..9367965893 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -52,6 +51,7 @@ #include #include #include +#include #include #include #include @@ -240,6 +240,74 @@ struct MappingPass : impl::MappingPassBase { } }; + /// Describes the graph F of arXiv:1602.05150v3. + struct FGraph { + explicit FGraph(std::shared_ptr device) + : f_(device->qubits()), device_(std::move(device)) {}; + + /// Build F-graph: Add edges to F for each edge in the coupling graph. + /// Note that this assumes that the coupling graph is directed, but + /// symmetric (essentially: undirected). + void construct(const Layout& from, const Layout& to) { + for (const auto u : device_->qubits()) { + for (const auto v : device_->neighboursOf(u)) { + if (shouldAddEdge(u, v, from, to)) { + f_.addEdge(u, v); + } + } + } + } + + /// Try to find a directed cycle in the F graph. If there is one, + /// we can apply a happy swap chain. Note that this happy swap chain + /// does not include the final back edge closing the cycle because the + /// first SWAP changes the token (the qubit) on the target, invalidating + /// the edge in F. + std::optional> findHappySWAPChain() { + const auto cycle = f_.findCycle(); + if (!cycle) { + return std::nullopt; + } + + SmallVector swaps; + for (size_t i = cycle->size() - 1; i > 0; --i) { + swaps.emplace_back((*cycle)[i], (*cycle)[i - 1]); + } + return swaps; + } + + /// Find an unhappy SWAP. That is, find an edge (u, v), where exchanging u + /// and v, reduces u's distance to its target location (by one) and + /// increases v's distance from 0 (already at the correct location) to one. + std::optional findUnhappySWAP() { + for (const auto u : f_.getNodes()) { + for (const auto v : f_.getNeighbours(u)) { + if (f_.getDegree(v) == 0) { + return std::make_pair(u, v); + } + } + } + + return std::nullopt; + } + + /// Reset the F graph for rebuilding. + void reset() { f_.clearEdges(); } + + private: + /// Return true, if moving the program qubit on hardware qubit u to hardware + /// qubit v brings it closer to its destination hardware qubit. + bool shouldAddEdge(size_t u, size_t v, const Layout& from, + const Layout& to) { + const auto dest = to.getHardwareIndex(from.getProgramIndex(u)); + return device_->distanceBetween(v, dest) < + device_->distanceBetween(u, dest); + } + + Graph f_; + std::shared_ptr device_; + }; + public: /// Construct default mapping pass. MappingPass() = default; @@ -755,181 +823,92 @@ struct MappingPass : impl::MappingPassBase { return failure(); } - /// Return the sequence of SWAPs to move from one layout to another. + /// Return the SWAP sequence to move from one layout to another. /// Implements the 4-Approximation algorithm described in arXiv:1602.05150v3. SmallVector restore(const Layout& from, const Layout& to) { - Layout curr(from); - Graph f(device->qubits()); + FGraph f(device); SmallVector swaps; - const auto shouldAddEdge = [&](size_t u, size_t v) { - const auto prog = curr.getProgramIndex(u); - const auto hwGoal = to.getHardwareIndex(prog); - return device->distanceBetween(v, hwGoal) < - device->distanceBetween(u, hwGoal); - }; - while (true) { - - // Build F-graph: Add edges to F for each edge in the coupling graph. - // Note that this assumes that the coupling graph is directed, but - // symmetric (essentially: undirected). - - f.clearEdges(); - for (const auto u : device->qubits()) { - for (const auto v : device->neighboursOf(u)) { - if (shouldAddEdge(u, v)) { - f.addEdge(u, v); - } - } - } - - // Try to find a directed cycle in the F graph. If there is one, - // we can apply a happy swap chain. Note that this happy swap chain - // does not include the final back edge closing the cycle because the - // first SWAP changes the token (the qubit) on the target, invalidating - // the edge in F. - - if (const auto cycle = f.findCycle(); cycle) { - for (size_t i = cycle->size() - 1; i > 0; --i) { - curr.swap((*cycle)[i], (*cycle)[i - 1]); - swaps.emplace_back((*cycle)[i], (*cycle)[i - 1]); + f.reset(); + f.construct(curr, to); + + const auto happy = f.findHappySWAPChain(); + if (happy) { + for (const auto& swap : *happy) { + swaps.emplace_back(swap); + curr.swap(swap.first, swap.second); } continue; } - // Otherwise, search for an unhappy SWAP. That is, search for an edge (u, - // v), where exchanging u and v, reduces u's distance to its target - // location (by one) and increases v's distance from 0 (already at the - // correct location) to one. - - bool found{false}; - for (const auto u : f.getNodes()) { - for (const auto v : f.getNeighbours(u)) { - if (f.getDegree(v) == 0) { - curr.swap(u, v); - swaps.emplace_back(u, v); - found = true; - break; - } - } - - if (found) { - break; - } - } - // If there are no happy or unhappy swaps anymore, // the final placement of every token is reached. - if (!found) { + const auto unhappy = f.findUnhappySWAP(); + if (!unhappy) { break; } + + swaps.emplace_back(*unhappy); + curr.swap(unhappy->first, unhappy->second); } return swaps; } - /// Return the sequence of SWAPs to move from one layout to another. - /// Implements the 4-Approximation algorithm described in arXiv:1602.05150v3. + /// Return a pair of SWAP sequences to transform two layouts into each other. + /// Inspired by the 4-Approximation algorithm described in arXiv:1602.05150v3, + /// with the key difference that the goal permutation is not static. std::pair, SmallVector> - converge(const Layout& from, const Layout& to) { - - Layout lhs(from); - Layout rhs(to); - - Graph lhsF(device->qubits()); - Graph rhsF(device->qubits()); + converge(const Layout& lhs, const Layout& rhs) { + std::array layouts{Layout(lhs), Layout(rhs)}; + std::array graphs{FGraph(device), FGraph(device)}; + std::array, 2> swaps{}; - SmallVector lhsSwaps; - SmallVector rhsSwaps; - - const auto shouldAddEdge = [&](size_t u, size_t v, const Layout& f, - const Layout& t) { - const auto prog = f.getProgramIndex(u); - const auto hwGoal = t.getHardwareIndex(prog); - return device->distanceBetween(v, hwGoal) < - device->distanceBetween(u, hwGoal); - }; + std::mt19937 gen(seed); + std::uniform_int_distribution coin(0, 1); while (true) { - - // Build F-graph: Add edges to F for each edge in the coupling graph. - // Note that this assumes that the coupling graph is directed, but - // symmetric (essentially: undirected). - - lhsF.clearEdges(); - rhsF.clearEdges(); - - for (const auto u : device->qubits()) { - for (const auto v : device->neighboursOf(u)) { - if (shouldAddEdge(u, v, lhs, rhs)) { - lhsF.addEdge(u, v); - } - } - } - - for (const auto u : device->qubits()) { - for (const auto v : device->neighboursOf(u)) { - if (shouldAddEdge(u, v, rhs, lhs)) { - rhsF.addEdge(u, v); + size_t i = 0; + for (; i < 2; ++i) { + FGraph& f = graphs[i]; + + f.reset(); + f.construct(layouts[i], layouts[(i + 1) % 2]); + + const auto happy = f.findHappySWAPChain(); + if (happy) { + for (const auto& swap : *happy) { + swaps[i].emplace_back(swap); + layouts[i].swap(swap.first, swap.second); } + break; } } - // Try to find a directed cycle in the F graph. If there is one, - // we can apply a happy swap chain. Note that this happy swap chain - // does not include the final back edge closing the cycle because the - // first SWAP changes the token (the qubit) on the target, invalidating - // the edge in F. - - if (const auto cycle = lhsF.findCycle(); cycle) { - for (size_t i = cycle->size() - 1; i > 0; --i) { - lhs.swap((*cycle)[i], (*cycle)[i - 1]); - lhsSwaps.emplace_back((*cycle)[i], (*cycle)[i - 1]); - } + // If we exit early from the loop, we've found a happy SWAP chain. + if (i != 2) { continue; } - if (const auto cycle = rhsF.findCycle(); cycle) { - for (size_t i = cycle->size() - 1; i > 0; --i) { - rhs.swap((*cycle)[i], (*cycle)[i - 1]); - rhsSwaps.emplace_back((*cycle)[i], (*cycle)[i - 1]); - } - continue; - } + // Otherwise, we randomly apply an unhappy SWAP to one of the layouts. + // If there is no happy or unhappy swaps anymore, the final placement of + // every token is reached. - // Otherwise, search for an unhappy SWAP. That is, search for an edge (u, - // v), where exchanging u and v, reduces u's distance to its target - // location (by one) and increases v's distance from 0 (already at the - // correct location) to one. - - bool found{false}; - for (const auto u : lhsF.getNodes()) { - for (const auto v : lhsF.getNeighbours(u)) { - if (lhsF.getDegree(v) == 0) { - lhs.swap(u, v); - lhsSwaps.emplace_back(u, v); - found = true; - break; - } - } - - if (found) { - break; - } - } - - // If there are no happy or unhappy swaps anymore, - // the final placement of every token is reached. + i = coin(gen); - if (!found) { + const auto unhappy = graphs[i].findUnhappySWAP(); + if (!unhappy) { break; } + + swaps[i].emplace_back(*unhappy); + layouts[i].swap(unhappy->first, unhappy->second); } - return std::make_pair(lhsSwaps, rhsSwaps); + return std::make_pair(std::move(swaps[0]), std::move(swaps[1])); } /// Skip to the end of the two-qubit block for both wire iterators, where @@ -1103,7 +1082,8 @@ struct MappingPass : impl::MappingPassBase { return stack; } - /// TODO: + /// Processes the recursive stack item by routing the nested operation and + /// inserting epilogue SWAPs. template requires(Mode != RoutingMode::Hot || Direction == WireDirection::Forward) LogicalResult dispatch(const RecursiveRoutingStackItem& item, @@ -1134,8 +1114,7 @@ struct MappingPass : impl::MappingPassBase { child.infos.map(index, prog); } - const auto res = route(child, stats, rewriter); - if (failed(res)) { + if (failed(route(child, stats, rewriter))) { return failure(); } @@ -1198,27 +1177,29 @@ struct MappingPass : impl::MappingPassBase { } for (auto& child : children) { - const auto res = route(child, stats, rewriter); - if (failed(res)) { + if (failed(route(child, stats, rewriter))) { return failure(); } + } - const auto swaps = converge(child.layout, parent.layout); + if constexpr (Mode == RoutingMode::Hot) { - // if constexpr (Mode == RoutingMode::Hot) { + // After routing the branch body, all iterators point to + // std::default_sentinel. To move the iterators to the + // correct qubit SSA values for the epilogue SWAPs, + // decrement each twice: (sentinel → yield → + // unitary/block arg). - // // After routing the loop body, all iterators point to - // // std::default_sentinel. To move the iterators to the - // // correct qubit SSA values for the epilogue SWAPs, - // // decrement each twice: (sentinel → yield → - // // unitary/block arg). + llvm::for_each(children[0].wires, + [](auto& it) { std::advance(it, -2); }); + llvm::for_each(children[1].wires, + [](auto& it) { std::advance(it, -2); }); + } - // llvm::for_each(child.wires, - // [](auto& it) { std::advance(it, -2); }); - // } + const auto swaps = converge(children[0].layout, children[1].layout); - // insertSWAPs(swaps, child, stats, rewriter); - } + insertSWAPs(swaps.first, children[0], stats, rewriter); + insertSWAPs(swaps.second, children[1], stats, rewriter); if constexpr (Mode == RoutingMode::Hot) { From 8e8720391c8169cd9ed859bd79dfdea9b572deb4 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 8 Jul 2026 12:37:27 +0200 Subject: [PATCH 12/23] Final touches --- mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 9367965893..bed9d066f2 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -1151,6 +1151,11 @@ struct MappingPass : impl::MappingPassBase { std::array children{RoutingBundle{.layout = parent.layout}, RoutingBundle{.layout = parent.layout}}; + // Map parent (results) to child values (qubits). Going + // forwards, the recursive routing starts at block + // arguments, while the backwards go starts at the yielded + // values. + for (size_t i : indices) { const auto prog = parent.infos.lookupProgram(i); const auto res = cast(parent.wires[i].qubit()); From 153ceddc042d6e1067814d64d1ce431c87ce09e0 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 8 Jul 2026 12:41:29 +0200 Subject: [PATCH 13/23] Remove debug 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 5133522ce6..bed9d066f2 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include From 94c9b1e38a0a50da50f7a8b1447ef88776280575 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 8 Jul 2026 12:44:26 +0200 Subject: [PATCH 14/23] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64023dfa60..1fc3a9b65d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,7 +40,7 @@ releases may include breaking changes. [#1676], [#1706], [#1776]) ([**@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], [#1748], [#1805]) ([**@MatthiasReumann**], + [#1600], [#1664], [#1709], [#1716], [#1748], [#1805], [#1870]) ([**@MatthiasReumann**], [**@burgholzer**]) - ✨ Add initial infrastructure for new QC and QCO MLIR dialects ([#1264], [#1330], [#1402], [#1428], [#1430], [#1436], [#1443], [#1446], [#1464], @@ -600,6 +600,7 @@ changelogs._ +[#1870]: https://github.com/munich-quantum-toolkit/core/pull/1870 [#1848]: https://github.com/munich-quantum-toolkit/core/pull/1848 [#1844]: https://github.com/munich-quantum-toolkit/core/pull/1844 [#1842]: https://github.com/munich-quantum-toolkit/core/pull/1842 From c5dd843d0b16df73ff0327099139a739fda926b8 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 8 Jul 2026 12:44:56 +0200 Subject: [PATCH 15/23] Add missing include --- 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 36a1783de8..4549a45947 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include using namespace mlir; From e0d6756705b1257c202fc318194ba39b182d1a54 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 8 Jul 2026 10:45:27 +0000 Subject: [PATCH 16/23] =?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 --- CHANGELOG.md | 4 ++-- .../unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fc3a9b65d..6263f32800 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,8 +40,8 @@ releases may include breaking changes. [#1676], [#1706], [#1776]) ([**@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], [#1748], [#1805], [#1870]) ([**@MatthiasReumann**], - [**@burgholzer**]) + [#1600], [#1664], [#1709], [#1716], [#1748], [#1805], [#1870]) + ([**@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], diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp index 4549a45947..e8a9b56941 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -32,12 +32,12 @@ #include #include +#include #include #include #include #include #include -#include #include using namespace mlir; From f55ff36c9eba29bb61a84d8dcb82af5847f91ca5 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 8 Jul 2026 13:12:29 +0200 Subject: [PATCH 17/23] Remove unused Traits --- mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index bed9d066f2..5bd72e31bf 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -1238,10 +1238,8 @@ struct MappingPass : impl::MappingPassBase { requires(Mode != RoutingMode::Hot || Direction == WireDirection::Forward) LogicalResult route(RoutingBundle& bundle, Statistics& stats, IRRewriter* rewriter = nullptr) { - using Traits = WireTraversalTraits; - auto& [wires, infos, layout] = bundle; - + while (true) { while (true) { From 28a2c9cae12b5d4957007ffb7b7fca7f6a222099 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 9 Jul 2026 12:42:20 +0200 Subject: [PATCH 18/23] Use "replaceWithAdditionalQubits" method --- .../QCO/Transforms/Mapping/Mapping.cpp | 64 +++---------------- 1 file changed, 8 insertions(+), 56 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 5bd72e31bf..5fbc686714 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -398,14 +398,13 @@ struct MappingPass : impl::MappingPassBase { OpBuilder::InsertionGuard guard(rewriter); rewriter.setInsertionPoint(forOp); - const auto naddons = addons.size(); const auto res = forOp.replaceWithAdditionalIterOperands(rewriter, addons, true); assert(succeeded(res)); - auto newForOp = cast(*res); - for (const auto [before, after] : - llvm::zip_equal(addons, newForOp.getResults().take_back(naddons))) { + + for (const auto [before, after] : llvm::zip_equal( + addons, newForOp.getResults().take_back(addons.size()))) { rewriter.replaceAllUsesExcept(before, after, newForOp); } return newForOp; @@ -419,60 +418,13 @@ struct MappingPass : impl::MappingPassBase { OpBuilder::InsertionGuard guard(rewriter); rewriter.setInsertionPoint(ifOp); - const auto naddons = addons.size(); - - SmallVector inits; - llvm::append_range(inits, ifOp.getQubits()); - llvm::append_range(inits, addons); - - auto newIfOp = - rewriter.create(ifOp->getLoc(), ifOp.getCondition(), inits); - - // The qco::IfOp implements the SingleBlockImplicitTerminator trait. - assert(ifOp.getThenRegion().hasOneBlock()); - assert(ifOp.getElseRegion().hasOneBlock()); - - const std::array oldBlocks{ - &ifOp.getThenRegion().getBlocks().front(), - &ifOp.getElseRegion().getBlocks().front(), - }; - - SmallVector argTypes(inits.size(), - QubitType::get(rewriter.getContext())); - SmallVector locs(inits.size(), newIfOp->getLoc()); - - const std::array newBlocks{ - rewriter.createBlock(&newIfOp.getThenRegion(), {}, argTypes, locs), - rewriter.createBlock(&newIfOp.getElseRegion(), {}, argTypes, locs), - }; - - for (const auto [oldBlock, newBlock] : - llvm::zip_equal(oldBlocks, newBlocks)) { - rewriter.mergeBlocks( - oldBlock, newBlock, - newBlock->getArguments().take_front(oldBlock->getNumArguments())); - - auto yield = cast(newBlock->getTerminator()); - - SmallVector results; - llvm::append_range(results, yield.getTargets()); - llvm::append_range(results, newBlock->getArguments().take_back(naddons)); - rewriter.setInsertionPoint(yield); - rewriter.replaceOpWithNewOp(yield, results); - } + auto newIfOp = ifOp.replaceWithAdditionalQubits(rewriter, addons); - for (const auto [oldResult, newResult] : - llvm::zip_first(ifOp.getResults(), newIfOp.getResults())) { - rewriter.replaceAllUsesWith(oldResult, newResult); + for (const auto [before, after] : llvm::zip_equal( + addons, newIfOp->getResults().take_back(addons.size()))) { + rewriter.replaceAllUsesExcept(before, after, newIfOp); } - for (const auto [oldUse, newResult] : - llvm::zip_equal(addons, newIfOp.getResults().take_back(naddons))) { - rewriter.replaceAllUsesExcept(oldUse, newResult, newIfOp); - } - - rewriter.eraseOp(ifOp); - return newIfOp; } @@ -1239,7 +1191,7 @@ struct MappingPass : impl::MappingPassBase { LogicalResult route(RoutingBundle& bundle, Statistics& stats, IRRewriter* rewriter = nullptr) { auto& [wires, infos, layout] = bundle; - + while (true) { while (true) { From 1906f13517646d48b90a92b4c32dcda787867bd4 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 9 Jul 2026 12:55:22 +0200 Subject: [PATCH 19/23] Update mapping condition --- .../QCO/Transforms/Mapping/test_mapping.cpp | 47 ++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp index e8a9b56941..88f3e7897e 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -138,6 +138,8 @@ isExecutable(Region& body, DenseMap& m, m.try_emplace(succ, hw); } + std::array, 2> finalPermutation{}; + for (size_t i = 0; i < 2; ++i) { Region* body = regions[i]; if (!isExecutable(*body, mappings[i], couplingSet)) { @@ -147,15 +149,15 @@ isExecutable(Region& body, DenseMap& m, Block& block = body->getBlocks().front(); auto yield = cast(block.getTerminator()); - for (const auto& [arg, yielded] : - llvm::zip_equal(body->getArguments(), yield.getTargets())) { - if (mappings[i].at(arg) != mappings[i].at(yielded)) { - llvm::dbgs() << "qco::IfOp: layout not restored!\n"; - executable = false; - return; - } + for (const auto v : yield.getTargets()) { + finalPermutation[i].emplace_back(mappings[i].at(v)); } } + + if (finalPermutation[0] != finalPermutation[1]) { + executable = false; + return; + } }) .Case([&](auto op) { const auto pred = op.getQubitIn(); @@ -632,7 +634,7 @@ TEST_P(MappingPassTest, Sabre) { EXPECT_TRUE(isExecutable(entry, device.couplingSet)); } -TEST_P(MappingPassTest, RandomGHZ) { +TEST_P(MappingPassTest, RandomOrderGHZ) { const auto& device = GetParam(); QCOProgramBuilder builder(context.get()); @@ -649,14 +651,25 @@ TEST_P(MappingPassTest, RandomGHZ) { qubits[0] = builder.h(qubits[0]); std::tie(qubits[0], cregs[0]) = builder.measure(qubits[0]); - qubits = builder.qcoIf(cregs[0], qubits, [&](ValueRange args) { - SmallVector values(args); - values[0] = builder.h(values[0]); - for (size_t i = 1; i < 9; ++i) { - std::tie(values[0], values[i]) = builder.cx(values[0], values[i]); - } - return values; - }); + qubits = builder.qcoIf( + cregs[0], qubits, + [&](ValueRange args) { + SmallVector values(args); + values[0] = builder.h(values[0]); + for (size_t i = 1; i < 9; ++i) { + std::tie(values[0], values[i]) = builder.cx(values[0], values[i]); + } + return values; + }, + [&](ValueRange args) { + SmallVector values(args); + values[8] = builder.h(values[8]); + for (size_t i = 8; i > 0; --i) { + std::tie(values[8], values[i - 1]) = + builder.cx(values[8], values[i - 1]); + } + return values; + }); qubits = builder.barrier(qubits); @@ -675,6 +688,8 @@ TEST_P(MappingPassTest, RandomGHZ) { runPass(m.get(), device.couplingSet, MappingPassOptions{.ntrials = 1}); auto entry = getEntryPoint(m.get()); + // entry->dumpPretty(); + ASSERT_TRUE(res.succeeded()); EXPECT_TRUE(isExecutable(entry, device.couplingSet)); } From 29c7ba538c14eedc12474844ddf10b51092e992b Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 9 Jul 2026 13:04:54 +0200 Subject: [PATCH 20/23] Remove left-over func dump --- mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp | 2 -- 1 file changed, 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 88f3e7897e..7038ef1b0c 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -688,8 +688,6 @@ TEST_P(MappingPassTest, RandomOrderGHZ) { runPass(m.get(), device.couplingSet, MappingPassOptions{.ntrials = 1}); auto entry = getEntryPoint(m.get()); - // entry->dumpPretty(); - ASSERT_TRUE(res.succeeded()); EXPECT_TRUE(isExecutable(entry, device.couplingSet)); } From cc63e32c798b365e61487d2047ec46c72bd2cf2a Mon Sep 17 00:00:00 2001 From: burgholzer Date: Thu, 9 Jul 2026 22:40:00 +0200 Subject: [PATCH 21/23] :zap: Optimized performance and scalability of the quantum circuit mapping pass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • Addons Construction: Refined the filtering logic in scf::ForOp and IfOp to use SmallPtrSet and llvm::make_filter_range. This achieves $O(N+M)$ complexity, efficiently filtering qubit inputs and eliminating expensive intermediate DenseSet creation and repeated is_contained checks. • WireInfos Mapping: Replaced DenseMap with std::vector for wire-to-program index lookups. This provides $O(1)$ direct access and improves cache locality, which is critical for scalability in larger circuits. • A Search Optimization: Replaced DenseSet with SmallVector in the A search expansion set. This leverages the small, constant architecture connectivity to avoid unnecessary hashing and heap allocations in the hot search loop. --- mlir/include/mlir/Dialect/QCO/Utils/Graph.h | 4 +- .../QCO/Transforms/Mapping/Mapping.cpp | 242 +++++++++--------- 2 files changed, 126 insertions(+), 120 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Utils/Graph.h b/mlir/include/mlir/Dialect/QCO/Utils/Graph.h index f7e5096808..6cd25c8144 100644 --- a/mlir/include/mlir/Dialect/QCO/Utils/Graph.h +++ b/mlir/include/mlir/Dialect/QCO/Utils/Graph.h @@ -75,7 +75,9 @@ class Graph { [[nodiscard]] size_t getNumNodes() const { return adj_.size(); } /// Return the degree of a node. - [[nodiscard]] size_t getDegree(size_t id) { return adj_.at(id).size(); } + [[nodiscard]] size_t getDegree(const size_t id) const { + return adj_.at(id).size(); + } /// Return the max degree of the graph. [[nodiscard]] size_t getMaxDegree() const; diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 5fbc686714..f9ab91f18f 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -82,19 +82,19 @@ struct MappingPass : impl::MappingPassBase { class AugmentedDevice { public: explicit AugmentedDevice( - const llvm::DenseSet>& couplingSet) + const DenseSet>& couplingSet) : coupling_(couplingSet), dist_(coupling_.getDistMatrix()) {} /// Return the device's number of qubits. [[nodiscard]] size_t nqubits() const { return coupling_.getNumNodes(); } /// Return true if two qubits are adjacent. - [[nodiscard]] bool areAdjacent(size_t u, size_t v) const { + [[nodiscard]] bool areAdjacent(const size_t u, const size_t v) const { return dist_[u][v] == 1UL; } /// Return the length of the shortest path between two qubits. - [[nodiscard]] size_t distanceBetween(size_t u, size_t v) const { + [[nodiscard]] size_t distanceBetween(const size_t u, const size_t v) const { const auto dist = dist_[u][v]; if (dist == UINT64_MAX) { report_fatal_error("Failed to compute the distance between qubits " + @@ -109,7 +109,7 @@ struct MappingPass : impl::MappingPassBase { } /// Return all neighbours of a qubit. - [[nodiscard]] ArrayRef neighboursOf(size_t u) const { + [[nodiscard]] ArrayRef neighboursOf(const size_t u) const { return coupling_.getNeighbours(u); } @@ -123,24 +123,30 @@ struct MappingPass : impl::MappingPassBase { struct WireInfos { /// Return the mapped wire index of a program index. - [[nodiscard]] size_t lookupIndex(size_t prog) const { - return programToIndex_.at(prog); + [[nodiscard]] size_t lookupIndex(const size_t prog) const { + return programToIndex_[prog]; } /// Return the mapped program index of a wire index. - [[nodiscard]] size_t lookupProgram(size_t index) const { - return indexToProgram_.at(index); + [[nodiscard]] size_t lookupProgram(const size_t index) const { + return indexToProgram_[index]; } /// Bidirectionally map a wire index to a program index. /// Overwrites existing mappings. - void map(size_t index, size_t prog) { + void map(const size_t index, const size_t prog) { + if (index >= indexToProgram_.size()) { + indexToProgram_.resize(index + 1); + } + if (prog >= programToIndex_.size()) { + programToIndex_.resize(prog + 1); + } indexToProgram_[index] = prog; programToIndex_[prog] = index; } /// Swap two program indices. - void swap(size_t prog0, size_t prog1) { + void swap(const size_t prog0, const size_t prog1) { const auto i0 = lookupIndex(prog0); const auto i1 = lookupIndex(prog1); std::swap(programToIndex_[prog0], programToIndex_[prog1]); @@ -149,9 +155,9 @@ struct MappingPass : impl::MappingPassBase { private: /// Maps the i-th wire index to a program index. - DenseMap indexToProgram_; + SmallVector indexToProgram_; /// Maps a program index to the i-th wire index. - DenseMap programToIndex_; + SmallVector programToIndex_; }; /// Statistics collected while routing. @@ -263,15 +269,16 @@ struct MappingPass : impl::MappingPassBase { /// does not include the final back edge closing the cycle because the /// first SWAP changes the token (the qubit) on the target, invalidating /// the edge in F. - std::optional> findHappySWAPChain() { - const auto cycle = f_.findCycle(); - if (!cycle) { + std::optional> findHappySWAPChain() const { + const auto optCycle = f_.findCycle(); + if (!optCycle) { return std::nullopt; } + const auto& cycle = *optCycle; SmallVector swaps; - for (size_t i = cycle->size() - 1; i > 0; --i) { - swaps.emplace_back((*cycle)[i], (*cycle)[i - 1]); + for (size_t i = cycle.size() - 1; i > 0; --i) { + swaps.emplace_back(cycle[i], cycle[i - 1]); } return swaps; } @@ -279,11 +286,11 @@ struct MappingPass : impl::MappingPassBase { /// Find an unhappy SWAP. That is, find an edge (u, v), where exchanging u /// and v, reduces u's distance to its target location (by one) and /// increases v's distance from 0 (already at the correct location) to one. - std::optional findUnhappySWAP() { + std::optional findUnhappySWAP() const { for (const auto u : f_.getNodes()) { for (const auto v : f_.getNeighbours(u)) { if (f_.getDegree(v) == 0) { - return std::make_pair(u, v); + return {{u, v}}; } } } @@ -297,8 +304,8 @@ struct MappingPass : impl::MappingPassBase { private: /// Return true, if moving the program qubit on hardware qubit u to hardware /// qubit v brings it closer to its destination hardware qubit. - bool shouldAddEdge(size_t u, size_t v, const Layout& from, - const Layout& to) { + bool shouldAddEdge(const size_t u, const size_t v, const Layout& from, + const Layout& to) const { const auto dest = to.getHardwareIndex(from.getProgramIndex(u)); return device_->distanceBetween(v, dest) < device_->distanceBetween(u, dest); @@ -313,12 +320,12 @@ struct MappingPass : impl::MappingPassBase { MappingPass() = default; /// Construct default mapping pass with options. - explicit MappingPass(MappingPassOptions options) : MappingPassBase(options) {} + explicit MappingPass(const MappingPassOptions& options) + : MappingPassBase(options) {} /// Construct mapping from coupling set. - explicit MappingPass( - const llvm::DenseSet>& couplingSet, - MappingPassOptions options) + explicit MappingPass(const DenseSet>& couplingSet, + const MappingPassOptions& options) : MappingPassBase(options), device(std::make_shared(couplingSet)) {} @@ -386,7 +393,7 @@ struct MappingPass : impl::MappingPassBase { numSwaps += stats.nswaps; // Fix SSA Dominance issues. - llvm::for_each(body.getBlocks(), [](Block& b) { sortTopologically(&b); }); + for_each(body.getBlocks(), [](Block& b) { sortTopologically(&b); }); } private: @@ -410,11 +417,10 @@ struct MappingPass : impl::MappingPassBase { return newForOp; } - /// Extend the qubit arguments of an `qco::IfOp` by adding a given range of + /// Extend the qubit arguments of an `IfOp` by adding a given range of /// additional SSA values. Replaces the existing operation and returns the /// newly created one. - static qco::IfOp extend(qco::IfOp ifOp, ValueRange addons, - IRRewriter& rewriter) { + static IfOp extend(IfOp ifOp, ValueRange addons, IRRewriter& rewriter) { OpBuilder::InsertionGuard guard(rewriter); rewriter.setInsertionPoint(ifOp); @@ -506,8 +512,7 @@ struct MappingPass : impl::MappingPassBase { Wires wires; WireInfos infos; - for (auto alloc : - llvm::make_early_inc_range(body.getOps())) { + for (auto alloc : make_early_inc_range(body.getOps())) { TensorIterator it(alloc.getResult()); while (it != std::default_sentinel) { // Get the operation and early increment to avoid issues after erasure. @@ -560,14 +565,14 @@ struct MappingPass : impl::MappingPassBase { stack.emplace_back(body, DenseSet{}); while (!stack.empty()) { - auto [region, qubits] = stack.pop_back_val(); - - for (Operation& op : llvm::make_early_inc_range(region.getOps())) { + for (auto [region, qubits] = stack.pop_back_val(); + Operation& op : make_early_inc_range(region.getOps())) { TypeSwitch(&op) - .Case([&](StaticOp op) { qubits.insert(op.getQubit()); }) - .Case([&](UnitaryOpInterface& op) { - for (const auto [pred, succ] : - llvm::zip_equal(op.getInputQubits(), op.getOutputQubits())) { + .Case( + [&](StaticOp staticOp) { qubits.insert(staticOp.getQubit()); }) + .Case([&](UnitaryOpInterface& uOp) { + for (const auto [pred, succ] : llvm::zip_equal( + uOp.getInputQubits(), uOp.getOutputQubits())) { qubits.insert(succ); qubits.erase(pred); } @@ -575,14 +580,14 @@ struct MappingPass : impl::MappingPassBase { .Case([&](scf::ForOp forOp) { assert(qubits.size() == layout.nqubits()); - DenseSet addons(qubits); llvm::for_each(forOp.getInits(), - [&](auto v) { addons.erase(v); }); - auto newForOp = extend(forOp, to_vector(addons), rewriter); + [&](Value v) { qubits.erase(v); }); - for (OpOperand& operand : newForOp.getInitsMutable()) { - qubits.insert(newForOp.getTiedLoopResult(&operand)); - qubits.erase(operand.get()); + auto newForOp = extend(forOp, to_vector(qubits), rewriter); + for (const auto [init, result] : llvm::zip_equal( + newForOp.getInits(), *newForOp.getLoopResults())) { + qubits.insert(result); + qubits.erase(init); } stack.emplace_back( @@ -590,17 +595,18 @@ struct MappingPass : impl::MappingPassBase { DenseSet(newForOp.getRegionIterArgs().begin(), newForOp.getRegionIterArgs().end())); }) - .Case([&](IfOp ifOp) { + .Case([&](IfOp ifOp) { assert(qubits.size() == layout.nqubits()); - DenseSet addons(qubits); llvm::for_each(ifOp.getQubits(), - [&](auto v) { addons.erase(v); }); - auto newIfOp = extend(ifOp, to_vector(addons), rewriter); + [&](Value v) { qubits.erase(v); }); - for (OpOperand& operand : newIfOp.getQubitsMutable()) { - qubits.insert(newIfOp.getTiedResult(&operand)); - qubits.erase(operand.get()); + auto newIfOp = extend(ifOp, to_vector(qubits), rewriter); + + for (const auto [qubit, result] : + llvm::zip_equal(newIfOp.getQubits(), newIfOp.getResults())) { + qubits.insert(result); + qubits.erase(qubit); } const auto thenArgs = newIfOp.getThenRegion().getArguments(); @@ -612,9 +618,9 @@ struct MappingPass : impl::MappingPassBase { newIfOp.getElseRegion(), DenseSet(elseArgs.begin(), elseArgs.end())); }) - .Case([&](auto op) { - qubits.insert(op.getQubitOut()); - qubits.erase(op.getQubitIn()); + .Case([&](auto resetOp) { + qubits.insert(resetOp.getQubitOut()); + qubits.erase(resetOp.getQubitIn()); }) .Case([&](auto) { llvm::reportFatalInternalError("unexpected dynamic qubit alloc"); @@ -691,7 +697,7 @@ struct MappingPass : impl::MappingPassBase { /// /// Returns `failure`, if the A* search fails. FailureOr> search(const Window& window, - const Layout& layout) { + const Layout& layout) const { constexpr size_t cap = 25'000'000UL; const size_t b = device->maxDegree() * ((device->nqubits() + 1) / 2); @@ -712,7 +718,7 @@ struct MappingPass : impl::MappingPassBase { frontier.emplace(root); DenseMap, size_t> bestDepth; - DenseSet expansionSet; + SmallVector expansionSet; size_t i = 0; while (!frontier.empty() && i < budget) { @@ -727,8 +733,8 @@ struct MappingPass : impl::MappingPassBase { const auto [it, inserted] = bestDepth.try_emplace( curr->layout.getProgramToHardware(), curr->depth); if (!inserted) { - const auto otherDepth = it->getSecond(); - if (curr->depth >= otherDepth) { + if (const auto otherDepth = it->getSecond(); + curr->depth >= otherDepth) { ++i; continue; } @@ -742,7 +748,7 @@ struct MappingPass : impl::MappingPassBase { if (curr->isGoal(window.front(), *device)) { SmallVector seq(curr->depth); size_t j = seq.size() - 1; - for (Node* n = curr; n->parent != nullptr; n = n->parent) { + for (const Node* n = curr; n->parent != nullptr; n = n->parent) { seq[j] = n->swap; --j; } @@ -751,18 +757,18 @@ struct MappingPass : impl::MappingPassBase { } // Given a layout, create child-nodes for each possible SWAP - // between two neighbouring hardware qubits. + // between two neighboring hardware qubits. expansionSet.clear(); - const auto& [q0, q1] = window.front(); - for (const auto prog : {q0, q1}) { + for (const auto& [q0, q1] = window.front(); const auto prog : {q0, q1}) { for (const auto hw0 = curr->layout.getHardwareIndex(prog); const auto hw1 : device->neighboursOf(hw0)) { // Ensure consistent hashing/comparison. const IndexPairType swap = std::minmax(hw0, hw1); - if (!expansionSet.insert(swap).second) { + if (is_contained(expansionSet, swap)) { continue; } + expansionSet.push_back(swap); frontier.emplace(std::construct_at(arena.Allocate(), curr, swap, window, *device, params)); @@ -777,7 +783,8 @@ struct MappingPass : impl::MappingPassBase { /// Return the SWAP sequence to move from one layout to another. /// Implements the 4-Approximation algorithm described in arXiv:1602.05150v3. - SmallVector restore(const Layout& from, const Layout& to) { + SmallVector restore(const Layout& from, + const Layout& to) const { Layout curr(from); FGraph f(device); SmallVector swaps; @@ -786,8 +793,7 @@ struct MappingPass : impl::MappingPassBase { f.reset(); f.construct(curr, to); - const auto happy = f.findHappySWAPChain(); - if (happy) { + if (const auto happy = f.findHappySWAPChain()) { for (const auto& swap : *happy) { swaps.emplace_back(swap); curr.swap(swap.first, swap.second); @@ -814,13 +820,13 @@ struct MappingPass : impl::MappingPassBase { /// Inspired by the 4-Approximation algorithm described in arXiv:1602.05150v3, /// with the key difference that the goal permutation is not static. std::pair, SmallVector> - converge(const Layout& lhs, const Layout& rhs) { + converge(const Layout& lhs, const Layout& rhs) const { std::array layouts{Layout(lhs), Layout(rhs)}; std::array graphs{FGraph(device), FGraph(device)}; std::array, 2> swaps{}; std::mt19937 gen(seed); - std::uniform_int_distribution coin(0, 1); + std::uniform_int_distribution coin(0, 1); while (true) { size_t i = 0; @@ -830,8 +836,7 @@ struct MappingPass : impl::MappingPassBase { f.reset(); f.construct(layouts[i], layouts[(i + 1) % 2]); - const auto happy = f.findHappySWAPChain(); - if (happy) { + if (const auto happy = f.findHappySWAPChain()) { for (const auto& swap : *happy) { swaps[i].emplace_back(swap); layouts[i].swap(swap.first, swap.second); @@ -860,7 +865,7 @@ struct MappingPass : impl::MappingPassBase { layouts[i].swap(unhappy->first, unhappy->second); } - return std::make_pair(std::move(swaps[0]), std::move(swaps[1])); + return {std::move(swaps[0]), std::move(swaps[1])}; } /// Skip to the end of the two-qubit block for both wire iterators, where @@ -871,9 +876,9 @@ struct MappingPass : impl::MappingPassBase { // Traverses the pair of wire iterators in tandem until a two-qubit // operation is found. If the two-qubit operation is equivalent, continue. - // Otherwise stop. + // Otherwise, stop. - std::array block{it0, it1}; + std::array block{it0, it1}; while (true) { for (auto& it : block) { while (Traits::isActive(it)) { @@ -1002,34 +1007,35 @@ struct MappingPass : impl::MappingPassBase { // nested regions and the respective wire indices of their inputs onto the // result stack. - walkProgramGraph(wires, [&](const ReadyRange& ready, - ReleasedOps& released) { - if (ready.empty()) { - return WalkResult::advance(); - } + walkProgramGraph( + wires, [&](const ReadyRange& ready, ReleasedOps& released) { + if (ready.empty()) { + return WalkResult::advance(); + } - for (const auto& [readyOp, indices] : ready) { - TypeSwitch(readyOp) - .template Case( - [&](BarrierOp op) { released.emplace_back(op); }) - .template Case([&](UnitaryOpInterface op) { - const auto prog0 = infos.lookupProgram(indices[0]); - const auto prog1 = infos.lookupProgram(indices[1]); - const auto [hw0, hw1] = layout.getHardwareIndices(prog0, prog1); - if (device->areAdjacent(hw0, hw1)) { - released.emplace_back(op); - } - }) - .template Case( - [&](auto op) { stack.emplace_back(op, indices); }); - } + for (const auto& [readyOp, indices] : ready) { + TypeSwitch(readyOp) + .template Case( + [&](BarrierOp op) { released.emplace_back(op); }) + .template Case([&](UnitaryOpInterface op) { + const auto prog0 = infos.lookupProgram(indices[0]); + const auto prog1 = infos.lookupProgram(indices[1]); + if (const auto [hw0, hw1] = + layout.getHardwareIndices(prog0, prog1); + device->areAdjacent(hw0, hw1)) { + released.emplace_back(op); + } + }) + .template Case( + [&](auto op) { stack.emplace_back(op, indices); }); + } - if (released.empty()) { - return WalkResult::interrupt(); - } + if (released.empty()) { + return WalkResult::interrupt(); + } - return WalkResult::advance(); - }); + return WalkResult::advance(); + }); return stack; } @@ -1080,7 +1086,7 @@ struct MappingPass : impl::MappingPassBase { // decrement each twice: (sentinel → yield → // unitary/block arg). - llvm::for_each(child.wires, [](auto& it) { std::advance(it, -2); }); + for_each(child.wires, [](auto& it) { std::advance(it, -2); }); } insertSWAPs(swaps, child, stats, rewriter); @@ -1092,14 +1098,14 @@ struct MappingPass : impl::MappingPassBase { // Finally, move past the operation with nested regions by // incrementing the respective global wires. - llvm::for_each(indices, [&](size_t i) { + for_each(indices, [&](size_t i) { std::advance(parent.wires[i], WireTraversalTraits::stride()); }); return success(); }) - .template Case([&](qco::IfOp ifOp) { + .template Case([&](IfOp ifOp) { std::array children{RoutingBundle{.layout = parent.layout}, RoutingBundle{.layout = parent.layout}}; @@ -1118,17 +1124,17 @@ struct MappingPass : impl::MappingPassBase { ifOp.getTiedElseBlockArgument(qubit)}; if constexpr (Direction == WireDirection::Forward) { - for (size_t i = 0; i < children.size(); ++i) { - children[i].wires.emplace_back(args[i]); - children[i].infos.map(index, prog); + for (size_t j = 0; j < children.size(); ++j) { + children[j].wires.emplace_back(args[j]); + children[j].infos.map(index, prog); } } else { const std::array yields{ ifOp.getTiedThenYieldedValue(args[0])->get(), ifOp.getTiedElseYieldedValue(args[1])->get()}; - for (size_t i = 0; i < children.size(); ++i) { - children[i].wires.emplace_back(yields[i]); - children[i].infos.map(index, prog); + for (size_t j = 0; j < children.size(); ++j) { + children[j].wires.emplace_back(yields[j]); + children[j].infos.map(index, prog); } } } @@ -1147,20 +1153,19 @@ struct MappingPass : impl::MappingPassBase { // decrement each twice: (sentinel → yield → // unitary/block arg). - llvm::for_each(children[0].wires, - [](auto& it) { std::advance(it, -2); }); - llvm::for_each(children[1].wires, - [](auto& it) { std::advance(it, -2); }); + for_each(children[0].wires, [](auto& it) { std::advance(it, -2); }); + for_each(children[1].wires, [](auto& it) { std::advance(it, -2); }); } - const auto swaps = converge(children[0].layout, children[1].layout); + const auto [fst, snd] = + converge(children[0].layout, children[1].layout); - insertSWAPs(swaps.first, children[0], stats, rewriter); - insertSWAPs(swaps.second, children[1], stats, rewriter); + insertSWAPs(fst, children[0], stats, rewriter); + insertSWAPs(snd, children[1], stats, rewriter); if constexpr (Mode == RoutingMode::Hot) { - // The qco::IfOp implements the SingleBlockImplicitTerminator trait. + // The IfOp implements the SingleBlockImplicitTerminator trait. assert(ifOp.getThenRegion().hasOneBlock()); assert(ifOp.getElseRegion().hasOneBlock()); @@ -1171,7 +1176,7 @@ struct MappingPass : impl::MappingPassBase { // Finally, move past the operation with nested regions by // incrementing the respective global wires. - llvm::for_each(indices, [&](size_t i) { + for_each(indices, [&](size_t i) { std::advance(parent.wires[i], WireTraversalTraits::stride()); }); @@ -1242,7 +1247,7 @@ struct MappingPass : impl::MappingPassBase { // multi-qubit op of the current or subsequent layer or to a sink (and // thus std::default_sentinel). - llvm::for_each(wires, [](auto& it) { std::advance(it, 1); }); + for_each(wires, [](auto& it) { std::advance(it, 1); }); } } @@ -1255,7 +1260,7 @@ struct MappingPass : impl::MappingPassBase { } // namespace std::unique_ptr -createMappingPass(const llvm::DenseSet>& couplingSet, +createMappingPass(const DenseSet>& couplingSet, MappingPassOptions options) { // Verify the assumption that the coupling set is symmetric: @@ -1264,7 +1269,6 @@ createMappingPass(const llvm::DenseSet>& couplingSet, for (const auto& [u, v] : couplingSet) { if (u == v) { llvm::reportFatalUsageError("Found an invalid (u, u) edge."); - return nullptr; } if (!couplingSet.contains({v, u})) { From 9457ca52a7f4d35939c1ed7f72a33fa32e03c996 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 10 Jul 2026 07:46:45 +0200 Subject: [PATCH 22/23] Add [[nodiscard]] attributes --- mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index f9ab91f18f..9e3db04893 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -269,7 +269,7 @@ struct MappingPass : impl::MappingPassBase { /// does not include the final back edge closing the cycle because the /// first SWAP changes the token (the qubit) on the target, invalidating /// the edge in F. - std::optional> findHappySWAPChain() const { + [[nodiscard]] std::optional> findHappySWAPChain() const { const auto optCycle = f_.findCycle(); if (!optCycle) { return std::nullopt; @@ -286,7 +286,7 @@ struct MappingPass : impl::MappingPassBase { /// Find an unhappy SWAP. That is, find an edge (u, v), where exchanging u /// and v, reduces u's distance to its target location (by one) and /// increases v's distance from 0 (already at the correct location) to one. - std::optional findUnhappySWAP() const { + [[nodiscard]] std::optional findUnhappySWAP() const { for (const auto u : f_.getNodes()) { for (const auto v : f_.getNeighbours(u)) { if (f_.getDegree(v) == 0) { @@ -304,7 +304,7 @@ struct MappingPass : impl::MappingPassBase { private: /// Return true, if moving the program qubit on hardware qubit u to hardware /// qubit v brings it closer to its destination hardware qubit. - bool shouldAddEdge(const size_t u, const size_t v, const Layout& from, + [[nodiscard]] bool shouldAddEdge(const size_t u, const size_t v, const Layout& from, const Layout& to) const { const auto dest = to.getHardwareIndex(from.getProgramIndex(u)); return device_->distanceBetween(v, dest) < @@ -783,7 +783,7 @@ struct MappingPass : impl::MappingPassBase { /// Return the SWAP sequence to move from one layout to another. /// Implements the 4-Approximation algorithm described in arXiv:1602.05150v3. - SmallVector restore(const Layout& from, + [[nodiscard]] SmallVector restore(const Layout& from, const Layout& to) const { Layout curr(from); FGraph f(device); @@ -819,7 +819,7 @@ struct MappingPass : impl::MappingPassBase { /// Return a pair of SWAP sequences to transform two layouts into each other. /// Inspired by the 4-Approximation algorithm described in arXiv:1602.05150v3, /// with the key difference that the goal permutation is not static. - std::pair, SmallVector> + [[nodiscard]] std::pair, SmallVector> converge(const Layout& lhs, const Layout& rhs) const { std::array layouts{Layout(lhs), Layout(rhs)}; std::array graphs{FGraph(device), FGraph(device)}; From f260e6c2afc83532d6b156ab024ae0c297f8f500 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 10 Jul 2026 05:57:28 +0000 Subject: [PATCH 23/23] =?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 | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 9e3db04893..e93c14b180 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -269,7 +269,8 @@ struct MappingPass : impl::MappingPassBase { /// does not include the final back edge closing the cycle because the /// first SWAP changes the token (the qubit) on the target, invalidating /// the edge in F. - [[nodiscard]] std::optional> findHappySWAPChain() const { + [[nodiscard]] std::optional> + findHappySWAPChain() const { const auto optCycle = f_.findCycle(); if (!optCycle) { return std::nullopt; @@ -304,8 +305,9 @@ struct MappingPass : impl::MappingPassBase { private: /// Return true, if moving the program qubit on hardware qubit u to hardware /// qubit v brings it closer to its destination hardware qubit. - [[nodiscard]] bool shouldAddEdge(const size_t u, const size_t v, const Layout& from, - const Layout& to) const { + [[nodiscard]] bool shouldAddEdge(const size_t u, const size_t v, + const Layout& from, + const Layout& to) const { const auto dest = to.getHardwareIndex(from.getProgramIndex(u)); return device_->distanceBetween(v, dest) < device_->distanceBetween(u, dest); @@ -784,7 +786,7 @@ struct MappingPass : impl::MappingPassBase { /// Return the SWAP sequence to move from one layout to another. /// Implements the 4-Approximation algorithm described in arXiv:1602.05150v3. [[nodiscard]] SmallVector restore(const Layout& from, - const Layout& to) const { + const Layout& to) const { Layout curr(from); FGraph f(device); SmallVector swaps; @@ -819,7 +821,8 @@ struct MappingPass : impl::MappingPassBase { /// Return a pair of SWAP sequences to transform two layouts into each other. /// Inspired by the 4-Approximation algorithm described in arXiv:1602.05150v3, /// with the key difference that the goal permutation is not static. - [[nodiscard]] std::pair, SmallVector> + [[nodiscard]] std::pair, + SmallVector> converge(const Layout& lhs, const Layout& rhs) const { std::array layouts{Layout(lhs), Layout(rhs)}; std::array graphs{FGraph(device), FGraph(device)};