From 924487f8ff1ce634d3cb18bf7e370192da443022 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 20 Feb 2026 13:27:54 +0100 Subject: [PATCH 01/37] Setup mapping pass --- mlir/include/mlir/CMakeLists.txt | 3 +- mlir/include/mlir/Passes/CMakeLists.txt | 12 +++ mlir/include/mlir/Passes/Passes.h | 32 ++++++ mlir/include/mlir/Passes/Passes.td | 26 +++++ mlir/lib/CMakeLists.txt | 3 +- mlir/lib/Compiler/CMakeLists.txt | 1 + mlir/lib/Compiler/CompilerPipeline.cpp | 2 + mlir/lib/Passes/CMakeLists.txt | 43 ++++++++ .../Passes/Transpilation/HeuristicMapping.cpp | 100 ++++++++++++++++++ 9 files changed, 220 insertions(+), 2 deletions(-) create mode 100644 mlir/include/mlir/Passes/CMakeLists.txt create mode 100644 mlir/include/mlir/Passes/Passes.h create mode 100644 mlir/include/mlir/Passes/Passes.td create mode 100644 mlir/lib/Passes/CMakeLists.txt create mode 100644 mlir/lib/Passes/Transpilation/HeuristicMapping.cpp diff --git a/mlir/include/mlir/CMakeLists.txt b/mlir/include/mlir/CMakeLists.txt index 272dfcf570..7d688914a2 100644 --- a/mlir/include/mlir/CMakeLists.txt +++ b/mlir/include/mlir/CMakeLists.txt @@ -6,5 +6,6 @@ # # Licensed under the MIT License -add_subdirectory(Dialect) add_subdirectory(Conversion) +add_subdirectory(Dialect) +add_subdirectory(Passes) diff --git a/mlir/include/mlir/Passes/CMakeLists.txt b/mlir/include/mlir/Passes/CMakeLists.txt new file mode 100644 index 0000000000..9d11c5ca00 --- /dev/null +++ b/mlir/include/mlir/Passes/CMakeLists.txt @@ -0,0 +1,12 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +set(LLVM_TARGET_DEFINITIONS Passes.td) +mlir_tablegen(Passes.h.inc -gen-pass-decls -name QCO) +add_public_tablegen_target(QcoPassesIncGen) +add_mlir_doc(Passes QcoPasses Passes/ -gen-pass-doc) diff --git a/mlir/include/mlir/Passes/Passes.h b/mlir/include/mlir/Passes/Passes.h new file mode 100644 index 0000000000..db55cc4039 --- /dev/null +++ b/mlir/include/mlir/Passes/Passes.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include "mlir/Dialect/QCO/IR/QCODialect.h" + +#include +#include +#include + +namespace mlir::qco { + +#define GEN_PASS_DECL +#include "mlir/Passes/Passes.h.inc" // IWYU pragma: export + +//===----------------------------------------------------------------------===// +// Registration +//===----------------------------------------------------------------------===// + +/// Generate the code for registering passes. +#define GEN_PASS_REGISTRATION +#include "mlir/Passes/Passes.h.inc" // IWYU pragma: export + +} // namespace mlir::qco diff --git a/mlir/include/mlir/Passes/Passes.td b/mlir/include/mlir/Passes/Passes.td new file mode 100644 index 0000000000..774b8e9734 --- /dev/null +++ b/mlir/include/mlir/Passes/Passes.td @@ -0,0 +1,26 @@ +// Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +// Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +// All rights reserved. +// +// SPDX-License-Identifier: MIT +// +// Licensed under the MIT License + +#ifndef QCO_PASSES +#define QCO_PASSES + +include "mlir/Pass/PassBase.td" + +//===----------------------------------------------------------------------===// +// Transpilation Passes +//===----------------------------------------------------------------------===// + +def HeuristicMappingPass : Pass<"map", "mlir::ModuleOp"> { + let dependentDialects = [ "mlir::qco::QCODialect" ]; + let summary = "This pass ensures that a program meets the connectivity constraints of a given architecture."; + let description = [{ + This pass inserts SWAP operations to ensure two-qubit gates are executable on a given target architecture. + }]; +} + +#endif // QCO_PASSES diff --git a/mlir/lib/CMakeLists.txt b/mlir/lib/CMakeLists.txt index afc74137bd..7f46084b8c 100644 --- a/mlir/lib/CMakeLists.txt +++ b/mlir/lib/CMakeLists.txt @@ -6,7 +6,8 @@ # # Licensed under the MIT License -add_subdirectory(Dialect) add_subdirectory(Conversion) add_subdirectory(Compiler) +add_subdirectory(Dialect) +add_subdirectory(Passes) add_subdirectory(Support) diff --git a/mlir/lib/Compiler/CMakeLists.txt b/mlir/lib/Compiler/CMakeLists.txt index 97b700b5c5..df7033e208 100644 --- a/mlir/lib/Compiler/CMakeLists.txt +++ b/mlir/lib/Compiler/CMakeLists.txt @@ -22,6 +22,7 @@ add_mlir_library( QCToQIR MQT::MLIRSupport MQT::ProjectOptions + QcoPasses DISABLE_INSTALL) # collect header files diff --git a/mlir/lib/Compiler/CompilerPipeline.cpp b/mlir/lib/Compiler/CompilerPipeline.cpp index 02183790f4..fd16657999 100644 --- a/mlir/lib/Compiler/CompilerPipeline.cpp +++ b/mlir/lib/Compiler/CompilerPipeline.cpp @@ -13,6 +13,7 @@ #include "mlir/Conversion/QCOToQC/QCOToQC.h" #include "mlir/Conversion/QCToQCO/QCToQCO.h" #include "mlir/Conversion/QCToQIR/QCToQIR.h" +#include "mlir/Passes/Passes.h" #include "mlir/Support/PrettyPrinting.h" #include @@ -161,6 +162,7 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, // Stage 5: Optimization passes // TODO: Add optimization passes + pm.addPass(qco::createHeuristicMappingPass()); addCleanupPasses(pm); if (failed(pm.run(module))) { return failure(); diff --git a/mlir/lib/Passes/CMakeLists.txt b/mlir/lib/Passes/CMakeLists.txt new file mode 100644 index 0000000000..5af2a221e3 --- /dev/null +++ b/mlir/lib/Passes/CMakeLists.txt @@ -0,0 +1,43 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +file(GLOB_RECURSE PASSES_SOURCES *.cpp) + +get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS) + +add_mlir_library( + QcoPasses + ${PASSES_SOURCES} + LINK_LIBS + PUBLIC + MQT::CoreIR + Eigen3::Eigen + PRIVATE + ${dialect_libs} + DEPENDS + QcoPassesIncGen) + +# collect header files +file(GLOB_RECURSE PASSES_HEADERS_SOURCE ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Passes/*.h) +file(GLOB_RECURSE PASSES_HEADERS_BUILD ${MQT_MLIR_BUILD_INCLUDE_DIR}/mlir/Passes/*.inc) + +# add public headers using file sets +target_sources( + QcoPasses + PUBLIC FILE_SET + HEADERS + BASE_DIRS + ${MQT_MLIR_SOURCE_INCLUDE_DIR} + FILES + ${PASSES_HEADERS_SOURCE} + FILE_SET + HEADERS + BASE_DIRS + ${MQT_MLIR_BUILD_INCLUDE_DIR} + FILES + ${PASSES_HEADERS_BUILD}) diff --git a/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp b/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp new file mode 100644 index 0000000000..8b6bb5aa70 --- /dev/null +++ b/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Passes/Passes.h" + +#include +#include +#include +#include +#include +#include + +#define DEBUG_TYPE "mapping-pass" + +namespace mlir::qco { +#define GEN_PASS_DEF_HEURISTICMAPPINGPASS +#include "mlir/Passes/Passes.h.inc" + +struct HeuristicMappingPass + : impl::HeuristicMappingPassBase { +private: + struct Architecture {}; + + /** + * @brief Describes a bidirectional program-to-hardware qubit mapping. + */ + struct Mapping {}; + + struct Circuit { + Circuit() = default; + + void extend(Value q) { qubits_.emplace_back(q); } + + template void extend(Range&& range) { + llvm::append_range(qubits_, std::forward(range)); + } + + [[nodiscard]] std::size_t size() const { return qubits_.size(); } + + private: + SmallVector qubits_; + }; + +public: + using HeuristicMappingPassBase::HeuristicMappingPassBase; + + void runOnOperation() override { + IRRewriter rewriter(&getContext()); + + Architecture arch; + + for (auto func : getOperation().getOps()) { + Region& region = func.getFunctionBody(); + + // Stage 1: Apply initial program-to-hardware mapping strategy. + Circuit circ; + circ.extend(llvm::map_range(region.getOps(), + [](AllocOp op) { return op.getResult(); })); + + Mapping mapping = computeInitialMapping(circ, arch); + + // Stage 2: Recomputing starting program-to-hardware mapping by + // repeating forwards and backwards traversals. + const std::size_t repeats = 10; + for (std::size_t i = 0; i < repeats; ++i) { + // mapping = forward(mapping, /*commit= */false) + // mapping = backward(mapping) + } + + // Stage 3: Commit mapping and final traversal. + commitMapping(func, mapping, rewriter); + // forward(mapping, /*commit= */true) + } + } + +private: + static Mapping computeInitialMapping(Circuit& circ, Architecture& arch) { + // TODO + } + + static void commitMapping(func::FuncOp& func, const Mapping& mapping, + IRRewriter& rewriter) { + // TODO + // rewriter.setInsertionPointToStart(body); + // SmallVector allocations(body->getOps()); + // for (auto [i, op] : llvm::enumerate(allocations)) { + // rewriter.setInsertionPoint(op); + // std::ignore = rewriter.replaceOpWithNewOp(op, i); + // } + }; +}; +} // namespace mlir::qco From f67bbfbf330aaf750fb585d71d88a0edb2eb9cab Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 26 Feb 2026 07:28:15 +0100 Subject: [PATCH 02/37] Add layering functionality --- mlir/include/mlir/Passes/Passes.td | 2 +- mlir/lib/Compiler/CMakeLists.txt | 1 + mlir/lib/Passes/CMakeLists.txt | 2 - .../Passes/Transpilation/HeuristicMapping.cpp | 464 +++++++++++++++++- mlir/tools/mqt-cc/mqt-cc.cpp | 2 +- 5 files changed, 440 insertions(+), 31 deletions(-) diff --git a/mlir/include/mlir/Passes/Passes.td b/mlir/include/mlir/Passes/Passes.td index 774b8e9734..07dcf43bdd 100644 --- a/mlir/include/mlir/Passes/Passes.td +++ b/mlir/include/mlir/Passes/Passes.td @@ -16,7 +16,7 @@ include "mlir/Pass/PassBase.td" //===----------------------------------------------------------------------===// def HeuristicMappingPass : Pass<"map", "mlir::ModuleOp"> { - let dependentDialects = [ "mlir::qco::QCODialect" ]; + let dependentDialects = ["mlir::qco::QCODialect"]; let summary = "This pass ensures that a program meets the connectivity constraints of a given architecture."; let description = [{ This pass inserts SWAP operations to ensure two-qubit gates are executable on a given target architecture. diff --git a/mlir/lib/Compiler/CMakeLists.txt b/mlir/lib/Compiler/CMakeLists.txt index a53a004456..cb09a030e4 100644 --- a/mlir/lib/Compiler/CMakeLists.txt +++ b/mlir/lib/Compiler/CMakeLists.txt @@ -20,6 +20,7 @@ add_mlir_library( QCToQCO QCOToQC QCToQIR + QcoPasses MQT::MLIRSupport DISABLE_INSTALL) diff --git a/mlir/lib/Passes/CMakeLists.txt b/mlir/lib/Passes/CMakeLists.txt index 5af2a221e3..92d1494592 100644 --- a/mlir/lib/Passes/CMakeLists.txt +++ b/mlir/lib/Passes/CMakeLists.txt @@ -15,8 +15,6 @@ add_mlir_library( ${PASSES_SOURCES} LINK_LIBS PUBLIC - MQT::CoreIR - Eigen3::Eigen PRIVATE ${dialect_libs} DEPENDS diff --git a/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp b/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp index 8b6bb5aa70..f42b6eba5c 100644 --- a/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp +++ b/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp @@ -9,44 +9,331 @@ */ #include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/IR/QCOInterfaces.h" +#include "mlir/Dialect/QCO/IR/QCOOps.h" +#include "mlir/Dialect/QCO/Utils/WireIterator.h" #include "mlir/Passes/Passes.h" +#include +#include +#include #include +#include +#include #include +#include +#include #include #include #include #include +#include +#include +#include -#define DEBUG_TYPE "mapping-pass" +#define DEBUG_TYPE "heuristic-mapping-pass" namespace mlir::qco { + #define GEN_PASS_DEF_HEURISTICMAPPINGPASS #include "mlir/Passes/Passes.h.inc" struct HeuristicMappingPass : impl::HeuristicMappingPassBase { private: - struct Architecture {}; + /** + * @brief A quantum accelerator's architecture. + * @details Computes all-shortest paths at construction. + */ + class Architecture { + public: + using CouplingSet = mlir::DenseSet>; + using NeighbourVector = mlir::SmallVector>; + + explicit Architecture(std::string name, std::size_t nqubits, + CouplingSet couplingSet) + : name_(std::move(name)), nqubits_(nqubits), + couplingSet_(std::move(couplingSet)), neighbours_(nqubits), + dist_(nqubits, llvm::SmallVector(nqubits, UINT64_MAX)), + prev_(nqubits, llvm::SmallVector(nqubits, UINT64_MAX)) { + floydWarshallWithPathReconstruction(); + collectNeighbours(); + } + + /** + * @brief Return the architecture's name. + */ + [[nodiscard]] constexpr std::string_view name() const { return name_; } + + /** + * @brief Return the architecture's number of qubits. + */ + [[nodiscard]] constexpr std::size_t nqubits() const { return nqubits_; } + + /** + * @brief Return true if @p u and @p v are adjacent. + */ + [[nodiscard]] bool areAdjacent(uint32_t u, uint32_t v) const { + return couplingSet_.contains({u, v}); + } + + /** + * @brief Collect the shortest SWAP sequence to make @p u and @p v adjacent. + * @returns The SWAP sequence from the destination (v) to source (u) qubit. + */ + [[nodiscard]] llvm::SmallVector> + shortestSWAPsBetween(uint32_t u, uint32_t v) const { + if (u == v) { + return {}; + } + + if (prev_[u][v] == UINT64_MAX) { + throw std::domain_error("No path between qubits " + std::to_string(u) + + " and " + std::to_string(v)); + } + + llvm::SmallVector> swaps; + uint32_t last = v; + uint32_t curr = prev_[u][v]; + + while (curr != u) { + swaps.emplace_back(last, curr); // Insert SWAP(last, curr). + last = curr; + curr = prev_[u][curr]; + } + + return swaps; + } + + /** + * @brief Return the length of the shortest path between @p u and @p v. + */ + [[nodiscard]] std::size_t distanceBetween(uint32_t u, uint32_t v) const { + if (dist_[u][v] == UINT64_MAX) { + throw std::domain_error("No path between qubits " + std::to_string(u) + + " and " + std::to_string(v)); + } + return dist_[u][v]; + } + + /** + * @brief Collect all neighbours of @p u. + */ + [[nodiscard]] llvm::SmallVector + neighboursOf(uint32_t u) const { + return neighbours_[u]; + } + + private: + using Matrix = llvm::SmallVector>; + + /** + * @brief Find all shortest paths in the coupling map between two qubits. + * @details Vertices are the qubits. Edges connected two qubits. Has a time + * and memory complexity of O(nqubits^3) and O(nqubits^2), respectively. + * @link Adapted from https://en.wikipedia.org/wiki/Floyd–Warshall_algorithm + */ + void floydWarshallWithPathReconstruction() { + for (const auto& [u, v] : couplingSet_) { + dist_[u][v] = 1; + prev_[u][v] = u; + } + for (std::size_t v = 0; v < nqubits(); ++v) { + dist_[v][v] = 0; + prev_[v][v] = v; + } + + for (std::size_t k = 0; k < nqubits(); ++k) { + for (std::size_t i = 0; i < nqubits(); ++i) { + for (std::size_t j = 0; j < nqubits(); ++j) { + if (dist_[i][k] == UINT64_MAX || dist_[k][j] == UINT64_MAX) { + continue; // avoid overflow with "infinite" distances + } + const std::size_t sum = dist_[i][k] + dist_[k][j]; + if (dist_[i][j] > sum) { + dist_[i][j] = sum; + prev_[i][j] = prev_[k][j]; + } + } + } + } + } + + /** + * @brief Collect the neighbours of all qubits. + * @details Has a time complexity of O(nqubits) + */ + void collectNeighbours() { + for (const auto& [u, v] : couplingSet_) { + neighbours_[u].push_back(v); + } + } + + std::string name_; + std::size_t nqubits_; + CouplingSet couplingSet_; + NeighbourVector neighbours_; + + Matrix dist_; + Matrix prev_; + }; /** - * @brief Describes a bidirectional program-to-hardware qubit mapping. + * @brief A qubit layout that maps program and hardware indices without + * storing Values. Used for efficient memory usage when Value tracking isn't + * needed. + * + * Note that we use the terminology "hardware" and "program" qubits here, + * because "virtual" (opposed to physical) and "static" (opposed to dynamic) + * are C++ keywords. */ - struct Mapping {}; + class [[nodiscard]] ThinLayout { + public: + explicit ThinLayout(const std::size_t nqubits) + : programToHardware_(nqubits), hardwareToProgram_(nqubits) {} + + /** + * @brief Insert program:hardware index mapping. + * @param prog The program index. + * @param hw The hardware index. + */ + void add(uint32_t prog, uint32_t hw) { + assert(prog < programToHardware_.size() && + "add: program index out of bounds"); + assert(hw < hardwareToProgram_.size() && + "add: hardware index out of bounds"); + programToHardware_[prog] = hw; + hardwareToProgram_[hw] = prog; + } + + /** + * @brief Look up program index for a hardware index. + * @param hw The hardware index. + * @return The program index of the respective hardware index. + */ + [[nodiscard]] uint32_t getProgramIndex(const uint32_t hw) const { + assert(hw < hardwareToProgram_.size() && + "getProgramIndex: hardware index out of bounds"); + return hardwareToProgram_[hw]; + } + + /** + * @brief Look up hardware index for a program index. + * @param prog The program index. + * @return The hardware index of the respective program index. + */ + [[nodiscard]] uint32_t getHardwareIndex(const uint32_t prog) const { + assert(prog < programToHardware_.size() && + "getHardwareIndex: program index out of bounds"); + return programToHardware_[prog]; + } + + /** + * @brief Convenience function to lookup multiple hardware indices at once. + * @param progs The program indices. + * @return A tuple of hardware indices. + */ + template + requires(sizeof...(ProgIndices) > 0) && + ((std::is_convertible_v) && ...) + [[nodiscard]] auto getHardwareIndices(ProgIndices... progs) const { + return std::tuple{getHardwareIndex(static_cast(progs))...}; + } + + /** + * @brief Convenience function to lookup multiple program indices at once. + * @param hws The hardware indices. + * @return A tuple of program indices. + */ + template + requires(sizeof...(HwIndices) > 0) && + ((std::is_convertible_v) && ...) + [[nodiscard]] auto getProgramIndices(HwIndices... hws) const { + return std::tuple{getProgramIndex(static_cast(hws))...}; + } + + /** + * @brief Swap the mapping to hardware indices of two program indices. + */ + void swap(const uint32_t prog0, const uint32_t prog1) { + const uint32_t hw0 = programToHardware_[prog0]; + const uint32_t hw1 = programToHardware_[prog1]; + + std::swap(programToHardware_[prog0], programToHardware_[prog1]); + std::swap(hardwareToProgram_[hw0], hardwareToProgram_[hw1]); + } + + /** + * @returns the number of qubits handled by the layout. + */ + [[nodiscard]] std::size_t getNumQubits() const { + return programToHardware_.size(); + } + + protected: + /** + * @brief Maps a program qubit index to its hardware index. + */ + mlir::SmallVector programToHardware_; + + /** + * @brief Maps a hardware qubit index to its program index. + */ + mlir::SmallVector hardwareToProgram_; + }; struct Circuit { + public: Circuit() = default; - void extend(Value q) { qubits_.emplace_back(q); } + void extend(TypedValue q) { qubits_.emplace_back(q); } template void extend(Range&& range) { llvm::append_range(qubits_, std::forward(range)); } + /// @returns the i-th qubit of the circuit. + [[nodiscard]] TypedValue qubit(std::size_t i) const { + return qubits_[i]; + } + + /// @returns the number of qubits the circuit contains. [[nodiscard]] std::size_t size() const { return qubits_.size(); } + /// @brief Assign hardware index to qubit. + void setHardwareIndex(const TypedValue q, + const std::size_t hwIndex) { + mapping_.try_emplace(q, hwIndex); + } + + /// @returns the hardware index associated with the qubit value. + [[nodiscard]] std::size_t + getHardwareIndex(const TypedValue q) const { + return mapping_.at(q); + } + + /// @returns a view of the qubits. + [[nodiscard]] ArrayRef> qubits() const { + return qubits_; + } + + /// @brief Replace dynamic qubits with static qubits. + // void staticize(const SmallVector& mapping, + // IRRewriter& rewriter) { + // for (const auto [i, m] : llvm::enumerate(mapping)) { + // Operation* alloc = m.qubit().getDefiningOp(); + // assert(llvm::isa(alloc)); + + // rewriter.setInsertionPoint(alloc); + // qubits_[i] = rewriter.replaceOpWithNewOp(alloc, m.index()); + // } + // } + private: - SmallVector qubits_; + /// @brief The qubits of the circuit. + SmallVector, 32> qubits_; + /// @brief Maps qubit values to hardware indices. + DenseMap, std::size_t> mapping_; }; public: @@ -55,46 +342,169 @@ struct HeuristicMappingPass void runOnOperation() override { IRRewriter rewriter(&getContext()); - Architecture arch; + static const Architecture::CouplingSet COUPLING{ + {0, 1}, {1, 0}, {0, 2}, {2, 0}, {1, 3}, {3, 1}, {2, 3}, + {3, 2}, {2, 4}, {4, 2}, {3, 5}, {5, 3}, {4, 5}, {5, 4}}; + + Architecture arch("RigettiNovera", 9, COUPLING); for (auto func : getOperation().getOps()) { Region& region = func.getFunctionBody(); // Stage 1: Apply initial program-to-hardware mapping strategy. + + // Find circuit qubits (via their allocations). Circuit circ; - circ.extend(llvm::map_range(region.getOps(), - [](AllocOp op) { return op.getResult(); })); + circ.extend(map_range(region.getOps(), + [](AllocOp op) { return op.getResult(); })); - Mapping mapping = computeInitialMapping(circ, arch); + // Compute layers. + SmallVector wires; + wires.reserve(circ.size()); + for (const auto q : circ.qubits()) { + wires.emplace_back(q); + } + SmallVector wireStarts(wires); + + const auto forwardLayers = + getLayers(wires, [&](std::size_t, const WireIterator& it) { + return it != std::default_sentinel; + }); + const auto backwardLayers = getLayers( + wires, [&](std::size_t idx, const WireIterator& it) { + return it != wireStarts[idx]; + }); + + llvm::dbgs() << "forward:\n"; + for (const auto& layer : forwardLayers) { + layer.dump(); + llvm::dbgs() << " --- layer end ---\n"; + } + + llvm::dbgs() << "backward:\n"; + for (const auto& layer : backwardLayers) { + layer.dump(); + llvm::dbgs() << " --- layer end ---\n"; + } + + ThinLayout layout(8); + + // Apply identity layout. + for (std::size_t i = 0; i < circ.size(); ++i) { + layout.add(i, i); + } // Stage 2: Recomputing starting program-to-hardware mapping by // repeating forwards and backwards traversals. - const std::size_t repeats = 10; + const std::size_t repeats = 1; for (std::size_t i = 0; i < repeats; ++i) { - // mapping = forward(mapping, /*commit= */false) + // forward(circ, arch, layout); // mapping = backward(mapping) } - // Stage 3: Commit mapping and final traversal. - commitMapping(func, mapping, rewriter); - // forward(mapping, /*commit= */true) + // Stage 3: Apply mapping and final traversal. + // circ.staticize(mapping, rewriter); + // forward(circ, arch, true); } } private: - static Mapping computeInitialMapping(Circuit& circ, Architecture& arch) { - // TODO - } + struct Layer { + /// @brief Set of two-qubit gates. + DenseSet> gates; - static void commitMapping(func::FuncOp& func, const Mapping& mapping, - IRRewriter& rewriter) { - // TODO - // rewriter.setInsertionPointToStart(body); - // SmallVector allocations(body->getOps()); - // for (auto [i, op] : llvm::enumerate(allocations)) { - // rewriter.setInsertionPoint(op); - // std::ignore = rewriter.replaceOpWithNewOp(op, i); - // } +#ifndef NDEBUG + LLVM_DUMP_METHOD void dump(llvm::raw_ostream& os = llvm::dbgs()) const { + os << "gates= "; + for (const auto [i1, i2] : gates) { + os << "(" << i1 << ", " << i2 << ") "; + } + os << "\n"; + } +#endif }; + + enum Direction : std::uint8_t { Forward, Backward }; + + template + [[nodiscard]] static SmallVector + getLayers(MutableArrayRef wires, EndCheckF endCheck) { + constexpr std::size_t step = d == Forward ? 1 : -1; + + SmallVector layers; + DenseMap occ; + + while (true) { + Layer l; + // SetVector> ssaFront; + + for (const auto [index, it] : llvm::enumerate(wires)) { + while (endCheck(index, it)) { + const auto res = + TypeSwitch(it.operation()) + .Case([&](UnitaryOpInterface op) { + assert(op.getNumQubits() > 0 && op.getNumQubits() <= 2); + + if (op.getNumQubits() == 1) { + std::ranges::advance(it, step); + return WalkResult::advance(); + } + + // ssaFront.insert( + // cast>(std::prev(it).qubit())); + + if (occ.contains(op)) { + const auto otherIndex = occ[op]; + l.gates.insert(std::make_pair(index, otherIndex)); + occ.erase(op); + } else { + occ.try_emplace(op, index); + } + + return WalkResult::interrupt(); + }) + .template Case([&](auto) { + std::ranges::advance(it, step); + return WalkResult::advance(); + }) + .Default([&](Operation* op) { + report_fatal_error("unknown op in wireiterator use: " + + op->getName().getStringRef()); + return WalkResult::interrupt(); + }); + + if (res.wasInterrupted()) { + break; + } + } + } + + // If there are no more two-qubit gates, early exit. + if (l.gates.empty()) { + break; + } + + for (auto [i1, i2] : l.gates) { + std::ranges::advance(wires[i1], step); + std::ranges::advance(wires[i2], step); + } + + layers.emplace_back(l); + } + + return layers; + } + + static ThinLayout getIdentityLayout(const Architecture& arch) { + ThinLayout layout(8); + for (std::size_t i = 0; i < arch.nqubits(); ++i) { + layout.add(i, i); + } + return layout; + } + + static void forward(const Circuit& circ, const Architecture& arch, + ThinLayout layout) {} }; } // namespace mlir::qco diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp index fd25e8d5ed..f9785af62f 100644 --- a/mlir/tools/mqt-cc/mqt-cc.cpp +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -140,7 +140,7 @@ int main(int argc, char** argv) { // Set up MLIR context with all required dialects DialectRegistry registry; registry.insert(); - registry.insert(); + registry.insert(); registry.insert(); registry.insert(); registry.insert(); From 45ca007064fd78ccab4944c167f5f8275af0bb24 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 26 Feb 2026 09:46:44 +0100 Subject: [PATCH 03/37] Add back and forth logic --- .../Passes/Transpilation/HeuristicMapping.cpp | 243 +++++++++++++++--- 1 file changed, 212 insertions(+), 31 deletions(-) diff --git a/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp b/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp index f42b6eba5c..4fc079a28d 100644 --- a/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp +++ b/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp @@ -41,6 +41,23 @@ namespace mlir::qco { struct HeuristicMappingPass : impl::HeuristicMappingPassBase { private: + struct Layer { + /// @brief Set of two-qubit gates. + DenseSet> gates; + +#ifndef NDEBUG + LLVM_DUMP_METHOD void dump(llvm::raw_ostream& os = llvm::dbgs()) const { + os << "gates= "; + for (const auto [i1, i2] : gates) { + os << "(" << i1 << ", " << i2 << ") "; + } + os << "\n"; + } +#endif + }; + + enum Direction : std::uint8_t { Forward, Backward }; + /** * @brief A quantum accelerator's architecture. * @details Computes all-shortest paths at construction. @@ -270,6 +287,20 @@ struct HeuristicMappingPass return programToHardware_.size(); } +#ifndef NDEBUG + LLVM_DUMP_METHOD void dump(llvm::raw_ostream& os = llvm::dbgs()) const { + os << "prog= "; + for (std::size_t i = 0; i < getNumQubits(); ++i) { + os << i << " "; + } + os << "\nhw= "; + for (std::size_t i = 0; i < getNumQubits(); ++i) { + os << programToHardware_[i] << " "; + } + os << "\n"; + } +#endif + protected: /** * @brief Maps a program qubit index to its hardware index. @@ -282,6 +313,160 @@ struct HeuristicMappingPass mlir::SmallVector hardwareToProgram_; }; + class AStarSearchEngine { + public: + explicit AStarSearchEngine(float alpha) { params_.alpha = alpha; } + + private: + struct Parameters { + float alpha{}; + }; + + struct Node { + SmallVector> sequence; + ThinLayout layout; + float f; + + /** + * @brief Construct a root node with the given layout. Initialize the + * sequence with an empty vector and set the cost to zero. + */ + explicit Node(ThinLayout layout) : layout(std::move(layout)), f(0) {} + + /** + * @brief Construct a non-root node from its parent node. Apply the given + * swap to the layout of the parent node and evaluate the cost. + */ + Node(const Node& parent, std::pair swap, + const Layer& layer, const Architecture& arch, + const Parameters& params) + : sequence(parent.sequence), layout(parent.layout), f(0) { + /// Apply node-specific swap to given layout. + layout.swap(layout.getProgramIndex(swap.first), + layout.getProgramIndex(swap.second)); + + // Add swap to sequence. + sequence.push_back(swap); + + // Evaluate cost function. + f = g(params.alpha) + h(layer, arch); // NOLINT + } + + /** + * @brief Return true if the current sequence of SWAPs makes all gates + * executable. + */ + [[nodiscard]] bool isGoal(Layer layer, const Architecture& arch) const { + return llvm::all_of( + layer.gates, [&](const std::pair gate) { + return arch.areAdjacent(layout.getHardwareIndex(gate.first), + layout.getHardwareIndex(gate.second)); + }); + } + + /** + * @returns The depth in the search tree. + */ + [[nodiscard]] std::size_t depth() const { return sequence.size(); } + + [[nodiscard]] bool operator>(const Node& rhs) const { return f > rhs.f; } + + private: + /** + * @brief Calculate the path cost for the A* search algorithm. + * + * The path cost function is the weighted sum of the currently required + * SWAPs. + */ + [[nodiscard]] float g(float alpha) const { + return (alpha * static_cast(depth())); + } + + /** + * @brief Calculate the heuristic cost for the A* search algorithm. + * + * Computes the minimal number of SWAPs required to route each gate in + * each layer. For each gate, this is determined by the shortest distance + * between its hardware qubits. Intuitively, this is the number of SWAPs + * that a naive router would insert to route the layers. + */ + [[nodiscard]] float h(const Layer& layer, + const Architecture& arch) const { + float costs{0}; + for (const auto [prog0, prog1] : layer.gates) { + const auto [hw0, hw1] = layout.getHardwareIndices(prog0, prog1); + const std::size_t nswaps = arch.distanceBetween(hw0, hw1) - 1; + costs += static_cast(nswaps); + } + return costs; + } + }; + + using MinQueue = + std::priority_queue, std::greater<>>; + + public: + [[nodiscard]] std::optional< + SmallVector>> + route(const Layer& layer, const ThinLayout& layout, + const Architecture& arch) const { + Node root(layout); + + /// Early exit. No SWAPs required: + if (root.isGoal(layer, arch)) { + return SmallVector>{}; + } + + /// Initialize queue. + MinQueue frontier{}; + frontier.emplace(root); + + /// Iterative searching and expanding. + while (!frontier.empty()) { + Node curr = frontier.top(); + frontier.pop(); + + if (curr.isGoal(layer, arch)) { + return curr.sequence; + } + + /// Expand frontier with all neighbouring SWAPs in the current front. + expand(frontier, curr, layer, arch); + } + + return std::nullopt; + } + + private: + /// @brief Expand frontier with all neighbouring SWAPs in the current front. + void expand(MinQueue& frontier, const Node& parent, const Layer& layer, + const Architecture& arch) const { + DenseSet> expansionSet{}; + + if (!parent.sequence.empty()) { + expansionSet.insert(parent.sequence.back()); + } + + for (const std::pair gate : layer.gates) { + for (const auto prog : {gate.first, gate.second}) { + const auto hw0 = parent.layout.getHardwareIndex(prog); + for (const auto hw1 : arch.neighboursOf(hw0)) { + /// Ensure consistent hashing/comparison. + const std::pair swap = + std::minmax(hw0, hw1); + if (!expansionSet.insert(swap).second) { + continue; + } + + frontier.emplace(parent, swap, layer, arch, params_); + } + } + } + } + + Parameters params_; + }; + struct Circuit { public: Circuit() = default; @@ -342,12 +527,16 @@ struct HeuristicMappingPass void runOnOperation() override { IRRewriter rewriter(&getContext()); + // TODO: Hardcoded architecture. static const Architecture::CouplingSet COUPLING{ - {0, 1}, {1, 0}, {0, 2}, {2, 0}, {1, 3}, {3, 1}, {2, 3}, - {3, 2}, {2, 4}, {4, 2}, {3, 5}, {5, 3}, {4, 5}, {5, 4}}; + {0, 3}, {3, 0}, {0, 1}, {1, 0}, {1, 4}, {4, 1}, {1, 2}, {2, 1}, + {2, 5}, {5, 2}, {3, 6}, {6, 3}, {3, 4}, {4, 3}, {4, 7}, {7, 4}, + {4, 5}, {5, 4}, {5, 8}, {8, 5}, {6, 7}, {7, 6}, {7, 8}, {8, 7}}; Architecture arch("RigettiNovera", 9, COUPLING); + AStarSearchEngine engine(0.5); + for (auto func : getOperation().getOps()) { Region& region = func.getFunctionBody(); @@ -387,21 +576,19 @@ struct HeuristicMappingPass llvm::dbgs() << " --- layer end ---\n"; } - ThinLayout layout(8); - - // Apply identity layout. - for (std::size_t i = 0; i < circ.size(); ++i) { - layout.add(i, i); - } + // TODO: More initial layout methods. + auto layout = getIdentityLayout(arch); // Stage 2: Recomputing starting program-to-hardware mapping by // repeating forwards and backwards traversals. - const std::size_t repeats = 1; + const std::size_t repeats = 10; for (std::size_t i = 0; i < repeats; ++i) { - // forward(circ, arch, layout); - // mapping = backward(mapping) + process(forwardLayers, engine, arch, layout); + process(backwardLayers, engine, arch, layout); } + layout.dump(); + // Stage 3: Apply mapping and final traversal. // circ.staticize(mapping, rewriter); // forward(circ, arch, true); @@ -409,23 +596,6 @@ struct HeuristicMappingPass } private: - struct Layer { - /// @brief Set of two-qubit gates. - DenseSet> gates; - -#ifndef NDEBUG - LLVM_DUMP_METHOD void dump(llvm::raw_ostream& os = llvm::dbgs()) const { - os << "gates= "; - for (const auto [i1, i2] : gates) { - os << "(" << i1 << ", " << i2 << ") "; - } - os << "\n"; - } -#endif - }; - - enum Direction : std::uint8_t { Forward, Backward }; - template [[nodiscard]] static SmallVector getLayers(MutableArrayRef wires, EndCheckF endCheck) { @@ -497,14 +667,25 @@ struct HeuristicMappingPass } static ThinLayout getIdentityLayout(const Architecture& arch) { - ThinLayout layout(8); + ThinLayout layout(arch.nqubits()); for (std::size_t i = 0; i < arch.nqubits(); ++i) { layout.add(i, i); } return layout; } - static void forward(const Circuit& circ, const Architecture& arch, - ThinLayout layout) {} + // TODO: Naming. + static void process(ArrayRef layers, + const AStarSearchEngine& engine, const Architecture& arch, + ThinLayout& layout) { + for (const auto& layer : layers) { + const auto swaps = + engine.route({layer}, layout, arch); // TODO: Check optional. + for (const auto [hw0, hw1] : *swaps) { + const auto [prog0, prog1] = layout.getProgramIndices(hw0, hw1); + layout.swap(prog0, prog1); + } + } + } }; } // namespace mlir::qco From 76b66c6f55b5aae0a93e8153412241471c448ebc Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 26 Feb 2026 10:22:18 +0100 Subject: [PATCH 04/37] Add placement logic --- .../Passes/Transpilation/HeuristicMapping.cpp | 158 ++++++------------ 1 file changed, 49 insertions(+), 109 deletions(-) diff --git a/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp b/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp index 4fc079a28d..74ed4af08b 100644 --- a/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp +++ b/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp @@ -41,6 +41,8 @@ namespace mlir::qco { struct HeuristicMappingPass : impl::HeuristicMappingPassBase { private: + using QubitValue = TypedValue; + struct Layer { /// @brief Set of two-qubit gates. DenseSet> gates; @@ -204,9 +206,17 @@ struct HeuristicMappingPass * because "virtual" (opposed to physical) and "static" (opposed to dynamic) * are C++ keywords. */ - class [[nodiscard]] ThinLayout { + class [[nodiscard]] Layout { public: - explicit ThinLayout(const std::size_t nqubits) + static Layout identity(const std::size_t nqubits) { + Layout layout(nqubits); + for (std::size_t i = 0; i < nqubits; ++i) { + layout.add(i, i); + } + return layout; + } + + explicit Layout(const std::size_t nqubits) : programToHardware_(nqubits), hardwareToProgram_(nqubits) {} /** @@ -287,20 +297,6 @@ struct HeuristicMappingPass return programToHardware_.size(); } -#ifndef NDEBUG - LLVM_DUMP_METHOD void dump(llvm::raw_ostream& os = llvm::dbgs()) const { - os << "prog= "; - for (std::size_t i = 0; i < getNumQubits(); ++i) { - os << i << " "; - } - os << "\nhw= "; - for (std::size_t i = 0; i < getNumQubits(); ++i) { - os << programToHardware_[i] << " "; - } - os << "\n"; - } -#endif - protected: /** * @brief Maps a program qubit index to its hardware index. @@ -324,14 +320,14 @@ struct HeuristicMappingPass struct Node { SmallVector> sequence; - ThinLayout layout; + Layout layout; float f; /** * @brief Construct a root node with the given layout. Initialize the * sequence with an empty vector and set the cost to zero. */ - explicit Node(ThinLayout layout) : layout(std::move(layout)), f(0) {} + explicit Node(Layout layout) : layout(std::move(layout)), f(0) {} /** * @brief Construct a non-root node from its parent node. Apply the given @@ -408,7 +404,7 @@ struct HeuristicMappingPass public: [[nodiscard]] std::optional< SmallVector>> - route(const Layer& layer, const ThinLayout& layout, + route(const Layer& layer, const Layout& layout, const Architecture& arch) const { Node root(layout); @@ -467,60 +463,6 @@ struct HeuristicMappingPass Parameters params_; }; - struct Circuit { - public: - Circuit() = default; - - void extend(TypedValue q) { qubits_.emplace_back(q); } - - template void extend(Range&& range) { - llvm::append_range(qubits_, std::forward(range)); - } - - /// @returns the i-th qubit of the circuit. - [[nodiscard]] TypedValue qubit(std::size_t i) const { - return qubits_[i]; - } - - /// @returns the number of qubits the circuit contains. - [[nodiscard]] std::size_t size() const { return qubits_.size(); } - - /// @brief Assign hardware index to qubit. - void setHardwareIndex(const TypedValue q, - const std::size_t hwIndex) { - mapping_.try_emplace(q, hwIndex); - } - - /// @returns the hardware index associated with the qubit value. - [[nodiscard]] std::size_t - getHardwareIndex(const TypedValue q) const { - return mapping_.at(q); - } - - /// @returns a view of the qubits. - [[nodiscard]] ArrayRef> qubits() const { - return qubits_; - } - - /// @brief Replace dynamic qubits with static qubits. - // void staticize(const SmallVector& mapping, - // IRRewriter& rewriter) { - // for (const auto [i, m] : llvm::enumerate(mapping)) { - // Operation* alloc = m.qubit().getDefiningOp(); - // assert(llvm::isa(alloc)); - - // rewriter.setInsertionPoint(alloc); - // qubits_[i] = rewriter.replaceOpWithNewOp(alloc, m.index()); - // } - // } - - private: - /// @brief The qubits of the circuit. - SmallVector, 32> qubits_; - /// @brief Maps qubit values to hardware indices. - DenseMap, std::size_t> mapping_; - }; - public: using HeuristicMappingPassBase::HeuristicMappingPassBase; @@ -528,31 +470,23 @@ struct HeuristicMappingPass IRRewriter rewriter(&getContext()); // TODO: Hardcoded architecture. - static const Architecture::CouplingSet COUPLING{ - {0, 3}, {3, 0}, {0, 1}, {1, 0}, {1, 4}, {4, 1}, {1, 2}, {2, 1}, - {2, 5}, {5, 2}, {3, 6}, {6, 3}, {3, 4}, {4, 3}, {4, 7}, {7, 4}, - {4, 5}, {5, 4}, {5, 8}, {8, 5}, {6, 7}, {7, 6}, {7, 8}, {8, 7}}; - - Architecture arch("RigettiNovera", 9, COUPLING); - + Architecture arch("RigettiNovera", 9, + {{0, 3}, {3, 0}, {0, 1}, {1, 0}, {1, 4}, {4, 1}, + {1, 2}, {2, 1}, {2, 5}, {5, 2}, {3, 6}, {6, 3}, + {3, 4}, {4, 3}, {4, 7}, {7, 4}, {4, 5}, {5, 4}, + {5, 8}, {8, 5}, {6, 7}, {7, 6}, {7, 8}, {8, 7}}); AStarSearchEngine engine(0.5); for (auto func : getOperation().getOps()) { Region& region = func.getFunctionBody(); - // Stage 1: Apply initial program-to-hardware mapping strategy. + SmallVector dynQubits(map_range( + region.getOps(), [](AllocOp op) { return op.getResult(); })); - // Find circuit qubits (via their allocations). - Circuit circ; - circ.extend(map_range(region.getOps(), - [](AllocOp op) { return op.getResult(); })); + //// ---- TODO: Add a nice description what happens here. - // Compute layers. - SmallVector wires; - wires.reserve(circ.size()); - for (const auto q : circ.qubits()) { - wires.emplace_back(q); - } + SmallVector wires( + llvm::map_range(dynQubits, [](auto q) { return WireIterator(q); })); SmallVector wireStarts(wires); const auto forwardLayers = @@ -564,6 +498,8 @@ struct HeuristicMappingPass return it != wireStarts[idx]; }); + //// ---- + llvm::dbgs() << "forward:\n"; for (const auto& layer : forwardLayers) { layer.dump(); @@ -576,22 +512,25 @@ struct HeuristicMappingPass llvm::dbgs() << " --- layer end ---\n"; } - // TODO: More initial layout methods. - auto layout = getIdentityLayout(arch); + //// ---- TODO: Add a nice description what happens here. - // Stage 2: Recomputing starting program-to-hardware mapping by - // repeating forwards and backwards traversals. + auto layout = Layout::identity(arch.nqubits()); const std::size_t repeats = 10; for (std::size_t i = 0; i < repeats; ++i) { process(forwardLayers, engine, arch, layout); process(backwardLayers, engine, arch, layout); } - layout.dump(); + //// ---- + + //// ---- TODO: Add a nice description what happens here. + + place(dynQubits, layout, rewriter); + const auto statQubits = + map_range(region.getOps(), + [](StaticOp op) { return op.getResult(); }); - // Stage 3: Apply mapping and final traversal. - // circ.staticize(mapping, rewriter); - // forward(circ, arch, true); + //// ---- } } @@ -606,7 +545,7 @@ struct HeuristicMappingPass while (true) { Layer l; - // SetVector> ssaFront; + // SetVector>(std::prev(it).qubit())); + // cast dynQubits, Layout& layout, + IRRewriter& rewriter) { + for (const auto [prog, q] : llvm::enumerate(dynQubits)) { + const auto hw = layout.getHardwareIndex(prog); + rewriter.setInsertionPoint(q.getDefiningOp()); + rewriter.replaceOpWithNewOp(q.getDefiningOp(), hw); } - return layout; } // TODO: Naming. static void process(ArrayRef layers, const AStarSearchEngine& engine, const Architecture& arch, - ThinLayout& layout) { + Layout& layout) { for (const auto& layer : layers) { - const auto swaps = - engine.route({layer}, layout, arch); // TODO: Check optional. + // TODO: Check optional. + const auto swaps = engine.route({layer}, layout, arch); for (const auto [hw0, hw1] : *swaps) { const auto [prog0, prog1] = layout.getProgramIndices(hw0, hw1); layout.swap(prog0, prog1); From 50ab0f063b22338bdb3db5e554770b23eb60bfdf Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 26 Feb 2026 13:41:58 +0100 Subject: [PATCH 05/37] Improve placement logic --- .../Passes/Transpilation/HeuristicMapping.cpp | 47 ++++++++++++++----- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp b/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp index 74ed4af08b..94b381e3fb 100644 --- a/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp +++ b/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -26,6 +27,8 @@ #include #include #include +#include +#include #include #include #include @@ -467,8 +470,6 @@ struct HeuristicMappingPass using HeuristicMappingPassBase::HeuristicMappingPassBase; void runOnOperation() override { - IRRewriter rewriter(&getContext()); - // TODO: Hardcoded architecture. Architecture arch("RigettiNovera", 9, {{0, 3}, {3, 0}, {0, 1}, {1, 0}, {1, 4}, {4, 1}, @@ -525,10 +526,9 @@ struct HeuristicMappingPass //// ---- TODO: Add a nice description what happens here. - place(dynQubits, layout, rewriter); - const auto statQubits = - map_range(region.getOps(), - [](StaticOp op) { return op.getResult(); }); + IRRewriter rewriter(&getContext()); + rewriter.setInsertionPointToStart(®ion.front()); + const auto statQubits = place(dynQubits, layout, rewriter); //// ---- } @@ -605,21 +605,44 @@ struct HeuristicMappingPass return layers; } - static void place(ArrayRef dynQubits, Layout& layout, - IRRewriter& rewriter) { + static SmallVector + place(const ArrayRef dynQubits, const Layout& layout, + IRRewriter& rewriter) { + SmallVector statQubits; + statQubits.reserve(layout.getNumQubits()); + + // 1. Replace existing dynamic allocations with mapped static ones. for (const auto [prog, q] : llvm::enumerate(dynQubits)) { const auto hw = layout.getHardwareIndex(prog); - rewriter.setInsertionPoint(q.getDefiningOp()); - rewriter.replaceOpWithNewOp(q.getDefiningOp(), hw); + Operation* op = q.getDefiningOp(); + + rewriter.setInsertionPoint(op); + auto staticOp = rewriter.replaceOpWithNewOp(op, hw); + statQubits.push_back(staticOp.getQubit()); + } + + // Ensure subsequent insertions happen after the last replaced op. + if (!statQubits.empty()) { + rewriter.setInsertionPointAfter(statQubits.back().getDefiningOp()); } + + // 2. Create static qubits for the remaining (unused) hardware indices. + for (std::size_t prog = dynQubits.size(); prog < layout.getNumQubits(); + ++prog) { + const auto hw = layout.getHardwareIndex(prog); + auto staticOp = rewriter.create(rewriter.getUnknownLoc(), hw); + statQubits.push_back(staticOp.getQubit()); + } + + return statQubits; } - // TODO: Naming. + /// TODO: Naming. static void process(ArrayRef layers, const AStarSearchEngine& engine, const Architecture& arch, Layout& layout) { for (const auto& layer : layers) { - // TODO: Check optional. + /// TODO: Check optional. const auto swaps = engine.route({layer}, layout, arch); for (const auto [hw0, hw1] : *swaps) { const auto [prog0, prog1] = layout.getProgramIndices(hw0, hw1); From 7334ed3133ed26327989150c4002d1360902ed06 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 3 Mar 2026 09:35:45 +0100 Subject: [PATCH 06/37] Implement routing logic --- mlir/lib/Compiler/CompilerPipeline.cpp | 2 +- .../Passes/Transpilation/HeuristicMapping.cpp | 418 +++++++++++------- 2 files changed, 247 insertions(+), 173 deletions(-) diff --git a/mlir/lib/Compiler/CompilerPipeline.cpp b/mlir/lib/Compiler/CompilerPipeline.cpp index 00a422d9bc..6b50daeacd 100644 --- a/mlir/lib/Compiler/CompilerPipeline.cpp +++ b/mlir/lib/Compiler/CompilerPipeline.cpp @@ -149,7 +149,7 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, // Stage 5: Optimization passes // TODO: Add optimization passes pm.addPass(qco::createHeuristicMappingPass()); - addCleanupPasses(pm); + // addCleanupPasses(pm); if (failed(pm.run(module))) { return failure(); } diff --git a/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp b/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp index 94b381e3fb..0c1f237f2c 100644 --- a/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp +++ b/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp @@ -19,11 +19,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -45,32 +47,33 @@ struct HeuristicMappingPass : impl::HeuristicMappingPassBase { private: using QubitValue = TypedValue; + using IndexGate = std::pair; + using IndexGateSet = DenseSet; + enum Direction : std::uint8_t { Forward, Backward }; + + /// @details Using SetVector ensure a deterministic order. struct Layer { - /// @brief Set of two-qubit gates. - DenseSet> gates; - -#ifndef NDEBUG - LLVM_DUMP_METHOD void dump(llvm::raw_ostream& os = llvm::dbgs()) const { - os << "gates= "; - for (const auto [i1, i2] : gates) { - os << "(" << i1 << ", " << i2 << ") "; - } - os << "\n"; + SetVector indices; + SetVector ops; + + /// @brief Add a two-qubit gate to the layer. + void add(const IndexGate& gate, Operation* op) { + indices.insert(gate); + ops.insert(op); } -#endif - }; - enum Direction : std::uint8_t { Forward, Backward }; + /// @returns true if there are no two-qubit gates in this layer. + [[nodiscard]] bool empty() const { return ops.empty(); } + }; /** * @brief A quantum accelerator's architecture. - * @details Computes all-shortest paths at construction. */ - class Architecture { + class [[nodiscard]] Architecture { public: - using CouplingSet = mlir::DenseSet>; - using NeighbourVector = mlir::SmallVector>; + using CouplingSet = DenseSet>; + using NeighbourVector = SmallVector>; explicit Architecture(std::string name, std::size_t nqubits, CouplingSet couplingSet) @@ -141,13 +144,12 @@ struct HeuristicMappingPass /** * @brief Collect all neighbours of @p u. */ - [[nodiscard]] llvm::SmallVector - neighboursOf(uint32_t u) const { + [[nodiscard]] SmallVector neighboursOf(uint32_t u) const { return neighbours_[u]; } private: - using Matrix = llvm::SmallVector>; + using Matrix = SmallVector>; /** * @brief Find all shortest paths in the coupling map between two qubits. @@ -300,19 +302,31 @@ struct HeuristicMappingPass return programToHardware_.size(); } + void dump() { + llvm::dbgs() << "prog= "; + for (std::size_t i = 0; i < getNumQubits(); ++i) { + llvm::dbgs() << i << " "; + } + llvm::dbgs() << "\nhw= "; + for (std::size_t i = 0; i < getNumQubits(); ++i) { + llvm::dbgs() << programToHardware_[i] << ' '; + } + llvm::dbgs() << '\n'; + } + protected: /** * @brief Maps a program qubit index to its hardware index. */ - mlir::SmallVector programToHardware_; + SmallVector programToHardware_; /** * @brief Maps a hardware qubit index to its program index. */ - mlir::SmallVector hardwareToProgram_; + SmallVector hardwareToProgram_; }; - class AStarSearchEngine { + class [[nodiscard]] AStarSearchEngine { public: explicit AStarSearchEngine(float alpha) { params_.alpha = alpha; } @@ -322,7 +336,7 @@ struct HeuristicMappingPass }; struct Node { - SmallVector> sequence; + SmallVector sequence; Layout layout; float f; @@ -336,9 +350,8 @@ struct HeuristicMappingPass * @brief Construct a non-root node from its parent node. Apply the given * swap to the layout of the parent node and evaluate the cost. */ - Node(const Node& parent, std::pair swap, - const Layer& layer, const Architecture& arch, - const Parameters& params) + Node(const Node& parent, IndexGate swap, ArrayRef layer, + const Architecture& arch, const Parameters& params) : sequence(parent.sequence), layout(parent.layout), f(0) { /// Apply node-specific swap to given layout. layout.swap(layout.getProgramIndex(swap.first), @@ -355,12 +368,12 @@ struct HeuristicMappingPass * @brief Return true if the current sequence of SWAPs makes all gates * executable. */ - [[nodiscard]] bool isGoal(Layer layer, const Architecture& arch) const { - return llvm::all_of( - layer.gates, [&](const std::pair gate) { - return arch.areAdjacent(layout.getHardwareIndex(gate.first), - layout.getHardwareIndex(gate.second)); - }); + [[nodiscard]] bool isGoal(ArrayRef layer, + const Architecture& arch) const { + return llvm::all_of(layer, [&](const IndexGate gate) { + return arch.areAdjacent(layout.getHardwareIndex(gate.first), + layout.getHardwareIndex(gate.second)); + }); } /** @@ -389,10 +402,10 @@ struct HeuristicMappingPass * between its hardware qubits. Intuitively, this is the number of SWAPs * that a naive router would insert to route the layers. */ - [[nodiscard]] float h(const Layer& layer, + [[nodiscard]] float h(ArrayRef layer, const Architecture& arch) const { float costs{0}; - for (const auto [prog0, prog1] : layer.gates) { + for (const auto [prog0, prog1] : layer) { const auto [hw0, hw1] = layout.getHardwareIndices(prog0, prog1); const std::size_t nswaps = arch.distanceBetween(hw0, hw1) - 1; costs += static_cast(nswaps); @@ -405,15 +418,14 @@ struct HeuristicMappingPass std::priority_queue, std::greater<>>; public: - [[nodiscard]] std::optional< - SmallVector>> - route(const Layer& layer, const Layout& layout, + [[nodiscard]] std::optional> + route(ArrayRef layer, const Layout& layout, const Architecture& arch) const { Node root(layout); /// Early exit. No SWAPs required: if (root.isGoal(layer, arch)) { - return SmallVector>{}; + return SmallVector{}; } /// Initialize queue. @@ -438,21 +450,20 @@ struct HeuristicMappingPass private: /// @brief Expand frontier with all neighbouring SWAPs in the current front. - void expand(MinQueue& frontier, const Node& parent, const Layer& layer, - const Architecture& arch) const { - DenseSet> expansionSet{}; + void expand(MinQueue& frontier, const Node& parent, + ArrayRef layer, const Architecture& arch) const { + DenseSet expansionSet{}; if (!parent.sequence.empty()) { expansionSet.insert(parent.sequence.back()); } - for (const std::pair gate : layer.gates) { + for (const IndexGate& gate : layer) { for (const auto prog : {gate.first, gate.second}) { const auto hw0 = parent.layout.getHardwareIndex(prog); for (const auto hw1 : arch.neighboursOf(hw0)) { /// Ensure consistent hashing/comparison. - const std::pair swap = - std::minmax(hw0, hw1); + const IndexGate swap = std::minmax(hw0, hw1); if (!expansionSet.insert(swap).second) { continue; } @@ -470,6 +481,8 @@ struct HeuristicMappingPass using HeuristicMappingPassBase::HeuristicMappingPassBase; void runOnOperation() override { + constexpr std::size_t repeats = 10; + // TODO: Hardcoded architecture. Architecture arch("RigettiNovera", 9, {{0, 3}, {3, 0}, {0, 1}, {1, 0}, {1, 4}, {4, 1}, @@ -478,177 +491,238 @@ struct HeuristicMappingPass {5, 8}, {8, 5}, {6, 7}, {7, 6}, {7, 8}, {8, 7}}); AStarSearchEngine engine(0.5); + IRRewriter rewriter(&getContext()); for (auto func : getOperation().getOps()) { - Region& region = func.getFunctionBody(); + rewriter.setInsertionPointToStart(&func.getFunctionBody().front()); - SmallVector dynQubits(map_range( - region.getOps(), [](AllocOp op) { return op.getResult(); })); + SmallVector qubits( + map_range(func.getFunctionBody().getOps(), + [](AllocOp op) { return op.getResult(); })); //// ---- TODO: Add a nice description what happens here. - SmallVector wires( - llvm::map_range(dynQubits, [](auto q) { return WireIterator(q); })); - SmallVector wireStarts(wires); - - const auto forwardLayers = - getLayers(wires, [&](std::size_t, const WireIterator& it) { - return it != std::default_sentinel; - }); - const auto backwardLayers = getLayers( - wires, [&](std::size_t idx, const WireIterator& it) { - return it != wireStarts[idx]; - }); + auto wires = toWires(qubits); + const auto fLayers = collectLayers(wires); + const auto bLayers = collectLayers(wires); - //// ---- - - llvm::dbgs() << "forward:\n"; - for (const auto& layer : forwardLayers) { - layer.dump(); - llvm::dbgs() << " --- layer end ---\n"; + Layout layout = Layout::identity(arch.nqubits()); + for (std::size_t r = 0; r < repeats; ++r) { + route(fLayers, layout, engine, arch); + route(bLayers, layout, engine, arch); } - llvm::dbgs() << "backward:\n"; - for (const auto& layer : backwardLayers) { - layer.dump(); - llvm::dbgs() << " --- layer end ---\n"; - } + layout.dump(); - //// ---- TODO: Add a nice description what happens here. - - auto layout = Layout::identity(arch.nqubits()); - const std::size_t repeats = 10; - for (std::size_t i = 0; i < repeats; ++i) { - process(forwardLayers, engine, arch, layout); - process(backwardLayers, engine, arch, layout); - } + qubits = place(qubits, layout, func.getFunctionBody(), rewriter); + wires = toWires(qubits); //// ---- //// ---- TODO: Add a nice description what happens here. - IRRewriter rewriter(&getContext()); - rewriter.setInsertionPointToStart(®ion.front()); - const auto statQubits = place(dynQubits, layout, rewriter); + route(fLayers, layout, func.getFunctionBody(), rewriter, engine, arch); //// ---- + + sortTopologically(&func.getFunctionBody().front()); } } private: - template - [[nodiscard]] static SmallVector - getLayers(MutableArrayRef wires, EndCheckF endCheck) { - constexpr std::size_t step = d == Forward ? 1 : -1; + [[nodiscard]] static SmallVector + place(const ArrayRef dynQubits, const Layout& layout, + Region& functionBody, IRRewriter& rewriter) { + SmallVector statQubits(layout.getNumQubits()); - SmallVector layers; - DenseMap occ; + // 1. Replace existing dynamic allocations with mapped static ones. + for (const auto [prog, q] : llvm::enumerate(dynQubits)) { + rewriter.setInsertionPoint(q.getDefiningOp()); - while (true) { - Layer l; - // SetVector(it.operation()) - .Case([&](UnitaryOpInterface op) { - assert(op.getNumQubits() > 0 && op.getNumQubits() <= 2); - - if (op.getNumQubits() == 1) { - std::ranges::advance(it, step); - return WalkResult::advance(); - } - - // ssaFront.insert( - // cast([&](auto) { - std::ranges::advance(it, step); - return WalkResult::advance(); - }) - .Default([&](Operation* op) { - report_fatal_error("unknown op in wireiterator use: " + - op->getName().getStringRef()); - return WalkResult::interrupt(); - }); - - if (res.wasInterrupted()) { + const auto hw = layout.getHardwareIndex(prog); + statQubits[hw] = + rewriter.replaceOpWithNewOp(q.getDefiningOp(), hw) + .getQubit(); + } + + // 2. Create static qubits for the remaining (unused) hardware indices. + for (std::size_t prog = dynQubits.size(); prog < layout.getNumQubits(); + ++prog) { + rewriter.setInsertionPointToStart(&functionBody.front()); + + const auto hw = layout.getHardwareIndex(prog); + const auto q = + rewriter.create(rewriter.getUnknownLoc(), hw).getQubit(); + statQubits[hw] = q; + + rewriter.setInsertionPoint(functionBody.back().getTerminator()); + rewriter.create(rewriter.getUnknownLoc(), q); + } + + return statQubits; + } + + static void route(ArrayRef layers, Layout& layout, + const AStarSearchEngine& engine, const Architecture& arch) { + + for (const auto& layer : layers) { + const auto ref = layer.indices.getArrayRef(); + const auto swaps = engine.route(ref, layout, arch); + for (const auto [hw0, hw1] : *swaps) { + const auto [prog0, prog1] = layout.getProgramIndices(hw0, hw1); + + layout.swap(prog0, prog1); + } + } + } + + static void route(ArrayRef layers, Layout& layout, + Region& functionBody, IRRewriter& rewriter, + const AStarSearchEngine& engine, const Architecture& arch) { + // Collect all static qubits in array s.t. qubits[i] is the i-th static + // qubit. + SmallVector qubits(layout.getNumQubits()); + for (auto staticOp : functionBody.getOps()) { + qubits[staticOp.getIndex()] = staticOp.getQubit(); + } + + // Transform qubits to wires. + auto wires = toWires(qubits); + for (const auto& layer : layers) { + llvm::dbgs() << "--- layer start ---\n"; + for (auto& it : wires) { + + while (true) { + auto next = std::next(it); + if (isa(next.operation())) { break; } + + if (auto op = dyn_cast(next.operation())) { + if (op.getNumQubits() > 1) { + break; + } + } + + ++it; } } + llvm::dbgs() << "wires:\n"; - // If there are no more two-qubit gates, early exit. - if (l.gates.empty()) { - break; + for (auto& wire : wires) { + wire.qubit().dump(); } - for (auto [i1, i2] : l.gates) { - std::ranges::advance(wires[i1], step); - std::ranges::advance(wires[i2], step); + const auto swaps = + engine.route(layer.indices.getArrayRef(), layout, arch); + for (const auto [hw0, hw1] : *swaps) { + const auto [prog0, prog1] = layout.getProgramIndices(hw0, hw1); + + const auto in0 = wires[hw0].qubit(); + const auto in1 = wires[hw1].qubit(); + + auto swapOp = + rewriter.create(rewriter.getUnknownLoc(), in0, in1); + const auto out0 = swapOp.getQubit0Out(); + const auto out1 = swapOp.getQubit1Out(); + + rewriter.replaceAllUsesExcept(in0, out1, swapOp); + rewriter.replaceAllUsesExcept(in1, out0, swapOp); + + ++wires[hw0]; + ++wires[hw1]; + + layout.swap(prog0, prog1); + } + + for (const auto [prog0, prog1] : layer.indices) { + ++wires[layout.getHardwareIndex(prog0)]; + ++wires[layout.getHardwareIndex(prog1)]; } - layers.emplace_back(l); + llvm::dbgs() << "--- layer end ---\n"; } + } - return layers; + template + static SmallVector toWires(QubitRange qubits) { + return SmallVector( + map_range(qubits, [](auto q) { return WireIterator(q); })); } - static SmallVector - place(const ArrayRef dynQubits, const Layout& layout, - IRRewriter& rewriter) { - SmallVector statQubits; - statQubits.reserve(layout.getNumQubits()); + template Layer advance(MutableArrayRef wires) { + constexpr std::size_t step = d == Forward ? 1 : -1; + const auto stop = [](const WireIterator& it) { + if constexpr (d == Forward) { + return it != std::default_sentinel; + } else { + return !isa(it.operation()); + } + }; - // 1. Replace existing dynamic allocations with mapped static ones. - for (const auto [prog, q] : llvm::enumerate(dynQubits)) { - const auto hw = layout.getHardwareIndex(prog); - Operation* op = q.getDefiningOp(); + Layer layer{}; + DenseMap hits; + for (const auto [index, it] : llvm::enumerate(wires)) { + while (stop(it)) { + const auto res = + TypeSwitch(it.operation()) + .Case([&](UnitaryOpInterface op) { + assert(op.getNumQubits() > 0 && op.getNumQubits() <= 2); - rewriter.setInsertionPoint(op); - auto staticOp = rewriter.replaceOpWithNewOp(op, hw); - statQubits.push_back(staticOp.getQubit()); + if (op.getNumQubits() == 1) { + std::ranges::advance(it, step); + return WalkResult::advance(); + } + + if (hits.contains(op)) { + const auto otherIndex = hits[op]; + layer.add(std::make_pair(index, otherIndex), op); + hits.erase(op); + } else { + hits.try_emplace(op, index); + } + + return WalkResult::interrupt(); + }) + .template Case([&](auto) { + std::ranges::advance(it, step); + return WalkResult::advance(); + }) + .Default([&](Operation* op) { + report_fatal_error("unknown op in wireiterator use: " + + op->getName().getStringRef()); + return WalkResult::interrupt(); + }); + + if (res.wasInterrupted()) { + break; + } + } } - // Ensure subsequent insertions happen after the last replaced op. - if (!statQubits.empty()) { - rewriter.setInsertionPointAfter(statQubits.back().getDefiningOp()); - } + return layer; + } - // 2. Create static qubits for the remaining (unused) hardware indices. - for (std::size_t prog = dynQubits.size(); prog < layout.getNumQubits(); - ++prog) { - const auto hw = layout.getHardwareIndex(prog); - auto staticOp = rewriter.create(rewriter.getUnknownLoc(), hw); - statQubits.push_back(staticOp.getQubit()); - } + template + SmallVector collectLayers(MutableArrayRef wires) { + constexpr std::size_t step = d == Forward ? 1 : -1; - return statQubits; - } + SmallVector layers; + while (true) { + const auto layer = advance(wires); + if (layer.empty()) { + break; + } - /// TODO: Naming. - static void process(ArrayRef layers, - const AStarSearchEngine& engine, const Architecture& arch, - Layout& layout) { - for (const auto& layer : layers) { - /// TODO: Check optional. - const auto swaps = engine.route({layer}, layout, arch); - for (const auto [hw0, hw1] : *swaps) { - const auto [prog0, prog1] = layout.getProgramIndices(hw0, hw1); - layout.swap(prog0, prog1); + layers.emplace_back(layer); + + for (const auto [i1, i2] : layer.indices) { + std::ranges::advance(wires[i1], step); + std::ranges::advance(wires[i2], step); } } + + return layers; } }; } // namespace mlir::qco From be8d5e8f86ce4499abc24553a999ad717640390b Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 3 Mar 2026 09:43:10 +0100 Subject: [PATCH 07/37] Undo irrelevant change --- mlir/tools/mqt-cc/mqt-cc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp index f9785af62f..fd25e8d5ed 100644 --- a/mlir/tools/mqt-cc/mqt-cc.cpp +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -140,7 +140,7 @@ int main(int argc, char** argv) { // Set up MLIR context with all required dialects DialectRegistry registry; registry.insert(); - registry.insert(); + registry.insert(); registry.insert(); registry.insert(); registry.insert(); From abebcece43fa98db5a258d1eac1914de533de4ad Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 3 Mar 2026 09:47:48 +0100 Subject: [PATCH 08/37] Include queue --- mlir/lib/Passes/Transpilation/HeuristicMapping.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp b/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp index 0c1f237f2c..29ed7ca03c 100644 --- a/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp +++ b/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #define DEBUG_TYPE "heuristic-mapping-pass" From bf558157aa403de996cd49f8eeda6aa646a0ffcb Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 3 Mar 2026 15:26:56 +0100 Subject: [PATCH 09/37] Reimplement lookahead --- .../Passes/Transpilation/HeuristicMapping.cpp | 423 ++++++++---------- 1 file changed, 198 insertions(+), 225 deletions(-) diff --git a/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp b/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp index 29ed7ca03c..e61da3cf0a 100644 --- a/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp +++ b/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp @@ -14,11 +14,13 @@ #include "mlir/Dialect/QCO/Utils/WireIterator.h" #include "mlir/Passes/Passes.h" +#include +#include +#include #include +#include #include -#include #include -#include #include #include #include @@ -35,7 +37,14 @@ #include #include #include +#include #include +#include +#include +#include +#include +#include +#include #define DEBUG_TYPE "heuristic-mapping-pass" @@ -50,23 +59,9 @@ struct HeuristicMappingPass using QubitValue = TypedValue; using IndexGate = std::pair; using IndexGateSet = DenseSet; + using Layer = SetVector; - enum Direction : std::uint8_t { Forward, Backward }; - - /// @details Using SetVector ensure a deterministic order. - struct Layer { - SetVector indices; - SetVector ops; - - /// @brief Add a two-qubit gate to the layer. - void add(const IndexGate& gate, Operation* op) { - indices.insert(gate); - ops.insert(op); - } - - /// @returns true if there are no two-qubit gates in this layer. - [[nodiscard]] bool empty() const { return ops.empty(); } - }; + enum class Direction : std::uint8_t { Forward, Backward }; /** * @brief A quantum accelerator's architecture. @@ -103,34 +98,6 @@ struct HeuristicMappingPass return couplingSet_.contains({u, v}); } - /** - * @brief Collect the shortest SWAP sequence to make @p u and @p v adjacent. - * @returns The SWAP sequence from the destination (v) to source (u) qubit. - */ - [[nodiscard]] llvm::SmallVector> - shortestSWAPsBetween(uint32_t u, uint32_t v) const { - if (u == v) { - return {}; - } - - if (prev_[u][v] == UINT64_MAX) { - throw std::domain_error("No path between qubits " + std::to_string(u) + - " and " + std::to_string(v)); - } - - llvm::SmallVector> swaps; - uint32_t last = v; - uint32_t curr = prev_[u][v]; - - while (curr != u) { - swaps.emplace_back(last, curr); // Insert SWAP(last, curr). - last = curr; - curr = prev_[u][curr]; - } - - return swaps; - } - /** * @brief Return the length of the shortest path between @p u and @p v. */ @@ -214,6 +181,11 @@ struct HeuristicMappingPass */ class [[nodiscard]] Layout { public: + /** + * @brief Constructs the identity (i->i) layout. + * @param nqubits The number of qubits. + * @return The identity layout. + */ static Layout identity(const std::size_t nqubits) { Layout layout(nqubits); for (std::size_t i = 0; i < nqubits; ++i) { @@ -329,11 +301,23 @@ struct HeuristicMappingPass class [[nodiscard]] AStarSearchEngine { public: - explicit AStarSearchEngine(float alpha) { params_.alpha = alpha; } + explicit AStarSearchEngine(const float alpha, const float lambda, + const std::size_t nlookahead, Architecture& arch) + : w_(alpha, lambda, nlookahead), arch_(&arch) {} private: - struct Parameters { - float alpha{}; + struct Weights { + Weights(const float alpha, const float lambda, + const std::size_t nlookahead) + : alpha(alpha), decay(1 + nlookahead) { + decay[0] = 1.; + for (std::size_t i = 1; i < decay.size(); ++i) { + decay[i] = decay[i - 1] * lambda; + } + } + + float alpha; + SmallVector decay; }; struct Node { @@ -351,8 +335,8 @@ struct HeuristicMappingPass * @brief Construct a non-root node from its parent node. Apply the given * swap to the layout of the parent node and evaluate the cost. */ - Node(const Node& parent, IndexGate swap, ArrayRef layer, - const Architecture& arch, const Parameters& params) + Node(const Node& parent, IndexGate swap, ArrayRef layers, + const Architecture& arch, const Weights& w) : sequence(parent.sequence), layout(parent.layout), f(0) { /// Apply node-specific swap to given layout. layout.swap(layout.getProgramIndex(swap.first), @@ -362,14 +346,14 @@ struct HeuristicMappingPass sequence.push_back(swap); // Evaluate cost function. - f = g(params.alpha) + h(layer, arch); // NOLINT + f = g(w.alpha) + h(layers, arch, w); // NOLINT } /** - * @brief Return true if the current sequence of SWAPs makes all gates + * @returns true if the current sequence of SWAPs makes all gates * executable. */ - [[nodiscard]] bool isGoal(ArrayRef layer, + [[nodiscard]] bool isGoal(const Layer& layer, const Architecture& arch) const { return llvm::all_of(layer, [&](const IndexGate gate) { return arch.areAdjacent(layout.getHardwareIndex(gate.first), @@ -377,11 +361,6 @@ struct HeuristicMappingPass }); } - /** - * @returns The depth in the search tree. - */ - [[nodiscard]] std::size_t depth() const { return sequence.size(); } - [[nodiscard]] bool operator>(const Node& rhs) const { return f > rhs.f; } private: @@ -392,7 +371,7 @@ struct HeuristicMappingPass * SWAPs. */ [[nodiscard]] float g(float alpha) const { - return (alpha * static_cast(depth())); + return alpha * static_cast(sequence.size()); } /** @@ -403,13 +382,15 @@ struct HeuristicMappingPass * between its hardware qubits. Intuitively, this is the number of SWAPs * that a naive router would insert to route the layers. */ - [[nodiscard]] float h(ArrayRef layer, - const Architecture& arch) const { + [[nodiscard]] float h(ArrayRef layers, const Architecture& arch, + const Weights& w) const { float costs{0}; - for (const auto [prog0, prog1] : layer) { - const auto [hw0, hw1] = layout.getHardwareIndices(prog0, prog1); - const std::size_t nswaps = arch.distanceBetween(hw0, hw1) - 1; - costs += static_cast(nswaps); + for (const auto [i, layer] : llvm::enumerate(layers)) { + for (const auto [prog0, prog1] : layer) { + const auto [hw0, hw1] = layout.getHardwareIndices(prog0, prog1); + const std::size_t nswaps = arch.distanceBetween(hw0, hw1) - 1; + costs += w.decay[i] * static_cast(nswaps); + } } return costs; } @@ -420,62 +401,58 @@ struct HeuristicMappingPass public: [[nodiscard]] std::optional> - route(ArrayRef layer, const Layout& layout, - const Architecture& arch) const { + search(ArrayRef layers, const Layout& layout) { Node root(layout); - /// Early exit. No SWAPs required: - if (root.isGoal(layer, arch)) { + // Early exit. No SWAPs required: + if (root.isGoal(layers.front(), *arch_)) { return SmallVector{}; } - /// Initialize queue. + // Initialize queue. MinQueue frontier{}; frontier.emplace(root); - /// Iterative searching and expanding. + // Iterative searching and expanding. while (!frontier.empty()) { Node curr = frontier.top(); frontier.pop(); - if (curr.isGoal(layer, arch)) { + if (curr.isGoal(layers.front(), *arch_)) { return curr.sequence; } - /// Expand frontier with all neighbouring SWAPs in the current front. - expand(frontier, curr, layer, arch); - } + // Expand frontier with all neighbouring SWAPs in the current front. - return std::nullopt; - } - - private: - /// @brief Expand frontier with all neighbouring SWAPs in the current front. - void expand(MinQueue& frontier, const Node& parent, - ArrayRef layer, const Architecture& arch) const { - DenseSet expansionSet{}; + expansionSet_.clear(); - if (!parent.sequence.empty()) { - expansionSet.insert(parent.sequence.back()); - } + if (!curr.sequence.empty()) { + expansionSet_.insert(curr.sequence.back()); + } - for (const IndexGate& gate : layer) { - for (const auto prog : {gate.first, gate.second}) { - const auto hw0 = parent.layout.getHardwareIndex(prog); - for (const auto hw1 : arch.neighboursOf(hw0)) { - /// Ensure consistent hashing/comparison. - const IndexGate swap = std::minmax(hw0, hw1); - if (!expansionSet.insert(swap).second) { - continue; + for (const IndexGate& gate : layers.front()) { + for (const auto prog : {gate.first, gate.second}) { + const auto hw0 = curr.layout.getHardwareIndex(prog); + for (const auto hw1 : arch_->neighboursOf(hw0)) { + /// Ensure consistent hashing/comparison. + const IndexGate swap = std::minmax(hw0, hw1); + if (!expansionSet_.insert(swap).second) { + continue; + } + + frontier.emplace(curr, swap, layers, *arch_, w_); } - - frontier.emplace(parent, swap, layer, arch, params_); } } } + + return std::nullopt; } - Parameters params_; + private: + Weights w_; + Architecture* arch_; + DenseSet expansionSet_; }; public: @@ -483,6 +460,7 @@ struct HeuristicMappingPass void runOnOperation() override { constexpr std::size_t repeats = 10; + constexpr std::size_t nlookahead = 2; // TODO: Hardcoded architecture. Architecture arch("RigettiNovera", 9, @@ -490,108 +468,109 @@ struct HeuristicMappingPass {1, 2}, {2, 1}, {2, 5}, {5, 2}, {3, 6}, {6, 3}, {3, 4}, {4, 3}, {4, 7}, {7, 4}, {4, 5}, {5, 4}, {5, 8}, {8, 5}, {6, 7}, {7, 6}, {7, 8}, {8, 7}}); - AStarSearchEngine engine(0.5); + + AStarSearchEngine engine(0.5, 0.8, nlookahead, arch); IRRewriter rewriter(&getContext()); for (auto func : getOperation().getOps()) { rewriter.setInsertionPointToStart(&func.getFunctionBody().front()); - SmallVector qubits( + SmallVector dynamics( map_range(func.getFunctionBody().getOps(), [](AllocOp op) { return op.getResult(); })); - //// ---- TODO: Add a nice description what happens here. + auto wires = toWires(dynamics); + const auto fLayers = collectLayers(wires); + const auto bLayers = collectLayers(wires); - auto wires = toWires(qubits); - const auto fLayers = collectLayers(wires); - const auto bLayers = collectLayers(wires); + // + // Compute initial layout by routing the forward and backward layers. + // Layout layout = Layout::identity(arch.nqubits()); for (std::size_t r = 0; r < repeats; ++r) { - route(fLayers, layout, engine, arch); - route(bLayers, layout, engine, arch); + if (route(fLayers, layout, nlookahead, engine, arch).failed()) { + signalPassFailure(); + return; + } + if (route(bLayers, layout, nlookahead, engine, arch).failed()) { + signalPassFailure(); + return; + } } - layout.dump(); - qubits = place(qubits, layout, func.getFunctionBody(), rewriter); - wires = toWires(qubits); - - //// ---- - - //// ---- TODO: Add a nice description what happens here. - - route(fLayers, layout, func.getFunctionBody(), rewriter, engine, arch); - - //// ---- - + // + // Apply layout (place) and insert SWAPs (route) afterwards. + // + + const auto statics = + place(dynamics, layout, func.getFunctionBody(), rewriter); + if (route(fLayers, layout, nlookahead, statics, rewriter, engine, arch) + .failed()) { + signalPassFailure(); + return; + }; sortTopologically(&func.getFunctionBody().front()); } } private: + static llvm::LogicalResult route(ArrayRef layers, Layout& layout, + const std::size_t nlookahead, + AStarSearchEngine& engine, + const Architecture& arch) { + + for (std::size_t i = 0; i < layers.size(); ++i) { + const std::size_t len = std::min(1 + nlookahead, layers.size() - i); + const auto window = layers.slice(i, len); + const auto swaps = engine.search(window, layout); + if (!swaps) { + return llvm::failure(); + } + for (const auto [hw0, hw1] : *swaps) { + const auto [prog0, prog1] = layout.getProgramIndices(hw0, hw1); + layout.swap(prog0, prog1); + } + } + + return llvm::success(); + } + [[nodiscard]] static SmallVector place(const ArrayRef dynQubits, const Layout& layout, Region& functionBody, IRRewriter& rewriter) { - SmallVector statQubits(layout.getNumQubits()); + SmallVector statics(layout.getNumQubits()); // 1. Replace existing dynamic allocations with mapped static ones. - for (const auto [prog, q] : llvm::enumerate(dynQubits)) { + for (const auto [p, q] : llvm::enumerate(dynQubits)) { + const auto hw = layout.getHardwareIndex(p); rewriter.setInsertionPoint(q.getDefiningOp()); - - const auto hw = layout.getHardwareIndex(prog); - statQubits[hw] = - rewriter.replaceOpWithNewOp(q.getDefiningOp(), hw) - .getQubit(); + auto op = rewriter.replaceOpWithNewOp(q.getDefiningOp(), hw); + statics[hw] = op.getQubit(); } // 2. Create static qubits for the remaining (unused) hardware indices. - for (std::size_t prog = dynQubits.size(); prog < layout.getNumQubits(); - ++prog) { + for (std::size_t p = dynQubits.size(); p < layout.getNumQubits(); ++p) { rewriter.setInsertionPointToStart(&functionBody.front()); - - const auto hw = layout.getHardwareIndex(prog); - const auto q = - rewriter.create(rewriter.getUnknownLoc(), hw).getQubit(); - statQubits[hw] = q; - + const auto hw = layout.getHardwareIndex(p); + auto op = rewriter.create(rewriter.getUnknownLoc(), hw); rewriter.setInsertionPoint(functionBody.back().getTerminator()); - rewriter.create(rewriter.getUnknownLoc(), q); + rewriter.create(rewriter.getUnknownLoc(), op.getQubit()); + statics[hw] = op.getQubit(); } - return statQubits; - } - - static void route(ArrayRef layers, Layout& layout, - const AStarSearchEngine& engine, const Architecture& arch) { - - for (const auto& layer : layers) { - const auto ref = layer.indices.getArrayRef(); - const auto swaps = engine.route(ref, layout, arch); - for (const auto [hw0, hw1] : *swaps) { - const auto [prog0, prog1] = layout.getProgramIndices(hw0, hw1); - - layout.swap(prog0, prog1); - } - } + return statics; } - static void route(ArrayRef layers, Layout& layout, - Region& functionBody, IRRewriter& rewriter, - const AStarSearchEngine& engine, const Architecture& arch) { - // Collect all static qubits in array s.t. qubits[i] is the i-th static - // qubit. - SmallVector qubits(layout.getNumQubits()); - for (auto staticOp : functionBody.getOps()) { - qubits[staticOp.getIndex()] = staticOp.getQubit(); - } - - // Transform qubits to wires. - auto wires = toWires(qubits); - for (const auto& layer : layers) { - llvm::dbgs() << "--- layer start ---\n"; + static LogicalResult route(ArrayRef layers, Layout& layout, + const std::size_t nlookahead, + ArrayRef statics, IRRewriter& rewriter, + AStarSearchEngine& engine, + const Architecture& arch) { + auto wires = toWires(statics); + for (std::size_t i = 0; i < layers.size(); ++i) { for (auto& it : wires) { - while (true) { auto next = std::next(it); if (isa(next.operation())) { @@ -607,14 +586,14 @@ struct HeuristicMappingPass ++it; } } - llvm::dbgs() << "wires:\n"; - for (auto& wire : wires) { - wire.qubit().dump(); + const std::size_t len = std::min(1 + nlookahead, layers.size() - i); + const auto window = layers.slice(i, len); + const auto swaps = engine.search(window, layout); + if (!swaps) { + return llvm::failure(); } - const auto swaps = - engine.route(layer.indices.getArrayRef(), layout, arch); for (const auto [hw0, hw1] : *swaps) { const auto [prog0, prog1] = layout.getProgramIndices(hw0, hw1); @@ -635,13 +614,13 @@ struct HeuristicMappingPass layout.swap(prog0, prog1); } - for (const auto [prog0, prog1] : layer.indices) { + for (const auto [prog0, prog1] : layers[i]) { ++wires[layout.getHardwareIndex(prog0)]; ++wires[layout.getHardwareIndex(prog1)]; } - - llvm::dbgs() << "--- layer end ---\n"; } + + return llvm::success(); } template @@ -650,77 +629,71 @@ struct HeuristicMappingPass map_range(qubits, [](auto q) { return WireIterator(q); })); } - template Layer advance(MutableArrayRef wires) { - constexpr std::size_t step = d == Forward ? 1 : -1; + template + SmallVector collectLayers(MutableArrayRef wires) { + constexpr std::size_t step = d == Direction::Forward ? 1 : -1; const auto stop = [](const WireIterator& it) { - if constexpr (d == Forward) { + if constexpr (d == Direction::Forward) { return it != std::default_sentinel; } else { return !isa(it.operation()); } }; - Layer layer{}; + SmallVector layers; DenseMap hits; - for (const auto [index, it] : llvm::enumerate(wires)) { - while (stop(it)) { - const auto res = - TypeSwitch(it.operation()) - .Case([&](UnitaryOpInterface op) { - assert(op.getNumQubits() > 0 && op.getNumQubits() <= 2); - - if (op.getNumQubits() == 1) { + + while (true) { + Layer layer{}; + for (const auto [index, it] : llvm::enumerate(wires)) { + while (stop(it)) { + const auto res = + TypeSwitch(it.operation()) + .Case([&](UnitaryOpInterface op) { + assert(op.getNumQubits() > 0 && op.getNumQubits() <= 2); + + if (op.getNumQubits() == 1) { + std::ranges::advance(it, step); + return WalkResult::advance(); + } + + if (hits.contains(op)) { + const auto otherIndex = hits[op]; + layer.insert(std::make_pair(index, otherIndex)); + + std::ranges::advance(wires[index], step); + std::ranges::advance(wires[otherIndex], step); + + hits.erase(op); + } else { + hits.try_emplace(op, index); + } + + return WalkResult::interrupt(); + }) + .template Case([&](auto) { std::ranges::advance(it, step); return WalkResult::advance(); - } - - if (hits.contains(op)) { - const auto otherIndex = hits[op]; - layer.add(std::make_pair(index, otherIndex), op); - hits.erase(op); - } else { - hits.try_emplace(op, index); - } - - return WalkResult::interrupt(); - }) - .template Case([&](auto) { - std::ranges::advance(it, step); - return WalkResult::advance(); - }) - .Default([&](Operation* op) { - report_fatal_error("unknown op in wireiterator use: " + - op->getName().getStringRef()); - return WalkResult::interrupt(); - }); - - if (res.wasInterrupted()) { - break; + }) + .Default([&](Operation* op) { + report_fatal_error("unknown op in wireiterator use: " + + op->getName().getStringRef()); + return WalkResult::interrupt(); + }); + + if (res.wasInterrupted()) { + break; + } } } - } - - return layer; - } - template - SmallVector collectLayers(MutableArrayRef wires) { - constexpr std::size_t step = d == Forward ? 1 : -1; - - SmallVector layers; - while (true) { - const auto layer = advance(wires); if (layer.empty()) { break; } layers.emplace_back(layer); - - for (const auto [i1, i2] : layer.indices) { - std::ranges::advance(wires[i1], step); - std::ranges::advance(wires[i2], step); - } + hits.clear(); } return layers; From 2ad9822c3cb176fd43884f6197311807d2578d93 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 3 Mar 2026 16:06:36 +0100 Subject: [PATCH 10/37] Clean up --- .../Passes/Transpilation/HeuristicMapping.cpp | 182 ++++++++++-------- 1 file changed, 106 insertions(+), 76 deletions(-) diff --git a/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp b/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp index e61da3cf0a..ffa9fde82e 100644 --- a/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp +++ b/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -37,7 +36,6 @@ #include #include #include -#include #include #include #include @@ -400,7 +398,7 @@ struct HeuristicMappingPass std::priority_queue, std::greater<>>; public: - [[nodiscard]] std::optional> + [[nodiscard]] llvm::FailureOr> search(ArrayRef layers, const Layout& layout) { Node root(layout); @@ -422,34 +420,35 @@ struct HeuristicMappingPass return curr.sequence; } - // Expand frontier with all neighbouring SWAPs in the current front. + expand(frontier, curr, layers); + } - expansionSet_.clear(); + return llvm::failure(); + } - if (!curr.sequence.empty()) { - expansionSet_.insert(curr.sequence.back()); - } + private: + void expand(MinQueue& frontier, const Node& node, ArrayRef layers) { + expansionSet_.clear(); - for (const IndexGate& gate : layers.front()) { - for (const auto prog : {gate.first, gate.second}) { - const auto hw0 = curr.layout.getHardwareIndex(prog); - for (const auto hw1 : arch_->neighboursOf(hw0)) { - /// Ensure consistent hashing/comparison. - const IndexGate swap = std::minmax(hw0, hw1); - if (!expansionSet_.insert(swap).second) { - continue; - } - - frontier.emplace(curr, swap, layers, *arch_, w_); + if (!node.sequence.empty()) { + expansionSet_.insert(node.sequence.back()); + } + + for (const IndexGate& gate : layers.front()) { + for (const auto prog : {gate.first, gate.second}) { + const auto hw0 = node.layout.getHardwareIndex(prog); + for (const auto hw1 : arch_->neighboursOf(hw0)) { + /// Ensure consistent hashing/comparison. + const IndexGate swap = std::minmax(hw0, hw1); + if (!expansionSet_.insert(swap).second) { + continue; } + + frontier.emplace(node, swap, layers, *arch_, w_); } } } - - return std::nullopt; } - - private: Weights w_; Architecture* arch_; DenseSet expansionSet_; @@ -472,42 +471,33 @@ struct HeuristicMappingPass AStarSearchEngine engine(0.5, 0.8, nlookahead, arch); IRRewriter rewriter(&getContext()); - for (auto func : getOperation().getOps()) { - rewriter.setInsertionPointToStart(&func.getFunctionBody().front()); - - SmallVector dynamics( - map_range(func.getFunctionBody().getOps(), - [](AllocOp op) { return op.getResult(); })); - auto wires = toWires(dynamics); - const auto fLayers = collectLayers(wires); - const auto bLayers = collectLayers(wires); + for (auto func : getOperation().getOps()) { + const auto dyn = collectDynamicQubits(func.getFunctionBody()); + const auto [ltr, rtl] = computeBidirectionalLayers(dyn); - // - // Compute initial layout by routing the forward and backward layers. - // + // Use the SABRE Approach to improve the initial layout choice (here: + // identity): Traverse the layers from left-to-right-to-left and + // cold-route along the way. Repeat this procedure "repeats" times. Layout layout = Layout::identity(arch.nqubits()); for (std::size_t r = 0; r < repeats; ++r) { - if (route(fLayers, layout, nlookahead, engine, arch).failed()) { + if (failed(routeCold(ltr, layout, nlookahead, engine))) { signalPassFailure(); return; } - if (route(bLayers, layout, nlookahead, engine, arch).failed()) { + if (failed(routeCold(ltr, layout, nlookahead, engine))) { signalPassFailure(); return; } } layout.dump(); - // - // Apply layout (place) and insert SWAPs (route) afterwards. - // + // Once the initial layout is found, replace the dynamic with static + // qubits ("placement") and hot-route the circuit layer-by-layer. - const auto statics = - place(dynamics, layout, func.getFunctionBody(), rewriter); - if (route(fLayers, layout, nlookahead, statics, rewriter, engine, arch) - .failed()) { + const auto stat = place(dyn, layout, func.getFunctionBody(), rewriter); + if (failed(routeHot(ltr, layout, nlookahead, stat, engine, rewriter))) { signalPassFailure(); return; }; @@ -516,30 +506,31 @@ struct HeuristicMappingPass } private: - static llvm::LogicalResult route(ArrayRef layers, Layout& layout, - const std::size_t nlookahead, - AStarSearchEngine& engine, - const Architecture& arch) { - - for (std::size_t i = 0; i < layers.size(); ++i) { - const std::size_t len = std::min(1 + nlookahead, layers.size() - i); - const auto window = layers.slice(i, len); - const auto swaps = engine.search(window, layout); - if (!swaps) { - return llvm::failure(); - } - for (const auto [hw0, hw1] : *swaps) { - const auto [prog0, prog1] = layout.getProgramIndices(hw0, hw1); - layout.swap(prog0, prog1); - } - } + /** + * @brief Collect dynamic qubits contained in the given function body. + * @returns a vector of SSA values produced by qco.alloc operations. + */ + [[nodiscard]] static SmallVector + collectDynamicQubits(Region& funcBody) { + return SmallVector(map_range( + funcBody.getOps(), [](AllocOp op) { return op.getResult(); })); + } - return llvm::success(); + /** + * @brief Computes forwards and backwards layers. + * @returns a pair of vectors of layers, where [0]=forward and [1]=backward. + */ + [[nodiscard]] static std::pair, SmallVector> + computeBidirectionalLayers(ArrayRef dyn) { + auto wires = toWires(dyn); + const auto ltr = collectLayers(wires); + const auto rtl = collectLayers(wires); + return std::make_pair(ltr, rtl); } [[nodiscard]] static SmallVector place(const ArrayRef dynQubits, const Layout& layout, - Region& functionBody, IRRewriter& rewriter) { + Region& funcBody, IRRewriter& rewriter) { SmallVector statics(layout.getNumQubits()); // 1. Replace existing dynamic allocations with mapped static ones. @@ -552,10 +543,10 @@ struct HeuristicMappingPass // 2. Create static qubits for the remaining (unused) hardware indices. for (std::size_t p = dynQubits.size(); p < layout.getNumQubits(); ++p) { - rewriter.setInsertionPointToStart(&functionBody.front()); + rewriter.setInsertionPointToStart(&funcBody.front()); const auto hw = layout.getHardwareIndex(p); auto op = rewriter.create(rewriter.getUnknownLoc(), hw); - rewriter.setInsertionPoint(functionBody.back().getTerminator()); + rewriter.setInsertionPoint(funcBody.back().getTerminator()); rewriter.create(rewriter.getUnknownLoc(), op.getQubit()); statics[hw] = op.getQubit(); } @@ -563,13 +554,48 @@ struct HeuristicMappingPass return statics; } - static LogicalResult route(ArrayRef layers, Layout& layout, - const std::size_t nlookahead, - ArrayRef statics, IRRewriter& rewriter, - AStarSearchEngine& engine, - const Architecture& arch) { - auto wires = toWires(statics); + /** + * @brief "Cold" routing of the given layers. + * @details Iterates over a sliding window of layers and uses the A* search + * engine to find a sequence of SWAPs that makes that layer executable. + * Instead of inserted these SWAPs into the IR, this function only updates the + * layout. + * @returns llvm::failure() if A* search isn't able to find a solution. + */ + static llvm::LogicalResult routeCold(ArrayRef layers, Layout& layout, + const std::size_t nlookahead, + AStarSearchEngine& engine) { for (std::size_t i = 0; i < layers.size(); ++i) { + const std::size_t len = std::min(1 + nlookahead, layers.size() - i); + const auto window = layers.slice(i, len); + const auto swaps = engine.search(window, layout); + if (failed(swaps)) { + return llvm::failure(); + } + for (const auto [hw0, hw1] : *swaps) { + const auto [prog0, prog1] = layout.getProgramIndices(hw0, hw1); + layout.swap(prog0, prog1); + } + } + + return llvm::success(); + } + + /** + * @brief "Hot" routing of the given layers. + * @details Iterates over a sliding window of layers and uses the A* search + * engine to find a sequence of SWAPs that makes that layer executable. + * This function inserts SWAP ops. + * @returns llvm::failure() if A* search isn't able to find a solution. + */ + static LogicalResult routeHot(ArrayRef ltr, Layout& layout, + const std::size_t nlookahead, + ArrayRef statics, + AStarSearchEngine& engine, + IRRewriter& rewriter) { + auto wires = toWires(statics); + + for (std::size_t i = 0; i < ltr.size(); ++i) { for (auto& it : wires) { while (true) { auto next = std::next(it); @@ -587,10 +613,10 @@ struct HeuristicMappingPass } } - const std::size_t len = std::min(1 + nlookahead, layers.size() - i); - const auto window = layers.slice(i, len); + const std::size_t len = std::min(1 + nlookahead, ltr.size() - i); + const auto window = ltr.slice(i, len); const auto swaps = engine.search(window, layout); - if (!swaps) { + if (failed(swaps)) { return llvm::failure(); } @@ -614,7 +640,7 @@ struct HeuristicMappingPass layout.swap(prog0, prog1); } - for (const auto [prog0, prog1] : layers[i]) { + for (const auto [prog0, prog1] : ltr[i]) { ++wires[layout.getHardwareIndex(prog0)]; ++wires[layout.getHardwareIndex(prog1)]; } @@ -623,6 +649,10 @@ struct HeuristicMappingPass return llvm::success(); } + /** + * @brief Transform a range of qubit values to a vector of wire iterators. + * @returns a vector of wire iterators. + */ template static SmallVector toWires(QubitRange qubits) { return SmallVector( @@ -630,7 +660,7 @@ struct HeuristicMappingPass } template - SmallVector collectLayers(MutableArrayRef wires) { + static SmallVector collectLayers(MutableArrayRef wires) { constexpr std::size_t step = d == Direction::Forward ? 1 : -1; const auto stop = [](const WireIterator& it) { if constexpr (d == Direction::Forward) { From 02f8492f330d4484cddd3daab19b21c7d34f2e4e Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 3 Mar 2026 16:36:37 +0100 Subject: [PATCH 11/37] Improve API --- .../Passes/Transpilation/HeuristicMapping.cpp | 84 +++++++++---------- 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp b/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp index ffa9fde82e..ffd9067c90 100644 --- a/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp +++ b/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp @@ -256,14 +256,14 @@ struct HeuristicMappingPass } /** - * @brief Swap the mapping to hardware indices of two program indices. + * @brief Swap the mapping to program indices of two hardware indices. */ - void swap(const uint32_t prog0, const uint32_t prog1) { - const uint32_t hw0 = programToHardware_[prog0]; - const uint32_t hw1 = programToHardware_[prog1]; + void swap(const uint32_t hw0, const uint32_t hw1) { + const uint32_t prog0 = hardwareToProgram_[hw0]; + const uint32_t prog1 = hardwareToProgram_[hw1]; - std::swap(programToHardware_[prog0], programToHardware_[prog1]); std::swap(hardwareToProgram_[hw0], hardwareToProgram_[hw1]); + std::swap(programToHardware_[prog0], programToHardware_[prog1]); } /** @@ -337,8 +337,7 @@ struct HeuristicMappingPass const Architecture& arch, const Weights& w) : sequence(parent.sequence), layout(parent.layout), f(0) { /// Apply node-specific swap to given layout. - layout.swap(layout.getProgramIndex(swap.first), - layout.getProgramIndex(swap.second)); + layout.swap(swap.first, swap.second); // Add swap to sequence. sequence.push_back(swap); @@ -459,7 +458,7 @@ struct HeuristicMappingPass void runOnOperation() override { constexpr std::size_t repeats = 10; - constexpr std::size_t nlookahead = 2; + constexpr std::size_t nlookahead = 3; // TODO: Hardcoded architecture. Architecture arch("RigettiNovera", 9, @@ -468,10 +467,9 @@ struct HeuristicMappingPass {3, 4}, {4, 3}, {4, 7}, {7, 4}, {4, 5}, {5, 4}, {5, 8}, {8, 5}, {6, 7}, {7, 6}, {7, 8}, {8, 7}}); - AStarSearchEngine engine(0.5, 0.8, nlookahead, arch); - IRRewriter rewriter(&getContext()); + AStarSearchEngine engine(0.5, 0.8, nlookahead, arch); for (auto func : getOperation().getOps()) { const auto dyn = collectDynamicQubits(func.getFunctionBody()); const auto [ltr, rtl] = computeBidirectionalLayers(dyn); @@ -572,9 +570,9 @@ struct HeuristicMappingPass if (failed(swaps)) { return llvm::failure(); } + for (const auto [hw0, hw1] : *swaps) { - const auto [prog0, prog1] = layout.getProgramIndices(hw0, hw1); - layout.swap(prog0, prog1); + layout.swap(hw0, hw1); } } @@ -593,56 +591,56 @@ struct HeuristicMappingPass ArrayRef statics, AStarSearchEngine& engine, IRRewriter& rewriter) { - auto wires = toWires(statics); - - for (std::size_t i = 0; i < ltr.size(); ++i) { - for (auto& it : wires) { - while (true) { - auto next = std::next(it); - if (isa(next.operation())) { - break; - } - - if (auto op = dyn_cast(next.operation())) { - if (op.getNumQubits() > 1) { - break; - } - } + constexpr auto advanceFront = [](WireIterator& it) { + while (true) { + const auto next = std::next(it); + if (isa(next.operation())) { + break; + } - ++it; + auto op = dyn_cast(next.operation()); + if (op && op.getNumQubits() > 1) { + break; } + + std::ranges::advance(it, 1); } + }; + + auto wires = toWires(statics); + for (const auto [i, layer] : llvm::enumerate(ltr)) { + llvm::for_each(wires, advanceFront); - const std::size_t len = std::min(1 + nlookahead, ltr.size() - i); + const auto len = std::min(1 + nlookahead, ltr.size() - i); const auto window = ltr.slice(i, len); const auto swaps = engine.search(window, layout); if (failed(swaps)) { return llvm::failure(); } + const auto unknown = rewriter.getUnknownLoc(); for (const auto [hw0, hw1] : *swaps) { - const auto [prog0, prog1] = layout.getProgramIndices(hw0, hw1); - const auto in0 = wires[hw0].qubit(); const auto in1 = wires[hw1].qubit(); - auto swapOp = - rewriter.create(rewriter.getUnknownLoc(), in0, in1); - const auto out0 = swapOp.getQubit0Out(); - const auto out1 = swapOp.getQubit1Out(); + auto op = rewriter.create(unknown, in0, in1); + const auto out0 = op.getQubit0Out(); + const auto out1 = op.getQubit1Out(); - rewriter.replaceAllUsesExcept(in0, out1, swapOp); - rewriter.replaceAllUsesExcept(in1, out0, swapOp); + rewriter.replaceAllUsesExcept(in0, out1, op); + rewriter.replaceAllUsesExcept(in1, out0, op); - ++wires[hw0]; - ++wires[hw1]; + // Jump over the SWAPOp. + std::ranges::advance(wires[hw0], 1); + std::ranges::advance(wires[hw1], 1); - layout.swap(prog0, prog1); + layout.swap(hw0, hw1); } - for (const auto [prog0, prog1] : ltr[i]) { - ++wires[layout.getHardwareIndex(prog0)]; - ++wires[layout.getHardwareIndex(prog1)]; + // Jump over two-qubit gates contained in the layer. + for (const auto [prog0, prog1] : layer) { + std::ranges::advance(wires[layout.getHardwareIndex(prog0)], 1); + std::ranges::advance(wires[layout.getHardwareIndex(prog1)], 1); } } From 20262f91f1d3b79eef2009d14ea1e2295f8461ed Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 3 Mar 2026 16:53:15 +0100 Subject: [PATCH 12/37] Use function for A* --- mlir/lib/Compiler/CompilerPipeline.cpp | 2 +- .../Passes/Transpilation/HeuristicMapping.cpp | 298 +++++++++--------- 2 files changed, 150 insertions(+), 150 deletions(-) diff --git a/mlir/lib/Compiler/CompilerPipeline.cpp b/mlir/lib/Compiler/CompilerPipeline.cpp index 6b50daeacd..00a422d9bc 100644 --- a/mlir/lib/Compiler/CompilerPipeline.cpp +++ b/mlir/lib/Compiler/CompilerPipeline.cpp @@ -149,7 +149,7 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, // Stage 5: Optimization passes // TODO: Add optimization passes pm.addPass(qco::createHeuristicMappingPass()); - // addCleanupPasses(pm); + addCleanupPasses(pm); if (failed(pm.run(module))) { return failure(); } diff --git a/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp b/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp index ffd9067c90..11d403804a 100644 --- a/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp +++ b/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp @@ -59,6 +59,9 @@ struct HeuristicMappingPass using IndexGateSet = DenseSet; using Layer = SetVector; + /** + * @brief Specifies the layering direction. + */ enum class Direction : std::uint8_t { Forward, Backward }; /** @@ -297,162 +300,96 @@ struct HeuristicMappingPass SmallVector hardwareToProgram_; }; - class [[nodiscard]] AStarSearchEngine { - public: - explicit AStarSearchEngine(const float alpha, const float lambda, - const std::size_t nlookahead, Architecture& arch) - : w_(alpha, lambda, nlookahead), arch_(&arch) {} - - private: - struct Weights { - Weights(const float alpha, const float lambda, - const std::size_t nlookahead) - : alpha(alpha), decay(1 + nlookahead) { - decay[0] = 1.; - for (std::size_t i = 1; i < decay.size(); ++i) { - decay[i] = decay[i - 1] * lambda; - } - } - - float alpha; - SmallVector decay; - }; - - struct Node { - SmallVector sequence; - Layout layout; - float f; - - /** - * @brief Construct a root node with the given layout. Initialize the - * sequence with an empty vector and set the cost to zero. - */ - explicit Node(Layout layout) : layout(std::move(layout)), f(0) {} - - /** - * @brief Construct a non-root node from its parent node. Apply the given - * swap to the layout of the parent node and evaluate the cost. - */ - Node(const Node& parent, IndexGate swap, ArrayRef layers, - const Architecture& arch, const Weights& w) - : sequence(parent.sequence), layout(parent.layout), f(0) { - /// Apply node-specific swap to given layout. - layout.swap(swap.first, swap.second); - - // Add swap to sequence. - sequence.push_back(swap); - - // Evaluate cost function. - f = g(w.alpha) + h(layers, arch, w); // NOLINT + struct Weights { + Weights(const float alpha, const float lambda, const std::size_t nlookahead) + : alpha(alpha), decay(1 + nlookahead) { + decay[0] = 1.; + for (std::size_t i = 1; i < decay.size(); ++i) { + decay[i] = decay[i - 1] * lambda; } + } - /** - * @returns true if the current sequence of SWAPs makes all gates - * executable. - */ - [[nodiscard]] bool isGoal(const Layer& layer, - const Architecture& arch) const { - return llvm::all_of(layer, [&](const IndexGate gate) { - return arch.areAdjacent(layout.getHardwareIndex(gate.first), - layout.getHardwareIndex(gate.second)); - }); - } - - [[nodiscard]] bool operator>(const Node& rhs) const { return f > rhs.f; } - - private: - /** - * @brief Calculate the path cost for the A* search algorithm. - * - * The path cost function is the weighted sum of the currently required - * SWAPs. - */ - [[nodiscard]] float g(float alpha) const { - return alpha * static_cast(sequence.size()); - } - - /** - * @brief Calculate the heuristic cost for the A* search algorithm. - * - * Computes the minimal number of SWAPs required to route each gate in - * each layer. For each gate, this is determined by the shortest distance - * between its hardware qubits. Intuitively, this is the number of SWAPs - * that a naive router would insert to route the layers. - */ - [[nodiscard]] float h(ArrayRef layers, const Architecture& arch, - const Weights& w) const { - float costs{0}; - for (const auto [i, layer] : llvm::enumerate(layers)) { - for (const auto [prog0, prog1] : layer) { - const auto [hw0, hw1] = layout.getHardwareIndices(prog0, prog1); - const std::size_t nswaps = arch.distanceBetween(hw0, hw1) - 1; - costs += w.decay[i] * static_cast(nswaps); - } - } - return costs; - } - }; - - using MinQueue = - std::priority_queue, std::greater<>>; - - public: - [[nodiscard]] llvm::FailureOr> - search(ArrayRef layers, const Layout& layout) { - Node root(layout); + float alpha; + SmallVector decay; + }; - // Early exit. No SWAPs required: - if (root.isGoal(layers.front(), *arch_)) { - return SmallVector{}; - } + struct Node { + SmallVector sequence; + Layout layout; + float f; - // Initialize queue. - MinQueue frontier{}; - frontier.emplace(root); + /** + * @brief Construct a root node with the given layout. Initialize the + * sequence with an empty vector and set the cost to zero. + */ + explicit Node(Layout layout) : layout(std::move(layout)), f(0) {} - // Iterative searching and expanding. - while (!frontier.empty()) { - Node curr = frontier.top(); - frontier.pop(); + /** + * @brief Construct a non-root node from its parent node. Apply the given + * swap to the layout of the parent node and evaluate the cost. + */ + Node(const Node& parent, IndexGate swap, ArrayRef layers, + const Architecture& arch, const Weights& w) + : sequence(parent.sequence), layout(parent.layout), f(0) { + /// Apply node-specific swap to given layout. + layout.swap(swap.first, swap.second); - if (curr.isGoal(layers.front(), *arch_)) { - return curr.sequence; - } + // Add swap to sequence. + sequence.push_back(swap); - expand(frontier, curr, layers); - } + // Evaluate cost function. + f = g(w.alpha) + h(layers, arch, w); // NOLINT + } - return llvm::failure(); + /** + * @returns true if the current sequence of SWAPs makes all gates + * executable. + */ + [[nodiscard]] bool isGoal(const Layer& layer, + const Architecture& arch) const { + return llvm::all_of(layer, [&](const IndexGate gate) { + return arch.areAdjacent(layout.getHardwareIndex(gate.first), + layout.getHardwareIndex(gate.second)); + }); } - private: - void expand(MinQueue& frontier, const Node& node, ArrayRef layers) { - expansionSet_.clear(); + [[nodiscard]] bool operator>(const Node& rhs) const { return f > rhs.f; } - if (!node.sequence.empty()) { - expansionSet_.insert(node.sequence.back()); - } - - for (const IndexGate& gate : layers.front()) { - for (const auto prog : {gate.first, gate.second}) { - const auto hw0 = node.layout.getHardwareIndex(prog); - for (const auto hw1 : arch_->neighboursOf(hw0)) { - /// Ensure consistent hashing/comparison. - const IndexGate swap = std::minmax(hw0, hw1); - if (!expansionSet_.insert(swap).second) { - continue; - } + private: + /** + * @brief Calculate the path cost for the A* search algorithm. + * + * The path cost function is the weighted sum of the currently required + * SWAPs. + */ + [[nodiscard]] float g(float alpha) const { + return alpha * static_cast(sequence.size()); + } - frontier.emplace(node, swap, layers, *arch_, w_); - } + /** + * @brief Calculate the heuristic cost for the A* search algorithm. + * + * Computes the minimal number of SWAPs required to route each gate in + * each layer. For each gate, this is determined by the shortest distance + * between its hardware qubits. Intuitively, this is the number of SWAPs + * that a naive router would insert to route the layers. + */ + [[nodiscard]] float h(ArrayRef layers, const Architecture& arch, + const Weights& w) const { + float costs{0}; + for (const auto [i, layer] : llvm::enumerate(layers)) { + for (const auto [prog0, prog1] : layer) { + const auto [hw0, hw1] = layout.getHardwareIndices(prog0, prog1); + const std::size_t nswaps = arch.distanceBetween(hw0, hw1) - 1; + costs += w.decay[i] * static_cast(nswaps); } } + return costs; } - Weights w_; - Architecture* arch_; - DenseSet expansionSet_; }; + using MinQueue = std::priority_queue, std::greater<>>; + public: using HeuristicMappingPassBase::HeuristicMappingPassBase; @@ -469,7 +406,7 @@ struct HeuristicMappingPass IRRewriter rewriter(&getContext()); - AStarSearchEngine engine(0.5, 0.8, nlookahead, arch); + Weights weights(0.5, 0.8, nlookahead); /// TODO: Pass Options for (auto func : getOperation().getOps()) { const auto dyn = collectDynamicQubits(func.getFunctionBody()); const auto [ltr, rtl] = computeBidirectionalLayers(dyn); @@ -480,11 +417,11 @@ struct HeuristicMappingPass Layout layout = Layout::identity(arch.nqubits()); for (std::size_t r = 0; r < repeats; ++r) { - if (failed(routeCold(ltr, layout, nlookahead, engine))) { + if (failed(routeCold(ltr, layout, arch, weights, nlookahead))) { signalPassFailure(); return; } - if (failed(routeCold(ltr, layout, nlookahead, engine))) { + if (failed(routeCold(ltr, layout, arch, weights, nlookahead))) { signalPassFailure(); return; } @@ -495,7 +432,8 @@ struct HeuristicMappingPass // qubits ("placement") and hot-route the circuit layer-by-layer. const auto stat = place(dyn, layout, func.getFunctionBody(), rewriter); - if (failed(routeHot(ltr, layout, nlookahead, stat, engine, rewriter))) { + if (failed(routeHot(ltr, layout, nlookahead, stat, arch, weights, + rewriter))) { signalPassFailure(); return; }; @@ -526,6 +464,13 @@ struct HeuristicMappingPass return std::make_pair(ltr, rtl); } + /** + * @brief Perform placement. + * @details Replaces dynamic with static qubits. Extends the computation with + * as many static qubits the architecture supports. + * @returns vector of SSA values produced by qco.static operations, ordered by + * the static index s.t. [i] = qco.static i. + */ [[nodiscard]] static SmallVector place(const ArrayRef dynQubits, const Layout& layout, Region& funcBody, IRRewriter& rewriter) { @@ -552,6 +497,60 @@ struct HeuristicMappingPass return statics; } + /** + * @brief Perform A* search to find a sequence of SWAPs that makes the layer + * executable. + * @details TODO + * @returns a vector of hardware-index pairs (each denoting a SWAP) or + * llvm::failure() if A* fails. + */ + [[nodiscard]] static llvm::FailureOr> + search(ArrayRef layers, const Layout& layout, const Architecture& arch, + const Weights& weights) { + + Node root(layout); + if (root.isGoal(layers.front(), arch)) { + return SmallVector{}; + } + + MinQueue frontier{}; + frontier.emplace(root); + DenseSet expansionSet; + + while (!frontier.empty()) { + Node curr = frontier.top(); + frontier.pop(); + + if (curr.isGoal(layers.front(), arch)) { + return curr.sequence; + } + + // TODO: Explain expansion strategy. + + expansionSet.clear(); + if (!curr.sequence.empty()) { + expansionSet.insert(curr.sequence.back()); + } + + for (const IndexGate& gate : layers.front()) { + for (const auto prog : {gate.first, gate.second}) { + const auto hw0 = curr.layout.getHardwareIndex(prog); + for (const auto hw1 : arch.neighboursOf(hw0)) { + /// Ensure consistent hashing/comparison. + const IndexGate swap = std::minmax(hw0, hw1); + if (!expansionSet.insert(swap).second) { + continue; + } + + frontier.emplace(curr, swap, layers, arch, weights); + } + } + } + } + + return llvm::failure(); + } + /** * @brief "Cold" routing of the given layers. * @details Iterates over a sliding window of layers and uses the A* search @@ -561,12 +560,13 @@ struct HeuristicMappingPass * @returns llvm::failure() if A* search isn't able to find a solution. */ static llvm::LogicalResult routeCold(ArrayRef layers, Layout& layout, - const std::size_t nlookahead, - AStarSearchEngine& engine) { + const Architecture& arch, + const Weights& weights, + const std::size_t nlookahead) { for (std::size_t i = 0; i < layers.size(); ++i) { const std::size_t len = std::min(1 + nlookahead, layers.size() - i); const auto window = layers.slice(i, len); - const auto swaps = engine.search(window, layout); + const auto swaps = search(window, layout, arch, weights); if (failed(swaps)) { return llvm::failure(); } @@ -589,8 +589,8 @@ struct HeuristicMappingPass static LogicalResult routeHot(ArrayRef ltr, Layout& layout, const std::size_t nlookahead, ArrayRef statics, - AStarSearchEngine& engine, - IRRewriter& rewriter) { + const Architecture& arch, + const Weights& weights, IRRewriter& rewriter) { constexpr auto advanceFront = [](WireIterator& it) { while (true) { const auto next = std::next(it); @@ -613,7 +613,7 @@ struct HeuristicMappingPass const auto len = std::min(1 + nlookahead, ltr.size() - i); const auto window = ltr.slice(i, len); - const auto swaps = engine.search(window, layout); + const auto swaps = search(window, layout, arch, weights); if (failed(swaps)) { return llvm::failure(); } From 62e9fe8b1527c8d28e062b85873840e50263633c Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 4 Mar 2026 11:02:20 +0100 Subject: [PATCH 13/37] Minor code improvements --- mlir/include/mlir/Passes/Passes.td | 10 + .../Passes/Transpilation/HeuristicMapping.cpp | 340 +++++++++--------- 2 files changed, 190 insertions(+), 160 deletions(-) diff --git a/mlir/include/mlir/Passes/Passes.td b/mlir/include/mlir/Passes/Passes.td index 07dcf43bdd..23e139cd12 100644 --- a/mlir/include/mlir/Passes/Passes.td +++ b/mlir/include/mlir/Passes/Passes.td @@ -21,6 +21,16 @@ def HeuristicMappingPass : Pass<"map", "mlir::ModuleOp"> { let description = [{ This pass inserts SWAP operations to ensure two-qubit gates are executable on a given target architecture. }]; + let options = [ + Option<"archName", "arch", "std::string", "", + "The name of the targeted architecture.">, + Option<"nlookahead", "nlookahead", "std::size_t", "1", + "The number of lookahead steps.">, + Option<"alpha", "alpha", "float", "1.0F", + "The alpha factor in the cost function.">, + Option<"lambda", "lambda", "float", "0.5F", + "The lambda factor in the cost function."> + ]; } #endif // QCO_PASSES diff --git a/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp b/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp index 11d403804a..73b13c22e3 100644 --- a/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp +++ b/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp @@ -37,7 +37,6 @@ #include #include #include -#include #include #include #include @@ -55,9 +54,10 @@ struct HeuristicMappingPass : impl::HeuristicMappingPassBase { private: using QubitValue = TypedValue; - using IndexGate = std::pair; + using IndexType = std::size_t; + using IndexGate = std::pair; using IndexGateSet = DenseSet; - using Layer = SetVector; + using Layer = DenseSet; /** * @brief Specifies the layering direction. @@ -69,15 +69,15 @@ struct HeuristicMappingPass */ class [[nodiscard]] Architecture { public: - using CouplingSet = DenseSet>; - using NeighbourVector = SmallVector>; + using CouplingSet = DenseSet>; + using NeighbourVector = SmallVector>; explicit Architecture(std::string name, std::size_t nqubits, CouplingSet couplingSet) : name_(std::move(name)), nqubits_(nqubits), couplingSet_(std::move(couplingSet)), neighbours_(nqubits), - dist_(nqubits, llvm::SmallVector(nqubits, UINT64_MAX)), - prev_(nqubits, llvm::SmallVector(nqubits, UINT64_MAX)) { + dist_(nqubits, SmallVector(nqubits, UINT64_MAX)), + prev_(nqubits, SmallVector(nqubits, UINT64_MAX)) { floydWarshallWithPathReconstruction(); collectNeighbours(); } @@ -95,17 +95,19 @@ struct HeuristicMappingPass /** * @brief Return true if @p u and @p v are adjacent. */ - [[nodiscard]] bool areAdjacent(uint32_t u, uint32_t v) const { + [[nodiscard]] bool areAdjacent(std::size_t u, std::size_t v) const { return couplingSet_.contains({u, v}); } /** * @brief Return the length of the shortest path between @p u and @p v. */ - [[nodiscard]] std::size_t distanceBetween(uint32_t u, uint32_t v) const { + [[nodiscard]] std::size_t distanceBetween(std::size_t u, + std::size_t v) const { if (dist_[u][v] == UINT64_MAX) { - throw std::domain_error("No path between qubits " + std::to_string(u) + - " and " + std::to_string(v)); + report_fatal_error("Floyd-warshall failed to compute the distance " + "between qubits " + + Twine(u) + " and " + Twine(v)); } return dist_[u][v]; } @@ -113,12 +115,13 @@ struct HeuristicMappingPass /** * @brief Collect all neighbours of @p u. */ - [[nodiscard]] SmallVector neighboursOf(uint32_t u) const { + [[nodiscard]] SmallVector + neighboursOf(std::size_t u) const { return neighbours_[u]; } private: - using Matrix = SmallVector>; + using Matrix = SmallVector, 0>; /** * @brief Find all shortest paths in the coupling map between two qubits. @@ -140,7 +143,7 @@ struct HeuristicMappingPass for (std::size_t i = 0; i < nqubits(); ++i) { for (std::size_t j = 0; j < nqubits(); ++j) { if (dist_[i][k] == UINT64_MAX || dist_[k][j] == UINT64_MAX) { - continue; // avoid overflow with "infinite" distances + continue; // Avoid overflow with "infinite" distances. } const std::size_t sum = dist_[i][k] + dist_[k][j]; if (dist_[i][j] > sum) { @@ -195,15 +198,12 @@ struct HeuristicMappingPass return layout; } - explicit Layout(const std::size_t nqubits) - : programToHardware_(nqubits), hardwareToProgram_(nqubits) {} - /** * @brief Insert program:hardware index mapping. * @param prog The program index. * @param hw The hardware index. */ - void add(uint32_t prog, uint32_t hw) { + void add(IndexType prog, IndexType hw) { assert(prog < programToHardware_.size() && "add: program index out of bounds"); assert(hw < hardwareToProgram_.size() && @@ -217,7 +217,7 @@ struct HeuristicMappingPass * @param hw The hardware index. * @return The program index of the respective hardware index. */ - [[nodiscard]] uint32_t getProgramIndex(const uint32_t hw) const { + [[nodiscard]] IndexType getProgramIndex(const IndexType hw) const { assert(hw < hardwareToProgram_.size() && "getProgramIndex: hardware index out of bounds"); return hardwareToProgram_[hw]; @@ -228,7 +228,7 @@ struct HeuristicMappingPass * @param prog The program index. * @return The hardware index of the respective program index. */ - [[nodiscard]] uint32_t getHardwareIndex(const uint32_t prog) const { + [[nodiscard]] IndexType getHardwareIndex(const IndexType prog) const { assert(prog < programToHardware_.size() && "getHardwareIndex: program index out of bounds"); return programToHardware_[prog]; @@ -241,9 +241,9 @@ struct HeuristicMappingPass */ template requires(sizeof...(ProgIndices) > 0) && - ((std::is_convertible_v) && ...) + ((std::is_convertible_v) && ...) [[nodiscard]] auto getHardwareIndices(ProgIndices... progs) const { - return std::tuple{getHardwareIndex(static_cast(progs))...}; + return std::tuple{getHardwareIndex(static_cast(progs))...}; } /** @@ -253,36 +253,36 @@ struct HeuristicMappingPass */ template requires(sizeof...(HwIndices) > 0) && - ((std::is_convertible_v) && ...) + ((std::is_convertible_v) && ...) [[nodiscard]] auto getProgramIndices(HwIndices... hws) const { - return std::tuple{getProgramIndex(static_cast(hws))...}; + return std::tuple{getProgramIndex(static_cast(hws))...}; } /** * @brief Swap the mapping to program indices of two hardware indices. */ - void swap(const uint32_t hw0, const uint32_t hw1) { - const uint32_t prog0 = hardwareToProgram_[hw0]; - const uint32_t prog1 = hardwareToProgram_[hw1]; + void swap(const IndexType hw0, const IndexType hw1) { + const auto prog0 = hardwareToProgram_[hw0]; + const auto prog1 = hardwareToProgram_[hw1]; std::swap(hardwareToProgram_[hw0], hardwareToProgram_[hw1]); std::swap(programToHardware_[prog0], programToHardware_[prog1]); } /** - * @returns the number of qubits handled by the layout. + * @returns the number of qubits managed by the layout. */ - [[nodiscard]] std::size_t getNumQubits() const { + [[nodiscard]] std::size_t nqubits() const { return programToHardware_.size(); } void dump() { llvm::dbgs() << "prog= "; - for (std::size_t i = 0; i < getNumQubits(); ++i) { + for (std::size_t i = 0; i < nqubits(); ++i) { llvm::dbgs() << i << " "; } llvm::dbgs() << "\nhw= "; - for (std::size_t i = 0; i < getNumQubits(); ++i) { + for (std::size_t i = 0; i < nqubits(); ++i) { llvm::dbgs() << programToHardware_[i] << ' '; } llvm::dbgs() << '\n'; @@ -292,16 +292,24 @@ struct HeuristicMappingPass /** * @brief Maps a program qubit index to its hardware index. */ - SmallVector programToHardware_; + SmallVector programToHardware_; /** * @brief Maps a hardware qubit index to its program index. */ - SmallVector hardwareToProgram_; + SmallVector hardwareToProgram_; + + private: + explicit Layout(const std::size_t nqubits) + : programToHardware_(nqubits), hardwareToProgram_(nqubits) {} }; - struct Weights { - Weights(const float alpha, const float lambda, const std::size_t nlookahead) + /** + * @brief Parameters influencing the behavior of the A* search algorithm. + */ + struct Parameters { + Parameters(const float alpha, const float lambda, + const std::size_t nlookahead) : alpha(alpha), decay(1 + nlookahead) { decay[0] = 1.; for (std::size_t i = 1; i < decay.size(); ++i) { @@ -313,6 +321,9 @@ struct HeuristicMappingPass SmallVector decay; }; + /** + * @brief Describes a node in the A* search graph. + */ struct Node { SmallVector sequence; Layout layout; @@ -329,30 +340,34 @@ struct HeuristicMappingPass * swap to the layout of the parent node and evaluate the cost. */ Node(const Node& parent, IndexGate swap, ArrayRef layers, - const Architecture& arch, const Weights& w) + const Architecture& arch, const Parameters& params) : sequence(parent.sequence), layout(parent.layout), f(0) { - /// Apply node-specific swap to given layout. + // Apply node-specific swap to given layout. layout.swap(swap.first, swap.second); // Add swap to sequence. sequence.push_back(swap); // Evaluate cost function. - f = g(w.alpha) + h(layers, arch, w); // NOLINT + f = g(params.alpha) + h(layers, arch, params); // NOLINT } /** * @returns true if the current sequence of SWAPs makes all gates * executable. */ - [[nodiscard]] bool isGoal(const Layer& layer, + [[nodiscard]] bool isGoal(const Layer& front, const Architecture& arch) const { - return llvm::all_of(layer, [&](const IndexGate gate) { + return all_of(front, [&](const IndexGate gate) { return arch.areAdjacent(layout.getHardwareIndex(gate.first), layout.getHardwareIndex(gate.second)); }); } + /** + * @returns true iff. the costs of this node are higher than the one of @p + * rhs. + */ [[nodiscard]] bool operator>(const Node& rhs) const { return f > rhs.f; } private: @@ -372,16 +387,17 @@ struct HeuristicMappingPass * Computes the minimal number of SWAPs required to route each gate in * each layer. For each gate, this is determined by the shortest distance * between its hardware qubits. Intuitively, this is the number of SWAPs - * that a naive router would insert to route the layers. + * that a naive router would insert to route the layers (with a constant + * layout). */ [[nodiscard]] float h(ArrayRef layers, const Architecture& arch, - const Weights& w) const { + const Parameters& params) const { float costs{0}; - for (const auto [i, layer] : llvm::enumerate(layers)) { + for (const auto [decay, layer] : zip(params.decay, layers)) { for (const auto [prog0, prog1] : layer) { const auto [hw0, hw1] = layout.getHardwareIndices(prog0, prog1); const std::size_t nswaps = arch.distanceBetween(hw0, hw1) - 1; - costs += w.decay[i] * static_cast(nswaps); + costs += decay * static_cast(nswaps); } } return costs; @@ -395,8 +411,8 @@ struct HeuristicMappingPass void runOnOperation() override { constexpr std::size_t repeats = 10; - constexpr std::size_t nlookahead = 3; + Parameters weights(this->alpha, this->lambda, this->nlookahead); // TODO: Hardcoded architecture. Architecture arch("RigettiNovera", 9, {{0, 3}, {3, 0}, {0, 1}, {1, 0}, {1, 4}, {4, 1}, @@ -405,8 +421,6 @@ struct HeuristicMappingPass {5, 8}, {8, 5}, {6, 7}, {7, 6}, {7, 8}, {8, 7}}); IRRewriter rewriter(&getContext()); - - Weights weights(0.5, 0.8, nlookahead); /// TODO: Pass Options for (auto func : getOperation().getOps()) { const auto dyn = collectDynamicQubits(func.getFunctionBody()); const auto [ltr, rtl] = computeBidirectionalLayers(dyn); @@ -417,11 +431,11 @@ struct HeuristicMappingPass Layout layout = Layout::identity(arch.nqubits()); for (std::size_t r = 0; r < repeats; ++r) { - if (failed(routeCold(ltr, layout, arch, weights, nlookahead))) { + if (failed(routeCold(ltr, layout, arch, weights))) { signalPassFailure(); return; } - if (failed(routeCold(ltr, layout, arch, weights, nlookahead))) { + if (failed(routeCold(rtl, layout, arch, weights))) { signalPassFailure(); return; } @@ -432,8 +446,7 @@ struct HeuristicMappingPass // qubits ("placement") and hot-route the circuit layer-by-layer. const auto stat = place(dyn, layout, func.getFunctionBody(), rewriter); - if (failed(routeHot(ltr, layout, nlookahead, stat, arch, weights, - rewriter))) { + if (failed(routeHot(ltr, layout, stat, arch, weights, rewriter))) { signalPassFailure(); return; }; @@ -472,12 +485,12 @@ struct HeuristicMappingPass * the static index s.t. [i] = qco.static i. */ [[nodiscard]] static SmallVector - place(const ArrayRef dynQubits, const Layout& layout, - Region& funcBody, IRRewriter& rewriter) { - SmallVector statics(layout.getNumQubits()); + place(ArrayRef dynQubits, const Layout& layout, Region& funcBody, + IRRewriter& rewriter) { + SmallVector statics(layout.nqubits()); // 1. Replace existing dynamic allocations with mapped static ones. - for (const auto [p, q] : llvm::enumerate(dynQubits)) { + for (const auto [p, q] : enumerate(dynQubits)) { const auto hw = layout.getHardwareIndex(p); rewriter.setInsertionPoint(q.getDefiningOp()); auto op = rewriter.replaceOpWithNewOp(q.getDefiningOp(), hw); @@ -485,7 +498,7 @@ struct HeuristicMappingPass } // 2. Create static qubits for the remaining (unused) hardware indices. - for (std::size_t p = dynQubits.size(); p < layout.getNumQubits(); ++p) { + for (std::size_t p = dynQubits.size(); p < layout.nqubits(); ++p) { rewriter.setInsertionPointToStart(&funcBody.front()); const auto hw = layout.getHardwareIndex(p); auto op = rewriter.create(rewriter.getUnknownLoc(), hw); @@ -498,15 +511,15 @@ struct HeuristicMappingPass } /** - * @brief Perform A* search to find a sequence of SWAPs that makes the layer - * executable. + * @brief Perform A* search to find a sequence of SWAPs that makes the + * two-qubit operations inside the first layer (the front) executable. * @details TODO * @returns a vector of hardware-index pairs (each denoting a SWAP) or - * llvm::failure() if A* fails. + * failure() if A* fails. */ - [[nodiscard]] static llvm::FailureOr> + [[nodiscard]] static FailureOr> search(ArrayRef layers, const Layout& layout, const Architecture& arch, - const Weights& weights) { + const Parameters& weights) { Node root(layout); if (root.isGoal(layers.front(), arch)) { @@ -548,7 +561,98 @@ struct HeuristicMappingPass } } - return llvm::failure(); + return failure(); + } + + /** + * @brief Transform a range of qubit values to a vector of wire iterators. + * @returns a vector of wire iterators. + */ + template + static SmallVector toWires(QubitRange qubits) { + return SmallVector( + map_range(qubits, [](auto q) { return WireIterator(q); })); + } + + /** + * @brief Collect the layers of independently executable two-qubit gates of a + * circuit. + * @details Depending on the template parameter, the function collects the + * layers in forward or backward direction, respectively. Towards that end, + * the function traverses the def-use chain of each qubit until a two-qubit + * gate is found. If a two-qubit gate is visited twice, it is considered ready + * and inserted into the layer. This process is repeated until no more + * two-qubit are found anymore. + * @returns a vector of layers. + */ + template + static SmallVector collectLayers(MutableArrayRef wires) { + constexpr std::size_t step = d == Direction::Forward ? 1 : -1; + const auto stop = [](const WireIterator& it) { + if constexpr (d == Direction::Forward) { + return it != std::default_sentinel; + } else { + return !isa(it.operation()); + } + }; + + SmallVector layers; + DenseMap visited; + + while (true) { + Layer layer{}; + for (const auto [index, it] : enumerate(wires)) { + while (stop(it)) { + const auto res = + TypeSwitch(it.operation()) + .Case([&](UnitaryOpInterface op) { + assert(op.getNumQubits() > 0 && op.getNumQubits() <= 2); + + if (op.getNumQubits() == 1) { + std::ranges::advance(it, step); + return WalkResult::advance(); + } + + if (visited.contains(op)) { + const auto otherIndex = visited[op]; + layer.insert(std::make_pair(index, otherIndex)); + + std::ranges::advance(wires[index], step); + std::ranges::advance(wires[otherIndex], step); + + visited.erase(op); + } else { + visited.try_emplace(op, index); + } + + return WalkResult::interrupt(); + }) + .template Case([&](auto) { + std::ranges::advance(it, step); + return WalkResult::advance(); + }) + .Default([&](Operation* op) { + report_fatal_error("unknown op in wireiterator use: " + + op->getName().getStringRef()); + return WalkResult::interrupt(); + }); + + if (res.wasInterrupted()) { + break; + } + } + } + + if (layer.empty()) { + break; + } + + layers.emplace_back(layer); + visited.clear(); + } + + return layers; } /** @@ -557,18 +661,16 @@ struct HeuristicMappingPass * engine to find a sequence of SWAPs that makes that layer executable. * Instead of inserted these SWAPs into the IR, this function only updates the * layout. - * @returns llvm::failure() if A* search isn't able to find a solution. + * @returns failure() if A* search isn't able to find a solution. */ - static llvm::LogicalResult routeCold(ArrayRef layers, Layout& layout, - const Architecture& arch, - const Weights& weights, - const std::size_t nlookahead) { + LogicalResult routeCold(ArrayRef layers, Layout& layout, + const Architecture& arch, const Parameters& weights) { for (std::size_t i = 0; i < layers.size(); ++i) { const std::size_t len = std::min(1 + nlookahead, layers.size() - i); const auto window = layers.slice(i, len); const auto swaps = search(window, layout, arch, weights); if (failed(swaps)) { - return llvm::failure(); + return failure(); } for (const auto [hw0, hw1] : *swaps) { @@ -576,7 +678,7 @@ struct HeuristicMappingPass } } - return llvm::success(); + return success(); } /** @@ -584,13 +686,11 @@ struct HeuristicMappingPass * @details Iterates over a sliding window of layers and uses the A* search * engine to find a sequence of SWAPs that makes that layer executable. * This function inserts SWAP ops. - * @returns llvm::failure() if A* search isn't able to find a solution. + * @returns failure() if A* search isn't able to find a solution. */ - static LogicalResult routeHot(ArrayRef ltr, Layout& layout, - const std::size_t nlookahead, - ArrayRef statics, - const Architecture& arch, - const Weights& weights, IRRewriter& rewriter) { + LogicalResult routeHot(ArrayRef ltr, Layout& layout, + ArrayRef statics, const Architecture& arch, + const Parameters& weights, IRRewriter& rewriter) { constexpr auto advanceFront = [](WireIterator& it) { while (true) { const auto next = std::next(it); @@ -608,14 +708,14 @@ struct HeuristicMappingPass }; auto wires = toWires(statics); - for (const auto [i, layer] : llvm::enumerate(ltr)) { - llvm::for_each(wires, advanceFront); + for (const auto [i, layer] : enumerate(ltr)) { + for_each(wires, advanceFront); - const auto len = std::min(1 + nlookahead, ltr.size() - i); + const auto len = std::min(1 + this->nlookahead, ltr.size() - i); const auto window = ltr.slice(i, len); const auto swaps = search(window, layout, arch, weights); if (failed(swaps)) { - return llvm::failure(); + return failure(); } const auto unknown = rewriter.getUnknownLoc(); @@ -644,87 +744,7 @@ struct HeuristicMappingPass } } - return llvm::success(); - } - - /** - * @brief Transform a range of qubit values to a vector of wire iterators. - * @returns a vector of wire iterators. - */ - template - static SmallVector toWires(QubitRange qubits) { - return SmallVector( - map_range(qubits, [](auto q) { return WireIterator(q); })); - } - - template - static SmallVector collectLayers(MutableArrayRef wires) { - constexpr std::size_t step = d == Direction::Forward ? 1 : -1; - const auto stop = [](const WireIterator& it) { - if constexpr (d == Direction::Forward) { - return it != std::default_sentinel; - } else { - return !isa(it.operation()); - } - }; - - SmallVector layers; - DenseMap hits; - - while (true) { - Layer layer{}; - for (const auto [index, it] : llvm::enumerate(wires)) { - while (stop(it)) { - const auto res = - TypeSwitch(it.operation()) - .Case([&](UnitaryOpInterface op) { - assert(op.getNumQubits() > 0 && op.getNumQubits() <= 2); - - if (op.getNumQubits() == 1) { - std::ranges::advance(it, step); - return WalkResult::advance(); - } - - if (hits.contains(op)) { - const auto otherIndex = hits[op]; - layer.insert(std::make_pair(index, otherIndex)); - - std::ranges::advance(wires[index], step); - std::ranges::advance(wires[otherIndex], step); - - hits.erase(op); - } else { - hits.try_emplace(op, index); - } - - return WalkResult::interrupt(); - }) - .template Case([&](auto) { - std::ranges::advance(it, step); - return WalkResult::advance(); - }) - .Default([&](Operation* op) { - report_fatal_error("unknown op in wireiterator use: " + - op->getName().getStringRef()); - return WalkResult::interrupt(); - }); - - if (res.wasInterrupted()) { - break; - } - } - } - - if (layer.empty()) { - break; - } - - layers.emplace_back(layer); - hits.clear(); - } - - return layers; + return success(); } }; } // namespace mlir::qco From 4aa1bfe2c98664732251bf8f508d8e23bde93458 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 4 Mar 2026 11:07:59 +0100 Subject: [PATCH 14/37] Add "repeats" pass option --- mlir/include/mlir/Passes/Passes.td | 4 +++- mlir/lib/Passes/Transpilation/HeuristicMapping.cpp | 4 +--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mlir/include/mlir/Passes/Passes.td b/mlir/include/mlir/Passes/Passes.td index 23e139cd12..e11b1818d7 100644 --- a/mlir/include/mlir/Passes/Passes.td +++ b/mlir/include/mlir/Passes/Passes.td @@ -29,7 +29,9 @@ def HeuristicMappingPass : Pass<"map", "mlir::ModuleOp"> { Option<"alpha", "alpha", "float", "1.0F", "The alpha factor in the cost function.">, Option<"lambda", "lambda", "float", "0.5F", - "The lambda factor in the cost function."> + "The lambda factor in the cost function.">, + Option<"repeats", "repeats", "std::size_t", "2", + "The number of forwards and backwards traversal to improve the initial layout."> ]; } diff --git a/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp b/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp index 73b13c22e3..c72c4948cd 100644 --- a/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp +++ b/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp @@ -410,8 +410,6 @@ struct HeuristicMappingPass using HeuristicMappingPassBase::HeuristicMappingPassBase; void runOnOperation() override { - constexpr std::size_t repeats = 10; - Parameters weights(this->alpha, this->lambda, this->nlookahead); // TODO: Hardcoded architecture. Architecture arch("RigettiNovera", 9, @@ -430,7 +428,7 @@ struct HeuristicMappingPass // cold-route along the way. Repeat this procedure "repeats" times. Layout layout = Layout::identity(arch.nqubits()); - for (std::size_t r = 0; r < repeats; ++r) { + for (std::size_t r = 0; r < this->repeats; ++r) { if (failed(routeCold(ltr, layout, arch, weights))) { signalPassFailure(); return; From 753a83589625c821f9218862bc851a8e59ab6deb Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 4 Mar 2026 14:23:07 +0100 Subject: [PATCH 15/37] Add unit tests --- .../mlir/Passes/Mapping/Architecture.h | 90 +++++++++ mlir/lib/Compiler/CompilerPipeline.cpp | 1 - mlir/lib/Passes/Mapping/Architecture.cpp | 71 +++++++ .../HeuristicMapping.cpp | 112 +---------- mlir/unittests/CMakeLists.txt | 1 + mlir/unittests/Passes/CMakeLists.txt | 9 + mlir/unittests/Passes/Mapping/CMakeLists.txt | 17 ++ .../Passes/Mapping/test_heuristic_mapping.cpp | 188 ++++++++++++++++++ 8 files changed, 377 insertions(+), 112 deletions(-) create mode 100644 mlir/include/mlir/Passes/Mapping/Architecture.h create mode 100644 mlir/lib/Passes/Mapping/Architecture.cpp rename mlir/lib/Passes/{Transpilation => Mapping}/HeuristicMapping.cpp (85%) create mode 100644 mlir/unittests/Passes/CMakeLists.txt create mode 100644 mlir/unittests/Passes/Mapping/CMakeLists.txt create mode 100644 mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp diff --git a/mlir/include/mlir/Passes/Mapping/Architecture.h b/mlir/include/mlir/Passes/Mapping/Architecture.h new file mode 100644 index 0000000000..91dd193bbd --- /dev/null +++ b/mlir/include/mlir/Passes/Mapping/Architecture.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief A quantum accelerator's architecture. + */ +class [[nodiscard]] Architecture { +public: + using CouplingSet = mlir::DenseSet>; + using NeighbourVector = mlir::SmallVector>; + + explicit Architecture(std::string name, std::size_t nqubits, + CouplingSet couplingSet) + : name_(std::move(name)), nqubits_(nqubits), + couplingSet_(std::move(couplingSet)), neighbours_(nqubits), + dist_(nqubits, mlir::SmallVector(nqubits, UINT64_MAX)), + prev_(nqubits, mlir::SmallVector(nqubits, UINT64_MAX)) { + floydWarshallWithPathReconstruction(); + collectNeighbours(); + } + + /** + * @brief Return the architecture's name. + */ + [[nodiscard]] std::string_view name() const; + + /** + * @brief Return the architecture's number of qubits. + */ + [[nodiscard]] std::size_t nqubits() const; + + /** + * @brief Return true if @p u and @p v are adjacent. + */ + [[nodiscard]] bool areAdjacent(std::size_t u, std::size_t v) const; + + /** + * @brief Return the length of the shortest path between @p u and @p v. + */ + [[nodiscard]] std::size_t distanceBetween(std::size_t u, std::size_t v) const; + + /** + * @brief Collect all neighbours of @p u. + */ + [[nodiscard]] mlir::SmallVector + neighboursOf(std::size_t u) const; + +private: + using Matrix = mlir::SmallVector, 0>; + + /** + * @brief Find all shortest paths in the coupling map between two qubits. + * @details Vertices are the qubits. Edges connected two qubits. Has a time + * and memory complexity of O(nqubits^3) and O(nqubits^2), respectively. + * @link Adapted from https://en.wikipedia.org/wiki/Floyd–Warshall_algorithm + */ + void floydWarshallWithPathReconstruction(); + + /** + * @brief Collect the neighbours of all qubits. + * @details Has a time complexity of O(nqubits) + */ + void collectNeighbours(); + + std::string name_; + std::size_t nqubits_; + CouplingSet couplingSet_; + NeighbourVector neighbours_; + + Matrix dist_; + Matrix prev_; +}; diff --git a/mlir/lib/Compiler/CompilerPipeline.cpp b/mlir/lib/Compiler/CompilerPipeline.cpp index 00a422d9bc..337750ea8c 100644 --- a/mlir/lib/Compiler/CompilerPipeline.cpp +++ b/mlir/lib/Compiler/CompilerPipeline.cpp @@ -148,7 +148,6 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, // Stage 5: Optimization passes // TODO: Add optimization passes - pm.addPass(qco::createHeuristicMappingPass()); addCleanupPasses(pm); if (failed(pm.run(module))) { return failure(); diff --git a/mlir/lib/Passes/Mapping/Architecture.cpp b/mlir/lib/Passes/Mapping/Architecture.cpp new file mode 100644 index 0000000000..ed9e3aaaaf --- /dev/null +++ b/mlir/lib/Passes/Mapping/Architecture.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Passes/Mapping/Architecture.h" + +#include +#include +#include +#include + +using namespace mlir; + +std::string_view Architecture::name() const { return name_; } + +std::size_t Architecture::nqubits() const { return nqubits_; } + +bool Architecture::areAdjacent(std::size_t u, std::size_t v) const { + return couplingSet_.contains(std::make_pair(u, v)); +} + +std::size_t Architecture::distanceBetween(std::size_t u, std::size_t v) const { + if (dist_[u][v] == UINT64_MAX) { + report_fatal_error("Floyd-warshall failed to compute the distance " + "between qubits " + + Twine(u) + " and " + Twine(v)); + } + return dist_[u][v]; +} + +SmallVector Architecture::neighboursOf(std::size_t u) const { + return neighbours_[u]; +} + +void Architecture::floydWarshallWithPathReconstruction() { + for (const auto& [u, v] : couplingSet_) { + dist_[u][v] = 1; + prev_[u][v] = u; + } + for (std::size_t v = 0; v < nqubits(); ++v) { + dist_[v][v] = 0; + prev_[v][v] = v; + } + + for (std::size_t k = 0; k < nqubits(); ++k) { + for (std::size_t i = 0; i < nqubits(); ++i) { + for (std::size_t j = 0; j < nqubits(); ++j) { + if (dist_[i][k] == UINT64_MAX || dist_[k][j] == UINT64_MAX) { + continue; // Avoid overflow with "infinite" distances. + } + const std::size_t sum = dist_[i][k] + dist_[k][j]; + if (dist_[i][j] > sum) { + dist_[i][j] = sum; + prev_[i][j] = prev_[k][j]; + } + } + } + } +} + +void Architecture::collectNeighbours() { + for (const auto& [u, v] : couplingSet_) { + neighbours_[u].push_back(v); + } +} diff --git a/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp b/mlir/lib/Passes/Mapping/HeuristicMapping.cpp similarity index 85% rename from mlir/lib/Passes/Transpilation/HeuristicMapping.cpp rename to mlir/lib/Passes/Mapping/HeuristicMapping.cpp index c72c4948cd..97f8a81e1b 100644 --- a/mlir/lib/Passes/Transpilation/HeuristicMapping.cpp +++ b/mlir/lib/Passes/Mapping/HeuristicMapping.cpp @@ -12,6 +12,7 @@ #include "mlir/Dialect/QCO/IR/QCOInterfaces.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" #include "mlir/Dialect/QCO/Utils/WireIterator.h" +#include "mlir/Passes/Mapping/Architecture.h" #include "mlir/Passes/Passes.h" #include @@ -25,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -64,116 +64,6 @@ struct HeuristicMappingPass */ enum class Direction : std::uint8_t { Forward, Backward }; - /** - * @brief A quantum accelerator's architecture. - */ - class [[nodiscard]] Architecture { - public: - using CouplingSet = DenseSet>; - using NeighbourVector = SmallVector>; - - explicit Architecture(std::string name, std::size_t nqubits, - CouplingSet couplingSet) - : name_(std::move(name)), nqubits_(nqubits), - couplingSet_(std::move(couplingSet)), neighbours_(nqubits), - dist_(nqubits, SmallVector(nqubits, UINT64_MAX)), - prev_(nqubits, SmallVector(nqubits, UINT64_MAX)) { - floydWarshallWithPathReconstruction(); - collectNeighbours(); - } - - /** - * @brief Return the architecture's name. - */ - [[nodiscard]] constexpr std::string_view name() const { return name_; } - - /** - * @brief Return the architecture's number of qubits. - */ - [[nodiscard]] constexpr std::size_t nqubits() const { return nqubits_; } - - /** - * @brief Return true if @p u and @p v are adjacent. - */ - [[nodiscard]] bool areAdjacent(std::size_t u, std::size_t v) const { - return couplingSet_.contains({u, v}); - } - - /** - * @brief Return the length of the shortest path between @p u and @p v. - */ - [[nodiscard]] std::size_t distanceBetween(std::size_t u, - std::size_t v) const { - if (dist_[u][v] == UINT64_MAX) { - report_fatal_error("Floyd-warshall failed to compute the distance " - "between qubits " + - Twine(u) + " and " + Twine(v)); - } - return dist_[u][v]; - } - - /** - * @brief Collect all neighbours of @p u. - */ - [[nodiscard]] SmallVector - neighboursOf(std::size_t u) const { - return neighbours_[u]; - } - - private: - using Matrix = SmallVector, 0>; - - /** - * @brief Find all shortest paths in the coupling map between two qubits. - * @details Vertices are the qubits. Edges connected two qubits. Has a time - * and memory complexity of O(nqubits^3) and O(nqubits^2), respectively. - * @link Adapted from https://en.wikipedia.org/wiki/Floyd–Warshall_algorithm - */ - void floydWarshallWithPathReconstruction() { - for (const auto& [u, v] : couplingSet_) { - dist_[u][v] = 1; - prev_[u][v] = u; - } - for (std::size_t v = 0; v < nqubits(); ++v) { - dist_[v][v] = 0; - prev_[v][v] = v; - } - - for (std::size_t k = 0; k < nqubits(); ++k) { - for (std::size_t i = 0; i < nqubits(); ++i) { - for (std::size_t j = 0; j < nqubits(); ++j) { - if (dist_[i][k] == UINT64_MAX || dist_[k][j] == UINT64_MAX) { - continue; // Avoid overflow with "infinite" distances. - } - const std::size_t sum = dist_[i][k] + dist_[k][j]; - if (dist_[i][j] > sum) { - dist_[i][j] = sum; - prev_[i][j] = prev_[k][j]; - } - } - } - } - } - - /** - * @brief Collect the neighbours of all qubits. - * @details Has a time complexity of O(nqubits) - */ - void collectNeighbours() { - for (const auto& [u, v] : couplingSet_) { - neighbours_[u].push_back(v); - } - } - - std::string name_; - std::size_t nqubits_; - CouplingSet couplingSet_; - NeighbourVector neighbours_; - - Matrix dist_; - Matrix prev_; - }; - /** * @brief A qubit layout that maps program and hardware indices without * storing Values. Used for efficient memory usage when Value tracking isn't diff --git a/mlir/unittests/CMakeLists.txt b/mlir/unittests/CMakeLists.txt index da76b50c37..962a4a9018 100644 --- a/mlir/unittests/CMakeLists.txt +++ b/mlir/unittests/CMakeLists.txt @@ -18,3 +18,4 @@ add_subdirectory(programs) add_subdirectory(Compiler) add_subdirectory(Dialect) add_subdirectory(Conversion) +add_subdirectory(Passes) diff --git a/mlir/unittests/Passes/CMakeLists.txt b/mlir/unittests/Passes/CMakeLists.txt new file mode 100644 index 0000000000..30ddc4dc38 --- /dev/null +++ b/mlir/unittests/Passes/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +add_subdirectory(Mapping) diff --git a/mlir/unittests/Passes/Mapping/CMakeLists.txt b/mlir/unittests/Passes/Mapping/CMakeLists.txt new file mode 100644 index 0000000000..2b8c749876 --- /dev/null +++ b/mlir/unittests/Passes/Mapping/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +set(target_name mqt-core-mlir-unittest-mapping) +add_executable(${target_name} test_heuristic_mapping.cpp) + +target_link_libraries(${target_name} PRIVATE MLIRParser GTest::gtest_main MLIRQCProgramBuilder + QCToQCO QCOToQC QcoPasses) + +mqt_mlir_configure_unittest_target(${target_name}) + +gtest_discover_tests(${target_name} PROPERTIES LABELS mqt-mlir-unittests DISCOVERY_TIMEOUT 60) diff --git a/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp b/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp new file mode 100644 index 0000000000..f281df37cb --- /dev/null +++ b/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Conversion/QCOToQC/QCOToQC.h" +#include "mlir/Conversion/QCToQCO/QCToQCO.h" +#include "mlir/Dialect/QC/Builder/QCProgramBuilder.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/QC/IR/QCInterfaces.h" +#include "mlir/Dialect/QC/IR/QCOps.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Passes/Mapping/Architecture.h" +#include "mlir/Passes/Passes.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace mlir; + +namespace { +struct ArchitectureParam { + std::string name; + std::function factory; +}; + +class HeuristicMappingPassTest + : public testing::Test, + public testing::WithParamInterface { +public: + /** + * @brief Walks the IR and validates if each two-qubit op is executable on the + * given architecture. + * @returns true iff. all two-qubit gates are executable on the architecture. + */ + static bool isExecutable(OwningOpRef& module, + const Architecture& arch) { + auto entry = *(module->getOps().begin()); + DenseMap mappings; + for_each(entry.getOps(), [&](qc::StaticOp op) { + mappings.try_emplace(op.getQubit(), op.getIndex()); + }); + + bool executable = true; + std::ignore = module->walk([&](qc::UnitaryOpInterface op) { + if (op.getNumQubits() > 1) { + const auto i0 = mappings[op.getQubit(0)]; + const auto i1 = mappings[op.getQubit(1)]; + if (!arch.areAdjacent(i0, i1)) { + executable = false; + return WalkResult::interrupt(); + } + } + return WalkResult::advance(); + }); + + return executable; + } + + static Architecture getRigettiNovera() { + // TODO: At some point this should be provided via QDMI. + const static Architecture::CouplingSet COUPLING{ + {0, 3}, {3, 0}, {0, 1}, {1, 0}, {1, 4}, {4, 1}, {1, 2}, {2, 1}, + {2, 5}, {5, 2}, {3, 6}, {6, 3}, {3, 4}, {4, 3}, {4, 7}, {7, 4}, + {4, 5}, {5, 4}, {5, 8}, {8, 5}, {6, 7}, {7, 6}, {7, 8}, {8, 7}}; + return Architecture("RigettiNovera", 9, COUPLING); + } + +protected: + void SetUp() override { + // Register all necessary dialects + DialectRegistry registry; + registry.insert(); + context = std::make_unique(); + context->appendDialectRegistry(registry); + context->loadAllAvailableDialects(); + } + + static void runHeuristicMapping(OwningOpRef& module) { + PassManager pm(module->getContext()); + pm.addPass(createQCToQCO()); + pm.addPass(qco::createHeuristicMappingPass()); + pm.addPass(createQCOToQC()); + auto res = pm.run(*module); + ASSERT_FALSE(failed(res)); + } + + std::unique_ptr context; +}; +}; // namespace + +TEST_P(HeuristicMappingPassTest, GHZ) { + auto arch = GetParam().factory(); + + qc::QCProgramBuilder builder(context.get()); + builder.initialize(); + + const auto q0 = builder.allocQubit(); + const auto q1 = builder.allocQubit(); + const auto q2 = builder.allocQubit(); + + builder.h(q0); + builder.cx(q0, q1); + builder.cx(q0, q2); + + builder.dealloc(q0); + builder.dealloc(q1); + builder.dealloc(q2); + + auto module = builder.finalize(); + runHeuristicMapping(module); + EXPECT_TRUE(isExecutable(module, arch)); + + module->dump(); +} + +TEST_P(HeuristicMappingPassTest, Sabre) { + auto arch = GetParam().factory(); + + qc::QCProgramBuilder builder(context.get()); + builder.initialize(); + + const auto q0 = builder.allocQubit(); + const auto q1 = builder.allocQubit(); + const auto q2 = builder.allocQubit(); + const auto q3 = builder.allocQubit(); + const auto q4 = builder.allocQubit(); + const auto q5 = builder.allocQubit(); + + builder.h(q0); + builder.h(q1); + builder.h(q4); + + builder.z(q0); + builder.cx(q1, q2); + builder.cx(q4, q5); + + builder.cx(q0, q1); + builder.cx(q2, q3); + + builder.h(q2); + builder.h(q3); + + builder.cx(q1, q2); + builder.cx(q3, q5); + + builder.z(q3); + + builder.cx(q3, q4); + + builder.cx(q3, q0); + + builder.dealloc(q0); + builder.dealloc(q1); + builder.dealloc(q2); + builder.dealloc(q3); + builder.dealloc(q4); + builder.dealloc(q5); + + auto module = builder.finalize(); + runHeuristicMapping(module); + EXPECT_TRUE(isExecutable(module, arch)); + + module->dump(); +} + +INSTANTIATE_TEST_SUITE_P( + Architectures, HeuristicMappingPassTest, + testing::Values(ArchitectureParam{ + "RigettiNovera", &HeuristicMappingPassTest::getRigettiNovera}), + [](const testing::TestParamInfo& info) { + return info.param.name; + }); From 4586450e56e8cb8d7ec63a06d5c67e8bd46e6a00 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 4 Mar 2026 14:30:12 +0100 Subject: [PATCH 16/37] Fix linting issues --- mlir/lib/Compiler/CompilerPipeline.cpp | 1 - mlir/lib/Passes/Mapping/Architecture.cpp | 2 ++ mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp | 6 ++++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/mlir/lib/Compiler/CompilerPipeline.cpp b/mlir/lib/Compiler/CompilerPipeline.cpp index 337750ea8c..954860fd6c 100644 --- a/mlir/lib/Compiler/CompilerPipeline.cpp +++ b/mlir/lib/Compiler/CompilerPipeline.cpp @@ -13,7 +13,6 @@ #include "mlir/Conversion/QCOToQC/QCOToQC.h" #include "mlir/Conversion/QCToQCO/QCToQCO.h" #include "mlir/Conversion/QCToQIR/QCToQIR.h" -#include "mlir/Passes/Passes.h" #include "mlir/Support/PrettyPrinting.h" #include diff --git a/mlir/lib/Passes/Mapping/Architecture.cpp b/mlir/lib/Passes/Mapping/Architecture.cpp index ed9e3aaaaf..703c39ed04 100644 --- a/mlir/lib/Passes/Mapping/Architecture.cpp +++ b/mlir/lib/Passes/Mapping/Architecture.cpp @@ -11,8 +11,10 @@ #include "mlir/Passes/Mapping/Architecture.h" #include +#include #include #include +#include #include using namespace mlir; diff --git a/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp b/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp index f281df37cb..4276d9907f 100644 --- a/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp +++ b/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp @@ -18,16 +18,22 @@ #include "mlir/Passes/Mapping/Architecture.h" #include "mlir/Passes/Passes.h" +#include +#include #include +#include #include #include #include #include #include +#include +#include #include #include #include #include +#include #include using namespace mlir; From 068f88130a3f3f8518402e209016fec6b3f9527e Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 5 Mar 2026 08:54:23 +0100 Subject: [PATCH 17/37] Apply bunny suggestions --- mlir/lib/Compiler/CMakeLists.txt | 1 - mlir/lib/Passes/Mapping/HeuristicMapping.cpp | 10 ++++++++-- .../Passes/Mapping/test_heuristic_mapping.cpp | 4 ---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/mlir/lib/Compiler/CMakeLists.txt b/mlir/lib/Compiler/CMakeLists.txt index cb09a030e4..a53a004456 100644 --- a/mlir/lib/Compiler/CMakeLists.txt +++ b/mlir/lib/Compiler/CMakeLists.txt @@ -20,7 +20,6 @@ add_mlir_library( QCToQCO QCOToQC QCToQIR - QcoPasses MQT::MLIRSupport DISABLE_INSTALL) diff --git a/mlir/lib/Passes/Mapping/HeuristicMapping.cpp b/mlir/lib/Passes/Mapping/HeuristicMapping.cpp index 97f8a81e1b..8d962348bd 100644 --- a/mlir/lib/Passes/Mapping/HeuristicMapping.cpp +++ b/mlir/lib/Passes/Mapping/HeuristicMapping.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -311,6 +312,12 @@ struct HeuristicMappingPass IRRewriter rewriter(&getContext()); for (auto func : getOperation().getOps()) { const auto dyn = collectDynamicQubits(func.getFunctionBody()); + if (dyn.size() > arch.nqubits()) { + func.emitError() << "the targeted architecture supports" + << arch.nqubits() << " qubits, got " << dyn.size(); + signalPassFailure(); + } + const auto [ltr, rtl] = computeBidirectionalLayers(dyn); // Use the SABRE Approach to improve the initial layout choice (here: @@ -328,7 +335,6 @@ struct HeuristicMappingPass return; } } - layout.dump(); // Once the initial layout is found, replace the dynamic with static // qubits ("placement") and hot-route the circuit layer-by-layer. @@ -475,7 +481,7 @@ struct HeuristicMappingPass */ template static SmallVector collectLayers(MutableArrayRef wires) { - constexpr std::size_t step = d == Direction::Forward ? 1 : -1; + constexpr auto step = d == Direction::Forward ? 1 : -1; const auto stop = [](const WireIterator& it) { if constexpr (d == Direction::Forward) { return it != std::default_sentinel; diff --git a/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp b/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp index 4276d9907f..54803be0c7 100644 --- a/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp +++ b/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp @@ -131,8 +131,6 @@ TEST_P(HeuristicMappingPassTest, GHZ) { auto module = builder.finalize(); runHeuristicMapping(module); EXPECT_TRUE(isExecutable(module, arch)); - - module->dump(); } TEST_P(HeuristicMappingPassTest, Sabre) { @@ -181,8 +179,6 @@ TEST_P(HeuristicMappingPassTest, Sabre) { auto module = builder.finalize(); runHeuristicMapping(module); EXPECT_TRUE(isExecutable(module, arch)); - - module->dump(); } INSTANTIATE_TEST_SUITE_P( From ed658650d49a58e748b13af201e9bd57c61fbcda Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 5 Mar 2026 07:58:42 +0000 Subject: [PATCH 18/37] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/include/mlir/Passes/Mapping/Architecture.h | 3 ++- mlir/lib/Passes/Mapping/Architecture.cpp | 5 +++-- mlir/lib/Passes/Mapping/HeuristicMapping.cpp | 13 +++++++------ .../Passes/Mapping/test_heuristic_mapping.cpp | 7 ++++--- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/mlir/include/mlir/Passes/Mapping/Architecture.h b/mlir/include/mlir/Passes/Mapping/Architecture.h index 91dd193bbd..657f8bff41 100644 --- a/mlir/include/mlir/Passes/Mapping/Architecture.h +++ b/mlir/include/mlir/Passes/Mapping/Architecture.h @@ -10,12 +10,13 @@ #pragma once -#include #include #include #include #include #include + +#include #include #include diff --git a/mlir/lib/Passes/Mapping/Architecture.cpp b/mlir/lib/Passes/Mapping/Architecture.cpp index 703c39ed04..70997b8321 100644 --- a/mlir/lib/Passes/Mapping/Architecture.cpp +++ b/mlir/lib/Passes/Mapping/Architecture.cpp @@ -10,10 +10,11 @@ #include "mlir/Passes/Mapping/Architecture.h" -#include -#include #include #include + +#include +#include #include #include diff --git a/mlir/lib/Passes/Mapping/HeuristicMapping.cpp b/mlir/lib/Passes/Mapping/HeuristicMapping.cpp index 8d962348bd..00eb17448f 100644 --- a/mlir/lib/Passes/Mapping/HeuristicMapping.cpp +++ b/mlir/lib/Passes/Mapping/HeuristicMapping.cpp @@ -15,12 +15,6 @@ #include "mlir/Passes/Mapping/Architecture.h" #include "mlir/Passes/Passes.h" -#include -#include -#include -#include -#include -#include #include #include #include @@ -37,6 +31,13 @@ #include #include #include + +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp b/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp index 54803be0c7..7ee041b40c 100644 --- a/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp +++ b/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp @@ -18,12 +18,9 @@ #include "mlir/Passes/Mapping/Architecture.h" #include "mlir/Passes/Passes.h" -#include -#include #include #include #include -#include #include #include #include @@ -33,6 +30,10 @@ #include #include #include + +#include +#include +#include #include #include From 75d3c4ea5edfa0381fca817a288f7a80b6f398e3 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 5 Mar 2026 10:05:20 +0100 Subject: [PATCH 19/37] Add assertions --- mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp b/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp index 54803be0c7..471b1f7435 100644 --- a/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp +++ b/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp @@ -64,6 +64,10 @@ class HeuristicMappingPassTest bool executable = true; std::ignore = module->walk([&](qc::UnitaryOpInterface op) { if (op.getNumQubits() > 1) { + assert(op.getNumQubits() == 2 && + "Expected only 2-qubit gates after decomposition"); + assert(mappings.contains(op.getQubit(0)) && "Qubit 0 not in mapping"); + assert(mappings.contains(op.getQubit(1)) && "Qubit 1 not in mapping"); const auto i0 = mappings[op.getQubit(0)]; const auto i1 = mappings[op.getQubit(1)]; if (!arch.areAdjacent(i0, i1)) { From c0f0b22e272aea552cb5df13880a297e430c297c Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 5 Mar 2026 10:07:43 +0100 Subject: [PATCH 20/37] Minor improvements --- mlir/include/mlir/Passes/Passes.td | 2 -- mlir/lib/Passes/Mapping/HeuristicMapping.cpp | 16 ++++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/mlir/include/mlir/Passes/Passes.td b/mlir/include/mlir/Passes/Passes.td index e11b1818d7..45277e15a6 100644 --- a/mlir/include/mlir/Passes/Passes.td +++ b/mlir/include/mlir/Passes/Passes.td @@ -22,8 +22,6 @@ def HeuristicMappingPass : Pass<"map", "mlir::ModuleOp"> { This pass inserts SWAP operations to ensure two-qubit gates are executable on a given target architecture. }]; let options = [ - Option<"archName", "arch", "std::string", "", - "The name of the targeted architecture.">, Option<"nlookahead", "nlookahead", "std::size_t", "1", "The number of lookahead steps.">, Option<"alpha", "alpha", "float", "1.0F", diff --git a/mlir/lib/Passes/Mapping/HeuristicMapping.cpp b/mlir/lib/Passes/Mapping/HeuristicMapping.cpp index 00eb17448f..d8d53bee47 100644 --- a/mlir/lib/Passes/Mapping/HeuristicMapping.cpp +++ b/mlir/lib/Passes/Mapping/HeuristicMapping.cpp @@ -15,6 +15,12 @@ #include "mlir/Passes/Mapping/Architecture.h" #include "mlir/Passes/Passes.h" +#include +#include +#include +#include +#include +#include #include #include #include @@ -31,13 +37,6 @@ #include #include #include - -#include -#include -#include -#include -#include -#include #include #include #include @@ -314,9 +313,10 @@ struct HeuristicMappingPass for (auto func : getOperation().getOps()) { const auto dyn = collectDynamicQubits(func.getFunctionBody()); if (dyn.size() > arch.nqubits()) { - func.emitError() << "the targeted architecture supports" + func.emitError() << "the targeted architecture supports " << arch.nqubits() << " qubits, got " << dyn.size(); signalPassFailure(); + return; } const auto [ltr, rtl] = computeBidirectionalLayers(dyn); From b87beb0609dacc070c206a83e09550603f265077 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 5 Mar 2026 09:08:19 +0000 Subject: [PATCH 21/37] =?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/Passes/Mapping/HeuristicMapping.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/mlir/lib/Passes/Mapping/HeuristicMapping.cpp b/mlir/lib/Passes/Mapping/HeuristicMapping.cpp index d8d53bee47..c8a5db3c91 100644 --- a/mlir/lib/Passes/Mapping/HeuristicMapping.cpp +++ b/mlir/lib/Passes/Mapping/HeuristicMapping.cpp @@ -15,12 +15,6 @@ #include "mlir/Passes/Mapping/Architecture.h" #include "mlir/Passes/Passes.h" -#include -#include -#include -#include -#include -#include #include #include #include @@ -37,6 +31,13 @@ #include #include #include + +#include +#include +#include +#include +#include +#include #include #include #include From 87a22ffe6b1f543621f1901e587723ed9ee0f07c Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 5 Mar 2026 10:16:10 +0100 Subject: [PATCH 22/37] Fix linting --- .../Passes/Mapping/test_heuristic_mapping.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp b/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp index e3375dd205..d89758fd9c 100644 --- a/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp +++ b/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp @@ -18,9 +18,13 @@ #include "mlir/Passes/Mapping/Architecture.h" #include "mlir/Passes/Passes.h" +#include +#include +#include #include #include #include +#include #include #include #include @@ -30,10 +34,6 @@ #include #include #include - -#include -#include -#include #include #include @@ -105,7 +105,8 @@ class HeuristicMappingPassTest static void runHeuristicMapping(OwningOpRef& module) { PassManager pm(module->getContext()); pm.addPass(createQCToQCO()); - pm.addPass(qco::createHeuristicMappingPass()); + pm.addPass(qco::createHeuristicMappingPass(qco::HeuristicMappingPassOptions{ + .nlookahead = 5, .alpha = 1, .lambda = 0.85, .repeats = 2})); pm.addPass(createQCOToQC()); auto res = pm.run(*module); ASSERT_FALSE(failed(res)); From 84286a43dcad7841064f5962794d674919ae94c5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 5 Mar 2026 09:17:09 +0000 Subject: [PATCH 23/37] =?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/unittests/Passes/Mapping/test_heuristic_mapping.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp b/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp index d89758fd9c..71665a89d6 100644 --- a/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp +++ b/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp @@ -18,13 +18,9 @@ #include "mlir/Passes/Mapping/Architecture.h" #include "mlir/Passes/Passes.h" -#include -#include -#include #include #include #include -#include #include #include #include @@ -34,6 +30,11 @@ #include #include #include + +#include +#include +#include +#include #include #include From 4766cbd37a4a7a0cc0ff8609c6c64a210d99331d Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 5 Mar 2026 10:30:32 +0100 Subject: [PATCH 24/37] Improve code quality --- mlir/lib/Passes/Mapping/HeuristicMapping.cpp | 27 +++++++++++-------- .../Passes/Mapping/test_heuristic_mapping.cpp | 10 +++---- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/mlir/lib/Passes/Mapping/HeuristicMapping.cpp b/mlir/lib/Passes/Mapping/HeuristicMapping.cpp index c8a5db3c91..5330caa346 100644 --- a/mlir/lib/Passes/Mapping/HeuristicMapping.cpp +++ b/mlir/lib/Passes/Mapping/HeuristicMapping.cpp @@ -15,6 +15,12 @@ #include "mlir/Passes/Mapping/Architecture.h" #include "mlir/Passes/Passes.h" +#include +#include +#include +#include +#include +#include #include #include #include @@ -31,13 +37,6 @@ #include #include #include - -#include -#include -#include -#include -#include -#include #include #include #include @@ -409,7 +408,6 @@ struct HeuristicMappingPass /** * @brief Perform A* search to find a sequence of SWAPs that makes the * two-qubit operations inside the first layer (the front) executable. - * @details TODO * @returns a vector of hardware-index pairs (each denoting a SWAP) or * failure() if A* fails. */ @@ -434,7 +432,8 @@ struct HeuristicMappingPass return curr.sequence; } - // TODO: Explain expansion strategy. + // Given a layout, create child-nodes for each possible SWAP between + // two neighbouring hardware qubits. expansionSet.clear(); if (!curr.sequence.empty()) { @@ -587,7 +586,10 @@ struct HeuristicMappingPass LogicalResult routeHot(ArrayRef ltr, Layout& layout, ArrayRef statics, const Architecture& arch, const Parameters& weights, IRRewriter& rewriter) { - constexpr auto advanceFront = [](WireIterator& it) { + + // Helper function that advances the iterator to the input qubit (the + // operation producing it) of a deallocation or two-qubit op. + const auto advFront = [](WireIterator& it) { while (true) { const auto next = std::next(it); if (isa(next.operation())) { @@ -605,8 +607,11 @@ struct HeuristicMappingPass auto wires = toWires(statics); for (const auto [i, layer] : enumerate(ltr)) { - for_each(wires, advanceFront); + // Advance all wires to the next front of one-qubit outputs (the SSA + // values). + for_each(wires, advFront); + // Collect window and use A* to find and insert a sequence of swaps. const auto len = std::min(1 + this->nlookahead, ltr.size() - i); const auto window = ltr.slice(i, len); const auto swaps = search(window, layout, arch, weights); diff --git a/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp b/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp index 71665a89d6..a1b7b3bdd5 100644 --- a/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp +++ b/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp @@ -18,9 +18,12 @@ #include "mlir/Passes/Mapping/Architecture.h" #include "mlir/Passes/Passes.h" +#include +#include #include #include #include +#include #include #include #include @@ -30,11 +33,6 @@ #include #include #include - -#include -#include -#include -#include #include #include @@ -43,7 +41,7 @@ using namespace mlir; namespace { struct ArchitectureParam { std::string name; - std::function factory; + Architecture (*factory)(); }; class HeuristicMappingPassTest From 23864ef3e796fea85d2681e2893f326e7f7679ca Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 5 Mar 2026 09:31:02 +0000 Subject: [PATCH 25/37] =?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/Passes/Mapping/HeuristicMapping.cpp | 13 +++++++------ .../Passes/Mapping/test_heuristic_mapping.cpp | 7 ++++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/mlir/lib/Passes/Mapping/HeuristicMapping.cpp b/mlir/lib/Passes/Mapping/HeuristicMapping.cpp index 5330caa346..bda0ba50db 100644 --- a/mlir/lib/Passes/Mapping/HeuristicMapping.cpp +++ b/mlir/lib/Passes/Mapping/HeuristicMapping.cpp @@ -15,12 +15,6 @@ #include "mlir/Passes/Mapping/Architecture.h" #include "mlir/Passes/Passes.h" -#include -#include -#include -#include -#include -#include #include #include #include @@ -37,6 +31,13 @@ #include #include #include + +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp b/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp index a1b7b3bdd5..487bb6f9be 100644 --- a/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp +++ b/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp @@ -18,12 +18,9 @@ #include "mlir/Passes/Mapping/Architecture.h" #include "mlir/Passes/Passes.h" -#include -#include #include #include #include -#include #include #include #include @@ -33,6 +30,10 @@ #include #include #include + +#include +#include +#include #include #include From 519464ae46d1aadfd41984d4ffe8fe0dc9c32a2b Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 5 Mar 2026 11:00:16 +0100 Subject: [PATCH 26/37] Remove sortTopologically --- mlir/lib/Passes/Mapping/HeuristicMapping.cpp | 33 +++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/mlir/lib/Passes/Mapping/HeuristicMapping.cpp b/mlir/lib/Passes/Mapping/HeuristicMapping.cpp index 5330caa346..5787d81c55 100644 --- a/mlir/lib/Passes/Mapping/HeuristicMapping.cpp +++ b/mlir/lib/Passes/Mapping/HeuristicMapping.cpp @@ -301,7 +301,7 @@ struct HeuristicMappingPass using HeuristicMappingPassBase::HeuristicMappingPassBase; void runOnOperation() override { - Parameters weights(this->alpha, this->lambda, this->nlookahead); + Parameters params(this->alpha, this->lambda, this->nlookahead); // TODO: Hardcoded architecture. Architecture arch("RigettiNovera", 9, {{0, 3}, {3, 0}, {0, 1}, {1, 0}, {1, 4}, {4, 1}, @@ -327,11 +327,11 @@ struct HeuristicMappingPass Layout layout = Layout::identity(arch.nqubits()); for (std::size_t r = 0; r < this->repeats; ++r) { - if (failed(routeCold(ltr, layout, arch, weights))) { + if (failed(routeCold(ltr, layout, arch, params))) { signalPassFailure(); return; } - if (failed(routeCold(rtl, layout, arch, weights))) { + if (failed(routeCold(rtl, layout, arch, params))) { signalPassFailure(); return; } @@ -341,11 +341,10 @@ struct HeuristicMappingPass // qubits ("placement") and hot-route the circuit layer-by-layer. const auto stat = place(dyn, layout, func.getFunctionBody(), rewriter); - if (failed(routeHot(ltr, layout, stat, arch, weights, rewriter))) { + if (failed(routeHot(ltr, layout, stat, arch, params, rewriter))) { signalPassFailure(); return; }; - sortTopologically(&func.getFunctionBody().front()); } } @@ -413,7 +412,7 @@ struct HeuristicMappingPass */ [[nodiscard]] static FailureOr> search(ArrayRef layers, const Layout& layout, const Architecture& arch, - const Parameters& weights) { + const Parameters& params) { Node root(layout); if (root.isGoal(layers.front(), arch)) { @@ -450,7 +449,7 @@ struct HeuristicMappingPass continue; } - frontier.emplace(curr, swap, layers, arch, weights); + frontier.emplace(curr, swap, layers, arch, params); } } } @@ -559,11 +558,11 @@ struct HeuristicMappingPass * @returns failure() if A* search isn't able to find a solution. */ LogicalResult routeCold(ArrayRef layers, Layout& layout, - const Architecture& arch, const Parameters& weights) { + const Architecture& arch, const Parameters& params) { for (std::size_t i = 0; i < layers.size(); ++i) { const std::size_t len = std::min(1 + nlookahead, layers.size() - i); const auto window = layers.slice(i, len); - const auto swaps = search(window, layout, arch, weights); + const auto swaps = search(window, layout, arch, params); if (failed(swaps)) { return failure(); } @@ -585,8 +584,7 @@ struct HeuristicMappingPass */ LogicalResult routeHot(ArrayRef ltr, Layout& layout, ArrayRef statics, const Architecture& arch, - const Parameters& weights, IRRewriter& rewriter) { - + const Parameters& params, IRRewriter& rewriter) { // Helper function that advances the iterator to the input qubit (the // operation producing it) of a deallocation or two-qubit op. const auto advFront = [](WireIterator& it) { @@ -614,16 +612,27 @@ struct HeuristicMappingPass // Collect window and use A* to find and insert a sequence of swaps. const auto len = std::min(1 + this->nlookahead, ltr.size() - i); const auto window = ltr.slice(i, len); - const auto swaps = search(window, layout, arch, weights); + const auto swaps = search(window, layout, arch, params); if (failed(swaps)) { return failure(); } const auto unknown = rewriter.getUnknownLoc(); for (const auto [hw0, hw1] : *swaps) { + Operation* op0 = wires[hw0].operation(); + Operation* op1 = wires[hw1].operation(); const auto in0 = wires[hw0].qubit(); const auto in1 = wires[hw1].qubit(); + // Reorder to avoid SSA dominance issues. + assert(op0->getBlock()->isOpOrderValid() && + "An invalid op order leads to a significant runtime overhead."); + if (op0->isBeforeInBlock(op1)) { + rewriter.setInsertionPointAfterValue(in1); + } else { + rewriter.setInsertionPointAfterValue(in0); + } + auto op = rewriter.create(unknown, in0, in1); const auto out0 = op.getQubit0Out(); const auto out1 = op.getQubit1Out(); From 5bceb0b226d8513714730cb5f2032be36b4fa3ac Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 5 Mar 2026 12:10:04 +0100 Subject: [PATCH 27/37] Fix linting --- mlir/lib/Passes/Mapping/HeuristicMapping.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/mlir/lib/Passes/Mapping/HeuristicMapping.cpp b/mlir/lib/Passes/Mapping/HeuristicMapping.cpp index b3f102092c..4b14a8c690 100644 --- a/mlir/lib/Passes/Mapping/HeuristicMapping.cpp +++ b/mlir/lib/Passes/Mapping/HeuristicMapping.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include From 5c987aedb4ad1dbe40d113e215954f204f257527 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 9 Mar 2026 08:13:12 +0100 Subject: [PATCH 28/37] Apply review suggestions --- mlir/include/mlir/Passes/Passes.td | 4 ++-- .../{HeuristicMapping.cpp => Mapping.cpp} | 23 +++++++++---------- mlir/unittests/Passes/Mapping/CMakeLists.txt | 2 +- ...heuristic_mapping.cpp => test_mapping.cpp} | 19 ++++++++------- 4 files changed, 23 insertions(+), 25 deletions(-) rename mlir/lib/Passes/Mapping/{HeuristicMapping.cpp => Mapping.cpp} (97%) rename mlir/unittests/Passes/Mapping/{test_heuristic_mapping.cpp => test_mapping.cpp} (90%) diff --git a/mlir/include/mlir/Passes/Passes.td b/mlir/include/mlir/Passes/Passes.td index 45277e15a6..173c4d7294 100644 --- a/mlir/include/mlir/Passes/Passes.td +++ b/mlir/include/mlir/Passes/Passes.td @@ -15,7 +15,7 @@ include "mlir/Pass/PassBase.td" // Transpilation Passes //===----------------------------------------------------------------------===// -def HeuristicMappingPass : Pass<"map", "mlir::ModuleOp"> { +def MappingPass : Pass<"place-and-route", "mlir::ModuleOp"> { let dependentDialects = ["mlir::qco::QCODialect"]; let summary = "This pass ensures that a program meets the connectivity constraints of a given architecture."; let description = [{ @@ -28,7 +28,7 @@ def HeuristicMappingPass : Pass<"map", "mlir::ModuleOp"> { "The alpha factor in the cost function.">, Option<"lambda", "lambda", "float", "0.5F", "The lambda factor in the cost function.">, - Option<"repeats", "repeats", "std::size_t", "2", + Option<"iterations", "iterations", "std::size_t", "2", "The number of forwards and backwards traversal to improve the initial layout."> ]; } diff --git a/mlir/lib/Passes/Mapping/HeuristicMapping.cpp b/mlir/lib/Passes/Mapping/Mapping.cpp similarity index 97% rename from mlir/lib/Passes/Mapping/HeuristicMapping.cpp rename to mlir/lib/Passes/Mapping/Mapping.cpp index 4b14a8c690..2228f74059 100644 --- a/mlir/lib/Passes/Mapping/HeuristicMapping.cpp +++ b/mlir/lib/Passes/Mapping/Mapping.cpp @@ -44,15 +44,14 @@ #include #include -#define DEBUG_TYPE "heuristic-mapping-pass" +#define DEBUG_TYPE "mapping-pass" namespace mlir::qco { -#define GEN_PASS_DEF_HEURISTICMAPPINGPASS +#define GEN_PASS_DEF_MAPPINGPASS #include "mlir/Passes/Passes.h.inc" -struct HeuristicMappingPass - : impl::HeuristicMappingPassBase { +struct MappingPass : impl::MappingPassBase { private: using QubitValue = TypedValue; using IndexType = std::size_t; @@ -237,7 +236,7 @@ struct HeuristicMappingPass layout.swap(swap.first, swap.second); // Add swap to sequence. - sequence.push_back(swap); + sequence.emplace_back(swap); // Evaluate cost function. f = g(params.alpha) + h(layers, arch, params); // NOLINT @@ -298,7 +297,7 @@ struct HeuristicMappingPass using MinQueue = std::priority_queue, std::greater<>>; public: - using HeuristicMappingPassBase::HeuristicMappingPassBase; + using MappingPassBase::MappingPassBase; void runOnOperation() override { Parameters params(this->alpha, this->lambda, this->nlookahead); @@ -323,10 +322,10 @@ struct HeuristicMappingPass // Use the SABRE Approach to improve the initial layout choice (here: // identity): Traverse the layers from left-to-right-to-left and - // cold-route along the way. Repeat this procedure "repeats" times. + // cold-route along the way. Repeat this procedure "iterations" times. Layout layout = Layout::identity(arch.nqubits()); - for (std::size_t r = 0; r < this->repeats; ++r) { + for (std::size_t r = 0; r < this->iterations; ++r) { if (failed(routeCold(ltr, layout, arch, params))) { signalPassFailure(); return; @@ -482,7 +481,7 @@ struct HeuristicMappingPass template static SmallVector collectLayers(MutableArrayRef wires) { constexpr auto step = d == Direction::Forward ? 1 : -1; - const auto stop = [](const WireIterator& it) { + const auto shouldContinue = [](const WireIterator& it) { if constexpr (d == Direction::Forward) { return it != std::default_sentinel; } else { @@ -496,7 +495,7 @@ struct HeuristicMappingPass while (true) { Layer layer{}; for (const auto [index, it] : enumerate(wires)) { - while (stop(it)) { + while (shouldContinue(it)) { const auto res = TypeSwitch(it.operation()) .Case([&](UnitaryOpInterface op) { @@ -553,8 +552,8 @@ struct HeuristicMappingPass * @brief "Cold" routing of the given layers. * @details Iterates over a sliding window of layers and uses the A* search * engine to find a sequence of SWAPs that makes that layer executable. - * Instead of inserted these SWAPs into the IR, this function only updates the - * layout. + * Instead of inserting these SWAPs into the IR, this function only updates + * the layout. * @returns failure() if A* search isn't able to find a solution. */ LogicalResult routeCold(ArrayRef layers, Layout& layout, diff --git a/mlir/unittests/Passes/Mapping/CMakeLists.txt b/mlir/unittests/Passes/Mapping/CMakeLists.txt index 2b8c749876..39aa3fbcc1 100644 --- a/mlir/unittests/Passes/Mapping/CMakeLists.txt +++ b/mlir/unittests/Passes/Mapping/CMakeLists.txt @@ -7,7 +7,7 @@ # Licensed under the MIT License set(target_name mqt-core-mlir-unittest-mapping) -add_executable(${target_name} test_heuristic_mapping.cpp) +add_executable(${target_name} test_mapping.cpp) target_link_libraries(${target_name} PRIVATE MLIRParser GTest::gtest_main MLIRQCProgramBuilder QCToQCO QCOToQC QcoPasses) diff --git a/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp b/mlir/unittests/Passes/Mapping/test_mapping.cpp similarity index 90% rename from mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp rename to mlir/unittests/Passes/Mapping/test_mapping.cpp index 487bb6f9be..a8b613d9bb 100644 --- a/mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp +++ b/mlir/unittests/Passes/Mapping/test_mapping.cpp @@ -45,9 +45,8 @@ struct ArchitectureParam { Architecture (*factory)(); }; -class HeuristicMappingPassTest - : public testing::Test, - public testing::WithParamInterface { +class MappingPassTest : public testing::Test, + public testing::WithParamInterface { public: /** * @brief Walks the IR and validates if each two-qubit op is executable on the @@ -105,8 +104,8 @@ class HeuristicMappingPassTest static void runHeuristicMapping(OwningOpRef& module) { PassManager pm(module->getContext()); pm.addPass(createQCToQCO()); - pm.addPass(qco::createHeuristicMappingPass(qco::HeuristicMappingPassOptions{ - .nlookahead = 5, .alpha = 1, .lambda = 0.85, .repeats = 2})); + pm.addPass(qco::createMappingPass(qco::MappingPassOptions{ + .nlookahead = 5, .alpha = 1, .lambda = 0.85, .iterations = 2})); pm.addPass(createQCOToQC()); auto res = pm.run(*module); ASSERT_FALSE(failed(res)); @@ -116,7 +115,7 @@ class HeuristicMappingPassTest }; }; // namespace -TEST_P(HeuristicMappingPassTest, GHZ) { +TEST_P(MappingPassTest, GHZ) { auto arch = GetParam().factory(); qc::QCProgramBuilder builder(context.get()); @@ -139,7 +138,7 @@ TEST_P(HeuristicMappingPassTest, GHZ) { EXPECT_TRUE(isExecutable(module, arch)); } -TEST_P(HeuristicMappingPassTest, Sabre) { +TEST_P(MappingPassTest, Sabre) { auto arch = GetParam().factory(); qc::QCProgramBuilder builder(context.get()); @@ -188,9 +187,9 @@ TEST_P(HeuristicMappingPassTest, Sabre) { } INSTANTIATE_TEST_SUITE_P( - Architectures, HeuristicMappingPassTest, - testing::Values(ArchitectureParam{ - "RigettiNovera", &HeuristicMappingPassTest::getRigettiNovera}), + Architectures, MappingPassTest, + testing::Values(ArchitectureParam{"RigettiNovera", + &MappingPassTest::getRigettiNovera}), [](const testing::TestParamInfo& info) { return info.param.name; }); From 4437e61e174bf749e20d7172eaf4276f2a8210a3 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 9 Mar 2026 08:14:14 +0100 Subject: [PATCH 29/37] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b8176ba05..6014d71bfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Added - ✨ Add initial infrastructure for new QC and QCO MLIR dialects - ([#1264], [#1330], [#1402], [#1428], [#1430], [#1436], [#1443], [#1446], [#1464], [#1465], [#1470], [#1471], [#1472], [#1474], [#1475], [#1506], [#1510], [#1513], [#1521]) + ([#1264], [#1330], [#1402], [#1428], [#1430], [#1436], [#1443], [#1446], [#1464], [#1465], [#1470], [#1471], [#1472], [#1474], [#1475], [#1506], [#1510], [#1513], [#1521], [#1537]) ([**@burgholzer**], [**@denialhaag**], [**@taminob**], [**@DRovara**], [**@li-mingbao**], [**@Ectras**], [**@MatthiasReumann**]) ### Changed @@ -327,6 +327,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool +[#1537]: https://github.com/munich-quantum-toolkit/core/pull/1537 [#1521]: https://github.com/munich-quantum-toolkit/core/pull/1521 [#1513]: https://github.com/munich-quantum-toolkit/core/pull/1513 [#1510]: https://github.com/munich-quantum-toolkit/core/pull/1510 From d76330b624f4cfdaa3e78e7a92cf6268780220d0 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 9 Mar 2026 13:43:52 +0100 Subject: [PATCH 30/37] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6014d71bfb..24841a097d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,9 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Added +- ✨ Add mapping pass for the QCO dialect ([#1537]) ([**@MatthiasReumann**]) - ✨ Add initial infrastructure for new QC and QCO MLIR dialects - ([#1264], [#1330], [#1402], [#1428], [#1430], [#1436], [#1443], [#1446], [#1464], [#1465], [#1470], [#1471], [#1472], [#1474], [#1475], [#1506], [#1510], [#1513], [#1521], [#1537]) + ([#1264], [#1330], [#1402], [#1428], [#1430], [#1436], [#1443], [#1446], [#1464], [#1465], [#1470], [#1471], [#1472], [#1474], [#1475], [#1506], [#1510], [#1513], [#1521]) ([**@burgholzer**], [**@denialhaag**], [**@taminob**], [**@DRovara**], [**@li-mingbao**], [**@Ectras**], [**@MatthiasReumann**]) ### Changed From 2fad2f6c0eec6a8fa01c0c6c1a7d042e3c36d53d Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 9 Mar 2026 13:44:06 +0100 Subject: [PATCH 31/37] Rename module to moduleOp --- .../unittests/Passes/Mapping/test_mapping.cpp | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/mlir/unittests/Passes/Mapping/test_mapping.cpp b/mlir/unittests/Passes/Mapping/test_mapping.cpp index a8b613d9bb..92d43d2d9e 100644 --- a/mlir/unittests/Passes/Mapping/test_mapping.cpp +++ b/mlir/unittests/Passes/Mapping/test_mapping.cpp @@ -53,16 +53,16 @@ class MappingPassTest : public testing::Test, * given architecture. * @returns true iff. all two-qubit gates are executable on the architecture. */ - static bool isExecutable(OwningOpRef& module, + static bool isExecutable(OwningOpRef& moduleOp, const Architecture& arch) { - auto entry = *(module->getOps().begin()); + auto entry = *(moduleOp->getOps().begin()); DenseMap mappings; for_each(entry.getOps(), [&](qc::StaticOp op) { mappings.try_emplace(op.getQubit(), op.getIndex()); }); bool executable = true; - std::ignore = module->walk([&](qc::UnitaryOpInterface op) { + std::ignore = moduleOp->walk([&](qc::UnitaryOpInterface op) { if (op.getNumQubits() > 1) { assert(op.getNumQubits() == 2 && "Expected only 2-qubit gates after decomposition"); @@ -101,14 +101,14 @@ class MappingPassTest : public testing::Test, context->loadAllAvailableDialects(); } - static void runHeuristicMapping(OwningOpRef& module) { - PassManager pm(module->getContext()); + static void runHeuristicMapping(OwningOpRef& moduleOp) { + PassManager pm(moduleOp->getContext()); pm.addPass(createQCToQCO()); pm.addPass(qco::createMappingPass(qco::MappingPassOptions{ .nlookahead = 5, .alpha = 1, .lambda = 0.85, .iterations = 2})); pm.addPass(createQCOToQC()); - auto res = pm.run(*module); - ASSERT_FALSE(failed(res)); + auto res = pm.run(*moduleOp); + ASSERT_TRUE(succeeded(res)); } std::unique_ptr context; @@ -133,9 +133,9 @@ TEST_P(MappingPassTest, GHZ) { builder.dealloc(q1); builder.dealloc(q2); - auto module = builder.finalize(); - runHeuristicMapping(module); - EXPECT_TRUE(isExecutable(module, arch)); + auto moduleOp = builder.finalize(); + runHeuristicMapping(moduleOp); + EXPECT_TRUE(isExecutable(moduleOp, arch)); } TEST_P(MappingPassTest, Sabre) { @@ -181,9 +181,9 @@ TEST_P(MappingPassTest, Sabre) { builder.dealloc(q4); builder.dealloc(q5); - auto module = builder.finalize(); - runHeuristicMapping(module); - EXPECT_TRUE(isExecutable(module, arch)); + auto moduleOp = builder.finalize(); + runHeuristicMapping(moduleOp); + EXPECT_TRUE(isExecutable(moduleOp, arch)); } INSTANTIATE_TEST_SUITE_P( From d29a4ac3ad855805c964840a10c988a90603322f Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 9 Mar 2026 14:28:43 +0100 Subject: [PATCH 32/37] Add tablegen documentation --- mlir/include/mlir/Passes/Passes.td | 31 +++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/mlir/include/mlir/Passes/Passes.td b/mlir/include/mlir/Passes/Passes.td index 173c4d7294..d70047004a 100644 --- a/mlir/include/mlir/Passes/Passes.td +++ b/mlir/include/mlir/Passes/Passes.td @@ -17,15 +17,40 @@ include "mlir/Pass/PassBase.td" def MappingPass : Pass<"place-and-route", "mlir::ModuleOp"> { let dependentDialects = ["mlir::qco::QCODialect"]; - let summary = "This pass ensures that a program meets the connectivity constraints of a given architecture."; + let summary = "This pass ensures that a program meets the connectivity constraints of a target architecture."; let description = [{ - This pass inserts SWAP operations to ensure two-qubit gates are executable on a given target architecture. + This pass initially assigns static qubits to the dynamically allocated ones ("placement") + by creating an initial dynamic-to-static mapping (the initial "layout"). Consequently, it + traverses the circuit and inserts `SWAP` operations such that all two-qubit operations are + executable on the target architecture ("routing"). + + For routing, the pass first divides the circuit into layers. A layer is a set of + independently executable two-qubit operations. Subsequently, the pass performs an A* + search for each layer to find and insert a sequence of `SWAP` operations that makes these + two-qubit operations executable. The A* search also considers the subsequent `nlookahead` + layers. + + The cost function of the A* search is defined as follows: + + * `f(n) = g(n) + h(n)` + * `g(n) = alpha * depth(n)` + * `h(n) = sum(pow(lambda, i) * h(L, pi) for [i, L] in enumerate(layers))` + + Where: + * `pi` is the dynamic-to-static mapping associated with search node `n`. + * `layers` is an array of layers with size `1 + nlookahead`. + * `depth(n)` returns the distance from the node `n` to the root node. + * `dist(i, j)` returns the distance between the qubits `i` and `j` on the target's coupling graph. + * `h(L, pi) := sum(dist(pi[gate.first], pi[gate.second]) for gate in L)` + + To improve on the chosen initial layout, the pass iteratively performs `iterations` forward and + backward routing traversals. }]; let options = [ Option<"nlookahead", "nlookahead", "std::size_t", "1", "The number of lookahead steps.">, Option<"alpha", "alpha", "float", "1.0F", - "The alpha factor in the cost function.">, + "The alpha factor in the cost function. Must be > 0.">, Option<"lambda", "lambda", "float", "0.5F", "The lambda factor in the cost function.">, Option<"iterations", "iterations", "std::size_t", "2", From d77a2f2c81d267fd1c1a2200a9a7bf6013392ffb Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 9 Mar 2026 14:46:06 +0100 Subject: [PATCH 33/37] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Tweak=20the=20change?= =?UTF-8?q?log=20entry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24841a097d..daa2ce3bcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Added -- ✨ Add mapping pass for the QCO dialect ([#1537]) ([**@MatthiasReumann**]) +- ✨ Add a `place-and-route` pass for mapping circuits to architectures with restricted topologies ([#1537]) ([**@MatthiasReumann**]) - ✨ Add initial infrastructure for new QC and QCO MLIR dialects ([#1264], [#1330], [#1402], [#1428], [#1430], [#1436], [#1443], [#1446], [#1464], [#1465], [#1470], [#1471], [#1472], [#1474], [#1475], [#1506], [#1510], [#1513], [#1521]) ([**@burgholzer**], [**@denialhaag**], [**@taminob**], [**@DRovara**], [**@li-mingbao**], [**@Ectras**], [**@MatthiasReumann**]) From 66146de594d8f988356ec457da20c9ca058e1959 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 9 Mar 2026 14:54:04 +0100 Subject: [PATCH 34/37] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Tweak=20the=20pass?= =?UTF-8?q?=20docstring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/include/mlir/Passes/Passes.td | 32 +++++++++++++++++------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/mlir/include/mlir/Passes/Passes.td b/mlir/include/mlir/Passes/Passes.td index d70047004a..8c984d1595 100644 --- a/mlir/include/mlir/Passes/Passes.td +++ b/mlir/include/mlir/Passes/Passes.td @@ -19,32 +19,36 @@ def MappingPass : Pass<"place-and-route", "mlir::ModuleOp"> { let dependentDialects = ["mlir::qco::QCODialect"]; let summary = "This pass ensures that a program meets the connectivity constraints of a target architecture."; let description = [{ - This pass initially assigns static qubits to the dynamically allocated ones ("placement") - by creating an initial dynamic-to-static mapping (the initial "layout"). Consequently, it - traverses the circuit and inserts `SWAP` operations such that all two-qubit operations are - executable on the target architecture ("routing"). + This pass maps the dynamically allocated qubits in a quantum program to the static qubits of a target architecture. + The pass performs both placement and routing of the qubits to ensure that all two-qubit operations in the program + can be executed on the target architecture. - For routing, the pass first divides the circuit into layers. A layer is a set of - independently executable two-qubit operations. Subsequently, the pass performs an A* - search for each layer to find and insert a sequence of `SWAP` operations that makes these - two-qubit operations executable. The A* search also considers the subsequent `nlookahead` - layers. + First, the pass assigns static qubits to the dynamically allocated ones by creating an initial dynamic-to-static + mapping, which is referred to as the initial layout. Then, it traverses the circuit and inserts `SWAP` operations to + ensure that all two-qubit operations are executable on the target architecture, a process known as routing. + + For routing, the pass first divides the circuit into layers. A layer is a set of independently executable (sequences + or blocks of) two-qubit operations. Subsequently, the pass performs an A* search for each layer to find and insert a + sequence of `SWAP` operations that makes these two-qubit operations executable. The A* search also considers + subsequent layers, which are determined by the `nlookahead` parameter. The cost function of the A* search is defined as follows: * `f(n) = g(n) + h(n)` * `g(n) = alpha * depth(n)` - * `h(n) = sum(pow(lambda, i) * h(L, pi) for [i, L] in enumerate(layers))` + * `h(n) = sum(pow(lambda, i) * h(L, p) for [i, L] in enumerate(layers))` Where: - * `pi` is the dynamic-to-static mapping associated with search node `n`. + * `p` is the dynamic-to-static mapping associated with search node `n`. * `layers` is an array of layers with size `1 + nlookahead`. * `depth(n)` returns the distance from the node `n` to the root node. * `dist(i, j)` returns the distance between the qubits `i` and `j` on the target's coupling graph. - * `h(L, pi) := sum(dist(pi[gate.first], pi[gate.second]) for gate in L)` + * `h(L, pi) := sum(dist(p[gate.first], p[gate.second]) for gate in L)` - To improve on the chosen initial layout, the pass iteratively performs `iterations` forward and - backward routing traversals. + To iteratively refine the mapping, the pass performs multiple forward and backward traversals of the circuit. In + each traversal, the pass routes the circuit and updates the dynamic-to-static mapping based on the routing decisions + made during that traversal. By performing multiple traversals, the pass can iteratively refine the mapping and + potentially find a more optimal solution. This is behavior is controlled by the `iterations` parameter. }]; let options = [ Option<"nlookahead", "nlookahead", "std::size_t", "1", From fbaa8a0d979adede16d8ba4500d417e159a7d053 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 9 Mar 2026 14:58:21 +0100 Subject: [PATCH 35/37] =?UTF-8?q?=F0=9F=8E=A8=20naming=20consistency=20`it?= =?UTF-8?q?erations`=20->=20`niterations`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/include/mlir/Passes/Passes.td | 4 ++-- mlir/lib/Passes/Mapping/Mapping.cpp | 4 ++-- mlir/unittests/Passes/Mapping/test_mapping.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mlir/include/mlir/Passes/Passes.td b/mlir/include/mlir/Passes/Passes.td index 8c984d1595..62bb1afdf7 100644 --- a/mlir/include/mlir/Passes/Passes.td +++ b/mlir/include/mlir/Passes/Passes.td @@ -48,7 +48,7 @@ def MappingPass : Pass<"place-and-route", "mlir::ModuleOp"> { To iteratively refine the mapping, the pass performs multiple forward and backward traversals of the circuit. In each traversal, the pass routes the circuit and updates the dynamic-to-static mapping based on the routing decisions made during that traversal. By performing multiple traversals, the pass can iteratively refine the mapping and - potentially find a more optimal solution. This is behavior is controlled by the `iterations` parameter. + potentially find a more optimal solution. This is behavior is controlled by the `niterations` parameter. }]; let options = [ Option<"nlookahead", "nlookahead", "std::size_t", "1", @@ -57,7 +57,7 @@ def MappingPass : Pass<"place-and-route", "mlir::ModuleOp"> { "The alpha factor in the cost function. Must be > 0.">, Option<"lambda", "lambda", "float", "0.5F", "The lambda factor in the cost function.">, - Option<"iterations", "iterations", "std::size_t", "2", + Option<"niterations", "niterations", "std::size_t", "2", "The number of forwards and backwards traversal to improve the initial layout."> ]; } diff --git a/mlir/lib/Passes/Mapping/Mapping.cpp b/mlir/lib/Passes/Mapping/Mapping.cpp index 2228f74059..95ac785493 100644 --- a/mlir/lib/Passes/Mapping/Mapping.cpp +++ b/mlir/lib/Passes/Mapping/Mapping.cpp @@ -322,10 +322,10 @@ struct MappingPass : impl::MappingPassBase { // Use the SABRE Approach to improve the initial layout choice (here: // identity): Traverse the layers from left-to-right-to-left and - // cold-route along the way. Repeat this procedure "iterations" times. + // cold-route along the way. Repeat this procedure "niterations" times. Layout layout = Layout::identity(arch.nqubits()); - for (std::size_t r = 0; r < this->iterations; ++r) { + for (std::size_t r = 0; r < this->niterations; ++r) { if (failed(routeCold(ltr, layout, arch, params))) { signalPassFailure(); return; diff --git a/mlir/unittests/Passes/Mapping/test_mapping.cpp b/mlir/unittests/Passes/Mapping/test_mapping.cpp index 92d43d2d9e..858baed5aa 100644 --- a/mlir/unittests/Passes/Mapping/test_mapping.cpp +++ b/mlir/unittests/Passes/Mapping/test_mapping.cpp @@ -105,7 +105,7 @@ class MappingPassTest : public testing::Test, PassManager pm(moduleOp->getContext()); pm.addPass(createQCToQCO()); pm.addPass(qco::createMappingPass(qco::MappingPassOptions{ - .nlookahead = 5, .alpha = 1, .lambda = 0.85, .iterations = 2})); + .nlookahead = 5, .alpha = 1, .lambda = 0.85, .niterations = 2})); pm.addPass(createQCOToQC()); auto res = pm.run(*moduleOp); ASSERT_TRUE(succeeded(res)); From 2471c6b99ff4e9c9f2ace5d9fe476fa94f901bf7 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 9 Mar 2026 14:59:02 +0100 Subject: [PATCH 36/37] =?UTF-8?q?=F0=9F=9A=A8=20avoid=20a=20couple=20of=20?= =?UTF-8?q?compiler=20warnings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- mlir/lib/Passes/Mapping/Mapping.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mlir/lib/Passes/Mapping/Mapping.cpp b/mlir/lib/Passes/Mapping/Mapping.cpp index 95ac785493..0720cdbaa8 100644 --- a/mlir/lib/Passes/Mapping/Mapping.cpp +++ b/mlir/lib/Passes/Mapping/Mapping.cpp @@ -283,8 +283,8 @@ struct MappingPass : impl::MappingPassBase { [[nodiscard]] float h(ArrayRef layers, const Architecture& arch, const Parameters& params) const { float costs{0}; - for (const auto [decay, layer] : zip(params.decay, layers)) { - for (const auto [prog0, prog1] : layer) { + for (const auto& [decay, layer] : zip(params.decay, layers)) { + for (const auto& [prog0, prog1] : layer) { const auto [hw0, hw1] = layout.getHardwareIndices(prog0, prog1); const std::size_t nswaps = arch.distanceBetween(hw0, hw1) - 1; costs += decay * static_cast(nswaps); @@ -566,7 +566,7 @@ struct MappingPass : impl::MappingPassBase { return failure(); } - for (const auto [hw0, hw1] : *swaps) { + for (const auto& [hw0, hw1] : *swaps) { layout.swap(hw0, hw1); } } @@ -617,7 +617,7 @@ struct MappingPass : impl::MappingPassBase { } const auto unknown = rewriter.getUnknownLoc(); - for (const auto [hw0, hw1] : *swaps) { + for (const auto& [hw0, hw1] : *swaps) { Operation* op0 = wires[hw0].operation(); Operation* op1 = wires[hw1].operation(); const auto in0 = wires[hw0].qubit(); @@ -647,7 +647,7 @@ struct MappingPass : impl::MappingPassBase { } // Jump over two-qubit gates contained in the layer. - for (const auto [prog0, prog1] : layer) { + for (const auto& [prog0, prog1] : layer) { std::ranges::advance(wires[layout.getHardwareIndex(prog0)], 1); std::ranges::advance(wires[layout.getHardwareIndex(prog1)], 1); } From d0e355b0e322e026657ec12d1c0c860f3f3fb87c Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 9 Mar 2026 16:09:59 +0100 Subject: [PATCH 37/37] Change pi to p --- mlir/include/mlir/Passes/Passes.td | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/include/mlir/Passes/Passes.td b/mlir/include/mlir/Passes/Passes.td index 62bb1afdf7..6f090fd44d 100644 --- a/mlir/include/mlir/Passes/Passes.td +++ b/mlir/include/mlir/Passes/Passes.td @@ -43,7 +43,7 @@ def MappingPass : Pass<"place-and-route", "mlir::ModuleOp"> { * `layers` is an array of layers with size `1 + nlookahead`. * `depth(n)` returns the distance from the node `n` to the root node. * `dist(i, j)` returns the distance between the qubits `i` and `j` on the target's coupling graph. - * `h(L, pi) := sum(dist(p[gate.first], p[gate.second]) for gate in L)` + * `h(L, p) := sum(dist(p[gate.first], p[gate.second]) for gate in L)` To iteratively refine the mapping, the pass performs multiple forward and backward traversals of the circuit. In each traversal, the pass routes the circuit and updates the dynamic-to-static mapping based on the routing decisions