From da037a186e12fd62aebf20f96139748bcc195bf9 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 6 Aug 2026 08:22:52 +0200 Subject: [PATCH 01/10] Improve constness --- .../QCO/Transforms/Mapping/Mapping.cpp | 148 +++++++++++++----- 1 file changed, 109 insertions(+), 39 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 33dfb4c517..0b219095b9 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -78,11 +79,19 @@ 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 }; + struct RecursiveRoutingStackItem { + /// The SCF op. + Operation* op; + /// Indices into a vector of wires, where the order of indices has no + /// meaning. + SmallVector indices; + }; + + using RecursiveRoutingStack = SmallVector; + struct WireInfos { /// Return the mapped wire index of a program index. [[nodiscard]] size_t lookupIndex(const size_t prog) const { @@ -149,12 +158,15 @@ struct MappingPass : impl::MappingPassBase { /// Statistics collected while routing. struct Statistics { + /// The number of inserted swaps. size_t nswaps{0}; }; /// Parameters influencing the behavior of the A* search algorithm. struct Parameters { + /// The path weight. float alpha; + /// The lookahead decay factor. float lambda; }; @@ -163,6 +175,20 @@ struct MappingPass : impl::MappingPassBase { Wires wires; WireInfos infos; Layout layout; + + struct Patch { + std::optional layout; + std::optional infos; + }; + + void applyPatch(const Patch& patch) { + if (patch.layout) { + layout = *patch.layout; + } + if (patch.infos) { + infos = *patch.infos; + } + } }; /// Describes a node in the A* search graph. @@ -525,6 +551,56 @@ struct MappingPass : impl::MappingPassBase { return newWhileOp; } + void place(RecursiveRoutingStackItem& item, Wires& wires, + IRRewriter& rewriter) { + assert(wires.size() == target->numQubits()); + + const auto nmissing = target->numQubits() - item.indices.size(); + + SmallVector missingQubits; + missingQubits.reserve(nmissing); + + for (size_t i = 0; i < wires.size(); ++i) { + + /// TODO: Use SetVector for indices? + bool found = false; + for (const size_t j : item.indices) { + if (i == j) { + found = true; + break; + } + } + + if (!found) { + item.indices.emplace_back(i); + if (wires[i] == std::default_sentinel) { + missingQubits.emplace_back(std::prev(wires[i], 2).qubit()); + } else { + missingQubits.emplace_back(wires[i].qubit()); + } + } + } + + TypeSwitch(item.op) + .Case([&](scf::ForOp forOp) { + item.op = extend(forOp, missingQubits, rewriter); + }) + .Case([&](scf::WhileOp whileOp) { + item.op = extend(whileOp, missingQubits, rewriter); + }) + .Case( + [&](IfOp ifOp) { item.op = extend(ifOp, missingQubits, rewriter); }) + .Case([&](IndexSwitchOp switchOp) { + item.op = extend(switchOp, missingQubits, rewriter); + }) + .Default([](Operation* op) -> SmallVector { + report_fatal_error("unhandled region op in dispatch: " + + op->getName().getStringRef()); + }); + + item.op->getParentOp()->dumpPretty(); + } + /// Return the wires of a dynamic computation. /// Scalar `qco.alloc` operations define program qubits directly. For /// `qtensor` allocations, the mapping pass assumes an extraction and @@ -698,10 +774,6 @@ struct MappingPass : impl::MappingPassBase { SinkOp::create(rewriter, body.getLoc(), qubit); } - // Finally, update the SCF operations such that they take all static qubits - // as input. To handle recursively nested SCF operations, use a stack of - // (region, mapping) pairs. - SmallVector>> stack; stack.emplace_back(body, DenseSet{}); @@ -1300,10 +1372,10 @@ struct MappingPass : impl::MappingPassBase { /// inserting epilogue SWAPs. template requires(Mode != RoutingMode::Hot || Direction == WireDirection::Forward) - LogicalResult dispatch(const RecursiveRoutingStackItem& item, - RoutingBundle& parent, Statistics& stats, - IRRewriter* rewriter = nullptr) { - const auto& [op, indices] = item; + FailureOr + dispatch(const RecursiveRoutingStackItem& item, const RoutingBundle& parent, + Statistics& stats, IRRewriter* rewriter = nullptr) { + const auto [op, indices] = item; SmallVector permutation(indices.size()); SmallVector children = @@ -1320,10 +1392,6 @@ struct MappingPass : impl::MappingPassBase { return SmallVector( switchOp.getNumRegions(), RoutingBundle{.layout = parent.layout}); - }) - .Default([](Operation* op) -> SmallVector { - report_fatal_error("unhandled region op in dispatch: " + - op->getName().getStringRef()); }); SmallVector> resultToQubitIndex(op->getNumResults()); @@ -1524,31 +1592,22 @@ struct MappingPass : impl::MappingPassBase { } } - // If the operation is a scf::ForOp, where the parent.layout = - // child.layout, we are done. Otherwise, propagate the final layout and - // index-to-program mapping to the parent. - - if (!isa(op)) { - WireInfos realigendInfos; - for (size_t i = 0; i < parent.wires.size(); ++i) { - const auto oldProg = parent.infos.lookupProgram(i); - const auto oldHw = parent.layout.getHardwareIndex(oldProg); - const auto newProg = exit.getProgramIndex(oldHw); - realigendInfos.insertOrUpdate(i, newProg); - } + // If the operation is a scf::ForOp, where the parent.layout = child.layout, + // we are done. Otherwise, propagate a patch with the final layout and + // index-to-program mapping. - parent.layout = exit; - parent.infos = std::move(realigendInfos); + if (isa(op)) { + return RoutingBundle::Patch{}; } - // Finally, move past the operation with nested regions by - // incrementing the respective global wires. - - for_each(indices, [&](size_t i) { - std::advance(parent.wires[i], WireTraversalTraits::stride()); - }); - - return success(); + RoutingBundle::Patch patch{.layout = exit, .infos = WireInfos{}}; + for (size_t i = 0; i < parent.wires.size(); ++i) { + const auto oldProg = parent.infos.lookupProgram(i); + const auto oldHw = parent.layout.getHardwareIndex(oldProg); + const auto newProg = exit.getProgramIndex(oldHw); + patch.infos->insertOrUpdate(i, newProg); + } + return patch; } /// Iterates over a dynamically computed window of layers and uses A* search @@ -1569,11 +1628,22 @@ struct MappingPass : impl::MappingPassBase { if (stack.empty()) { break; } - for (const auto& item : stack) { - if (dispatch(item, bundle, stats, rewriter) - .failed()) { + + for (auto& item : stack) { + const auto patch = + dispatch(item, bundle, stats, rewriter); + if (failed(patch)) { return failure(); } + + bundle.applyPatch(*patch); + + // Once the SCF op is mapped, move past this op by incrementing the + // respective global wires. + + for_each(item.indices, [&](size_t i) { + std::advance(wires[i], WireTraversalTraits::stride()); + }); } } From 9479b25a1d3b9d2cb029599fc2870ece9c63384c Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 6 Aug 2026 09:16:05 +0200 Subject: [PATCH 02/10] Remove mutable statistics parameter --- .../QCO/Transforms/Mapping/Mapping.cpp | 81 +++++++++++-------- 1 file changed, 49 insertions(+), 32 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 0b219095b9..c9520ff1d2 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -28,7 +28,6 @@ #include #include #include -#include #include #include #include @@ -85,8 +84,7 @@ struct MappingPass : impl::MappingPassBase { struct RecursiveRoutingStackItem { /// The SCF op. Operation* op; - /// Indices into a vector of wires, where the order of indices has no - /// meaning. + /// Indices into a wire vector, where the order of indices has no meaning. SmallVector indices; }; @@ -160,6 +158,9 @@ struct MappingPass : impl::MappingPassBase { struct Statistics { /// The number of inserted swaps. size_t nswaps{0}; + + /// Merge another statistics object into this one. + void merge(const Statistics& other) { nswaps += other.nswaps; } }; /// Parameters influencing the behavior of the A* search algorithm. @@ -392,20 +393,20 @@ struct MappingPass : impl::MappingPassBase { std::tie(wires, infos) = std::move(place(body, *layout, *comp, rewriter)); - Statistics stats; RoutingBundle bundle{.wires = std::move(wires), .infos = std::move(infos), .layout = std::move(*layout)}; - const auto res = route( - bundle, stats, &rewriter); - if (res.failed()) { + const auto routeRes = + route(bundle, &rewriter); + if (failed(routeRes)) { func.emitError() << "failed to map the function"; signalPassFailure(); return; } // Collect statistics. + const auto stats = *routeRes; numSwaps += stats.nswaps; // Fix SSA Dominance issues. @@ -926,13 +927,17 @@ struct MappingPass : impl::MappingPassBase { parallelForEach(&getContext(), trials, [&, this](Trial& t) { for (size_t i = 0; i < niterations; ++i) { - if (route(t.bundle, t.stats).failed()) { + const auto fwRouteRes = route(t.bundle); + if (failed(fwRouteRes)) { return; } - t.stats.nswaps = 0; - if (route(t.bundle, t.stats).failed()) { + + const auto bwRouteRes = route(t.bundle); + if (failed(bwRouteRes)) { return; } + + t.stats = *bwRouteRes; } t.success = true; @@ -1369,12 +1374,14 @@ struct MappingPass : impl::MappingPassBase { } /// Processes the recursive stack item by routing the nested operation and - /// inserting epilogue SWAPs. + /// inserting a SWAP appendix. Returns a pair of the patch to apply to the + /// parent bundle and the accumulated statistics, or `failure` if routing + /// fails. template requires(Mode != RoutingMode::Hot || Direction == WireDirection::Forward) - FailureOr + FailureOr> dispatch(const RecursiveRoutingStackItem& item, const RoutingBundle& parent, - Statistics& stats, IRRewriter* rewriter = nullptr) { + IRRewriter* rewriter = nullptr) { const auto [op, indices] = item; SmallVector permutation(indices.size()); @@ -1476,11 +1483,16 @@ struct MappingPass : impl::MappingPassBase { // qubit op (note: might be a measurement) before the yield. // TODO: Parallelize multiple children, if possible. + Statistics totalStats; + for (auto& child : children) { - if (failed(route(child, stats, rewriter))) { + const auto stats = route(child, rewriter); + if (failed(stats)) { return failure(); } + totalStats.merge(*stats); + if constexpr (Mode == RoutingMode::Hot) { for_each(child.wires, [](auto& it) { std::advance(it, -2); }); } @@ -1509,10 +1521,13 @@ struct MappingPass : impl::MappingPassBase { children[1].infos.insertOrUpdate(i, prog); } - if (failed(route(children[1], stats, rewriter))) { + const auto stats = route(children[1], rewriter); + if (failed(stats)) { return failure(); } + totalStats.merge(*stats); + if constexpr (Mode == RoutingMode::Hot) { for_each(children[1].wires, [](auto& it) { std::advance(it, -2); }); } @@ -1526,12 +1541,12 @@ struct MappingPass : impl::MappingPassBase { TypeSwitch(op) .Case([&](scf::ForOp) { const auto swaps = restore(children[0].layout, parent.layout); - insertSWAPs(swaps, children[0], stats, rewriter); + insertSWAPs(swaps, children[0], totalStats, rewriter); return parent.layout; }) .template Case([&](scf::WhileOp) { const auto swaps = restore(children[1].layout, parent.layout); - insertSWAPs(swaps, children[1], stats, rewriter); + insertSWAPs(swaps, children[1], totalStats, rewriter); // The scf::YieldOp is the terminator in the before region and // thus determines the final output layout. return children[0].layout; @@ -1539,8 +1554,8 @@ struct MappingPass : impl::MappingPassBase { .template Case([&](IfOp) { const auto [convergedLayout, fst, snd] = converge(children[0].layout, children[1].layout); - insertSWAPs(fst, children[0], stats, rewriter); - insertSWAPs(snd, children[1], stats, rewriter); + insertSWAPs(fst, children[0], totalStats, rewriter); + insertSWAPs(snd, children[1], totalStats, rewriter); return convergedLayout; }) .template Case([&](IndexSwitchOp) { @@ -1550,7 +1565,7 @@ struct MappingPass : impl::MappingPassBase { })); for (RoutingBundle& child : children) { const auto swaps = restore(child.layout, winner); - insertSWAPs(swaps, child, stats, rewriter); + insertSWAPs(swaps, child, totalStats, rewriter); } return winner; }); @@ -1597,7 +1612,7 @@ struct MappingPass : impl::MappingPassBase { // index-to-program mapping. if (isa(op)) { - return RoutingBundle::Patch{}; + return std::make_pair(RoutingBundle::Patch{}, totalStats); } RoutingBundle::Patch patch{.layout = exit, .infos = WireInfos{}}; @@ -1607,22 +1622,24 @@ struct MappingPass : impl::MappingPassBase { const auto newProg = exit.getProgramIndex(oldHw); patch.infos->insertOrUpdate(i, newProg); } - return patch; + + return std::make_pair(patch, totalStats); } /// 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. + /// inserts the SWAPs into the IR. Returns `FailureOr` containing + /// the accumulated statistics on success, or `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) { + FailureOr route(RoutingBundle& bundle, + IRRewriter* rewriter = nullptr) { auto& [wires, infos, layout] = bundle; + Statistics stats; while (true) { - while (true) { const auto stack = advance(wires, infos, layout); if (stack.empty()) { @@ -1630,13 +1647,13 @@ struct MappingPass : impl::MappingPassBase { } for (auto& item : stack) { - const auto patch = - dispatch(item, bundle, stats, rewriter); - if (failed(patch)) { + const auto res = dispatch(item, bundle, rewriter); + if (failed(res)) { return failure(); } - bundle.applyPatch(*patch); + bundle.applyPatch(res->first); + stats.merge(res->second); // Once the SCF op is mapped, move past this op by incrementing the // respective global wires. @@ -1686,7 +1703,7 @@ struct MappingPass : impl::MappingPassBase { } } - return success(); + return stats; } std::optional target; From 3bc49d2a838a1044e540339c398a573e75911e15 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 7 Aug 2026 08:37:33 +0200 Subject: [PATCH 03/10] Implement inline-place --- .../QCO/Transforms/Mapping/Mapping.cpp | 291 ++++++------------ 1 file changed, 101 insertions(+), 190 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index c9520ff1d2..b731f68f20 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -81,15 +81,13 @@ struct MappingPass : impl::MappingPassBase { enum class RoutingMode : bool { Cold, Hot }; - struct RecursiveRoutingStackItem { - /// The SCF op. - Operation* op; + struct CompositeUnitary { + /// The composite op (e.g. SCF). + Operation* op = nullptr; /// Indices into a wire vector, where the order of indices has no meaning. SmallVector indices; }; - using RecursiveRoutingStack = SmallVector; - struct WireInfos { /// Return the mapped wire index of a program index. [[nodiscard]] size_t lookupIndex(const size_t prog) const { @@ -158,7 +156,6 @@ struct MappingPass : impl::MappingPassBase { struct Statistics { /// The number of inserted swaps. size_t nswaps{0}; - /// Merge another statistics object into this one. void merge(const Statistics& other) { nswaps += other.nswaps; } }; @@ -180,6 +177,7 @@ struct MappingPass : impl::MappingPassBase { struct Patch { std::optional layout; std::optional infos; + std::optional wires; }; void applyPatch(const Patch& patch) { @@ -189,6 +187,9 @@ struct MappingPass : impl::MappingPassBase { if (patch.infos) { infos = *patch.infos; } + if (patch.wires) { + wires = *patch.wires; + } } }; @@ -420,6 +421,13 @@ struct MappingPass : impl::MappingPassBase { values, [](Value value) { return isa(value.getType()); })); } + /// Return the qubit values in `values`, preserving their relative order. + static SmallVector getQubitValues(ResultRange values) { + return to_vector(llvm::make_filter_range(values, [](OpResult value) { + return isa(value.getType()); + })); + } + /// 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. @@ -552,56 +560,6 @@ struct MappingPass : impl::MappingPassBase { return newWhileOp; } - void place(RecursiveRoutingStackItem& item, Wires& wires, - IRRewriter& rewriter) { - assert(wires.size() == target->numQubits()); - - const auto nmissing = target->numQubits() - item.indices.size(); - - SmallVector missingQubits; - missingQubits.reserve(nmissing); - - for (size_t i = 0; i < wires.size(); ++i) { - - /// TODO: Use SetVector for indices? - bool found = false; - for (const size_t j : item.indices) { - if (i == j) { - found = true; - break; - } - } - - if (!found) { - item.indices.emplace_back(i); - if (wires[i] == std::default_sentinel) { - missingQubits.emplace_back(std::prev(wires[i], 2).qubit()); - } else { - missingQubits.emplace_back(wires[i].qubit()); - } - } - } - - TypeSwitch(item.op) - .Case([&](scf::ForOp forOp) { - item.op = extend(forOp, missingQubits, rewriter); - }) - .Case([&](scf::WhileOp whileOp) { - item.op = extend(whileOp, missingQubits, rewriter); - }) - .Case( - [&](IfOp ifOp) { item.op = extend(ifOp, missingQubits, rewriter); }) - .Case([&](IndexSwitchOp switchOp) { - item.op = extend(switchOp, missingQubits, rewriter); - }) - .Default([](Operation* op) -> SmallVector { - report_fatal_error("unhandled region op in dispatch: " + - op->getName().getStringRef()); - }); - - item.op->getParentOp()->dumpPretty(); - } - /// Return the wires of a dynamic computation. /// Scalar `qco.alloc` operations define program qubits directly. For /// `qtensor` allocations, the mapping pass assumes an extraction and @@ -775,122 +733,6 @@ struct MappingPass : impl::MappingPassBase { SinkOp::create(rewriter, body.getLoc(), qubit); } - SmallVector>> stack; - stack.emplace_back(body, DenseSet{}); - - while (!stack.empty()) { - for (auto [region, qubits] = stack.pop_back_val(); - Operation& op : make_early_inc_range(region.getOps())) { - TypeSwitch(&op) - .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); - } - }) - .Case([&](scf::ForOp forOp) { - assert(qubits.size() == layout.nqubits()); - - llvm::for_each(getQubitValues(forOp.getInits()), - [&](Value v) { qubits.erase(v); }); - - auto newForOp = extend(forOp, to_vector(qubits), rewriter); - for (const auto [init, result] : llvm::zip_equal( - newForOp.getInits(), *newForOp.getLoopResults())) { - if (isa(init.getType())) { - qubits.insert(result); - qubits.erase(init); - } - } - - const auto regionQubits = - getQubitValues(newForOp.getRegionIterArgs()); - stack.emplace_back( - newForOp.getRegion(), - DenseSet(regionQubits.begin(), regionQubits.end())); - }) - .Case([&](scf::WhileOp whileOp) { - assert(qubits.size() == layout.nqubits()); - - llvm::for_each(getQubitValues(whileOp.getInits()), - [&](Value v) { qubits.erase(v); }); - - auto newWhileOp = extend(whileOp, to_vector(qubits), rewriter); - for (const auto [init, result] : llvm::zip_equal( - newWhileOp.getInits(), newWhileOp.getResults())) { - if (isa(init.getType())) { - qubits.insert(result); - qubits.erase(init); - } - } - - const auto beforeArgs = - getQubitValues(newWhileOp.getBeforeArguments()); - const auto afterArgs = - getQubitValues(newWhileOp.getAfterArguments()); - stack.emplace_back( - newWhileOp.getBefore(), - DenseSet(beforeArgs.begin(), beforeArgs.end())); - stack.emplace_back( - newWhileOp.getAfter(), - DenseSet(afterArgs.begin(), afterArgs.end())); - }) - .Case([&](IfOp ifOp) { - assert(qubits.size() == layout.nqubits()); - - llvm::for_each(ifOp.getQubits(), - [&](Value v) { qubits.erase(v); }); - - auto newIfOp = extend(ifOp, to_vector(qubits), rewriter); - - for (const auto [qubit, result] : llvm::zip_equal( - newIfOp.getQubits(), newIfOp.getLinearResults())) { - qubits.insert(result); - qubits.erase(qubit); - } - - 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( - newIfOp.getElseRegion(), - DenseSet(elseArgs.begin(), elseArgs.end())); - }) - .Case([&](IndexSwitchOp switchOp) { - assert(qubits.size() == layout.nqubits()); - - llvm::for_each(switchOp.getTargets(), - [&](Value value) { qubits.erase(value); }); - - auto newSwitchOp = extend(switchOp, to_vector(qubits), rewriter); - for (const auto [target, result] : - llvm::zip_equal(newSwitchOp.getTargets(), - newSwitchOp.getLinearResults())) { - qubits.insert(result); - qubits.erase(target); - } - - for (Region* region : newSwitchOp.getRegions()) { - const auto args = region->getArguments(); - stack.emplace_back(*region, - DenseSet(args.begin(), args.end())); - } - }) - .Case([&](auto resetOp) { - qubits.insert(resetOp.getQubitOut()); - qubits.erase(resetOp.getQubitIn()); - }) - .Case([&](auto) { - llvm::reportFatalInternalError("unexpected dynamic qubit alloc"); - }); - } - } - return {wires, infos}; } @@ -1301,14 +1143,13 @@ 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 - RecursiveRoutingStack advance(Wires& wires, const WireInfos& infos, - const Layout& layout) { + SmallVector advance(Wires& wires, const WireInfos& infos, + const Layout& layout) { DenseSet visited; - RecursiveRoutingStack stack; + SmallVector composites; - // Advance wires past all executable gates and push operations with - // nested regions and the respective wire indices of their inputs onto the - // result stack. + // Advance wires past all executable gates and push composite unitaries and + // the respective wire indices of their inputs onto the vector. walkProgramGraph(wires, [&](const ReadyMap& ready, ReleasedOps& released) { @@ -1334,7 +1175,7 @@ struct MappingPass : impl::MappingPassBase { if (op->getNumRegions() > 0 && visited.insert(op).second) { assert((isa(op))); - stack.emplace_back(op, indices); + composites.emplace_back(op, indices); continue; } } @@ -1346,7 +1187,70 @@ struct MappingPass : impl::MappingPassBase { return WalkResult::advance(); }); - return stack; + return composites; + } + + /// Extends the composite unitary's operation to cover all target qubits by + /// adding operands for indices not in the composite's index set. Returns a + /// patch with the updated wire mapping which preserves the parent's wire + /// infos and layout. + RoutingBundle::Patch place(CompositeUnitary& composite, + const RoutingBundle& parent, + IRRewriter& rewriter) { + DenseSet included; // Already included indices. + included.reserve(composite.indices.size()); + + // Maps the i-th included index to its result number. + DenseMap indexToResultNum; + indexToResultNum.reserve(composite.indices.size()); + + for (const auto index : composite.indices) { + const WireIterator& it = parent.wires[index]; + indexToResultNum.try_emplace( + index, cast(it.qubit()).getResultNumber()); + included.insert(index); + } + + const auto allIndices = to_vector(llvm::seq(target->numQubits())); + + const SmallVector excluded(llvm::make_filter_range( + allIndices, [&](const size_t i) { return !included.contains(i); })); + + const SmallVector addons(map_range(excluded, [&](const size_t i) { + // Make sure the qubits point to an already processed operation. + const auto& it = std::prev( + parent.wires[i], parent.wires[i] == std::default_sentinel ? 2 : 1); + return it.qubit(); + })); + + composite = CompositeUnitary{ + .op = TypeSwitch(composite.op) + .Case( + [&](auto cfOp) { return extend(cfOp, addons, rewriter); }) + .Default([](Operation* op) { + report_fatal_error("place: unhandled op: " + + op->getName().getStringRef()); + return nullptr; + }), + .indices = allIndices}; + + const auto results = composite.op->getResults(); + + Wires wires(allIndices.size()); + for (size_t index : included) { + wires[index] = WireIterator(results[indexToResultNum.at(index)]); + } + for (const auto [index, res] : + llvm::zip_equal(excluded, results.take_back(excluded.size()))) { + wires[index] = WireIterator(res); + } + + assert(llvm::all_of(wires, [&](WireIterator& it) { + return it.operation() == composite.op; + })); + + return RoutingBundle::Patch{ + .layout = std::nullopt, .infos = std::nullopt, .wires = wires}; } /// Return `values` with only the qubit entries realigned according to the @@ -1373,16 +1277,16 @@ struct MappingPass : impl::MappingPassBase { return realigned; } - /// Processes the recursive stack item by routing the nested operation and + /// Processes the composite unitary by routing the nested operation and /// inserting a SWAP appendix. Returns a pair of the patch to apply to the /// parent bundle and the accumulated statistics, or `failure` if routing /// fails. template requires(Mode != RoutingMode::Hot || Direction == WireDirection::Forward) FailureOr> - dispatch(const RecursiveRoutingStackItem& item, const RoutingBundle& parent, + dispatch(const CompositeUnitary& composite, const RoutingBundle& parent, IRRewriter* rewriter = nullptr) { - const auto [op, indices] = item; + const auto [op, indices] = composite; SmallVector permutation(indices.size()); SmallVector children = @@ -1639,15 +1543,22 @@ struct MappingPass : impl::MappingPassBase { auto& [wires, infos, layout] = bundle; Statistics stats; + while (true) { while (true) { - const auto stack = advance(wires, infos, layout); - if (stack.empty()) { + auto composites = advance(wires, infos, layout); + if (composites.empty()) { break; } - for (auto& item : stack) { - const auto res = dispatch(item, bundle, rewriter); + for (auto& composite : composites) { + if constexpr (Mode == RoutingMode::Hot) { + const auto patch = place(composite, bundle, *rewriter); + bundle.applyPatch(patch); + } + + const auto res = + dispatch(composite, bundle, rewriter); if (failed(res)) { return failure(); } @@ -1655,10 +1566,10 @@ struct MappingPass : impl::MappingPassBase { bundle.applyPatch(res->first); stats.merge(res->second); - // Once the SCF op is mapped, move past this op by incrementing the - // respective global wires. + // Once the composite is mapped, move past this op by incrementing the + // respective wires. - for_each(item.indices, [&](size_t i) { + for_each(composite.indices, [&](size_t i) { std::advance(wires[i], WireTraversalTraits::stride()); }); } From bb255f73f0176f05cb73ae42b5a1f1659294cdcb Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 7 Aug 2026 08:47:07 +0200 Subject: [PATCH 04/10] Fix lint --- mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index b731f68f20..418415db7d 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -22,14 +22,12 @@ #include "mlir/Dialect/QTensor/Utils/TensorIterator.h" #include "mlir/Dialect/Utils/Utils.h" -#include #include #include #include #include #include #include -#include #include #include #include From 8ac9f906f69370c7f964e460e9cb2da2fda2d325 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 7 Aug 2026 08:49:59 +0200 Subject: [PATCH 05/10] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 061cddfd4d..e8ff7cb10e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,7 +82,7 @@ releases may include breaking changes. circuits to compiler-target topologies while preserving target site IDs and materializing routing workspace on demand ([#1537], [#1547], [#1568], [#1581], [#1583], [#1588], [#1600], [#1664], [#1709], [#1716], [#1748], [#1805], - [#1870], [#1904], [#1911], [#1951], [#1997]) ([**@MatthiasReumann**], + [#1870], [#1904], [#1911], [#1951], [#1997], [#2016]) ([**@MatthiasReumann**], [**@burgholzer**]) - ✨ Add a pass for qubit reuse in quantum programs, as well as related auxiliary passes and patterns ([#1705], [#1755], [#1756], [#1923], [#1924]) @@ -721,6 +721,7 @@ for previous changelogs._ +[#2016]: https://github.com/munich-quantum-toolkit/core/pull/2016 [#2011]: https://github.com/munich-quantum-toolkit/core/pull/2011 [#2007]: https://github.com/munich-quantum-toolkit/core/pull/2007 [#2006]: https://github.com/munich-quantum-toolkit/core/pull/2006 From 57962fb42decf3ee9adcd72b2128cac89f83d038 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 7 Aug 2026 09:04:37 +0200 Subject: [PATCH 06/10] Apply bunny suggestions --- mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 418415db7d..bf14fbad2a 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -419,13 +419,6 @@ struct MappingPass : impl::MappingPassBase { values, [](Value value) { return isa(value.getType()); })); } - /// Return the qubit values in `values`, preserving their relative order. - static SmallVector getQubitValues(ResultRange values) { - return to_vector(llvm::make_filter_range(values, [](OpResult value) { - return isa(value.getType()); - })); - } - /// 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. @@ -1284,7 +1277,7 @@ struct MappingPass : impl::MappingPassBase { FailureOr> dispatch(const CompositeUnitary& composite, const RoutingBundle& parent, IRRewriter* rewriter = nullptr) { - const auto [op, indices] = composite; + const auto& [op, indices] = composite; SmallVector permutation(indices.size()); SmallVector children = From b4320ae352c4f2f71fb18c305027b628d108230f Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Mon, 10 Aug 2026 02:01:07 +0200 Subject: [PATCH 07/10] =?UTF-8?q?=F0=9F=90=9B=20Preserve=20block=20order?= =?UTF-8?q?=20during=20inline=20placement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sort simultaneously ready composite operations in their owning block order before threading target qubits through them. Add a regression test for quantum-disjoint loops linked by classical SSA dependencies. Assisted-by: GPT-5 via Codex --- .../QCO/Transforms/Mapping/Mapping.cpp | 10 +++ .../QCO/Transforms/Mapping/test_mapping.cpp | 65 +++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index bf14fbad2a..43bfee751c 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -1178,6 +1178,16 @@ struct MappingPass : impl::MappingPassBase { return WalkResult::advance(); }); + // Preserve the block order when multiple independent composite operations + // become ready at once. Hot routing threads every qubit through each + // composite, so processing a later operation first could introduce a + // use-before-definition for an earlier operation. + llvm::sort(composites, + [](const CompositeUnitary& lhs, const CompositeUnitary& rhs) { + assert(lhs.op->getBlock() == rhs.op->getBlock()); + return lhs.op->isBeforeInBlock(rhs.op); + }); + return composites; } diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp index fd2d50a253..0577dd5261 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -943,6 +943,71 @@ TEST_P(MappingPassTest, MapParallelLoops) { EXPECT_TRUE(isExecutable(getEntryPoint(m.get()), target)); } +TEST_P(MappingPassTest, MapParallelLoopsWithClassicalDependencies) { + const auto& target = GetParam(); + constexpr StringLiteral source = R"mlir( + module { + func.func @main() attributes {passthrough = ["entry_point"]} { + %c0 = arith.constant 0 : index + %c1 = arith.constant 1 : index + %q0 = qco.alloc : !qco.qubit + %q1 = qco.alloc : !qco.qubit + %q2 = qco.alloc : !qco.qubit + %q3 = qco.alloc : !qco.qubit + %q4 = qco.alloc : !qco.qubit + %q5 = qco.alloc : !qco.qubit + %q6 = qco.alloc : !qco.qubit + %q7 = qco.alloc : !qco.qubit + %a0, %a1, %s1 = scf.for %i = %c0 to %c1 step %c1 + iter_args(%x = %q0, %y = %q1, %s = %c1) + -> (!qco.qubit, !qco.qubit, index) { + %nx, %ny = qco.swap %x, %y + : !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit + scf.yield %nx, %ny, %s : !qco.qubit, !qco.qubit, index + } + %b0, %b1, %s2 = scf.for %i = %c0 to %s1 step %c1 + iter_args(%x = %q2, %y = %q3, %s = %s1) + -> (!qco.qubit, !qco.qubit, index) { + %nx, %ny = qco.swap %x, %y + : !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit + scf.yield %nx, %ny, %s : !qco.qubit, !qco.qubit, index + } + %d0, %d1, %s3 = scf.for %i = %c0 to %s2 step %c1 + iter_args(%x = %q4, %y = %q5, %s = %s2) + -> (!qco.qubit, !qco.qubit, index) { + %nx, %ny = qco.swap %x, %y + : !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit + scf.yield %nx, %ny, %s : !qco.qubit, !qco.qubit, index + } + %e0, %e1, %s4 = scf.for %i = %c0 to %s3 step %c1 + iter_args(%x = %q6, %y = %q7, %s = %s3) + -> (!qco.qubit, !qco.qubit, index) { + %nx, %ny = qco.swap %x, %y + : !qco.qubit, !qco.qubit -> !qco.qubit, !qco.qubit + scf.yield %nx, %ny, %s : !qco.qubit, !qco.qubit, index + } + qco.sink %a0 : !qco.qubit + qco.sink %a1 : !qco.qubit + qco.sink %b0 : !qco.qubit + qco.sink %b1 : !qco.qubit + qco.sink %d0 : !qco.qubit + qco.sink %d1 : !qco.qubit + qco.sink %e0 : !qco.qubit + qco.sink %e1 : !qco.qubit + return + } + } + )mlir"; + + auto m = parseSourceString(source, context.get()); + ASSERT_TRUE(m); + ASSERT_TRUE(succeeded(verify(*m))); + ASSERT_TRUE( + runPass(m.get(), target, MappingPassOptions{.ntrials = 1}).succeeded()); + EXPECT_TRUE(succeeded(verify(*m))); + EXPECT_TRUE(isExecutable(getEntryPoint(m.get()), target)); +} + TEST_P(MappingPassTest, MapForWithClassicalIterArg) { const auto& target = GetParam(); constexpr StringLiteral source = R"mlir( From 0e44c0c94364578765fac958d3eef0b134307c9c Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Mon, 10 Aug 2026 02:03:22 +0200 Subject: [PATCH 08/10] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Move=20routing=20pat?= =?UTF-8?q?ches=20into=20bundles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consume routing patches and move their optional layout, wire information, and iterator containers into the destination bundle instead of copying target-sized state. Assisted-by: GPT-5 via Codex --- .../QCO/Transforms/Mapping/Mapping.cpp | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 43bfee751c..f08ba32a7b 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -178,15 +178,15 @@ struct MappingPass : impl::MappingPassBase { std::optional wires; }; - void applyPatch(const Patch& patch) { + void applyPatch(Patch&& patch) { if (patch.layout) { - layout = *patch.layout; + layout = std::move(*patch.layout); } if (patch.infos) { - infos = *patch.infos; + infos = std::move(*patch.infos); } if (patch.wires) { - wires = *patch.wires; + wires = std::move(*patch.wires); } } }; @@ -1250,8 +1250,9 @@ struct MappingPass : impl::MappingPassBase { return it.operation() == composite.op; })); - return RoutingBundle::Patch{ - .layout = std::nullopt, .infos = std::nullopt, .wires = wires}; + return RoutingBundle::Patch{.layout = std::nullopt, + .infos = std::nullopt, + .wires = std::move(wires)}; } /// Return `values` with only the qubit entries realigned according to the @@ -1442,7 +1443,7 @@ struct MappingPass : impl::MappingPassBase { // using the restore (scf::ForOp, scf::While), converge (IfOp), and vote // and restore (IndexSwitchOp) strategies. - const Layout exit = + Layout exit = TypeSwitch(op) .Case([&](scf::ForOp) { const auto swaps = restore(children[0].layout, parent.layout); @@ -1520,15 +1521,16 @@ struct MappingPass : impl::MappingPassBase { return std::make_pair(RoutingBundle::Patch{}, totalStats); } - RoutingBundle::Patch patch{.layout = exit, .infos = WireInfos{}}; + RoutingBundle::Patch patch{.layout = std::nullopt, .infos = WireInfos{}}; for (size_t i = 0; i < parent.wires.size(); ++i) { const auto oldProg = parent.infos.lookupProgram(i); const auto oldHw = parent.layout.getHardwareIndex(oldProg); const auto newProg = exit.getProgramIndex(oldHw); patch.infos->insertOrUpdate(i, newProg); } + patch.layout = std::move(exit); - return std::make_pair(patch, totalStats); + return std::make_pair(std::move(patch), totalStats); } /// Iterates over a dynamically computed window of layers and uses A* search @@ -1554,17 +1556,16 @@ struct MappingPass : impl::MappingPassBase { for (auto& composite : composites) { if constexpr (Mode == RoutingMode::Hot) { - const auto patch = place(composite, bundle, *rewriter); - bundle.applyPatch(patch); + auto patch = place(composite, bundle, *rewriter); + bundle.applyPatch(std::move(patch)); } - const auto res = - dispatch(composite, bundle, rewriter); + auto res = dispatch(composite, bundle, rewriter); if (failed(res)) { return failure(); } - bundle.applyPatch(res->first); + bundle.applyPatch(std::move(res->first)); stats.merge(res->second); // Once the composite is mapped, move past this op by incrementing the From 5c3111cf42fae02cb6dfc46f4cdfdb78fa0cbaeb Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 10 Aug 2026 08:28:55 +0200 Subject: [PATCH 09/10] Fix lint --- mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index f08ba32a7b..b2c298ec51 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -179,14 +179,15 @@ struct MappingPass : impl::MappingPassBase { }; void applyPatch(Patch&& patch) { + Patch p = std::move(patch); if (patch.layout) { - layout = std::move(*patch.layout); + layout = std::move(*p.layout); } if (patch.infos) { - infos = std::move(*patch.infos); + infos = std::move(*p.infos); } if (patch.wires) { - wires = std::move(*patch.wires); + wires = std::move(*p.wires); } } }; From 0cd3b543297fad81c6b6196fc2a8deddab42fb38 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 10 Aug 2026 09:24:09 +0200 Subject: [PATCH 10/10] Final lint fix --- mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index b2c298ec51..b258c5b1ec 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -180,13 +180,13 @@ struct MappingPass : impl::MappingPassBase { void applyPatch(Patch&& patch) { Patch p = std::move(patch); - if (patch.layout) { + if (p.layout) { layout = std::move(*p.layout); } - if (patch.infos) { + if (p.infos) { infos = std::move(*p.infos); } - if (patch.wires) { + if (p.wires) { wires = std::move(*p.wires); } }