From 5a23021368ca340c4e141d16b3724c5e20025c00 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 3 Nov 2025 07:34:00 +0100 Subject: [PATCH 01/56] Add RoutingDriver struct --- .../Transpilation/sc/RoutingPass.cpp | 280 ++++++++++-------- 1 file changed, 154 insertions(+), 126 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingPass.cpp index 1a120741c4..a050fdaca6 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingPass.cpp @@ -122,30 +122,32 @@ void replaceAllUsesInRegionAndChildrenExcept(Value oldValue, Value newValue, class Mapper { public: - explicit Mapper(std::unique_ptr arch, - std::unique_ptr scheduler, + explicit Mapper(std::unique_ptr scheduler, std::unique_ptr router, Pass::Statistic& numSwaps) - : arch_(std::move(arch)), scheduler_(std::move(scheduler)), - router_(std::move(router)), numSwaps_(&numSwaps) {} + : scheduler_(std::move(scheduler)), router_(std::move(router)), + numSwaps_(&numSwaps) {} /** * @returns true iff @p op is executable on the targeted architecture. */ - [[nodiscard]] bool isExecutable(UnitaryInterface op) { + [[nodiscard]] static bool isExecutable(UnitaryInterface op, + const Architecture& arch, + const Layout& layout) { const auto [in0, in1] = getIns(op); - return arch().areAdjacent(stack().top().lookupHardwareIndex(in0), - stack().top().lookupHardwareIndex(in1)); + return arch.areAdjacent(layout.lookupHardwareIndex(in0), + layout.lookupHardwareIndex(in1)); } /** * @brief Insert SWAPs such that the gates provided by the scheduler are * executable. */ - void map(UnitaryInterface op, PatternRewriter& rewriter) { - const auto layers = scheduler_->schedule(op, stack().top()); - const auto swaps = router_->route(layers, stack().top(), arch()); - insert(swaps, op->getLoc(), rewriter); - historyStack_.top().append(swaps.begin(), swaps.end()); + void map(UnitaryInterface op, const Architecture& arch, Layout& layout, + SmallVector& history, PatternRewriter& rewriter) { + const auto layers = scheduler_->schedule(op, layout); + const auto swaps = router_->route(layers, layout, arch); + insert(swaps, op->getLoc(), layout, rewriter); + history.append(swaps.begin(), swaps.end()); } /** @@ -155,36 +157,20 @@ class Mapper { * * @todo Remove SWAP history and use advanced strategies. */ - void restore(Location location, PatternRewriter& rewriter) { - const auto swaps = llvm::to_vector(llvm::reverse(historyStack_.top())); - insert(swaps, location, rewriter); + void restore(Location location, const SmallVector& history, + Layout& layout, PatternRewriter& rewriter) { + const auto swaps = llvm::to_vector(llvm::reverse(history)); + insert(swaps, location, layout, rewriter); } - /** - * @returns reference to the stack object. - */ - [[nodiscard]] LayoutStack& stack() { return stack_; } - - /** - * @returns reference to the history stack object. - */ - [[nodiscard]] LayoutStack>& historyStack() { - return historyStack_; - } - - /** - * @returns reference to architecture object. - */ - [[nodiscard]] Architecture& arch() const { return *arch_; } - private: - void insert(ArrayRef swaps, Location location, + void insert(ArrayRef swaps, Location location, Layout& layout, PatternRewriter& rewriter) { for (const auto [hw0, hw1] : swaps) { - const Value in0 = stack().top().lookupHardwareValue(hw0); - const Value in1 = stack().top().lookupHardwareValue(hw1); + const Value in0 = layout.lookupHardwareValue(hw0); + const Value in1 = layout.lookupHardwareValue(hw1); [[maybe_unused]] const auto [prog0, prog1] = - stack().top().getProgramIndices(hw0, hw1); + layout.getProgramIndices(hw0, hw1); LLVM_DEBUG({ llvm::dbgs() << llvm::format( @@ -201,37 +187,82 @@ class Mapper { replaceAllUsesInRegionAndChildrenExcept( in1, out0, swap->getParentRegion(), swap, rewriter); - stack().top().swap(in0, in1); - stack().top().remapQubitValue(in0, out0); - stack().top().remapQubitValue(in1, out1); + layout.swap(in0, in1); + layout.remapQubitValue(in0, out0); + layout.remapQubitValue(in1, out1); (*numSwaps_)++; } } - std::unique_ptr arch_; std::unique_ptr scheduler_; std::unique_ptr router_; + Pass::Statistic* numSwaps_; +}; + +struct RoutingDriver { + RoutingDriver(std::unique_ptr& mapper, + std::unique_ptr& arch) + : mapper_(std::move(mapper)), arch_(std::move(arch)) {} + + LogicalResult route(ModuleOp module); + +private: + WalkResult handleFunc([[maybe_unused]] func::FuncOp op); + WalkResult handleReturn([[maybe_unused]] func::ReturnOp op); + WalkResult handleFor(scf::ForOp op); + WalkResult handleIf(scf::IfOp op); + WalkResult handleYield(scf::YieldOp op, PatternRewriter& rewriter); + WalkResult handleQubit(QubitOp op); + WalkResult handleUnitary(UnitaryInterface op, PatternRewriter& rewriter); + WalkResult handleReset(ResetOp op); + WalkResult handleMeasure(MeasureOp op); + + /** + * @returns reference to the stack object. + */ + [[nodiscard]] LayoutStack& stack() { return stack_; } + + /** + * @returns reference to the history stack object. + */ + [[nodiscard]] LayoutStack>& historyStack() { + return historyStack_; + } + + /** + * @returns reference to architecture object. + */ + [[nodiscard]] Architecture& arch() const { return *arch_; } + + /** + * @returns reference to mapper object. + */ + [[nodiscard]] Mapper& mapper() const { return *mapper_; } + + SmallVector ops_; + LayoutStack stack_{}; LayoutStack> historyStack_{}; - Pass::Statistic* numSwaps_; + std::unique_ptr mapper_; + std::unique_ptr arch_; }; /** * @brief Push new state onto the stack. */ -WalkResult handleFunc([[maybe_unused]] func::FuncOp op, Mapper& mapper) { - assert(mapper.stack().empty() && "handleFunc: stack must be empty"); +WalkResult RoutingDriver::handleFunc([[maybe_unused]] func::FuncOp op) { + assert(stack().empty() && "handleFunc: stack must be empty"); LLVM_DEBUG({ llvm::dbgs() << "handleFunc: entry_point= " << op.getSymName() << '\n'; }); /// Function body state. - mapper.stack().emplace(mapper.arch().nqubits()); - mapper.historyStack().emplace(); + stack().emplace(arch().nqubits()); + historyStack().emplace(); return WalkResult::advance(); } @@ -240,28 +271,28 @@ WalkResult handleFunc([[maybe_unused]] func::FuncOp op, Mapper& mapper) { * @brief Indicates the end of a region defined by a function. Consequently, * we pop the region's state from the stack. */ -WalkResult handleReturn(Mapper& mapper) { - mapper.stack().pop(); - mapper.historyStack().pop(); +WalkResult RoutingDriver::handleReturn([[maybe_unused]] func::ReturnOp op) { + stack().pop(); + historyStack().pop(); return WalkResult::advance(); } /** * @brief Push new state for the loop body onto the stack. */ -WalkResult handleFor(scf::ForOp op, Mapper& mapper) { +WalkResult RoutingDriver::handleFor(scf::ForOp op) { /// Loop body state. - mapper.stack().duplicateTop(); - mapper.historyStack().emplace(); + stack().duplicateTop(); + historyStack().emplace(); /// Forward out-of-loop and in-loop values. - const auto initArgs = op.getInitArgs().take_front(mapper.arch().nqubits()); - const auto results = op.getResults().take_front(mapper.arch().nqubits()); - const auto iterArgs = - op.getRegionIterArgs().take_front(mapper.arch().nqubits()); + const auto nqubits = arch().nqubits(); + const auto initArgs = op.getInitArgs().take_front(nqubits); + const auto results = op.getResults().take_front(nqubits); + const auto iterArgs = op.getRegionIterArgs().take_front(nqubits); for (const auto [arg, res, iter] : llvm::zip(initArgs, results, iterArgs)) { - mapper.stack().getItemAtDepth(FOR_PARENT_DEPTH).remapQubitValue(arg, res); - mapper.stack().top().remapQubitValue(arg, iter); + stack().getItemAtDepth(FOR_PARENT_DEPTH).remapQubitValue(arg, res); + stack().top().remapQubitValue(arg, iter); } return WalkResult::advance(); @@ -270,16 +301,16 @@ WalkResult handleFor(scf::ForOp op, Mapper& mapper) { /** * @brief Push two new states for the then and else branches onto the stack. */ -WalkResult handleIf(scf::IfOp op, Mapper& mapper) { +WalkResult RoutingDriver::handleIf(scf::IfOp op) { /// Prepare stack. - mapper.stack().duplicateTop(); /// Else. - mapper.stack().duplicateTop(); /// Then. - mapper.historyStack().emplace(); - mapper.historyStack().emplace(); + stack().duplicateTop(); /// Else. + stack().duplicateTop(); /// Then. + historyStack().emplace(); + historyStack().emplace(); /// Forward out-of-if values. - const auto results = op->getResults().take_front(mapper.arch().nqubits()); - Layout& layoutBeforeIf = mapper.stack().getItemAtDepth(IF_PARENT_DEPTH); + const auto results = op->getResults().take_front(arch().nqubits()); + Layout& layoutBeforeIf = stack().getItemAtDepth(IF_PARENT_DEPTH); for (const auto [hw, res] : llvm::enumerate(results)) { const Value q = layoutBeforeIf.lookupHardwareValue(hw); layoutBeforeIf.remapQubitValue(q, res); @@ -301,16 +332,17 @@ WalkResult handleIf(scf::IfOp op, Mapper& mapper) { * a 'for' of 'if' region always requires 2 * #(SWAPs required for region) * additional SWAPS. */ -WalkResult handleYield(scf::YieldOp op, Mapper& mapper, - PatternRewriter& rewriter) { +WalkResult RoutingDriver::handleYield(scf::YieldOp op, + PatternRewriter& rewriter) { if (!isa(op->getParentOp()) && !isa(op->getParentOp())) { return WalkResult::skip(); } - mapper.restore(op->getLoc(), rewriter); - mapper.stack().pop(); - mapper.historyStack().pop(); + mapper().restore(op->getLoc(), historyStack().top(), stack().top(), rewriter); + + stack().pop(); + historyStack().pop(); return WalkResult::advance(); } @@ -321,9 +353,9 @@ WalkResult handleYield(scf::YieldOp op, Mapper& mapper, * * Thanks to the placement pass, we can apply the identity layout here. */ -WalkResult handleQubit(QubitOp op, Mapper& mapper) { +WalkResult RoutingDriver::handleQubit(QubitOp op) { const std::size_t index = op.getIndex(); - mapper.stack().top().add(index, index, op.getQubit()); + stack().top().add(index, index, op.getQubit()); return WalkResult::advance(); } @@ -331,8 +363,8 @@ WalkResult handleQubit(QubitOp op, Mapper& mapper) { * @brief Ensures the executability of two-qubit gates on the given target * architecture by inserting SWAPs. */ -WalkResult handleUnitary(UnitaryInterface op, Mapper& mapper, - PatternRewriter& rewriter) { +WalkResult RoutingDriver::handleUnitary(UnitaryInterface op, + PatternRewriter& rewriter) { const std::vector inQubits = op.getAllInQubits(); const std::vector outQubits = op.getAllOutQubits(); const std::size_t nacts = inQubits.size(); @@ -344,7 +376,7 @@ WalkResult handleUnitary(UnitaryInterface op, Mapper& mapper, if (isa(op)) { for (const auto [in, out] : llvm::zip(inQubits, outQubits)) { - mapper.stack().top().remapQubitValue(in, out); + stack().top().remapQubitValue(in, out); } return WalkResult::advance(); } @@ -356,35 +388,34 @@ WalkResult handleUnitary(UnitaryInterface op, Mapper& mapper, /// Single-qubit: Forward mapping. if (nacts == 1) { - mapper.stack().top().remapQubitValue(inQubits[0], outQubits[0]); + stack().top().remapQubitValue(inQubits[0], outQubits[0]); return WalkResult::advance(); } - if (!mapper.isExecutable(op)) { - mapper.map(op, rewriter); + if (!Mapper::isExecutable(op, arch(), stack().top())) { + mapper().map(op, arch(), stack().top(), historyStack().top(), rewriter); } const auto [execIn0, execIn1] = getIns(op); const auto [execOut0, execOut1] = getOuts(op); LLVM_DEBUG({ - llvm::dbgs() << llvm::format( - "handleUnitary: gate= p%d:h%d, p%d:h%d\n", - mapper.stack().top().lookupProgramIndex(execIn0), - mapper.stack().top().lookupHardwareIndex(execIn0), - mapper.stack().top().lookupProgramIndex(execIn1), - mapper.stack().top().lookupHardwareIndex(execIn1)); + llvm::dbgs() << llvm::format("handleUnitary: gate= p%d:h%d, p%d:h%d\n", + stack().top().lookupProgramIndex(execIn0), + stack().top().lookupHardwareIndex(execIn0), + stack().top().lookupProgramIndex(execIn1), + stack().top().lookupHardwareIndex(execIn1)); }); if (isa(op)) { - mapper.stack().top().swap(execIn0, execIn1); - mapper.historyStack().top().push_back( - {mapper.stack().top().lookupHardwareIndex(execIn0), - mapper.stack().top().lookupHardwareIndex(execIn1)}); + stack().top().swap(execIn0, execIn1); + historyStack().top().push_back( + {stack().top().lookupHardwareIndex(execIn0), + stack().top().lookupHardwareIndex(execIn1)}); } - mapper.stack().top().remapQubitValue(execIn0, execOut0); - mapper.stack().top().remapQubitValue(execIn1, execOut1); + stack().top().remapQubitValue(execIn0, execOut0); + stack().top().remapQubitValue(execIn1, execOut1); return WalkResult::advance(); } @@ -392,16 +423,16 @@ WalkResult handleUnitary(UnitaryInterface op, Mapper& mapper, /** * @brief Update layout. */ -WalkResult handleReset(ResetOp op, Mapper& mapper) { - mapper.stack().top().remapQubitValue(op.getInQubit(), op.getOutQubit()); +WalkResult RoutingDriver::handleReset(ResetOp op) { + stack().top().remapQubitValue(op.getInQubit(), op.getOutQubit()); return WalkResult::advance(); } /** * @brief Update layout. */ -WalkResult handleMeasure(MeasureOp op, Mapper& mapper) { - mapper.stack().top().remapQubitValue(op.getInQubit(), op.getOutQubit()); +WalkResult RoutingDriver::handleMeasure(MeasureOp op) { + stack().top().remapQubitValue(op.getInQubit(), op.getOutQubit()); return WalkResult::advance(); } @@ -427,21 +458,17 @@ WalkResult handleMeasure(MeasureOp op, Mapper& mapper) { * custom driver would be required in any case, which adds unnecessary code to * maintain. */ -LogicalResult route(ModuleOp module, MLIRContext* ctx, Mapper& mapper) { - PatternRewriter rewriter(ctx); - - /// Prepare work-list. - SmallVector worklist; +LogicalResult RoutingDriver::route(ModuleOp module) { for (const auto func : module.getOps()) { if (!isEntryPoint(func)) { continue; // Ignore non entry_point functions for now. } - func->walk( - [&](Operation* op) { worklist.push_back(op); }); + func->walk([&](Operation* op) { ops_.push_back(op); }); } - /// Iterate work-list. - for (Operation* curr : worklist) { + PatternRewriter rewriter(module->getContext()); + + for (Operation* curr : ops_) { if (curr == nullptr) { continue; // Skip erased ops. } @@ -456,29 +483,25 @@ LogicalResult route(ModuleOp module, MLIRContext* ctx, Mapper& mapper) { TypeSwitch(curr) /// mqtopt Dialect .Case([&](UnitaryInterface op) { - return handleUnitary(op, mapper, rewriter); + return handleUnitary(op, rewriter); }) - .Case([&](QubitOp op) { return handleQubit(op, mapper); }) - .Case([&](ResetOp op) { return handleReset(op, mapper); }) - .Case( - [&](MeasureOp op) { return handleMeasure(op, mapper); }) + .Case([&](QubitOp op) { return handleQubit(op); }) + .Case([&](ResetOp op) { return handleReset(op); }) + .Case([&](MeasureOp op) { return handleMeasure(op); }) /// built-in Dialect .Case([&]([[maybe_unused]] ModuleOp op) { return WalkResult::advance(); }) /// func Dialect - .Case( - [&](func::FuncOp op) { return handleFunc(op, mapper); }) + .Case([&](func::FuncOp op) { return handleFunc(op); }) .Case([&]([[maybe_unused]] func::ReturnOp op) { - return handleReturn(mapper); + return handleReturn(op); }) /// scf Dialect - .Case( - [&](scf::ForOp op) { return handleFor(op, mapper); }) - .Case([&](scf::IfOp op) { return handleIf(op, mapper); }) - .Case([&](scf::YieldOp op) { - return handleYield(op, mapper, rewriter); - }) + .Case([&](scf::ForOp op) { return handleFor(op); }) + .Case([&](scf::IfOp op) { return handleIf(op); }) + .Case( + [&](scf::YieldOp op) { return handleYield(op, rewriter); }) /// Skip the rest. .Default([](auto) { return WalkResult::skip(); }); @@ -503,6 +526,7 @@ struct RoutingPassSC final : impl::RoutingPassSCBase { return; } + auto mapper = getMapper(); auto arch = getArchitecture(archName); if (!arch) { emitError(UnknownLoc::get(&getContext())) @@ -511,25 +535,29 @@ struct RoutingPassSC final : impl::RoutingPassSCBase { return; } - Mapper mapper = getMapper(std::move(arch)); - if (failed(route(getOperation(), &getContext(), mapper))) { + RoutingDriver driver(mapper, arch); + + if (failed(driver.route(getOperation()))) { signalPassFailure(); } } private: - [[nodiscard]] Mapper getMapper(std::unique_ptr arch) { + [[nodiscard]] std::unique_ptr getMapper() { switch (static_cast(method)) { - case RoutingMethod::Naive: + case RoutingMethod::Naive: { LLVM_DEBUG({ llvm::dbgs() << "getRouter: method=naive\n"; }); - return Mapper(std::move(arch), std::make_unique(), - std::make_unique(), numSwaps); - case RoutingMethod::AStar: + return std::make_unique(std::make_unique(), + std::make_unique(), + numSwaps); + } + case RoutingMethod::AStar: { LLVM_DEBUG({ llvm::dbgs() << "getRouter: method=astar\n"; }); const HeuristicWeights weights(alpha, lambda, nlookahead); - return Mapper(std::move(arch), - std::make_unique(nlookahead), - std::make_unique(weights), numSwaps); + return std::make_unique( + std::make_unique(nlookahead), + std::make_unique(weights), numSwaps); + } } llvm_unreachable("Unknown method"); From eb4e104038c27c56c5abc7736d6a9bd01adec688 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 5 Nov 2025 06:28:50 +0100 Subject: [PATCH 02/56] Remove worklist from route function --- .../Transpilation/sc/RoutingPass.cpp | 83 ++++++++----------- 1 file changed, 36 insertions(+), 47 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingPass.cpp index a050fdaca6..d7d8355a9c 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingPass.cpp @@ -142,12 +142,12 @@ class Mapper { * @brief Insert SWAPs such that the gates provided by the scheduler are * executable. */ - void map(UnitaryInterface op, const Architecture& arch, Layout& layout, - SmallVector& history, PatternRewriter& rewriter) { + [[nodiscard]] RouterResult map(UnitaryInterface op, const Architecture& arch, + Layout& layout, PatternRewriter& rewriter) { const auto layers = scheduler_->schedule(op, layout); const auto swaps = router_->route(layers, layout, arch); insert(swaps, op->getLoc(), layout, rewriter); - history.append(swaps.begin(), swaps.end()); + return swaps; } /** @@ -241,8 +241,6 @@ struct RoutingDriver { */ [[nodiscard]] Mapper& mapper() const { return *mapper_; } - SmallVector ops_; - LayoutStack stack_{}; LayoutStack> historyStack_{}; @@ -365,6 +363,7 @@ WalkResult RoutingDriver::handleQubit(QubitOp op) { */ WalkResult RoutingDriver::handleUnitary(UnitaryInterface op, PatternRewriter& rewriter) { + LLVM_DEBUG(llvm::dbgs() << "handleUnitary: gate=" << op->getName() << '\n'); const std::vector inQubits = op.getAllInQubits(); const std::vector outQubits = op.getAllOutQubits(); const std::size_t nacts = inQubits.size(); @@ -392,8 +391,11 @@ WalkResult RoutingDriver::handleUnitary(UnitaryInterface op, return WalkResult::advance(); } + /// Not-executable two-qubit: Map first then forward. if (!Mapper::isExecutable(op, arch(), stack().top())) { - mapper().map(op, arch(), stack().top(), historyStack().top(), rewriter); + LLVM_DEBUG(llvm::dbgs() << "\tnot executable\n"); + const auto swaps = mapper().map(op, arch(), stack().top(), rewriter); + historyStack().top().append(swaps.begin(), swaps.end()); } const auto [execIn0, execIn1] = getIns(op); @@ -459,51 +461,39 @@ WalkResult RoutingDriver::handleMeasure(MeasureOp op) { * maintain. */ LogicalResult RoutingDriver::route(ModuleOp module) { + PatternRewriter rewriter(module->getContext()); for (const auto func : module.getOps()) { if (!isEntryPoint(func)) { continue; // Ignore non entry_point functions for now. } - func->walk([&](Operation* op) { ops_.push_back(op); }); - } - - PatternRewriter rewriter(module->getContext()); - - for (Operation* curr : ops_) { - if (curr == nullptr) { - continue; // Skip erased ops. - } - const OpBuilder::InsertionGuard guard(rewriter); - rewriter.setInsertionPoint(curr); - - /// TypeSwitch performs sequential dyn_cast checks. - /// Hence, always put most frequent ops first. - - const auto res = - TypeSwitch(curr) - /// mqtopt Dialect - .Case([&](UnitaryInterface op) { - return handleUnitary(op, rewriter); - }) - .Case([&](QubitOp op) { return handleQubit(op); }) - .Case([&](ResetOp op) { return handleReset(op); }) - .Case([&](MeasureOp op) { return handleMeasure(op); }) - /// built-in Dialect - .Case([&]([[maybe_unused]] ModuleOp op) { - return WalkResult::advance(); - }) - /// func Dialect - .Case([&](func::FuncOp op) { return handleFunc(op); }) - .Case([&]([[maybe_unused]] func::ReturnOp op) { - return handleReturn(op); - }) - /// scf Dialect - .Case([&](scf::ForOp op) { return handleFor(op); }) - .Case([&](scf::IfOp op) { return handleIf(op); }) - .Case( - [&](scf::YieldOp op) { return handleYield(op, rewriter); }) - /// Skip the rest. - .Default([](auto) { return WalkResult::skip(); }); + const auto res = func->walk([&](Operation* curr) { + const OpBuilder::InsertionGuard guard(rewriter); + rewriter.setInsertionPoint(curr); + return TypeSwitch(curr) + /// mqtopt Dialect + .Case( + [&](UnitaryInterface op) { return handleUnitary(op, rewriter); }) + .Case([&](QubitOp op) { return handleQubit(op); }) + .Case([&](ResetOp op) { return handleReset(op); }) + .Case([&](MeasureOp op) { return handleMeasure(op); }) + /// built-in Dialect + .Case([&]([[maybe_unused]] ModuleOp op) { + return WalkResult::advance(); + }) + /// func Dialect + .Case([&](func::FuncOp op) { return handleFunc(op); }) + .Case([&]([[maybe_unused]] func::ReturnOp op) { + return handleReturn(op); + }) + /// scf Dialect + .Case([&](scf::ForOp op) { return handleFor(op); }) + .Case([&](scf::IfOp op) { return handleIf(op); }) + .Case( + [&](scf::YieldOp op) { return handleYield(op, rewriter); }) + /// Skip the rest. + .Default([](auto) { return WalkResult::skip(); }); + }); if (res.wasInterrupted()) { return failure(); @@ -536,7 +526,6 @@ struct RoutingPassSC final : impl::RoutingPassSCBase { } RoutingDriver driver(mapper, arch); - if (failed(driver.route(getOperation()))) { signalPassFailure(); } From 5750df504a45da3a2dbb71a5b5bb2e23b0bcf494 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 5 Nov 2025 14:08:30 +0100 Subject: [PATCH 03/56] Add working global scheduler and routing process --- .../MQTOpt/Transforms/Transpilation/Layout.h | 3 +- .../MQTOpt/Transforms/Transpilation/Router.h | 31 +- .../Transforms/Transpilation/Scheduler.h | 259 +++++++++ .../Transpilation/sc/PlacementPass.cpp | 2 +- .../Transpilation/sc/RoutingPass.cpp | 491 +++++++++--------- 5 files changed, 516 insertions(+), 270 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h index 91d43f4458..f87cabd1cb 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h @@ -122,8 +122,7 @@ class [[nodiscard]] ThinLayout { /** * @brief Enhanced layout that extends ThinLayout with Value tracking - * capabilities. This is the recommended replacement for the original Layout - * class. + * capabilities. */ class [[nodiscard]] Layout : public ThinLayout { public: diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h index 88b6698c25..eaf5d5cded 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -35,7 +36,8 @@ of gates. */ struct RouterBase { virtual ~RouterBase() = default; - [[nodiscard]] virtual RouterResult route(const Layers&, const ThinLayout&, + [[nodiscard]] virtual RouterResult route(std::span, + const ThinLayout&, const Architecture&) const = 0; }; @@ -43,17 +45,17 @@ struct RouterBase { * @brief Use shortest path swapping to make one gate executable. */ struct NaiveRouter final : RouterBase { - [[nodiscard]] RouterResult route(const Layers& layers, + [[nodiscard]] RouterResult route(std::span layers, const ThinLayout& layout, const Architecture& arch) const override { - if (layers.size() != 1 || layers.front().size() != 1) { + if (layers.size() != 1 || layers.front().gates.size() != 1) { throw std::invalid_argument( "NaiveRouter expects exactly one layer with one gate"); } /// This assumes an avg. of 16 SWAPs per gate. SmallVector swaps; - for (const auto [prog0, prog1] : layers.front()) { + for (const auto [prog0, prog1] : layers.front().gates) { const auto [hw0, hw1] = layout.getHardwareIndices(prog0, prog1); const auto path = arch.shortestPathBetween(hw0, hw1); for (std::size_t i = 0; i < path.size() - 2; ++i) { @@ -106,8 +108,9 @@ struct AStarHeuristicRouter final : RouterBase { * @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, QubitIndexPair swap, const Layers& layers, - const Architecture& arch, const HeuristicWeights& weights) + Node(const Node& parent, QubitIndexPair swap, + std::span layers, const Architecture& arch, + const HeuristicWeights& weights) : sequence(parent.sequence), layout(parent.layout), f(0) { /// Apply node-specific swap to given layout. layout.swap(layout.getProgramIndex(swap.first), @@ -158,11 +161,12 @@ struct AStarHeuristicRouter final : RouterBase { * its hardware qubits. Intuitively, this is the number of SWAPs that a * naive router would insert to route the layers. */ - [[nodiscard]] float h(const Layers& layers, const Architecture& arch, + [[nodiscard]] float h(std::span layers, + const Architecture& arch, const HeuristicWeights& weights) const { float nn{0}; for (const auto [i, layer] : llvm::enumerate(layers)) { - for (const auto [prog0, prog1] : layer) { + for (const auto [prog0, prog1] : layer.gates) { const auto [hw0, hw1] = layout.getHardwareIndices(prog0, prog1); const std::size_t dist = arch.distanceBetween(hw0, hw1); const std::size_t nswaps = dist < 2 ? 0 : dist - 2; @@ -176,13 +180,13 @@ struct AStarHeuristicRouter final : RouterBase { using MinQueue = std::priority_queue, std::greater<>>; public: - [[nodiscard]] RouterResult route(const Layers& layers, + [[nodiscard]] RouterResult route(std::span layers, const ThinLayout& layout, const Architecture& arch) const override { Node root(layout); /// Early exit. No SWAPs required: - if (root.isGoal(layers.front(), arch)) { + if (root.isGoal(layers.front().gates, arch)) { return {}; } @@ -198,7 +202,7 @@ struct AStarHeuristicRouter final : RouterBase { Node curr = frontier.top(); frontier.pop(); - if (curr.isGoal(layers.front(), arch)) { + if (curr.isGoal(layers.front().gates, arch)) { return curr.sequence; } @@ -223,10 +227,11 @@ struct AStarHeuristicRouter final : RouterBase { /** * @brief Expand frontier with all neighbouring SWAPs in the current front. */ - void expand(MinQueue& frontier, const Node& parent, const Layers& layers, + void expand(MinQueue& frontier, const Node& parent, + std::span layers, const Architecture& arch) const { llvm::SmallDenseSet swaps{}; - for (const QubitIndexPair gate : layers.front()) { + for (const QubitIndexPair gate : layers.front().gates) { for (const auto prog : {gate.first, gate.second}) { const auto hw0 = parent.layout.getHardwareIndex(prog); for (const auto hw1 : arch.neighboursOf(hw0)) { diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Scheduler.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Scheduler.h index e64b729d9b..0abb584e24 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Scheduler.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Scheduler.h @@ -13,11 +13,13 @@ #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" +#include "mlir/IR/PatternMatch.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Support/ErrorHandling.h" #include +#include #include #include #include @@ -25,6 +27,7 @@ #include #include #include +#include #define DEBUG_TYPE "route-sc" @@ -273,4 +276,260 @@ struct ParallelOpScheduler final : SchedulerBase { std::size_t nlayers_; }; + +struct ScheduledLayer { + Operation* anchor; + SmallVector gates; +}; + +using ScheduledLayers = SmallVector; + +struct SlidingWindow { + SlidingWindow(std::span schedule, + const std::size_t nlookahead) + : schedule_(schedule), nlookahead_(nlookahead) {} + + [[nodiscard]] std::span current() const { + const std::size_t nlayers = schedule_.size(); + return schedule_.subspan(offset_, offset_ + nlookahead_ <= nlayers + ? nlookahead_ + : nlayers - offset_); + } + + void advance() { ++offset_; } + +private: + std::span schedule_; + std::size_t nlookahead_; + std::size_t offset_{}; +}; + +struct ParallelOpFullScheduler { + [[nodiscard]] static ScheduledLayers schedule(Region& region, Layout layout, + PatternRewriter& rewriter) { + ScheduledLayers layers; + + /// Worklist of active qubits. + SmallVector wl; + SmallVector nextWl; + wl.reserve(layout.getHardwareQubits().size()); + nextWl.reserve(layout.getHardwareQubits().size()); + + // Initialize worklist. + llvm::copy_if(layout.getHardwareQubits(), std::back_inserter(wl), + [](Value q) { return !q.use_empty(); }); + + /// Set of two-qubit gates seen at least once. + llvm::SmallDenseSet openTwoQubit; + + /// Vector of two-qubit gates seen twice. + SmallVector readyTwoQubit; + + SmallVector order; + order.reserve(std::distance(region.begin(), region.end())); + + while (!wl.empty()) { + for (const Value q : wl) { + const auto opt = advanceToTwoQubitGate(q, region, layout, order); + if (!opt) { + continue; + } + + const auto& [qNext, gate] = opt.value(); + + if (q != qNext) { + layout.remapQubitValue(q, qNext); + } + + if (!openTwoQubit.insert(gate).second) { + readyTwoQubit.push_back(gate); + openTwoQubit.erase(gate); + continue; + } + } + + if (readyTwoQubit.empty()) { + break; + } + + nextWl.clear(); + + /// At this point all qubit values are remapped to the input of a + /// two-qubit gate: "value.user == two-qubit-gate". + /// + /// We release the ready two-qubit gates by forwarding their inputs + /// to their outputs. Then, we advance to the end of its two-qubit block. + /// Intuitively, whenever a gate in readyTwoQubit is routed, all one and + /// two-qubit gates acting on the same qubits are executable as well. + + /// Can use front() as anchor because of reordering. + ScheduledLayer layer{.anchor = readyTwoQubit.front(), .gates = {}}; + layer.gates.reserve(readyTwoQubit.size()); + + for (const auto& readyOp : readyTwoQubit) { + order.push_back(readyOp); + + /// Release. + const ValuePair ins = getIns(readyOp); + const ValuePair outs = getOuts(readyOp); + layer.gates.emplace_back(layout.lookupProgramIndex(ins.first), + layout.lookupProgramIndex(ins.second)); + + layout.remapQubitValue(ins.first, outs.first); + layout.remapQubitValue(ins.second, outs.second); + + /// Advance two-qubit block. + std::array gates; + std::array heads{outs.first, outs.second}; + + while (true) { + bool stop = false; + for (const auto [i, q] : llvm::enumerate(heads)) { + const auto opt = advanceToTwoQubitGate(q, region, layout, order); + if (!opt) { + heads[i] = nullptr; + stop = true; + break; + } + + const auto& [qNext, gate] = opt.value(); + + if (q != qNext) { + layout.remapQubitValue(q, qNext); + } + + heads[i] = qNext; + gates[i] = gate; + } + + if (stop || gates[0] != gates[1]) { + break; + } + + order.push_back(gates[0]); + + const ValuePair ins = getIns(gates[0]); + const ValuePair outs = getOuts(gates[0]); + layout.remapQubitValue(ins.first, outs.first); + layout.remapQubitValue(ins.second, outs.second); + heads = {outs.first, outs.second}; + } + + /// Initialize next worklist. + for (const auto q : heads) { + if (q != nullptr && !q.use_empty()) { + nextWl.push_back(q); + } + } + } + + layers.emplace_back(layer); + + /// Prepare for next iteration. + readyTwoQubit.clear(); + wl = std::move(nextWl); + } + + LLVM_DEBUG({ + llvm::dbgs() << "schedule: order=\n"; + for (Operation* op : order) { + llvm::dbgs() << op->getLoc() << '\n'; + } + }); + + /// Impose strict def-use chain ordering. + for (std::size_t i = 0; i < order.size() - 1; ++i) { + rewriter.moveOpAfter(order[i + 1], order[i]); + } + + LLVM_DEBUG({ + llvm::dbgs() << "schedule: layers=\n"; + for (const auto [i, layer] : llvm::enumerate(layers)) { + llvm::dbgs() << '\t' << i << "= "; + for (const auto [prog0, prog1] : layer.gates) { + llvm::dbgs() << "(" << prog0 << "," << prog1 << "), "; + } + llvm::dbgs() << "\tanchor= " << layer.anchor->getLoc(); + llvm::dbgs() << '\n'; + } + }); + + return layers; + } + +private: + /** + * @returns Next two-qubit gate on qubit wire, or std::nullopt if none exists. + */ + static std::optional> // TODO: Instead of value use vector of values. + advanceToTwoQubitGate(const Value q, Region& region, const Layout& layout, + SmallVector& order) { + Value head = q; + while (true) { + if (head.use_empty()) { // No two-qubit gate found. + return std::nullopt; + } + + Operation* user = getUserInRegion(head, ®ion); + if (user == nullptr) { // No two-qubit gate found. + return std::nullopt; + } + + bool endOfRegion = false; + std::optional twoQubitOp; + + TypeSwitch(user) + /// MQT + /// BarrierOp is a UnitaryInterface, however, requires special care. + .Case([&](BarrierOp op) { + for (const auto [in, out] : + llvm::zip_equal(op.getInQubits(), op.getOutQubits())) { + if (in == head) { + head = out; + return; + } + } + llvm_unreachable("head must be in barrier"); + }) + .Case([&](UnitaryInterface op) { + if (isTwoQubitGate(op)) { + twoQubitOp = op; + return; // Found a two-qubit gate, stop advancing head. + } + // Otherwise, advance head. + head = op.getOutQubits().front(); + order.push_back(op); + }) + .Case([&](ResetOp op) { head = op.getOutQubit(); }) + .Case([&](MeasureOp op) { head = op.getOutQubit(); }) + /// SCF + /// The scf funcs assume that the first n results are the hw qubits. + .Case([&](scf::ForOp op) { + head = op->getResult(layout.lookupHardwareIndex(q)); + }) + .Case([&](scf::IfOp op) { + head = op->getResult(layout.lookupHardwareIndex(q)); + }) + .Case([&](scf::YieldOp) { endOfRegion = true; }) + .Default([&]([[maybe_unused]] Operation* op) { + LLVM_DEBUG({ + llvm::dbgs() << "unknown operation in def-use chain: "; + op->dump(); + }); + llvm_unreachable("unknown operation in def-use chain"); + }); + + if (twoQubitOp) { // Two-qubit gate found. + return std::make_pair(head, *twoQubitOp); + } + + if (endOfRegion) { + return std::nullopt; + } + } + + return std::nullopt; + } +}; } // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp index 76b036738a..bb77ef2239 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp @@ -464,7 +464,7 @@ struct PlacementPassSC final : impl::PlacementPassSCBase { << '\n'; }); return std::make_unique(arch.nqubits(), - std::mt19937_64(seed)); + std::mt19937_64(1492999563)); } llvm_unreachable("Unknown strategy"); } diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingPass.cpp index d7d8355a9c..ca9d330414 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingPass.cpp @@ -15,7 +15,6 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Scheduler.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Stack.h" #include #include @@ -42,6 +41,8 @@ #include #include #include +#include +#include #include #include @@ -120,202 +121,151 @@ void replaceAllUsesInRegionAndChildrenExcept(Value oldValue, Value newValue, }); } -class Mapper { -public: - explicit Mapper(std::unique_ptr scheduler, - std::unique_ptr router, Pass::Statistic& numSwaps) - : scheduler_(std::move(scheduler)), router_(std::move(router)), - numSwaps_(&numSwaps) {} - - /** - * @returns true iff @p op is executable on the targeted architecture. - */ - [[nodiscard]] static bool isExecutable(UnitaryInterface op, - const Architecture& arch, - const Layout& layout) { - const auto [in0, in1] = getIns(op); - return arch.areAdjacent(layout.lookupHardwareIndex(in0), - layout.lookupHardwareIndex(in1)); - } - - /** - * @brief Insert SWAPs such that the gates provided by the scheduler are - * executable. - */ - [[nodiscard]] RouterResult map(UnitaryInterface op, const Architecture& arch, - Layout& layout, PatternRewriter& rewriter) { - const auto layers = scheduler_->schedule(op, layout); - const auto swaps = router_->route(layers, layout, arch); - insert(swaps, op->getLoc(), layout, rewriter); - return swaps; - } - - /** - * @brief Restore layout by uncomputing. - * - * History is cleared by the caller (e.g., via stack/history pop in handlers). - * - * @todo Remove SWAP history and use advanced strategies. - */ - void restore(Location location, const SmallVector& history, - Layout& layout, PatternRewriter& rewriter) { - const auto swaps = llvm::to_vector(llvm::reverse(history)); - insert(swaps, location, layout, rewriter); - } - -private: - void insert(ArrayRef swaps, Location location, Layout& layout, - PatternRewriter& rewriter) { - for (const auto [hw0, hw1] : swaps) { - const Value in0 = layout.lookupHardwareValue(hw0); - const Value in1 = layout.lookupHardwareValue(hw1); - [[maybe_unused]] const auto [prog0, prog1] = - layout.getProgramIndices(hw0, hw1); - - LLVM_DEBUG({ - llvm::dbgs() << llvm::format( - "route: swap= p%d:h%d, p%d:h%d <- p%d:h%d, p%d:h%d\n", prog1, hw0, - prog0, hw1, prog0, hw0, prog1, hw1); - }); - - auto swap = createSwap(location, in0, in1, rewriter); - const auto [out0, out1] = getOuts(swap); - - rewriter.setInsertionPointAfter(swap); - replaceAllUsesInRegionAndChildrenExcept( - in0, out1, swap->getParentRegion(), swap, rewriter); - replaceAllUsesInRegionAndChildrenExcept( - in1, out0, swap->getParentRegion(), swap, rewriter); - - layout.swap(in0, in1); - layout.remapQubitValue(in0, out0); - layout.remapQubitValue(in1, out1); - - (*numSwaps_)++; - } - } - - std::unique_ptr scheduler_; - std::unique_ptr router_; - - Pass::Statistic* numSwaps_; -}; +// class Mapper { +// public: +// explicit Mapper(std::unique_ptr scheduler, +// std::unique_ptr router, Pass::Statistic& +// numSwaps) +// : scheduler_(std::move(scheduler)), router_(std::move(router)), +// numSwaps_(&numSwaps) {} + +// /** +// * @returns true iff @p op is executable on the targeted architecture. +// */ +// [[nodiscard]] static bool isExecutable(UnitaryInterface op, +// const Architecture& arch, +// const Layout& layout) { +// const auto [in0, in1] = getIns(op); +// return arch.areAdjacent(layout.lookupHardwareIndex(in0), +// layout.lookupHardwareIndex(in1)); +// } + +// /** +// * @brief Insert SWAPs such that the gates provided by the scheduler are +// * executable. +// */ +// [[nodiscard]] RouterResult map(UnitaryInterface op, const Architecture& +// arch, +// Layout& layout, PatternRewriter& rewriter) { +// const auto layers = scheduler_->schedule(op, layout); +// const auto swaps = router_->route(layers, layout, arch); +// insert(swaps, op->getLoc(), layout, rewriter); +// return swaps; +// } + +// /** +// * @brief Restore layout by uncomputing. +// * +// * History is cleared by the caller (e.g., via stack/history pop in +// handlers). +// * +// * @todo Remove SWAP history and use advanced strategies. +// */ +// void restore(Location location, const SmallVector& history, +// Layout& layout, PatternRewriter& rewriter) { +// const auto swaps = llvm::to_vector(llvm::reverse(history)); +// insert(swaps, location, layout, rewriter); +// } + +// private: +// void insert(ArrayRef swaps, Location location, Layout& +// layout, +// PatternRewriter& rewriter) { +// for (const auto [hw0, hw1] : swaps) { +// const Value in0 = layout.lookupHardwareValue(hw0); +// const Value in1 = layout.lookupHardwareValue(hw1); +// [[maybe_unused]] const auto [prog0, prog1] = +// layout.getProgramIndices(hw0, hw1); + +// LLVM_DEBUG({ +// llvm::dbgs() << llvm::format( +// "route: swap= p%d:h%d, p%d:h%d <- p%d:h%d, p%d:h%d\n", prog1, +// hw0, prog0, hw1, prog0, hw0, prog1, hw1); +// }); + +// auto swap = createSwap(location, in0, in1, rewriter); +// const auto [out0, out1] = getOuts(swap); + +// rewriter.setInsertionPointAfter(swap); +// replaceAllUsesInRegionAndChildrenExcept( +// in0, out1, swap->getParentRegion(), swap, rewriter); +// replaceAllUsesInRegionAndChildrenExcept( +// in1, out0, swap->getParentRegion(), swap, rewriter); + +// layout.swap(in0, in1); +// layout.remapQubitValue(in0, out0); +// layout.remapQubitValue(in1, out1); + +// (*numSwaps_)++; +// } +// } + +// std::unique_ptr scheduler_; +// std::unique_ptr router_; + +// Pass::Statistic* numSwaps_; +// }; struct RoutingDriver { - RoutingDriver(std::unique_ptr& mapper, - std::unique_ptr& arch) - : mapper_(std::move(mapper)), arch_(std::move(arch)) {} + RoutingDriver(std::unique_ptr router, + std::unique_ptr arch) + : router_(std::move(router)), arch_(std::move(arch)) {} LogicalResult route(ModuleOp module); private: - WalkResult handleFunc([[maybe_unused]] func::FuncOp op); - WalkResult handleReturn([[maybe_unused]] func::ReturnOp op); - WalkResult handleFor(scf::ForOp op); - WalkResult handleIf(scf::IfOp op); - WalkResult handleYield(scf::YieldOp op, PatternRewriter& rewriter); - WalkResult handleQubit(QubitOp op); - WalkResult handleUnitary(UnitaryInterface op, PatternRewriter& rewriter); - WalkResult handleReset(ResetOp op); - WalkResult handleMeasure(MeasureOp op); - - /** - * @returns reference to the stack object. - */ - [[nodiscard]] LayoutStack& stack() { return stack_; } - - /** - * @returns reference to the history stack object. - */ - [[nodiscard]] LayoutStack>& historyStack() { - return historyStack_; - } - - /** - * @returns reference to architecture object. - */ - [[nodiscard]] Architecture& arch() const { return *arch_; } + WalkResult handleQubit(QubitOp op, Layout& layout); + WalkResult handleUnitary(UnitaryInterface op, Layout& layout, + SlidingWindow& window, PatternRewriter& rewriter); + WalkResult handleReset(ResetOp op, Layout& layout); + WalkResult handleMeasure(MeasureOp op, Layout& layout); - /** - * @returns reference to mapper object. - */ - [[nodiscard]] Mapper& mapper() const { return *mapper_; } - - LayoutStack stack_{}; - LayoutStack> historyStack_{}; - - std::unique_ptr mapper_; + std::unique_ptr router_; std::unique_ptr arch_; }; -/** - * @brief Push new state onto the stack. - */ -WalkResult RoutingDriver::handleFunc([[maybe_unused]] func::FuncOp op) { - assert(stack().empty() && "handleFunc: stack must be empty"); - - LLVM_DEBUG({ - llvm::dbgs() << "handleFunc: entry_point= " << op.getSymName() << '\n'; - }); - - /// Function body state. - stack().emplace(arch().nqubits()); - historyStack().emplace(); - - return WalkResult::advance(); -} - -/** - * @brief Indicates the end of a region defined by a function. Consequently, - * we pop the region's state from the stack. - */ -WalkResult RoutingDriver::handleReturn([[maybe_unused]] func::ReturnOp op) { - stack().pop(); - historyStack().pop(); - return WalkResult::advance(); -} - /** * @brief Push new state for the loop body onto the stack. */ -WalkResult RoutingDriver::handleFor(scf::ForOp op) { - /// Loop body state. - stack().duplicateTop(); - historyStack().emplace(); - - /// Forward out-of-loop and in-loop values. - const auto nqubits = arch().nqubits(); - const auto initArgs = op.getInitArgs().take_front(nqubits); - const auto results = op.getResults().take_front(nqubits); - const auto iterArgs = op.getRegionIterArgs().take_front(nqubits); - for (const auto [arg, res, iter] : llvm::zip(initArgs, results, iterArgs)) { - stack().getItemAtDepth(FOR_PARENT_DEPTH).remapQubitValue(arg, res); - stack().top().remapQubitValue(arg, iter); - } - - return WalkResult::advance(); -} +// WalkResult RoutingDriver::handleFor(scf::ForOp op) { +// /// Loop body state. +// stack().duplicateTop(); +// historyStack().emplace(); + +// /// Forward out-of-loop and in-loop values. +// const auto nqubits = arch().nqubits(); +// const auto initArgs = op.getInitArgs().take_front(nqubits); +// const auto results = op.getResults().take_front(nqubits); +// const auto iterArgs = op.getRegionIterArgs().take_front(nqubits); +// for (const auto [arg, res, iter] : llvm::zip(initArgs, results, iterArgs)) +// { +// stack().getItemAtDepth(FOR_PARENT_DEPTH).remapQubitValue(arg, res); +// stack().top().remapQubitValue(arg, iter); +// } + +// return WalkResult::advance(); +// } /** * @brief Push two new states for the then and else branches onto the stack. */ -WalkResult RoutingDriver::handleIf(scf::IfOp op) { - /// Prepare stack. - stack().duplicateTop(); /// Else. - stack().duplicateTop(); /// Then. - historyStack().emplace(); - historyStack().emplace(); - - /// Forward out-of-if values. - const auto results = op->getResults().take_front(arch().nqubits()); - Layout& layoutBeforeIf = stack().getItemAtDepth(IF_PARENT_DEPTH); - for (const auto [hw, res] : llvm::enumerate(results)) { - const Value q = layoutBeforeIf.lookupHardwareValue(hw); - layoutBeforeIf.remapQubitValue(q, res); - } - - return WalkResult::advance(); -} +// WalkResult RoutingDriver::handleIf(scf::IfOp op) { +// /// Prepare stack. +// stack().duplicateTop(); /// Else. +// stack().duplicateTop(); /// Then. +// historyStack().emplace(); +// historyStack().emplace(); + +// /// Forward out-of-if values. +// const auto results = op->getResults().take_front(arch().nqubits()); +// Layout& layoutBeforeIf = stack().getItemAtDepth(IF_PARENT_DEPTH); +// for (const auto [hw, res] : llvm::enumerate(results)) { +// const Value q = layoutBeforeIf.lookupHardwareValue(hw); +// layoutBeforeIf.remapQubitValue(q, res); +// } + +// return WalkResult::advance(); +// } /** * @brief Indicates the end of a region defined by a branching op. @@ -330,20 +280,21 @@ WalkResult RoutingDriver::handleIf(scf::IfOp op) { * a 'for' of 'if' region always requires 2 * #(SWAPs required for region) * additional SWAPS. */ -WalkResult RoutingDriver::handleYield(scf::YieldOp op, - PatternRewriter& rewriter) { - if (!isa(op->getParentOp()) && - !isa(op->getParentOp())) { - return WalkResult::skip(); - } +// WalkResult RoutingDriver::handleYield(scf::YieldOp op, +// PatternRewriter& rewriter) { +// if (!isa(op->getParentOp()) && +// !isa(op->getParentOp())) { +// return WalkResult::skip(); +// } - mapper().restore(op->getLoc(), historyStack().top(), stack().top(), rewriter); +// mapper().restore(op->getLoc(), historyStack().top(), stack().top(), +// rewriter); - stack().pop(); - historyStack().pop(); +// stack().pop(); +// historyStack().pop(); - return WalkResult::advance(); -} +// return WalkResult::advance(); +// } /** * @brief Add hardware qubit with respective program & hardware index to @@ -351,9 +302,9 @@ WalkResult RoutingDriver::handleYield(scf::YieldOp op, * * Thanks to the placement pass, we can apply the identity layout here. */ -WalkResult RoutingDriver::handleQubit(QubitOp op) { +WalkResult RoutingDriver::handleQubit(QubitOp op, Layout& layout) { const std::size_t index = op.getIndex(); - stack().top().add(index, index, op.getQubit()); + layout.add(index, index, op.getQubit()); return WalkResult::advance(); } @@ -361,9 +312,11 @@ WalkResult RoutingDriver::handleQubit(QubitOp op) { * @brief Ensures the executability of two-qubit gates on the given target * architecture by inserting SWAPs. */ -WalkResult RoutingDriver::handleUnitary(UnitaryInterface op, +WalkResult RoutingDriver::handleUnitary(UnitaryInterface op, Layout& layout, + SlidingWindow& window, PatternRewriter& rewriter) { LLVM_DEBUG(llvm::dbgs() << "handleUnitary: gate=" << op->getName() << '\n'); + const std::vector inQubits = op.getAllInQubits(); const std::vector outQubits = op.getAllOutQubits(); const std::size_t nacts = inQubits.size(); @@ -375,7 +328,7 @@ WalkResult RoutingDriver::handleUnitary(UnitaryInterface op, if (isa(op)) { for (const auto [in, out] : llvm::zip(inQubits, outQubits)) { - stack().top().remapQubitValue(in, out); + layout.remapQubitValue(in, out); } return WalkResult::advance(); } @@ -387,15 +340,44 @@ WalkResult RoutingDriver::handleUnitary(UnitaryInterface op, /// Single-qubit: Forward mapping. if (nacts == 1) { - stack().top().remapQubitValue(inQubits[0], outQubits[0]); + layout.remapQubitValue(inQubits[0], outQubits[0]); return WalkResult::advance(); } - /// Not-executable two-qubit: Map first then forward. - if (!Mapper::isExecutable(op, arch(), stack().top())) { - LLVM_DEBUG(llvm::dbgs() << "\tnot executable\n"); - const auto swaps = mapper().map(op, arch(), stack().top(), rewriter); - historyStack().top().append(swaps.begin(), swaps.end()); + /// Anchored two-qubit gate: Map first then forward. + auto layers = window.current(); + auto* anchor = layers.front().anchor; + if (anchor == op) { + LLVM_DEBUG(llvm::dbgs() << "\tanchored by scheduler\n"); + + const auto swaps = router_->route(layers, layout, *arch_); + for (const auto [hw0, hw1] : swaps) { + const Value in0 = layout.lookupHardwareValue(hw0); + const Value in1 = layout.lookupHardwareValue(hw1); + [[maybe_unused]] const auto [prog0, prog1] = + layout.getProgramIndices(hw0, hw1); + + LLVM_DEBUG({ + llvm::dbgs() << llvm::format( + "route: swap= p%d:h%d, p%d:h%d <- p%d:h%d, p%d:h%d\n", prog1, hw0, + prog0, hw1, prog0, hw0, prog1, hw1); + }); + + auto swap = createSwap(anchor->getLoc(), in0, in1, rewriter); + const auto [out0, out1] = getOuts(swap); + + rewriter.setInsertionPointAfter(swap); + replaceAllUsesInRegionAndChildrenExcept( + in0, out1, swap->getParentRegion(), swap, rewriter); + replaceAllUsesInRegionAndChildrenExcept( + in1, out0, swap->getParentRegion(), swap, rewriter); + + layout.swap(in0, in1); + layout.remapQubitValue(in0, out0); + layout.remapQubitValue(in1, out1); + } + + window.advance(); } const auto [execIn0, execIn1] = getIns(op); @@ -403,21 +385,18 @@ WalkResult RoutingDriver::handleUnitary(UnitaryInterface op, LLVM_DEBUG({ llvm::dbgs() << llvm::format("handleUnitary: gate= p%d:h%d, p%d:h%d\n", - stack().top().lookupProgramIndex(execIn0), - stack().top().lookupHardwareIndex(execIn0), - stack().top().lookupProgramIndex(execIn1), - stack().top().lookupHardwareIndex(execIn1)); + layout.lookupProgramIndex(execIn0), + layout.lookupHardwareIndex(execIn0), + layout.lookupProgramIndex(execIn1), + layout.lookupHardwareIndex(execIn1)); }); if (isa(op)) { - stack().top().swap(execIn0, execIn1); - historyStack().top().push_back( - {stack().top().lookupHardwareIndex(execIn0), - stack().top().lookupHardwareIndex(execIn1)}); + layout.swap(execIn0, execIn1); } - stack().top().remapQubitValue(execIn0, execOut0); - stack().top().remapQubitValue(execIn1, execOut1); + layout.remapQubitValue(execIn0, execOut0); + layout.remapQubitValue(execIn1, execOut1); return WalkResult::advance(); } @@ -425,16 +404,16 @@ WalkResult RoutingDriver::handleUnitary(UnitaryInterface op, /** * @brief Update layout. */ -WalkResult RoutingDriver::handleReset(ResetOp op) { - stack().top().remapQubitValue(op.getInQubit(), op.getOutQubit()); +WalkResult RoutingDriver::handleReset(ResetOp op, Layout& layout) { + layout.remapQubitValue(op.getInQubit(), op.getOutQubit()); return WalkResult::advance(); } /** * @brief Update layout. */ -WalkResult RoutingDriver::handleMeasure(MeasureOp op) { - stack().top().remapQubitValue(op.getInQubit(), op.getOutQubit()); +WalkResult RoutingDriver::handleMeasure(MeasureOp op, Layout& layout) { + layout.remapQubitValue(op.getInQubit(), op.getOutQubit()); return WalkResult::advance(); } @@ -462,41 +441,49 @@ WalkResult RoutingDriver::handleMeasure(MeasureOp op) { */ LogicalResult RoutingDriver::route(ModuleOp module) { PatternRewriter rewriter(module->getContext()); - for (const auto func : module.getOps()) { + + /// Prepare work-list. + for (func::FuncOp func : module.getOps()) { if (!isEntryPoint(func)) { continue; // Ignore non entry_point functions for now. } - const auto res = func->walk([&](Operation* curr) { + /// Prepare layout. + Layout layout(arch_->nqubits()); + for (QubitOp op : func.getOps()) { + std::ignore = handleQubit(op, layout); + } + + /// Schedule unitaries. + /// This function call reorders, and hence rewrites, the IR. + ScheduledLayers schedule = ParallelOpFullScheduler::schedule( + func.getFunctionBody(), layout, rewriter); + + SlidingWindow window(schedule, 1); + + for (Operation& op : func.getOps()) { + if (isa(op)) { + continue; // Skip already processed qubit ops. + } + const OpBuilder::InsertionGuard guard(rewriter); - rewriter.setInsertionPoint(curr); - return TypeSwitch(curr) - /// mqtopt Dialect - .Case( - [&](UnitaryInterface op) { return handleUnitary(op, rewriter); }) - .Case([&](QubitOp op) { return handleQubit(op); }) - .Case([&](ResetOp op) { return handleReset(op); }) - .Case([&](MeasureOp op) { return handleMeasure(op); }) - /// built-in Dialect - .Case([&]([[maybe_unused]] ModuleOp op) { - return WalkResult::advance(); - }) - /// func Dialect - .Case([&](func::FuncOp op) { return handleFunc(op); }) - .Case([&]([[maybe_unused]] func::ReturnOp op) { - return handleReturn(op); - }) - /// scf Dialect - .Case([&](scf::ForOp op) { return handleFor(op); }) - .Case([&](scf::IfOp op) { return handleIf(op); }) - .Case( - [&](scf::YieldOp op) { return handleYield(op, rewriter); }) - /// Skip the rest. - .Default([](auto) { return WalkResult::skip(); }); - }); - - if (res.wasInterrupted()) { - return failure(); + rewriter.setInsertionPoint(&op); + + const bool interrupted = + TypeSwitch(&op) + .Case([&](UnitaryInterface op) { + return handleUnitary(op, layout, window, rewriter); + }) + .Case( + [&](ResetOp op) { return handleReset(op, layout); }) + .Case( + [&](MeasureOp op) { return handleMeasure(op, layout); }) + .Default([](auto) { return WalkResult::skip(); }) // Skip rest. + .wasInterrupted(); + + if (interrupted) { + return failure(); + } } } @@ -516,7 +503,7 @@ struct RoutingPassSC final : impl::RoutingPassSCBase { return; } - auto mapper = getMapper(); + auto router = getRouter(); auto arch = getArchitecture(archName); if (!arch) { emitError(UnknownLoc::get(&getContext())) @@ -525,27 +512,23 @@ struct RoutingPassSC final : impl::RoutingPassSCBase { return; } - RoutingDriver driver(mapper, arch); + RoutingDriver driver(std::move(router), std::move(arch)); if (failed(driver.route(getOperation()))) { signalPassFailure(); } } private: - [[nodiscard]] std::unique_ptr getMapper() { + [[nodiscard]] std::unique_ptr getRouter() { switch (static_cast(method)) { case RoutingMethod::Naive: { LLVM_DEBUG({ llvm::dbgs() << "getRouter: method=naive\n"; }); - return std::make_unique(std::make_unique(), - std::make_unique(), - numSwaps); + return std::make_unique(); } case RoutingMethod::AStar: { LLVM_DEBUG({ llvm::dbgs() << "getRouter: method=astar\n"; }); const HeuristicWeights weights(alpha, lambda, nlookahead); - return std::make_unique( - std::make_unique(nlookahead), - std::make_unique(weights), numSwaps); + return std::make_unique(weights); } } From 50a66bc5b8e055de8f4ecd91adcc2499290f3b18 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 6 Nov 2025 07:46:23 +0100 Subject: [PATCH 04/56] Add mapper base and naive mapper files --- .../Transforms/Transpilation/MapperBase.h | 166 ++++++++++ .../Transforms/Transpilation/NaiveMapper.h | 293 ++++++++++++++++++ 2 files changed, 459 insertions(+) create mode 100644 mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/MapperBase.h create mode 100644 mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveMapper.h diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/MapperBase.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/MapperBase.h new file mode 100644 index 0000000000..01a7a34487 --- /dev/null +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/MapperBase.h @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEBUG_TYPE "route-sc" + +namespace mqt::ir::opt { + +using namespace mlir; + +/** + * @brief Create and return SWAPOp for two qubits. + * + * Expects the rewriter to be set to the correct position. + * + * @param location The Location to attach to the created op. + * @param in0 First input qubit SSA value. + * @param in1 Second input qubit SSA value. + * @param rewriter A PatternRewriter. + * @return The created SWAPOp. + */ +[[nodiscard]] inline SWAPOp createSwap(Location location, Value in0, Value in1, + PatternRewriter& rewriter) { + const SmallVector resultTypes{in0.getType(), in1.getType()}; + const SmallVector inQubits{in0, in1}; + + return rewriter.create( + /* location = */ location, + /* out_qubits = */ resultTypes, + /* pos_ctrl_out_qubits = */ TypeRange{}, + /* neg_ctrl_out_qubits = */ TypeRange{}, + /* static_params = */ nullptr, + /* params_mask = */ nullptr, + /* params = */ ValueRange{}, + /* in_qubits = */ inQubits, + /* pos_ctrl_in_qubits = */ ValueRange{}, + /* neg_ctrl_in_qubits = */ ValueRange{}); +} + +/** + * @brief Replace all uses of a value within a region and its nested regions, + * except for a specific operation. + * + * @param oldValue The value to replace + * @param newValue The new value to use + * @param region The region in which to perform replacements + * @param exceptOp Operation to exclude from replacements + * @param rewriter The pattern rewriter + */ +inline void replaceAllUsesInRegionAndChildrenExcept(Value oldValue, + Value newValue, + Region* region, + Operation* exceptOp, + PatternRewriter& rewriter) { + if (oldValue == newValue) { + return; + } + + rewriter.replaceUsesWithIf(oldValue, newValue, [&](OpOperand& use) { + Operation* user = use.getOwner(); + if (user == exceptOp) { + return false; + } + + // For other blocks, check if in region tree + Region* userRegion = user->getParentRegion(); + while (userRegion) { + if (userRegion == region) { + return true; + } + userRegion = userRegion->getParentRegion(); + } + return false; + }); +} + +class MapperBase { +public: + explicit MapperBase(std::unique_ptr arch) + : arch(std::move(arch)) {} + + virtual ~MapperBase() = default; + + [[nodiscard]] virtual LogicalResult + rewrite(func::FuncOp func, PatternRewriter& rewriter) const = 0; + + [[nodiscard]] LogicalResult rewrite(ModuleOp module) const { + PatternRewriter rewriter(module->getContext()); + for (auto func : module.getOps()) { + if (rewrite(func, rewriter).failed()) { + return failure(); + } + } + return success(); + } + +protected: + /** + * @brief Insert SWAPs at the rewriter's insertion point and update the + * layout. + */ + static void insertSWAPs(ArrayRef swaps, Layout& layout, + Location anchor, PatternRewriter& rewriter) { + for (const auto [hw0, hw1] : swaps) { + const Value in0 = layout.lookupHardwareValue(hw0); + const Value in1 = layout.lookupHardwareValue(hw1); + [[maybe_unused]] const auto [prog0, prog1] = + layout.getProgramIndices(hw0, hw1); + + LLVM_DEBUG({ + llvm::dbgs() << llvm::format( + "route: swap= p%d:h%d, p%d:h%d <- p%d:h%d, p%d:h%d\n", prog1, hw0, + prog0, hw1, prog0, hw0, prog1, hw1); + }); + + auto swap = createSwap(anchor, in0, in1, rewriter); + const auto [out0, out1] = getOuts(swap); + + rewriter.setInsertionPointAfter(swap); + replaceAllUsesInRegionAndChildrenExcept( + in0, out1, swap->getParentRegion(), swap, rewriter); + replaceAllUsesInRegionAndChildrenExcept( + in1, out0, swap->getParentRegion(), swap, rewriter); + + layout.swap(in0, in1); + layout.remapQubitValue(in0, out0); + layout.remapQubitValue(in1, out1); + } + } + + /** + * @returns true iff @p op is executable on the targeted architecture. + */ + [[nodiscard]] bool isExecutable(UnitaryInterface op, Layout& layout) const { + const auto [in0, in1] = getIns(op); + return arch->areAdjacent(layout.lookupHardwareIndex(in0), + layout.lookupHardwareIndex(in1)); + } + + std::unique_ptr arch; +}; +} // namespace mqt::ir::opt diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveMapper.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveMapper.h new file mode 100644 index 0000000000..af9ba3f5c2 --- /dev/null +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveMapper.h @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/MapperBase.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEBUG_TYPE "route-sc" + +namespace mqt::ir::opt { + +using namespace mlir; + +struct NaiveMapper final : MapperBase { + using MapperBase::MapperBase; + using SWAPHistory = SmallVector; + + LogicalResult rewrite(func::FuncOp func, + PatternRewriter& rewriter) const override { + LLVM_DEBUG(llvm::dbgs() << "handleFunc: " << func.getSymName() << '\n'); + + if (!isEntryPoint(func)) { + LLVM_DEBUG(llvm::dbgs() << "\tskip non entry\n"); + return success(); // Ignore non entry_point functions for now. + } + + Layout layout(arch->nqubits()); + SWAPHistory history; + return rewrite(func.getBody(), layout, history, rewriter); + } + + LogicalResult rewrite(Region& region, Layout& layout, SWAPHistory& history, + PatternRewriter& rewriter) const { + for (Operation& curr : region.getOps()) { + const OpBuilder::InsertionGuard guard(rewriter); + rewriter.setInsertionPoint(&curr); + + const auto res = + TypeSwitch(&curr) + /// mqtopt Dialect + .Case([&](UnitaryInterface op) { + return handleUnitary(op, layout, history, rewriter); + }) + .Case( + [&](QubitOp op) { return handleQubit(op, layout); }) + .Case( + [&](ResetOp op) { return handleReset(op, layout); }) + .Case( + [&](MeasureOp op) { return handleMeasure(op, layout); }) + /// built-in Dialect + .Case([&]([[maybe_unused]] ModuleOp op) { + return WalkResult::advance(); + }) + /// func Dialect + .Case([&]([[maybe_unused]] func::ReturnOp op) { + return WalkResult::advance(); + }) + /// scf Dialect + .Case([&](scf::ForOp op) { + return handleFor(op, layout, rewriter); + }) + .Case( + [&](scf::IfOp op) { return handleIf(op, layout, rewriter); }) + .Case([&](scf::YieldOp op) { + return handleYield(op, layout, history, rewriter); + }) + /// Skip the rest. + .Default([](auto) { return WalkResult::skip(); }); + + if (res.wasInterrupted()) { + return failure(); + } + } + + return success(); + } + +private: + /** + * @brief Copy the layout and recursively map the loop body. + */ + WalkResult handleFor(scf::ForOp op, Layout& layout, + PatternRewriter& rewriter) const { + /// Copy layout. + Layout forLayout(layout); + + /// Forward out-of-loop and in-loop values. + const auto initArgs = op.getInitArgs().take_front(arch->nqubits()); + const auto results = op.getResults().take_front(arch->nqubits()); + const auto iterArgs = op.getRegionIterArgs().take_front(arch->nqubits()); + for (const auto [arg, res, iter] : llvm::zip(initArgs, results, iterArgs)) { + layout.remapQubitValue(arg, res); + forLayout.remapQubitValue(arg, iter); + } + + /// Recursively handle loop region. + SWAPHistory history; + return rewrite(op.getRegion(), forLayout, history, rewriter); + } + + /** + * @brief Copy the layout for each branch and recursively map the branches. + */ + WalkResult handleIf(scf::IfOp op, Layout& layout, + PatternRewriter& rewriter) const { + /// Recursively handle each branch region. + Layout ifLayout(layout); + SWAPHistory ifHistory; + const auto ifRes = + rewrite(op.getThenRegion(), ifLayout, ifHistory, rewriter); + if (ifRes.failed()) { + return ifRes; + } + + Layout elseLayout(layout); + SWAPHistory elseHistory; + const auto elseRes = + rewrite(op.getElseRegion(), elseLayout, elseHistory, rewriter); + if (elseRes.failed()) { + return elseRes; + } + + /// Forward out-of-if values. + const auto results = op->getResults().take_front(arch->nqubits()); + for (const auto [hw, res] : llvm::enumerate(results)) { + const Value q = layout.lookupHardwareValue(hw); + layout.remapQubitValue(q, res); + } + + return WalkResult::advance(); + } + + /** + * @brief Indicates the end of a region defined by a scf op. + * + * Restores layout by uncomputation and replaces (invalid) yield. + * + * Using uncompute has the advantages of (1) being intuitive and + * (2) preserving the optimality of the original SWAP sequence. + * Essentially the better the routing algorithm the better the + * uncompute. Moreover, this has the nice property that routing + * a 'for' of 'if' region always requires 2 * #(SWAPs required for region) + * additional SWAPS. + */ + WalkResult handleYield(scf::YieldOp op, Layout& layout, SWAPHistory& history, + PatternRewriter& rewriter) const { + if (!isa(op->getParentOp()) && + !isa(op->getParentOp())) { + return WalkResult::skip(); + } + + /// Uncompute SWAPs. + NaiveMapper::insertSWAPs(llvm::to_vector(llvm::reverse(history)), layout, + op.getLoc(), rewriter); + + return WalkResult::advance(); + } + + /** + * @brief Add hardware qubit with respective program & hardware index to + * layout. + * + * Thanks to the placement pass, we can apply the identity layout here. + */ + WalkResult handleQubit(QubitOp op, Layout& layout) const { + const std::size_t index = op.getIndex(); + layout.add(index, index, op.getQubit()); + return WalkResult::advance(); + } + + /** + * @brief Ensures the executability of two-qubit gates on the given target + * architecture by inserting SWAPs. + */ + WalkResult handleUnitary(UnitaryInterface op, Layout& layout, + SWAPHistory& history, + PatternRewriter& rewriter) const { + const std::vector inQubits = op.getAllInQubits(); + const std::vector outQubits = op.getAllOutQubits(); + const std::size_t nacts = inQubits.size(); + + // Global-phase or zero-qubit unitary: Nothing to do. + if (nacts == 0) { + return WalkResult::advance(); + } + + if (isa(op)) { + for (const auto [in, out] : llvm::zip(inQubits, outQubits)) { + layout.remapQubitValue(in, out); + } + return WalkResult::advance(); + } + + /// Expect two-qubit gate decomposition. + if (nacts > 2) { + return op->emitOpError() << "acts on more than two qubits"; + } + + /// Single-qubit: Forward mapping. + if (nacts == 1) { + layout.remapQubitValue(inQubits[0], outQubits[0]); + return WalkResult::advance(); + } + + if (!isExecutable(op, layout)) { + route(op, layout, history, rewriter); + } + + const auto [execIn0, execIn1] = getIns(op); + const auto [execOut0, execOut1] = getOuts(op); + + LLVM_DEBUG({ + llvm::dbgs() << llvm::format("handleUnitary: gate= p%d:h%d, p%d:h%d\n", + layout.lookupProgramIndex(execIn0), + layout.lookupHardwareIndex(execIn0), + layout.lookupProgramIndex(execIn1), + layout.lookupHardwareIndex(execIn1)); + }); + + if (isa(op)) { + layout.swap(execIn0, execIn1); + history.push_back({layout.lookupHardwareIndex(execIn0), + layout.lookupHardwareIndex(execIn1)}); + } + + layout.remapQubitValue(execIn0, execOut0); + layout.remapQubitValue(execIn1, execOut1); + + return WalkResult::advance(); + } + + /** + * @brief Update layout. + */ + WalkResult handleReset(ResetOp op, Layout& layout) const { + layout.remapQubitValue(op.getInQubit(), op.getOutQubit()); + return WalkResult::advance(); + } + + /** + * @brief Update layout. + */ + WalkResult handleMeasure(MeasureOp op, Layout& layout) const { + layout.remapQubitValue(op.getInQubit(), op.getOutQubit()); + return WalkResult::advance(); + } + + /** + * @brief Use shortest path swapping to make the given unitary executable. + * @details Optimized for an avg. SWAP count of 16. + */ + void route(UnitaryInterface op, Layout& layout, SWAPHistory& history, + PatternRewriter& rewriter) const { + /// Find SWAPs. + SmallVector swaps; + const auto ins = getIns(op); + const auto hw0 = layout.lookupHardwareIndex(ins.first); + const auto hw1 = layout.lookupHardwareIndex(ins.second); + const auto path = arch->shortestPathBetween(hw0, hw1); + for (std::size_t i = 0; i < path.size() - 2; ++i) { + swaps.emplace_back(path[i], path[i + 1]); + } + + /// Append SWAPs to history. + history.append(swaps); + + /// Insert SWAPs. + MapperBase::insertSWAPs(swaps, layout, op.getLoc(), rewriter); + } +}; +} // namespace mqt::ir::opt From d6c702f2ae5465fd30f3b4870d4225f6abfd4dd3 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Sat, 8 Nov 2025 15:55:33 +0100 Subject: [PATCH 05/56] Implement driver approach --- .../Transforms/Transpilation/AStarDriver.h | 445 +++++++++++++++ .../{Router.h => AStarHeuristicRouter.h} | 74 +-- .../{NaiveMapper.h => NaiveDriver.h} | 28 +- .../{MapperBase.h => RoutingDriverBase.h} | 12 +- .../Transforms/Transpilation/Scheduler.h | 535 ------------------ .../Transpilation/sc/RoutingPass.cpp | 460 +-------------- 6 files changed, 494 insertions(+), 1060 deletions(-) create mode 100644 mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h rename mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/{Router.h => AStarHeuristicRouter.h} (69%) rename mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/{NaiveMapper.h => NaiveDriver.h} (92%) rename mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/{MapperBase.h => RoutingDriverBase.h} (93%) delete mode 100644 mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Scheduler.h diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h new file mode 100644 index 0000000000..13ebfed63a --- /dev/null +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h @@ -0,0 +1,445 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/RoutingDriverBase.h" + +#include "llvm/ADT/iterator_range.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEBUG_TYPE "route-sc" + +namespace mqt::ir::opt { + +using namespace mlir; + +struct Layer { + /// Operations that can be executed before the two-qubit gates. + SmallVector ops; + /// The two-qubit gates. + SmallVector twoQubitOps; + /// The program indices of the two-qubit gates within this layer. + SmallVector twoQubitIndices; + /// Operations that will be executable whenever all gates in the layer are + /// executable. + SmallVector blockOps; + + /// @returns true if the layer has no gates to route. + [[nodiscard]] bool empty() const { return twoQubitIndices.empty(); } + + /// @returns the scheduled operations in (to be) reordered order. + [[nodiscard]] auto getOps() { + return concat(ops, twoQubitOps, blockOps); + } +}; + +using Schedule = SmallVector; + +class Scheduler { +public: + explicit Scheduler(Region* region) : region(region) {} + + /** + * @brief Starting from the given layout, schedule all operations and divide + * the circuit into parallelly executable layers. + * @returns the schedule. + */ + [[nodiscard]] Schedule schedule(Layout layout) { + /// Worklist of qubits. + SmallVector qubits(layout.getHardwareQubits()); + /// Set of two-qubit gates seen at least once. + DenseSet openTwoQubit; + + Schedule schedule; + while (true) { + const auto result = getNextLayer(qubits, openTwoQubit, layout); + if (result.layer.empty()) { + break; + } + + schedule.emplace_back(result.layer); + qubits = result.qubits; + } + + return schedule; + } + +private: + struct NextLayerResult { + /// The next layer. + Layer layer; + /// The updated worklist of qubits for the next iteration. + SmallVector qubits; + }; + + struct AdvanceResult { + /// The input value of the unitary interface. + Value q = nullptr; + /// The unitary interface. + UnitaryInterface u = nullptr; + /// All operations visited until the two-qubit gate. + SmallVector ops; + /// @returns true iff. the advancement hit a two-qubit gate. + [[nodiscard]] bool valid() const { return u != nullptr; } + }; + + struct BlockAdvanceResult { + /// The final qubit values in the two-qubit block. + ValuePair outs; + /// All operations visited in the two-qubit block. + SmallVector ops; + }; + + /** + * @returns todo + */ + [[nodiscard]] NextLayerResult getNextLayer(ArrayRef qubits, + DenseSet& openTwoQubit, + Layout& layout) { + NextLayerResult bundle; + bundle.qubits.reserve(qubits.size()); + + for (const Value q : qubits) { + if (q.use_empty()) { + continue; + } + + const auto result = advanceToTwoQubitGate(q, layout); + if (!result.valid()) { + continue; + } + + if (q != result.q) { + layout.remapQubitValue(q, result.q); + } + + bundle.layer.ops.append(result.ops); + + if (!openTwoQubit.insert(result.u).second) { + const auto ins = getIns(result.u); + const auto outs = getOuts(result.u); + + bundle.layer.twoQubitOps.emplace_back(result.u); + bundle.layer.twoQubitIndices.emplace_back( + layout.lookupProgramIndex(ins.first), + layout.lookupProgramIndex(ins.second)); + + layout.remapQubitValue(ins.first, outs.first); + layout.remapQubitValue(ins.second, outs.second); + + const auto blockResult = skipTwoQubitBlock(outs, layout); + bundle.layer.blockOps.append(blockResult.ops); + + if (blockResult.outs.first != nullptr && + !blockResult.outs.first.use_empty()) { + bundle.qubits.push_back(blockResult.outs.first); + } + + if (blockResult.outs.second != nullptr && + !blockResult.outs.second.use_empty()) { + bundle.qubits.push_back(blockResult.outs.second); + } + + openTwoQubit.erase(result.u); + } + } + + return bundle; + } + + /** + * @returns todo + */ + AdvanceResult advanceToTwoQubitGate(const Value q, const Layout& layout) { + AdvanceResult result; + + Value head = q; + while (true) { + if (head.use_empty()) { // No two-qubit gate found. + break; + } + + Operation* user = getUserInRegion(head, region); + if (user == nullptr) { // No two-qubit gate found. + break; + } + + result.ops.push_back(user); + + bool endOfRegion = false; + TypeSwitch(user) + /// MQT + /// BarrierOp is a UnitaryInterface, however, requires special care. + .Case([&](BarrierOp op) { + for (const auto [in, out] : + llvm::zip_equal(op.getInQubits(), op.getOutQubits())) { + if (in == head) { + head = out; + return; + } + } + llvm_unreachable("head must be in barrier"); + }) + .Case([&](UnitaryInterface op) { + if (isTwoQubitGate(op)) { + result.u = op; + return; // Found a two-qubit gate, stop advancing head. + } + // Otherwise, advance head. + head = op.getOutQubits().front(); + }) + .Case([&](ResetOp op) { head = op.getOutQubit(); }) + .Case([&](MeasureOp op) { head = op.getOutQubit(); }) + + /// SCF + /// The scf funcs assume that the first n results are the hw qubits. + .Case([&](scf::ForOp op) { + head = op->getResult(layout.lookupHardwareIndex(q)); + }) + .Case([&](scf::IfOp op) { + head = op->getResult(layout.lookupHardwareIndex(q)); + }) + .Case([&](scf::YieldOp) { endOfRegion = true; }) + .Default([&]([[maybe_unused]] Operation* op) { + LLVM_DEBUG({ + llvm::dbgs() << "unknown operation in def-use chain: "; + op->dump(); + }); + llvm_unreachable("unknown operation in def-use chain"); + }); + + if (result.u) { // Two-qubit gate found. + result.ops.pop_back(); // Remove two-qubit gate from op list. + result.q = head; + break; + } + + if (endOfRegion) { + break; + } + } + + return result; + } + + /** + * @returns todo + */ + BlockAdvanceResult skipTwoQubitBlock(const ValuePair outs, Layout& layout) { + BlockAdvanceResult blockResult; + + std::array gates; + std::array heads{outs.first, outs.second}; + + while (true) { + bool stop = false; + for (const auto [i, q] : llvm::enumerate(heads)) { + const auto result = advanceToTwoQubitGate(q, layout); + if (!result.valid()) { + heads[i] = nullptr; + stop = true; + break; + } + + if (q != result.q) { + layout.remapQubitValue(q, result.q); + } + + blockResult.ops.append(result.ops); + + heads[i] = result.q; + gates[i] = result.u; + } + + if (stop || gates[0] != gates[1]) { + break; + } + + blockResult.ops.push_back(gates[0]); + + const ValuePair ins = getIns(gates[0]); + const ValuePair outs = getOuts(gates[0]); + layout.remapQubitValue(ins.first, outs.first); + layout.remapQubitValue(ins.second, outs.second); + heads = {outs.first, outs.second}; + } + + blockResult.outs = std::make_pair(heads[0], heads[1]); + + return blockResult; + } + + Region* region; +}; + +class AStarDriver final : public RoutingDriverBase { +public: + AStarDriver(std::unique_ptr arch, + const HeuristicWeights& weights, std::size_t nlookahead) + : RoutingDriverBase(std::move(arch)), router_(weights), + nlookahead_(nlookahead) {} + +private: + LogicalResult rewrite(func::FuncOp func, PatternRewriter& rewriter) override { + LLVM_DEBUG(llvm::dbgs() << "handleFunc: " << func.getSymName() << '\n'); + + if (!isEntryPoint(func)) { + LLVM_DEBUG(llvm::dbgs() << "\tskip non entry\n"); + return success(); // Ignore non entry_point functions for now. + } + + /// Find all static qubits and initialize layout. + /// In a circuit diagram this corresponds to finding the very + /// start of each circuit wire. + + Layout layout(arch->nqubits()); + for_each(func.getOps(), [&](QubitOp op) { + const std::size_t index = op.getIndex(); + layout.add(index, index, op.getQubit()); + }); + + return rewrite(func.getBody(), layout, rewriter); + } + + LogicalResult rewrite(Region& region, Layout& layout, + PatternRewriter& rewriter) const { + Scheduler scheduler(®ion); + Schedule schedule = scheduler.schedule(layout); + + /// Iterate over schedule in sliding windows of size 1 + nlookahead. + + Operation* prev{}; + + for (Schedule::iterator it = schedule.begin(); it != schedule.end(); ++it) { + const Schedule::iterator end = + std::min(it + 1 + nlookahead_, schedule.end()); + const auto window = llvm::make_range(it, end); + const auto ops = window.begin()->getOps(); + + for (Operation* curr : ops) { + + /// Impose a strict ordering of the operations. Note that + /// we reorder the operation before we insert any swaps. + + if (prev != nullptr) { + if (isa(prev) || isa(prev) || + isa(curr) || isa(curr)) { + continue; + } + + rewriter.moveOpAfter(curr, prev); + } + + const OpBuilder::InsertionGuard guard(rewriter); + rewriter.setInsertionPoint(curr); + + const auto res = + TypeSwitch(curr) + /// mqtopt Dialect + .Case([&](UnitaryInterface op) { + return handleUnitary(op, layout, window, rewriter); + }) + .Case( + [&](ResetOp op) { return handleReset(op, layout); }) + .Case( + [&](MeasureOp op) { return handleMeasure(op, layout); }) + /// built-in Dialect + .Case([&]([[maybe_unused]] ModuleOp op) { + return WalkResult::advance(); + }) + /// func Dialect + .Case([&]([[maybe_unused]] func::ReturnOp op) { + return WalkResult::advance(); + }) + /// scf Dialect + // .Case([&](scf::ForOp op) { + // return handleFor(op, layout, rewriter); + // }) + // .Case([&](scf::IfOp op) { + // return handleIf(op, layout, rewriter); + // }) + // .Case([&](scf::YieldOp op) { + // return handleYield(op, layout, history, rewriter); + // }) + /// Skip the rest. + .Default([](auto) { return WalkResult::skip(); }); + + if (res.wasInterrupted()) { + return failure(); + } + + prev = curr; + } + } + + return success(); + } + + /** + * @brief Ensures the executability of two-qubit gates on the given target + * architecture by inserting SWAPs. + */ + WalkResult handleUnitary(UnitaryInterface op, Layout& layout, + const ArrayRef window, + PatternRewriter& rewriter) const { + LLVM_DEBUG(llvm::dbgs() + << "handleUnitary: gate= " << op->getName() << '\n'); + /// If this is the first two-qubit op in the layer, route the layer + /// and remap afterwards. + if (op.getOperation() == window.front().twoQubitOps.front()) { + const auto layers = to_vector(map_range(window, [](const Layer& layer) { + return ArrayRef(layer.twoQubitIndices); + })); + const auto swaps = router_.route(layers, layout, *arch); + insertSWAPs(swaps, layout, op->getLoc(), rewriter); + } + + for (const auto [in, out] : + llvm::zip(op.getAllInQubits(), op.getAllOutQubits())) { + layout.remapQubitValue(in, out); + } + + return WalkResult::advance(); + } + + /** + * @brief Update layout. + */ + static WalkResult handleReset(ResetOp op, Layout& layout) { + layout.remapQubitValue(op.getInQubit(), op.getOutQubit()); + return WalkResult::advance(); + } + + /** + * @brief Update layout. + */ + static WalkResult handleMeasure(MeasureOp op, Layout& layout) { + layout.remapQubitValue(op.getInQubit(), op.getOutQubit()); + return WalkResult::advance(); + } + + AStarHeuristicRouter router_; + std::size_t nlookahead_; +}; +} // namespace mqt::ir::opt diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h similarity index 69% rename from mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h rename to mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h index eaf5d5cded..d9370cab37 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h @@ -13,13 +13,12 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Scheduler.h" + +#include "llvm/ADT/ArrayRef.h" #include #include #include -#include -#include #include #include @@ -30,42 +29,6 @@ namespace mqt::ir::opt { */ using RouterResult = SmallVector; -/** - * @brief A planner determines the sequence of swaps required to route an array -of gates. -*/ -struct RouterBase { - virtual ~RouterBase() = default; - [[nodiscard]] virtual RouterResult route(std::span, - const ThinLayout&, - const Architecture&) const = 0; -}; - -/** - * @brief Use shortest path swapping to make one gate executable. - */ -struct NaiveRouter final : RouterBase { - [[nodiscard]] RouterResult route(std::span layers, - const ThinLayout& layout, - const Architecture& arch) const override { - if (layers.size() != 1 || layers.front().gates.size() != 1) { - throw std::invalid_argument( - "NaiveRouter expects exactly one layer with one gate"); - } - - /// This assumes an avg. of 16 SWAPs per gate. - SmallVector swaps; - for (const auto [prog0, prog1] : layers.front().gates) { - const auto [hw0, hw1] = layout.getHardwareIndices(prog0, prog1); - const auto path = arch.shortestPathBetween(hw0, hw1); - for (std::size_t i = 0; i < path.size() - 2; ++i) { - swaps.emplace_back(path[i], path[i + 1]); - } - } - return swaps; - } -}; - /** * @brief Specifies the weights for different terms in the cost function f. */ @@ -86,12 +49,14 @@ struct HeuristicWeights { /** * @brief Use A*-search to make all gates executable. */ -struct AStarHeuristicRouter final : RouterBase { +struct AStarHeuristicRouter final { explicit AStarHeuristicRouter(HeuristicWeights weights) : weights_(std::move(weights)) {} private: using ClosedMap = DenseMap; + using Layer = ArrayRef; + using Layers = ArrayRef; struct Node { SmallVector sequence; @@ -108,9 +73,8 @@ struct AStarHeuristicRouter final : RouterBase { * @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, QubitIndexPair swap, - std::span layers, const Architecture& arch, - const HeuristicWeights& weights) + Node(const Node& parent, QubitIndexPair swap, Layers layers, + const Architecture& arch, const HeuristicWeights& weights) : sequence(parent.sequence), layout(parent.layout), f(0) { /// Apply node-specific swap to given layout. layout.swap(layout.getProgramIndex(swap.first), @@ -127,9 +91,8 @@ struct AStarHeuristicRouter final : RouterBase { * @brief Return true if the current sequence of SWAPs makes all gates * executable. */ - [[nodiscard]] bool isGoal(const ArrayRef& gates, - const Architecture& arch) const { - return std::ranges::all_of(gates, [&](const QubitIndexPair gate) { + [[nodiscard]] bool isGoal(Layer layer, const Architecture& arch) const { + return std::ranges::all_of(layer, [&](const QubitIndexPair gate) { return arch.areAdjacent(layout.getHardwareIndex(gate.first), layout.getHardwareIndex(gate.second)); }); @@ -161,12 +124,11 @@ struct AStarHeuristicRouter final : RouterBase { * its hardware qubits. Intuitively, this is the number of SWAPs that a * naive router would insert to route the layers. */ - [[nodiscard]] float h(std::span layers, - const Architecture& arch, + [[nodiscard]] float h(Layers layers, const Architecture& arch, const HeuristicWeights& weights) const { float nn{0}; for (const auto [i, layer] : llvm::enumerate(layers)) { - 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 dist = arch.distanceBetween(hw0, hw1); const std::size_t nswaps = dist < 2 ? 0 : dist - 2; @@ -180,13 +142,12 @@ struct AStarHeuristicRouter final : RouterBase { using MinQueue = std::priority_queue, std::greater<>>; public: - [[nodiscard]] RouterResult route(std::span layers, - const ThinLayout& layout, - const Architecture& arch) const override { + [[nodiscard]] RouterResult route(Layers layers, const ThinLayout& layout, + const Architecture& arch) const { Node root(layout); /// Early exit. No SWAPs required: - if (root.isGoal(layers.front().gates, arch)) { + if (root.isGoal(layers.front(), arch)) { return {}; } @@ -202,7 +163,7 @@ struct AStarHeuristicRouter final : RouterBase { Node curr = frontier.top(); frontier.pop(); - if (curr.isGoal(layers.front().gates, arch)) { + if (curr.isGoal(layers.front(), arch)) { return curr.sequence; } @@ -227,11 +188,10 @@ struct AStarHeuristicRouter final : RouterBase { /** * @brief Expand frontier with all neighbouring SWAPs in the current front. */ - void expand(MinQueue& frontier, const Node& parent, - std::span layers, + void expand(MinQueue& frontier, const Node& parent, Layers layers, const Architecture& arch) const { llvm::SmallDenseSet swaps{}; - for (const QubitIndexPair gate : layers.front().gates) { + for (const QubitIndexPair gate : layers.front()) { for (const auto prog : {gate.first, gate.second}) { const auto hw0 = parent.layout.getHardwareIndex(prog); for (const auto hw1 : arch.neighboursOf(hw0)) { diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveMapper.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveDriver.h similarity index 92% rename from mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveMapper.h rename to mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveDriver.h index af9ba3f5c2..144912b105 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveMapper.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveDriver.h @@ -14,7 +14,7 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/MapperBase.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/RoutingDriverBase.h" #include #include @@ -33,12 +33,14 @@ namespace mqt::ir::opt { using namespace mlir; -struct NaiveMapper final : MapperBase { - using MapperBase::MapperBase; +class NaiveDriver final : public RoutingDriverBase { using SWAPHistory = SmallVector; - LogicalResult rewrite(func::FuncOp func, - PatternRewriter& rewriter) const override { +public: + using RoutingDriverBase::RoutingDriverBase; + +private: + LogicalResult rewrite(func::FuncOp func, PatternRewriter& rewriter) override { LLVM_DEBUG(llvm::dbgs() << "handleFunc: " << func.getSymName() << '\n'); if (!isEntryPoint(func)) { @@ -97,7 +99,6 @@ struct NaiveMapper final : MapperBase { return success(); } -private: /** * @brief Copy the layout and recursively map the loop body. */ @@ -164,15 +165,16 @@ struct NaiveMapper final : MapperBase { * a 'for' of 'if' region always requires 2 * #(SWAPs required for region) * additional SWAPS. */ - WalkResult handleYield(scf::YieldOp op, Layout& layout, SWAPHistory& history, - PatternRewriter& rewriter) const { + static WalkResult handleYield(scf::YieldOp op, Layout& layout, + SWAPHistory& history, + PatternRewriter& rewriter) { if (!isa(op->getParentOp()) && !isa(op->getParentOp())) { return WalkResult::skip(); } /// Uncompute SWAPs. - NaiveMapper::insertSWAPs(llvm::to_vector(llvm::reverse(history)), layout, + NaiveDriver::insertSWAPs(llvm::to_vector(llvm::reverse(history)), layout, op.getLoc(), rewriter); return WalkResult::advance(); @@ -184,7 +186,7 @@ struct NaiveMapper final : MapperBase { * * Thanks to the placement pass, we can apply the identity layout here. */ - WalkResult handleQubit(QubitOp op, Layout& layout) const { + static WalkResult handleQubit(QubitOp op, Layout& layout) { const std::size_t index = op.getIndex(); layout.add(index, index, op.getQubit()); return WalkResult::advance(); @@ -254,7 +256,7 @@ struct NaiveMapper final : MapperBase { /** * @brief Update layout. */ - WalkResult handleReset(ResetOp op, Layout& layout) const { + static WalkResult handleReset(ResetOp op, Layout& layout) { layout.remapQubitValue(op.getInQubit(), op.getOutQubit()); return WalkResult::advance(); } @@ -262,7 +264,7 @@ struct NaiveMapper final : MapperBase { /** * @brief Update layout. */ - WalkResult handleMeasure(MeasureOp op, Layout& layout) const { + static WalkResult handleMeasure(MeasureOp op, Layout& layout) { layout.remapQubitValue(op.getInQubit(), op.getOutQubit()); return WalkResult::advance(); } @@ -287,7 +289,7 @@ struct NaiveMapper final : MapperBase { history.append(swaps); /// Insert SWAPs. - MapperBase::insertSWAPs(swaps, layout, op.getLoc(), rewriter); + RoutingDriverBase::insertSWAPs(swaps, layout, op.getLoc(), rewriter); } }; } // namespace mqt::ir::opt diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/MapperBase.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/RoutingDriverBase.h similarity index 93% rename from mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/MapperBase.h rename to mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/RoutingDriverBase.h index 01a7a34487..b27c90a7c5 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/MapperBase.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/RoutingDriverBase.h @@ -98,17 +98,17 @@ inline void replaceAllUsesInRegionAndChildrenExcept(Value oldValue, }); } -class MapperBase { +class RoutingDriverBase { public: - explicit MapperBase(std::unique_ptr arch) + explicit RoutingDriverBase(std::unique_ptr arch) : arch(std::move(arch)) {} - virtual ~MapperBase() = default; + virtual ~RoutingDriverBase() = default; - [[nodiscard]] virtual LogicalResult - rewrite(func::FuncOp func, PatternRewriter& rewriter) const = 0; + [[nodiscard]] virtual LogicalResult rewrite(func::FuncOp func, + PatternRewriter& rewriter) = 0; - [[nodiscard]] LogicalResult rewrite(ModuleOp module) const { + [[nodiscard]] LogicalResult rewrite(ModuleOp module) { PatternRewriter rewriter(module->getContext()); for (auto func : module.getOps()) { if (rewrite(func, rewriter).failed()) { diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Scheduler.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Scheduler.h deleted file mode 100644 index 0abb584e24..0000000000 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Scheduler.h +++ /dev/null @@ -1,535 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#pragma once - -#include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" -#include "mlir/IR/PatternMatch.h" - -#include "llvm/ADT/STLExtras.h" -#include "llvm/Support/ErrorHandling.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DEBUG_TYPE "route-sc" - -namespace mqt::ir::opt { - -/** - * @brief A vector of gates. - */ -using Layer = SmallVector; - -/** - * @brief A vector of layers. - * [0]=current, [1]=lookahead (optional), >=2 future layers - */ -using Layers = SmallVector; - -/** - * @brief A scheduler divides the circuit into routable sections. - */ -struct SchedulerBase { - virtual ~SchedulerBase() = default; - [[nodiscard]] virtual Layers schedule(UnitaryInterface op, - Layout layout) const = 0; -}; - -/** - * @brief A sequential scheduler simply returns the given op. - */ -struct SequentialOpScheduler final : SchedulerBase { - [[nodiscard]] Layers schedule(UnitaryInterface op, - Layout layout) const override { - const auto [in0, in1] = getIns(op); - return {{{layout.lookupProgramIndex(in0), layout.lookupProgramIndex(in1)}}}; - } -}; - -/** - * @brief A parallel scheduler collects 1 + nlookahead layers of parallelly - * executable gates. - */ -struct ParallelOpScheduler final : SchedulerBase { - explicit ParallelOpScheduler(const std::size_t nlookahead) - : nlayers_(1 + nlookahead) {} - - [[nodiscard]] Layers schedule(UnitaryInterface op, - Layout layout) const override { - Layers layers; - layers.reserve(nlayers_); - - /// Worklist of active qubits. - SmallVector wl; - SmallVector nextWl; - wl.reserve(layout.getHardwareQubits().size()); - nextWl.reserve(layout.getHardwareQubits().size()); - - // Initialize worklist. - llvm::copy_if(layout.getHardwareQubits(), std::back_inserter(wl), - [](Value q) { return !q.use_empty(); }); - - /// Set of two-qubit gates seen at least once. - llvm::SmallDenseSet openTwoQubit; - - /// Vector of two-qubit gates seen twice. - SmallVector readyTwoQubit; - - Region* region = op->getParentRegion(); - - while (!wl.empty() && layers.size() < nlayers_) { - for (const Value q : wl) { - const auto opt = advanceToTwoQubitGate(q, region, layout); - if (!opt) { - continue; - } - - const auto& [qNext, gate] = opt.value(); - - if (q != qNext) { - layout.remapQubitValue(q, qNext); - } - - if (!openTwoQubit.insert(gate).second) { - readyTwoQubit.push_back(gate); - openTwoQubit.erase(gate); - continue; - } - } - - if (readyTwoQubit.empty()) { - break; - } - - nextWl.clear(); - layers.emplace_back(); - layers.back().reserve(readyTwoQubit.size()); - - /// At this point all qubit values are remapped to the input of a - /// two-qubit gate: "value.user == two-qubit-gate". - /// - /// We release the ready two-qubit gates by forwarding their inputs - /// to their outputs. Then, we advance to the end of its two-qubit block. - /// Intuitively, whenever a gate in readyTwoQubit is routed, all one and - /// two-qubit gates acting on the same qubits are executable as well. - - for (const auto& op : readyTwoQubit) { - /// Release. - const ValuePair ins = getIns(op); - const ValuePair outs = getOuts(op); - layers.back().emplace_back(layout.lookupProgramIndex(ins.first), - layout.lookupProgramIndex(ins.second)); - layout.remapQubitValue(ins.first, outs.first); - layout.remapQubitValue(ins.second, outs.second); - - /// Advance two-qubit block. - std::array gates; - std::array heads{outs.first, outs.second}; - - while (true) { - bool stop = false; - for (const auto [i, q] : llvm::enumerate(heads)) { - const auto opt = advanceToTwoQubitGate(q, region, layout); - if (!opt) { - heads[i] = nullptr; - stop = true; - break; - } - - const auto& [qNext, gate] = opt.value(); - - if (q != qNext) { - layout.remapQubitValue(q, qNext); - } - - heads[i] = qNext; - gates[i] = gate; - } - - if (stop || gates[0] != gates[1]) { - break; - } - - const ValuePair ins = getIns(gates[0]); - const ValuePair outs = getOuts(gates[0]); - layout.remapQubitValue(ins.first, outs.first); - layout.remapQubitValue(ins.second, outs.second); - heads = {outs.first, outs.second}; - } - - /// Initialize next worklist. - for (const auto q : heads) { - if (q != nullptr && !q.use_empty()) { - nextWl.push_back(q); - } - } - } - - /// Prepare for next iteration. - readyTwoQubit.clear(); - wl = std::move(nextWl); - } - - LLVM_DEBUG({ - llvm::dbgs() << "schedule: layers=\n"; - for (const auto [i, layer] : llvm::enumerate(layers)) { - llvm::dbgs() << '\t' << i << "= "; - for (const auto [prog0, prog1] : layer) { - llvm::dbgs() << "(" << prog0 << "," << prog1 << "), "; - } - llvm::dbgs() << '\n'; - } - }); - - return layers; - } - -private: - /** - * @returns Next two-qubit gate on qubit wire, or std::nullopt if none exists. - */ - static std::optional> - advanceToTwoQubitGate(const Value q, Region* region, const Layout& layout) { - Value head = q; - while (true) { - if (head.use_empty()) { // No two-qubit gate found. - return std::nullopt; - } - - Operation* user = getUserInRegion(head, region); - if (user == nullptr) { // No two-qubit gate found. - return std::nullopt; - } - - bool endOfRegion = false; - std::optional twoQubitOp; - - TypeSwitch(user) - /// MQT - /// BarrierOp is a UnitaryInterface, however, requires special care. - .Case([&](BarrierOp op) { - for (const auto [in, out] : - llvm::zip_equal(op.getInQubits(), op.getOutQubits())) { - if (in == head) { - head = out; - return; - } - } - llvm_unreachable("head must be in barrier"); - }) - .Case([&](UnitaryInterface op) { - if (isTwoQubitGate(op)) { - twoQubitOp = op; - return; // Found a two-qubit gate, stop advancing head. - } - // Otherwise, advance head. - head = op.getOutQubits().front(); - }) - .Case([&](ResetOp op) { head = op.getOutQubit(); }) - .Case([&](MeasureOp op) { head = op.getOutQubit(); }) - /// SCF - /// The scf funcs assume that the first n results are the hw qubits. - .Case([&](scf::ForOp op) { - head = op->getResult(layout.lookupHardwareIndex(q)); - }) - .Case([&](scf::IfOp op) { - head = op->getResult(layout.lookupHardwareIndex(q)); - }) - .Case([&](scf::YieldOp) { endOfRegion = true; }) - .Default([&]([[maybe_unused]] Operation* op) { - LLVM_DEBUG({ - llvm::dbgs() << "unknown operation in def-use chain: "; - op->dump(); - }); - llvm_unreachable("unknown operation in def-use chain"); - }); - - if (twoQubitOp) { // Two-qubit gate found. - return std::make_pair(head, *twoQubitOp); - } - - if (endOfRegion) { - return std::nullopt; - } - } - - return std::nullopt; - } - - std::size_t nlayers_; -}; - -struct ScheduledLayer { - Operation* anchor; - SmallVector gates; -}; - -using ScheduledLayers = SmallVector; - -struct SlidingWindow { - SlidingWindow(std::span schedule, - const std::size_t nlookahead) - : schedule_(schedule), nlookahead_(nlookahead) {} - - [[nodiscard]] std::span current() const { - const std::size_t nlayers = schedule_.size(); - return schedule_.subspan(offset_, offset_ + nlookahead_ <= nlayers - ? nlookahead_ - : nlayers - offset_); - } - - void advance() { ++offset_; } - -private: - std::span schedule_; - std::size_t nlookahead_; - std::size_t offset_{}; -}; - -struct ParallelOpFullScheduler { - [[nodiscard]] static ScheduledLayers schedule(Region& region, Layout layout, - PatternRewriter& rewriter) { - ScheduledLayers layers; - - /// Worklist of active qubits. - SmallVector wl; - SmallVector nextWl; - wl.reserve(layout.getHardwareQubits().size()); - nextWl.reserve(layout.getHardwareQubits().size()); - - // Initialize worklist. - llvm::copy_if(layout.getHardwareQubits(), std::back_inserter(wl), - [](Value q) { return !q.use_empty(); }); - - /// Set of two-qubit gates seen at least once. - llvm::SmallDenseSet openTwoQubit; - - /// Vector of two-qubit gates seen twice. - SmallVector readyTwoQubit; - - SmallVector order; - order.reserve(std::distance(region.begin(), region.end())); - - while (!wl.empty()) { - for (const Value q : wl) { - const auto opt = advanceToTwoQubitGate(q, region, layout, order); - if (!opt) { - continue; - } - - const auto& [qNext, gate] = opt.value(); - - if (q != qNext) { - layout.remapQubitValue(q, qNext); - } - - if (!openTwoQubit.insert(gate).second) { - readyTwoQubit.push_back(gate); - openTwoQubit.erase(gate); - continue; - } - } - - if (readyTwoQubit.empty()) { - break; - } - - nextWl.clear(); - - /// At this point all qubit values are remapped to the input of a - /// two-qubit gate: "value.user == two-qubit-gate". - /// - /// We release the ready two-qubit gates by forwarding their inputs - /// to their outputs. Then, we advance to the end of its two-qubit block. - /// Intuitively, whenever a gate in readyTwoQubit is routed, all one and - /// two-qubit gates acting on the same qubits are executable as well. - - /// Can use front() as anchor because of reordering. - ScheduledLayer layer{.anchor = readyTwoQubit.front(), .gates = {}}; - layer.gates.reserve(readyTwoQubit.size()); - - for (const auto& readyOp : readyTwoQubit) { - order.push_back(readyOp); - - /// Release. - const ValuePair ins = getIns(readyOp); - const ValuePair outs = getOuts(readyOp); - layer.gates.emplace_back(layout.lookupProgramIndex(ins.first), - layout.lookupProgramIndex(ins.second)); - - layout.remapQubitValue(ins.first, outs.first); - layout.remapQubitValue(ins.second, outs.second); - - /// Advance two-qubit block. - std::array gates; - std::array heads{outs.first, outs.second}; - - while (true) { - bool stop = false; - for (const auto [i, q] : llvm::enumerate(heads)) { - const auto opt = advanceToTwoQubitGate(q, region, layout, order); - if (!opt) { - heads[i] = nullptr; - stop = true; - break; - } - - const auto& [qNext, gate] = opt.value(); - - if (q != qNext) { - layout.remapQubitValue(q, qNext); - } - - heads[i] = qNext; - gates[i] = gate; - } - - if (stop || gates[0] != gates[1]) { - break; - } - - order.push_back(gates[0]); - - const ValuePair ins = getIns(gates[0]); - const ValuePair outs = getOuts(gates[0]); - layout.remapQubitValue(ins.first, outs.first); - layout.remapQubitValue(ins.second, outs.second); - heads = {outs.first, outs.second}; - } - - /// Initialize next worklist. - for (const auto q : heads) { - if (q != nullptr && !q.use_empty()) { - nextWl.push_back(q); - } - } - } - - layers.emplace_back(layer); - - /// Prepare for next iteration. - readyTwoQubit.clear(); - wl = std::move(nextWl); - } - - LLVM_DEBUG({ - llvm::dbgs() << "schedule: order=\n"; - for (Operation* op : order) { - llvm::dbgs() << op->getLoc() << '\n'; - } - }); - - /// Impose strict def-use chain ordering. - for (std::size_t i = 0; i < order.size() - 1; ++i) { - rewriter.moveOpAfter(order[i + 1], order[i]); - } - - LLVM_DEBUG({ - llvm::dbgs() << "schedule: layers=\n"; - for (const auto [i, layer] : llvm::enumerate(layers)) { - llvm::dbgs() << '\t' << i << "= "; - for (const auto [prog0, prog1] : layer.gates) { - llvm::dbgs() << "(" << prog0 << "," << prog1 << "), "; - } - llvm::dbgs() << "\tanchor= " << layer.anchor->getLoc(); - llvm::dbgs() << '\n'; - } - }); - - return layers; - } - -private: - /** - * @returns Next two-qubit gate on qubit wire, or std::nullopt if none exists. - */ - static std::optional> // TODO: Instead of value use vector of values. - advanceToTwoQubitGate(const Value q, Region& region, const Layout& layout, - SmallVector& order) { - Value head = q; - while (true) { - if (head.use_empty()) { // No two-qubit gate found. - return std::nullopt; - } - - Operation* user = getUserInRegion(head, ®ion); - if (user == nullptr) { // No two-qubit gate found. - return std::nullopt; - } - - bool endOfRegion = false; - std::optional twoQubitOp; - - TypeSwitch(user) - /// MQT - /// BarrierOp is a UnitaryInterface, however, requires special care. - .Case([&](BarrierOp op) { - for (const auto [in, out] : - llvm::zip_equal(op.getInQubits(), op.getOutQubits())) { - if (in == head) { - head = out; - return; - } - } - llvm_unreachable("head must be in barrier"); - }) - .Case([&](UnitaryInterface op) { - if (isTwoQubitGate(op)) { - twoQubitOp = op; - return; // Found a two-qubit gate, stop advancing head. - } - // Otherwise, advance head. - head = op.getOutQubits().front(); - order.push_back(op); - }) - .Case([&](ResetOp op) { head = op.getOutQubit(); }) - .Case([&](MeasureOp op) { head = op.getOutQubit(); }) - /// SCF - /// The scf funcs assume that the first n results are the hw qubits. - .Case([&](scf::ForOp op) { - head = op->getResult(layout.lookupHardwareIndex(q)); - }) - .Case([&](scf::IfOp op) { - head = op->getResult(layout.lookupHardwareIndex(q)); - }) - .Case([&](scf::YieldOp) { endOfRegion = true; }) - .Default([&]([[maybe_unused]] Operation* op) { - LLVM_DEBUG({ - llvm::dbgs() << "unknown operation in def-use chain: "; - op->dump(); - }); - llvm_unreachable("unknown operation in def-use chain"); - }); - - if (twoQubitOp) { // Two-qubit gate found. - return std::make_pair(head, *twoQubitOp); - } - - if (endOfRegion) { - return std::nullopt; - } - } - - return std::nullopt; - } -}; -} // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingPass.cpp index ca9d330414..8c71d44332 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingPass.cpp @@ -8,13 +8,11 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include "mlir/Dialect/MQTOpt/Transforms/Passes.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Scheduler.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveDriver.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/RoutingDriverBase.h" #include #include @@ -41,10 +39,7 @@ #include #include #include -#include -#include #include -#include #define DEBUG_TYPE "route-sc" @@ -56,440 +51,6 @@ namespace mqt::ir::opt { namespace { using namespace mlir; -/** - * @brief Create and return SWAPOp for two qubits. - * - * Expects the rewriter to be set to the correct position. - * - * @param location The Location to attach to the created op. - * @param in0 First input qubit SSA value. - * @param in1 Second input qubit SSA value. - * @param rewriter A PatternRewriter. - * @return The created SWAPOp. - */ -[[nodiscard]] SWAPOp createSwap(Location location, Value in0, Value in1, - PatternRewriter& rewriter) { - const SmallVector resultTypes{in0.getType(), in1.getType()}; - const SmallVector inQubits{in0, in1}; - - return rewriter.create( - /* location = */ location, - /* out_qubits = */ resultTypes, - /* pos_ctrl_out_qubits = */ TypeRange{}, - /* neg_ctrl_out_qubits = */ TypeRange{}, - /* static_params = */ nullptr, - /* params_mask = */ nullptr, - /* params = */ ValueRange{}, - /* in_qubits = */ inQubits, - /* pos_ctrl_in_qubits = */ ValueRange{}, - /* neg_ctrl_in_qubits = */ ValueRange{}); -} - -/** - * @brief Replace all uses of a value within a region and its nested regions, - * except for a specific operation. - * - * @param oldValue The value to replace - * @param newValue The new value to use - * @param region The region in which to perform replacements - * @param exceptOp Operation to exclude from replacements - * @param rewriter The pattern rewriter - */ -void replaceAllUsesInRegionAndChildrenExcept(Value oldValue, Value newValue, - Region* region, - Operation* exceptOp, - PatternRewriter& rewriter) { - if (oldValue == newValue) { - return; - } - - rewriter.replaceUsesWithIf(oldValue, newValue, [&](OpOperand& use) { - Operation* user = use.getOwner(); - if (user == exceptOp) { - return false; - } - - // For other blocks, check if in region tree - Region* userRegion = user->getParentRegion(); - while (userRegion) { - if (userRegion == region) { - return true; - } - userRegion = userRegion->getParentRegion(); - } - return false; - }); -} - -// class Mapper { -// public: -// explicit Mapper(std::unique_ptr scheduler, -// std::unique_ptr router, Pass::Statistic& -// numSwaps) -// : scheduler_(std::move(scheduler)), router_(std::move(router)), -// numSwaps_(&numSwaps) {} - -// /** -// * @returns true iff @p op is executable on the targeted architecture. -// */ -// [[nodiscard]] static bool isExecutable(UnitaryInterface op, -// const Architecture& arch, -// const Layout& layout) { -// const auto [in0, in1] = getIns(op); -// return arch.areAdjacent(layout.lookupHardwareIndex(in0), -// layout.lookupHardwareIndex(in1)); -// } - -// /** -// * @brief Insert SWAPs such that the gates provided by the scheduler are -// * executable. -// */ -// [[nodiscard]] RouterResult map(UnitaryInterface op, const Architecture& -// arch, -// Layout& layout, PatternRewriter& rewriter) { -// const auto layers = scheduler_->schedule(op, layout); -// const auto swaps = router_->route(layers, layout, arch); -// insert(swaps, op->getLoc(), layout, rewriter); -// return swaps; -// } - -// /** -// * @brief Restore layout by uncomputing. -// * -// * History is cleared by the caller (e.g., via stack/history pop in -// handlers). -// * -// * @todo Remove SWAP history and use advanced strategies. -// */ -// void restore(Location location, const SmallVector& history, -// Layout& layout, PatternRewriter& rewriter) { -// const auto swaps = llvm::to_vector(llvm::reverse(history)); -// insert(swaps, location, layout, rewriter); -// } - -// private: -// void insert(ArrayRef swaps, Location location, Layout& -// layout, -// PatternRewriter& rewriter) { -// for (const auto [hw0, hw1] : swaps) { -// const Value in0 = layout.lookupHardwareValue(hw0); -// const Value in1 = layout.lookupHardwareValue(hw1); -// [[maybe_unused]] const auto [prog0, prog1] = -// layout.getProgramIndices(hw0, hw1); - -// LLVM_DEBUG({ -// llvm::dbgs() << llvm::format( -// "route: swap= p%d:h%d, p%d:h%d <- p%d:h%d, p%d:h%d\n", prog1, -// hw0, prog0, hw1, prog0, hw0, prog1, hw1); -// }); - -// auto swap = createSwap(location, in0, in1, rewriter); -// const auto [out0, out1] = getOuts(swap); - -// rewriter.setInsertionPointAfter(swap); -// replaceAllUsesInRegionAndChildrenExcept( -// in0, out1, swap->getParentRegion(), swap, rewriter); -// replaceAllUsesInRegionAndChildrenExcept( -// in1, out0, swap->getParentRegion(), swap, rewriter); - -// layout.swap(in0, in1); -// layout.remapQubitValue(in0, out0); -// layout.remapQubitValue(in1, out1); - -// (*numSwaps_)++; -// } -// } - -// std::unique_ptr scheduler_; -// std::unique_ptr router_; - -// Pass::Statistic* numSwaps_; -// }; - -struct RoutingDriver { - RoutingDriver(std::unique_ptr router, - std::unique_ptr arch) - : router_(std::move(router)), arch_(std::move(arch)) {} - - LogicalResult route(ModuleOp module); - -private: - WalkResult handleQubit(QubitOp op, Layout& layout); - WalkResult handleUnitary(UnitaryInterface op, Layout& layout, - SlidingWindow& window, PatternRewriter& rewriter); - WalkResult handleReset(ResetOp op, Layout& layout); - WalkResult handleMeasure(MeasureOp op, Layout& layout); - - std::unique_ptr router_; - std::unique_ptr arch_; -}; - -/** - * @brief Push new state for the loop body onto the stack. - */ -// WalkResult RoutingDriver::handleFor(scf::ForOp op) { -// /// Loop body state. -// stack().duplicateTop(); -// historyStack().emplace(); - -// /// Forward out-of-loop and in-loop values. -// const auto nqubits = arch().nqubits(); -// const auto initArgs = op.getInitArgs().take_front(nqubits); -// const auto results = op.getResults().take_front(nqubits); -// const auto iterArgs = op.getRegionIterArgs().take_front(nqubits); -// for (const auto [arg, res, iter] : llvm::zip(initArgs, results, iterArgs)) -// { -// stack().getItemAtDepth(FOR_PARENT_DEPTH).remapQubitValue(arg, res); -// stack().top().remapQubitValue(arg, iter); -// } - -// return WalkResult::advance(); -// } - -/** - * @brief Push two new states for the then and else branches onto the stack. - */ -// WalkResult RoutingDriver::handleIf(scf::IfOp op) { -// /// Prepare stack. -// stack().duplicateTop(); /// Else. -// stack().duplicateTop(); /// Then. -// historyStack().emplace(); -// historyStack().emplace(); - -// /// Forward out-of-if values. -// const auto results = op->getResults().take_front(arch().nqubits()); -// Layout& layoutBeforeIf = stack().getItemAtDepth(IF_PARENT_DEPTH); -// for (const auto [hw, res] : llvm::enumerate(results)) { -// const Value q = layoutBeforeIf.lookupHardwareValue(hw); -// layoutBeforeIf.remapQubitValue(q, res); -// } - -// return WalkResult::advance(); -// } - -/** - * @brief Indicates the end of a region defined by a branching op. - * Consequently, we pop the region's state from the stack. - * - * Restores layout by uncomputation and replaces (invalid) yield. - * - * Using uncompute has the advantages of (1) being intuitive and - * (2) preserving the optimality of the original SWAP sequence. - * Essentially the better the routing algorithm the better the - * uncompute. Moreover, this has the nice property that routing - * a 'for' of 'if' region always requires 2 * #(SWAPs required for region) - * additional SWAPS. - */ -// WalkResult RoutingDriver::handleYield(scf::YieldOp op, -// PatternRewriter& rewriter) { -// if (!isa(op->getParentOp()) && -// !isa(op->getParentOp())) { -// return WalkResult::skip(); -// } - -// mapper().restore(op->getLoc(), historyStack().top(), stack().top(), -// rewriter); - -// stack().pop(); -// historyStack().pop(); - -// return WalkResult::advance(); -// } - -/** - * @brief Add hardware qubit with respective program & hardware index to - * layout. - * - * Thanks to the placement pass, we can apply the identity layout here. - */ -WalkResult RoutingDriver::handleQubit(QubitOp op, Layout& layout) { - const std::size_t index = op.getIndex(); - layout.add(index, index, op.getQubit()); - return WalkResult::advance(); -} - -/** - * @brief Ensures the executability of two-qubit gates on the given target - * architecture by inserting SWAPs. - */ -WalkResult RoutingDriver::handleUnitary(UnitaryInterface op, Layout& layout, - SlidingWindow& window, - PatternRewriter& rewriter) { - LLVM_DEBUG(llvm::dbgs() << "handleUnitary: gate=" << op->getName() << '\n'); - - const std::vector inQubits = op.getAllInQubits(); - const std::vector outQubits = op.getAllOutQubits(); - const std::size_t nacts = inQubits.size(); - - // Global-phase or zero-qubit unitary: Nothing to do. - if (nacts == 0) { - return WalkResult::advance(); - } - - if (isa(op)) { - for (const auto [in, out] : llvm::zip(inQubits, outQubits)) { - layout.remapQubitValue(in, out); - } - return WalkResult::advance(); - } - - /// Expect two-qubit gate decomposition. - if (nacts > 2) { - return op->emitOpError() << "acts on more than two qubits"; - } - - /// Single-qubit: Forward mapping. - if (nacts == 1) { - layout.remapQubitValue(inQubits[0], outQubits[0]); - return WalkResult::advance(); - } - - /// Anchored two-qubit gate: Map first then forward. - auto layers = window.current(); - auto* anchor = layers.front().anchor; - if (anchor == op) { - LLVM_DEBUG(llvm::dbgs() << "\tanchored by scheduler\n"); - - const auto swaps = router_->route(layers, layout, *arch_); - for (const auto [hw0, hw1] : swaps) { - const Value in0 = layout.lookupHardwareValue(hw0); - const Value in1 = layout.lookupHardwareValue(hw1); - [[maybe_unused]] const auto [prog0, prog1] = - layout.getProgramIndices(hw0, hw1); - - LLVM_DEBUG({ - llvm::dbgs() << llvm::format( - "route: swap= p%d:h%d, p%d:h%d <- p%d:h%d, p%d:h%d\n", prog1, hw0, - prog0, hw1, prog0, hw0, prog1, hw1); - }); - - auto swap = createSwap(anchor->getLoc(), in0, in1, rewriter); - const auto [out0, out1] = getOuts(swap); - - rewriter.setInsertionPointAfter(swap); - replaceAllUsesInRegionAndChildrenExcept( - in0, out1, swap->getParentRegion(), swap, rewriter); - replaceAllUsesInRegionAndChildrenExcept( - in1, out0, swap->getParentRegion(), swap, rewriter); - - layout.swap(in0, in1); - layout.remapQubitValue(in0, out0); - layout.remapQubitValue(in1, out1); - } - - window.advance(); - } - - const auto [execIn0, execIn1] = getIns(op); - const auto [execOut0, execOut1] = getOuts(op); - - LLVM_DEBUG({ - llvm::dbgs() << llvm::format("handleUnitary: gate= p%d:h%d, p%d:h%d\n", - layout.lookupProgramIndex(execIn0), - layout.lookupHardwareIndex(execIn0), - layout.lookupProgramIndex(execIn1), - layout.lookupHardwareIndex(execIn1)); - }); - - if (isa(op)) { - layout.swap(execIn0, execIn1); - } - - layout.remapQubitValue(execIn0, execOut0); - layout.remapQubitValue(execIn1, execOut1); - - return WalkResult::advance(); -} - -/** - * @brief Update layout. - */ -WalkResult RoutingDriver::handleReset(ResetOp op, Layout& layout) { - layout.remapQubitValue(op.getInQubit(), op.getOutQubit()); - return WalkResult::advance(); -} - -/** - * @brief Update layout. - */ -WalkResult RoutingDriver::handleMeasure(MeasureOp op, Layout& layout) { - layout.remapQubitValue(op.getInQubit(), op.getOutQubit()); - return WalkResult::advance(); -} - -/** - * @brief Route the given module by inserting SWAPs. - * - * @details - * Collects all functions marked with the 'entry_point' attribute, builds a - * preorder worklist of their operations, and processes that list. Each - * operation is handled via a TypeSwitch and may rewrite the IR in place via - * the provided PatternRewriter. If any handler signals an error (interrupt), - * this function returns failure. - * - * @note - * We consciously avoid MLIR pattern drivers: Idiomatic MLIR transformation - * patterns are independent and order-agnostic. Since we require state-sharing - * between patterns for the transformation we violate this assumption. - * Essentially this is also the reason why we can't utilize MLIR's - * `applyPatternsGreedily` function. Moreover, we require pre-order traversal - * which current drivers of MLIR don't support. However, even if such a driver - * would exist, it would probably not return logical results which we require - * for error-handling (similarly to `walkAndApplyPatterns`). Consequently, a - * custom driver would be required in any case, which adds unnecessary code to - * maintain. - */ -LogicalResult RoutingDriver::route(ModuleOp module) { - PatternRewriter rewriter(module->getContext()); - - /// Prepare work-list. - for (func::FuncOp func : module.getOps()) { - if (!isEntryPoint(func)) { - continue; // Ignore non entry_point functions for now. - } - - /// Prepare layout. - Layout layout(arch_->nqubits()); - for (QubitOp op : func.getOps()) { - std::ignore = handleQubit(op, layout); - } - - /// Schedule unitaries. - /// This function call reorders, and hence rewrites, the IR. - ScheduledLayers schedule = ParallelOpFullScheduler::schedule( - func.getFunctionBody(), layout, rewriter); - - SlidingWindow window(schedule, 1); - - for (Operation& op : func.getOps()) { - if (isa(op)) { - continue; // Skip already processed qubit ops. - } - - const OpBuilder::InsertionGuard guard(rewriter); - rewriter.setInsertionPoint(&op); - - const bool interrupted = - TypeSwitch(&op) - .Case([&](UnitaryInterface op) { - return handleUnitary(op, layout, window, rewriter); - }) - .Case( - [&](ResetOp op) { return handleReset(op, layout); }) - .Case( - [&](MeasureOp op) { return handleMeasure(op, layout); }) - .Default([](auto) { return WalkResult::skip(); }) // Skip rest. - .wasInterrupted(); - - if (interrupted) { - return failure(); - } - } - } - - return success(); -} - /** * @brief This pass ensures that the connectivity constraints of the target * architecture are met. @@ -503,7 +64,6 @@ struct RoutingPassSC final : impl::RoutingPassSCBase { return; } - auto router = getRouter(); auto arch = getArchitecture(archName); if (!arch) { emitError(UnknownLoc::get(&getContext())) @@ -512,23 +72,25 @@ struct RoutingPassSC final : impl::RoutingPassSCBase { return; } - RoutingDriver driver(std::move(router), std::move(arch)); - if (failed(driver.route(getOperation()))) { + auto mapper = getMapper(std::move(arch)); + if (mapper->rewrite(getOperation()).failed()) { signalPassFailure(); } } private: - [[nodiscard]] std::unique_ptr getRouter() { + [[nodiscard]] std::unique_ptr + getMapper(std::unique_ptr arch) { switch (static_cast(method)) { case RoutingMethod::Naive: { - LLVM_DEBUG({ llvm::dbgs() << "getRouter: method=naive\n"; }); - return std::make_unique(); + LLVM_DEBUG({ llvm::dbgs() << "getMapper: method=naive\n"; }); + return std::make_unique(std::move(arch)); } case RoutingMethod::AStar: { LLVM_DEBUG({ llvm::dbgs() << "getRouter: method=astar\n"; }); const HeuristicWeights weights(alpha, lambda, nlookahead); - return std::make_unique(weights); + return std::make_unique(std::move(arch), weights, + nlookahead); } } From 260036c4b0a7414495df860ecfb7cb821f40c292 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Sun, 9 Nov 2025 12:28:26 +0100 Subject: [PATCH 06/56] Fix scf funcs for AStarDriver --- .../Transforms/Transpilation/AStarDriver.h | 390 ++++++++++++------ .../Transforms/Transpilation/NaiveDriver.h | 4 +- .../Transpilation/sc/PlacementPass.cpp | 2 +- .../Transforms/Transpilation/basics.mlir | 8 +- 4 files changed, 267 insertions(+), 137 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h index 13ebfed63a..f6b30607c2 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h @@ -16,10 +16,10 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/RoutingDriverBase.h" - -#include "llvm/ADT/iterator_range.h" +#include "mlir/Interfaces/ControlFlowInterfaces.h" #include +#include #include #include #include @@ -35,20 +35,18 @@ namespace mqt::ir::opt { using namespace mlir; struct Layer { + /// The program indices of the two-qubit gates within this layer. + SmallVector twoQubitIndices; + /// Operations that can be executed before the two-qubit gates. SmallVector ops; /// The two-qubit gates. SmallVector twoQubitOps; - /// The program indices of the two-qubit gates within this layer. - SmallVector twoQubitIndices; /// Operations that will be executable whenever all gates in the layer are /// executable. SmallVector blockOps; - /// @returns true if the layer has no gates to route. - [[nodiscard]] bool empty() const { return twoQubitIndices.empty(); } - - /// @returns the scheduled operations in (to be) reordered order. + /// @returns the scheduled operations in the layer. [[nodiscard]] auto getOps() { return concat(ops, twoQubitOps, blockOps); } @@ -57,6 +55,12 @@ struct Layer { using Schedule = SmallVector; class Scheduler { + /// The region where the schedule ops reside. + Region* region; + /// Counts the amount of occurrences for a given op. + /// Currently only used for two-qubit unitaries and scf ops. + DenseMap occurrences; + public: explicit Scheduler(Region* region) : region(region) {} @@ -66,22 +70,27 @@ class Scheduler { * @returns the schedule. */ [[nodiscard]] Schedule schedule(Layout layout) { - /// Worklist of qubits. - SmallVector qubits(layout.getHardwareQubits()); - /// Set of two-qubit gates seen at least once. - DenseSet openTwoQubit; + occurrences.clear(); Schedule schedule; - while (true) { - const auto result = getNextLayer(qubits, openTwoQubit, layout); - if (result.layer.empty()) { - break; - } - - schedule.emplace_back(result.layer); - qubits = result.qubits; + SmallVector qubits(layout.getHardwareQubits()); // worklist. + while (!qubits.empty()) { + const auto res = getNextLayer(qubits, layout); + schedule.emplace_back(res.layer); + qubits = res.qubits; } + LLVM_DEBUG({ + llvm::dbgs() << "schedule: layers=\n"; + for (const auto [i, layer] : llvm::enumerate(schedule)) { + llvm::dbgs() << '\t' << i << "= "; + for (const auto [prog0, prog1] : layer.twoQubitIndices) { + llvm::dbgs() << "(" << prog0 << "," << prog1 << "), "; + } + llvm::dbgs() << '\n'; + } + }); + return schedule; } @@ -94,14 +103,14 @@ class Scheduler { }; struct AdvanceResult { - /// The input value of the unitary interface. + /// The advanced qubit value on the wire. Value q = nullptr; - /// The unitary interface. - UnitaryInterface u = nullptr; - /// All operations visited until the two-qubit gate. + /// The (>=2)-qubit op which has q as argument. + Operation* op = nullptr; + /// All one-qubit ops visited until the (>=2)-qubit op. SmallVector ops; - /// @returns true iff. the advancement hit a two-qubit gate. - [[nodiscard]] bool valid() const { return u != nullptr; } + /// @returns true iff the advancement found a (>=2)-qubit op. + explicit operator bool() const { return op != nullptr; } }; struct BlockAdvanceResult { @@ -115,112 +124,153 @@ class Scheduler { * @returns todo */ [[nodiscard]] NextLayerResult getNextLayer(ArrayRef qubits, - DenseSet& openTwoQubit, Layout& layout) { - NextLayerResult bundle; - bundle.qubits.reserve(qubits.size()); + NextLayerResult next; + next.qubits.reserve(qubits.size()); for (const Value q : qubits) { if (q.use_empty()) { continue; } - const auto result = advanceToTwoQubitGate(q, layout); - if (!result.valid()) { + const auto res = advanceOneQubitOnWire(q); + + /// Append one-qubit op chain to layer. + next.layer.ops.append(res.ops); + + /// If no (>=2)-qubit op has been found continue. + if (!res) { continue; } - if (q != result.q) { - layout.remapQubitValue(q, result.q); + /// Otherwise map the current to the advanced qubit value. + if (q != res.q) { + layout.remapQubitValue(q, res.q); } - bundle.layer.ops.append(result.ops); + /// Handle the found (>=2)-qubit op based on its type: - if (!openTwoQubit.insert(result.u).second) { - const auto ins = getIns(result.u); - const auto outs = getOuts(result.u); + if (auto barrier = dyn_cast(res.op)) { + /// Once we've seen all inputs of the barrier, we can release and + /// forwards its qubit values. + if (++occurrences[barrier] == barrier->getNumResults()) { + for (const auto [in, out] : + llvm::zip_equal(barrier.getInQubits(), barrier.getOutQubits())) { + layout.remapQubitValue(in, out); + } + next.layer.blockOps.push_back(barrier); + } + continue; + } + + if (auto u = dyn_cast(res.op)) { + if (++occurrences[u] == 2) { + const auto ins = getIns(u); + const auto outs = getOuts(u); + + next.layer.twoQubitOps.emplace_back(u); + next.layer.twoQubitIndices.emplace_back( + layout.lookupProgramIndex(ins.first), + layout.lookupProgramIndex(ins.second)); - bundle.layer.twoQubitOps.emplace_back(result.u); - bundle.layer.twoQubitIndices.emplace_back( - layout.lookupProgramIndex(ins.first), - layout.lookupProgramIndex(ins.second)); + layout.remapQubitValue(ins.first, outs.first); + layout.remapQubitValue(ins.second, outs.second); - layout.remapQubitValue(ins.first, outs.first); - layout.remapQubitValue(ins.second, outs.second); + next.qubits.push_back(outs.first); + next.qubits.push_back(outs.second); - const auto blockResult = skipTwoQubitBlock(outs, layout); - bundle.layer.blockOps.append(blockResult.ops); + // const auto blockResult = skipTwoQubitBlock(outs, layout); + // next.layer.blockOps.append(blockResult.ops); - if (blockResult.outs.first != nullptr && - !blockResult.outs.first.use_empty()) { - bundle.qubits.push_back(blockResult.outs.first); + // if (blockResult.outs.first != nullptr && + // !blockResult.outs.first.use_empty()) { + // next.qubits.push_back(blockResult.outs.first); + // } + + // if (blockResult.outs.second != nullptr && + // !blockResult.outs.second.use_empty()) { + // next.qubits.push_back(blockResult.outs.second); + // } } + continue; + } - if (blockResult.outs.second != nullptr && - !blockResult.outs.second.use_empty()) { - bundle.qubits.push_back(blockResult.outs.second); + if (auto loop = dyn_cast(res.op)) { + if (++occurrences[loop] == loop->getNumResults()) { + for (const auto [in, out] : + llvm::zip_equal(loop.getInitArgs(), loop.getResults())) { + layout.remapQubitValue(in, out); + next.qubits.push_back(out); + } + next.layer.blockOps.push_back(loop); } + continue; + } - openTwoQubit.erase(result.u); + if (auto cond = dyn_cast(res.op)) { + if (++occurrences[cond] == cond->getNumResults()) { + for (const auto [in, out] : + llvm::zip_equal(layout.getHardwareQubits(), cond.getResults())) { + layout.remapQubitValue(in, out); + next.qubits.push_back(out); + } + next.layer.blockOps.push_back(cond); + } + } + + if (auto yield = dyn_cast(res.op)) { + if (++occurrences[yield] == yield.getResults().size()) { + next.layer.blockOps.push_back(yield); + } } } - return bundle; + return next; } /** * @returns todo */ - AdvanceResult advanceToTwoQubitGate(const Value q, const Layout& layout) { - AdvanceResult result; + AdvanceResult advanceOneQubitOnWire(const Value q) { + AdvanceResult res; + res.q = q; - Value head = q; while (true) { - if (head.use_empty()) { // No two-qubit gate found. + if (res.q.use_empty()) { break; } - Operation* user = getUserInRegion(head, region); - if (user == nullptr) { // No two-qubit gate found. - break; + Operation* user = getUserInRegion(res.q, region); + if (user == nullptr) { + /// Must be a branching op: + user = res.q.getUsers().begin()->getParentOp(); + assert(isa(user)); } - result.ops.push_back(user); - - bool endOfRegion = false; TypeSwitch(user) /// MQT - /// BarrierOp is a UnitaryInterface, however, requires special care. - .Case([&](BarrierOp op) { - for (const auto [in, out] : - llvm::zip_equal(op.getInQubits(), op.getOutQubits())) { - if (in == head) { - head = out; - return; - } - } - llvm_unreachable("head must be in barrier"); - }) + .Case([&](BarrierOp op) { res.op = op; }) .Case([&](UnitaryInterface op) { if (isTwoQubitGate(op)) { - result.u = op; + res.op = op; return; // Found a two-qubit gate, stop advancing head. } // Otherwise, advance head. - head = op.getOutQubits().front(); + res.q = op.getOutQubits().front(); + res.ops.push_back(user); /// Only add one-qubit gates. }) - .Case([&](ResetOp op) { head = op.getOutQubit(); }) - .Case([&](MeasureOp op) { head = op.getOutQubit(); }) - - /// SCF - /// The scf funcs assume that the first n results are the hw qubits. - .Case([&](scf::ForOp op) { - head = op->getResult(layout.lookupHardwareIndex(q)); + .Case([&](ResetOp op) { + res.q = op.getOutQubit(); + res.ops.push_back(user); }) - .Case([&](scf::IfOp op) { - head = op->getResult(layout.lookupHardwareIndex(q)); + .Case([&](MeasureOp op) { + res.q = op.getOutQubit(); + res.ops.push_back(user); }) - .Case([&](scf::YieldOp) { endOfRegion = true; }) + /// SCF + .Case( + [&](RegionBranchOpInterface op) { res.op = op; }) + .Case([&](scf::YieldOp op) { res.op = op; }) .Default([&]([[maybe_unused]] Operation* op) { LLVM_DEBUG({ llvm::dbgs() << "unknown operation in def-use chain: "; @@ -229,18 +279,12 @@ class Scheduler { llvm_unreachable("unknown operation in def-use chain"); }); - if (result.u) { // Two-qubit gate found. - result.ops.pop_back(); // Remove two-qubit gate from op list. - result.q = head; - break; - } - - if (endOfRegion) { + if (res.op != nullptr) { break; } } - return result; + return res; } /** @@ -255,21 +299,20 @@ class Scheduler { while (true) { bool stop = false; for (const auto [i, q] : llvm::enumerate(heads)) { - const auto result = advanceToTwoQubitGate(q, layout); - if (!result.valid()) { + const auto res = advanceOneQubitOnWire(q); + blockResult.ops.append(res.ops); + if (!res) { heads[i] = nullptr; stop = true; break; } - if (q != result.q) { - layout.remapQubitValue(q, result.q); + if (q != res.q) { + layout.remapQubitValue(q, res.q); } - blockResult.ops.append(result.ops); - - heads[i] = result.q; - gates[i] = result.u; + heads[i] = res.q; + gates[i] = dyn_cast(res.op); } if (stop || gates[0] != gates[1]) { @@ -289,11 +332,11 @@ class Scheduler { return blockResult; } - - Region* region; }; class AStarDriver final : public RoutingDriverBase { + using SWAPHistory = SmallVector; + public: AStarDriver(std::unique_ptr arch, const HeuristicWeights& weights, std::size_t nlookahead) @@ -319,11 +362,15 @@ class AStarDriver final : public RoutingDriverBase { layout.add(index, index, op.getQubit()); }); - return rewrite(func.getBody(), layout, rewriter); + SWAPHistory history; + return rewrite(func.getBody(), layout, history, rewriter); } - LogicalResult rewrite(Region& region, Layout& layout, + LogicalResult rewrite(Region& region, Layout& layout, SWAPHistory& history, PatternRewriter& rewriter) const { + + /// Generate schedule for region. + Scheduler scheduler(®ion); Schedule schedule = scheduler.schedule(layout); @@ -342,12 +389,7 @@ class AStarDriver final : public RoutingDriverBase { /// Impose a strict ordering of the operations. Note that /// we reorder the operation before we insert any swaps. - if (prev != nullptr) { - if (isa(prev) || isa(prev) || - isa(curr) || isa(curr)) { - continue; - } - + if (prev != nullptr && prev != curr && curr->isBeforeInBlock(prev)) { rewriter.moveOpAfter(curr, prev); } @@ -358,30 +400,22 @@ class AStarDriver final : public RoutingDriverBase { TypeSwitch(curr) /// mqtopt Dialect .Case([&](UnitaryInterface op) { - return handleUnitary(op, layout, window, rewriter); + return handleUnitary(op, layout, window, history, rewriter); }) .Case( [&](ResetOp op) { return handleReset(op, layout); }) .Case( [&](MeasureOp op) { return handleMeasure(op, layout); }) - /// built-in Dialect - .Case([&]([[maybe_unused]] ModuleOp op) { - return WalkResult::advance(); + /// scf Dialect + .Case([&](scf::ForOp op) { + return handleFor(op, layout, rewriter); }) - /// func Dialect - .Case([&]([[maybe_unused]] func::ReturnOp op) { - return WalkResult::advance(); + .Case([&](scf::IfOp op) { + return handleIf(op, layout, rewriter); + }) + .Case([&](scf::YieldOp op) { + return handleYield(op, layout, history, rewriter); }) - /// scf Dialect - // .Case([&](scf::ForOp op) { - // return handleFor(op, layout, rewriter); - // }) - // .Case([&](scf::IfOp op) { - // return handleIf(op, layout, rewriter); - // }) - // .Case([&](scf::YieldOp op) { - // return handleYield(op, layout, history, rewriter); - // }) /// Skip the rest. .Default([](auto) { return WalkResult::skip(); }); @@ -396,23 +430,112 @@ class AStarDriver final : public RoutingDriverBase { return success(); } + /** + * @brief Copy the layout and recursively map the loop body. + */ + WalkResult handleFor(scf::ForOp op, Layout& layout, + PatternRewriter& rewriter) const { + LLVM_DEBUG(llvm::dbgs() << "handleFor: recurse for loop body\n"); + + /// Copy layout. + Layout forLayout(layout); + + /// Forward out-of-loop and in-loop values. + const auto initArgs = op.getInitArgs().take_front(arch->nqubits()); + const auto results = op.getResults().take_front(arch->nqubits()); + const auto iterArgs = op.getRegionIterArgs().take_front(arch->nqubits()); + for (const auto [arg, res, iter] : llvm::zip(initArgs, results, iterArgs)) { + layout.remapQubitValue(arg, res); + forLayout.remapQubitValue(arg, iter); + } + + /// Recursively handle loop region. + SWAPHistory history; + return rewrite(op.getRegion(), forLayout, history, rewriter); + } + + /** + * @brief Copy the layout for each branch and recursively map the branches. + */ + WalkResult handleIf(scf::IfOp op, Layout& layout, + PatternRewriter& rewriter) const { + + /// Recursively handle each branch region. + + LLVM_DEBUG(llvm::dbgs() << "handleIf: recurse for then\n"); + Layout ifLayout(layout); + SWAPHistory ifHistory; + const auto ifRes = + rewrite(op.getThenRegion(), ifLayout, ifHistory, rewriter); + if (ifRes.failed()) { + return ifRes; + } + + LLVM_DEBUG(llvm::dbgs() << "handleIf: recurse for else\n"); + Layout elseLayout(layout); + SWAPHistory elseHistory; + const auto elseRes = + rewrite(op.getElseRegion(), elseLayout, elseHistory, rewriter); + if (elseRes.failed()) { + return elseRes; + } + + /// Forward out-of-if values. + const auto results = op->getResults().take_front(arch->nqubits()); + for (const auto [hw, res] : llvm::enumerate(results)) { + const Value q = layout.lookupHardwareValue(hw); + layout.remapQubitValue(q, res); + } + + return WalkResult::advance(); + } + + /** + * @brief Indicates the end of a region defined by a scf op. + * + * Restores layout by uncomputation and replaces (invalid) yield. + * + * Using uncompute has the advantages of (1) being intuitive and + * (2) preserving the optimality of the original SWAP sequence. + * Essentially the better the routing algorithm the better the + * uncompute. Moreover, this has the nice property that routing + * a 'for' of 'if' region always requires 2 * #(SWAPs required for region) + * additional SWAPS. + */ + static WalkResult handleYield(scf::YieldOp op, Layout& layout, + SWAPHistory& history, + PatternRewriter& rewriter) { + LLVM_DEBUG(llvm::dbgs() << "handleYield\n"); + + /// Uncompute SWAPs. + RoutingDriverBase::insertSWAPs(llvm::to_vector(llvm::reverse(history)), + layout, op.getLoc(), rewriter); + + return WalkResult::advance(); + } + /** * @brief Ensures the executability of two-qubit gates on the given target * architecture by inserting SWAPs. */ WalkResult handleUnitary(UnitaryInterface op, Layout& layout, const ArrayRef window, + SWAPHistory& history, PatternRewriter& rewriter) const { LLVM_DEBUG(llvm::dbgs() << "handleUnitary: gate= " << op->getName() << '\n'); + /// If this is the first two-qubit op in the layer, route the layer /// and remap afterwards. - if (op.getOperation() == window.front().twoQubitOps.front()) { + + if (!window.front().twoQubitOps.empty() && + op.getOperation() == window.front().twoQubitOps.front()) { const auto layers = to_vector(map_range(window, [](const Layer& layer) { return ArrayRef(layer.twoQubitIndices); })); const auto swaps = router_.route(layers, layout, *arch); insertSWAPs(swaps, layout, op->getLoc(), rewriter); + history.append(swaps); } for (const auto [in, out] : @@ -420,6 +543,13 @@ class AStarDriver final : public RoutingDriverBase { layout.remapQubitValue(in, out); } + if (isa(op)) { + const auto outs = getOuts(op); + layout.swap(outs.first, outs.second); + history.push_back({layout.lookupHardwareIndex(outs.first), + layout.lookupHardwareIndex(outs.second)}); + } + return WalkResult::advance(); } diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveDriver.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveDriver.h index 144912b105..a84a1c374e 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveDriver.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveDriver.h @@ -174,8 +174,8 @@ class NaiveDriver final : public RoutingDriverBase { } /// Uncompute SWAPs. - NaiveDriver::insertSWAPs(llvm::to_vector(llvm::reverse(history)), layout, - op.getLoc(), rewriter); + RoutingDriverBase::insertSWAPs(llvm::to_vector(llvm::reverse(history)), + layout, op.getLoc(), rewriter); return WalkResult::advance(); } diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp index bb77ef2239..76b036738a 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp @@ -464,7 +464,7 @@ struct PlacementPassSC final : impl::PlacementPassSCBase { << '\n'; }); return std::make_unique(arch.nqubits(), - std::mt19937_64(1492999563)); + std::mt19937_64(seed)); } llvm_unreachable("Unknown strategy"); } diff --git a/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/basics.mlir b/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/basics.mlir index 43df3366ec..a1ab6ad969 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/basics.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/basics.mlir @@ -8,10 +8,10 @@ // Instead of applying checks, the routing verifier pass ensures the validity of this program. -// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(placement-sc{strategy=identity arch=MQTTest}, route-sc{method=naive arch=MQTTest},verify-routing-sc{arch=MQTTest})" -verify-diagnostics | FileCheck %s -// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(placement-sc{strategy=identity arch=MQTTest}, route-sc{method=astar arch=MQTTest},verify-routing-sc{arch=MQTTest})" -verify-diagnostics | FileCheck %s -// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(placement-sc{strategy=identity arch=IBMFalcon}, route-sc{method=naive arch=IBMFalcon},verify-routing-sc{arch=IBMFalcon})" -verify-diagnostics | FileCheck %s -// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(placement-sc{strategy=identity arch=IBMFalcon}, route-sc{method=astar arch=IBMFalcon},verify-routing-sc{arch=IBMFalcon})" -verify-diagnostics | FileCheck %s +// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(canonicalize, placement-sc{strategy=identity arch=MQTTest}, route-sc{method=naive arch=MQTTest},verify-routing-sc{arch=MQTTest})" -verify-diagnostics | FileCheck %s +// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(canonicalize, placement-sc{strategy=identity arch=MQTTest}, route-sc{method=astar arch=MQTTest},verify-routing-sc{arch=MQTTest})" -verify-diagnostics | FileCheck %s +// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(canonicalize, placement-sc{strategy=identity arch=IBMFalcon}, route-sc{method=naive arch=IBMFalcon},verify-routing-sc{arch=IBMFalcon})" -verify-diagnostics | FileCheck %s +// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(canonicalize, placement-sc{strategy=identity arch=IBMFalcon}, route-sc{method=astar arch=IBMFalcon},verify-routing-sc{arch=IBMFalcon})" -verify-diagnostics | FileCheck %s module { // CHECK-LABEL: func.func @entrySABRE From d59bfc18f69879ff95f85d24d08a26284d844b94 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Sun, 9 Nov 2025 13:17:06 +0100 Subject: [PATCH 07/56] Add statistics struct --- .../Transforms/Transpilation/AStarDriver.h | 134 +++++++++--------- .../Transforms/Transpilation/NaiveDriver.h | 14 +- .../Transpilation/RoutingDriverBase.h | 18 ++- .../Transpilation/sc/RoutingPass.cpp | 8 +- 4 files changed, 91 insertions(+), 83 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h index f6b30607c2..67b9b17d12 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h @@ -37,18 +37,20 @@ using namespace mlir; struct Layer { /// The program indices of the two-qubit gates within this layer. SmallVector twoQubitIndices; - - /// Operations that can be executed before the two-qubit gates. + /// One-qubit ops. Scheduled before the two-qubit ops. SmallVector ops; - /// The two-qubit gates. + /// Two-qubit ops. Schedule before the two-qubit block ops. SmallVector twoQubitOps; - /// Operations that will be executable whenever all gates in the layer are - /// executable. + /// One and two-qubit ops released by the scheduling of the two-qubit ops. + /// Scheduled before any barrierLikeOp. SmallVector blockOps; + /// The op scheduled last in the layer. + /// This is an extra pointer to deal with barriers, if's, and for's. + SmallVector last; /// @returns the scheduled operations in the layer. [[nodiscard]] auto getOps() { - return concat(ops, twoQubitOps, blockOps); + return concat(ops, twoQubitOps, blockOps, last); } }; @@ -58,7 +60,7 @@ class Scheduler { /// The region where the schedule ops reside. Region* region; /// Counts the amount of occurrences for a given op. - /// Currently only used for two-qubit unitaries and scf ops. + /// Currently used for all (>=2)-qubit ops. DenseMap occurrences; public: @@ -158,7 +160,7 @@ class Scheduler { llvm::zip_equal(barrier.getInQubits(), barrier.getOutQubits())) { layout.remapQubitValue(in, out); } - next.layer.blockOps.push_back(barrier); + next.layer.last.push_back(barrier); } continue; } @@ -202,7 +204,7 @@ class Scheduler { layout.remapQubitValue(in, out); next.qubits.push_back(out); } - next.layer.blockOps.push_back(loop); + next.layer.last.push_back(loop); } continue; } @@ -214,13 +216,14 @@ class Scheduler { layout.remapQubitValue(in, out); next.qubits.push_back(out); } - next.layer.blockOps.push_back(cond); + next.layer.last.push_back(cond); } + continue; } if (auto yield = dyn_cast(res.op)) { if (++occurrences[yield] == yield.getResults().size()) { - next.layer.blockOps.push_back(yield); + next.layer.last.push_back(yield); } } } @@ -228,6 +231,52 @@ class Scheduler { return next; } + /** + * @returns todo + */ + BlockAdvanceResult skipTwoQubitBlock(const ValuePair outs, Layout& layout) { + BlockAdvanceResult blockResult; + + std::array gates; + std::array heads{outs.first, outs.second}; + + while (true) { + bool stop = false; + for (const auto [i, q] : llvm::enumerate(heads)) { + const auto res = advanceOneQubitOnWire(q); + blockResult.ops.append(res.ops); + if (!res) { + heads[i] = nullptr; + stop = true; + break; + } + + if (q != res.q) { + layout.remapQubitValue(q, res.q); + } + + heads[i] = res.q; + gates[i] = dyn_cast(res.op); + } + + if (stop || gates[0] != gates[1]) { + break; + } + + blockResult.ops.push_back(gates[0]); + + const ValuePair ins = getIns(gates[0]); + const ValuePair outs = getOuts(gates[0]); + layout.remapQubitValue(ins.first, outs.first); + layout.remapQubitValue(ins.second, outs.second); + heads = {outs.first, outs.second}; + } + + blockResult.outs = std::make_pair(heads[0], heads[1]); + + return blockResult; + } + /** * @returns todo */ @@ -244,7 +293,7 @@ class Scheduler { if (user == nullptr) { /// Must be a branching op: user = res.q.getUsers().begin()->getParentOp(); - assert(isa(user)); + assert(isa(user)); } TypeSwitch(user) @@ -286,61 +335,15 @@ class Scheduler { return res; } - - /** - * @returns todo - */ - BlockAdvanceResult skipTwoQubitBlock(const ValuePair outs, Layout& layout) { - BlockAdvanceResult blockResult; - - std::array gates; - std::array heads{outs.first, outs.second}; - - while (true) { - bool stop = false; - for (const auto [i, q] : llvm::enumerate(heads)) { - const auto res = advanceOneQubitOnWire(q); - blockResult.ops.append(res.ops); - if (!res) { - heads[i] = nullptr; - stop = true; - break; - } - - if (q != res.q) { - layout.remapQubitValue(q, res.q); - } - - heads[i] = res.q; - gates[i] = dyn_cast(res.op); - } - - if (stop || gates[0] != gates[1]) { - break; - } - - blockResult.ops.push_back(gates[0]); - - const ValuePair ins = getIns(gates[0]); - const ValuePair outs = getOuts(gates[0]); - layout.remapQubitValue(ins.first, outs.first); - layout.remapQubitValue(ins.second, outs.second); - heads = {outs.first, outs.second}; - } - - blockResult.outs = std::make_pair(heads[0], heads[1]); - - return blockResult; - } }; class AStarDriver final : public RoutingDriverBase { using SWAPHistory = SmallVector; public: - AStarDriver(std::unique_ptr arch, - const HeuristicWeights& weights, std::size_t nlookahead) - : RoutingDriverBase(std::move(arch)), router_(weights), + AStarDriver(const HeuristicWeights& weights, std::size_t nlookahead, + std::unique_ptr arch, const Statistics& stats) + : RoutingDriverBase(std::move(arch), stats), router_(weights), nlookahead_(nlookahead) {} private: @@ -502,14 +505,13 @@ class AStarDriver final : public RoutingDriverBase { * a 'for' of 'if' region always requires 2 * #(SWAPs required for region) * additional SWAPS. */ - static WalkResult handleYield(scf::YieldOp op, Layout& layout, - SWAPHistory& history, - PatternRewriter& rewriter) { + WalkResult handleYield(scf::YieldOp op, Layout& layout, SWAPHistory& history, + PatternRewriter& rewriter) const { LLVM_DEBUG(llvm::dbgs() << "handleYield\n"); /// Uncompute SWAPs. - RoutingDriverBase::insertSWAPs(llvm::to_vector(llvm::reverse(history)), - layout, op.getLoc(), rewriter); + this->insertSWAPs(llvm::to_vector(llvm::reverse(history)), layout, + op.getLoc(), rewriter); return WalkResult::advance(); } diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveDriver.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveDriver.h index a84a1c374e..a9bedcc9d2 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveDriver.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveDriver.h @@ -165,17 +165,11 @@ class NaiveDriver final : public RoutingDriverBase { * a 'for' of 'if' region always requires 2 * #(SWAPs required for region) * additional SWAPS. */ - static WalkResult handleYield(scf::YieldOp op, Layout& layout, - SWAPHistory& history, - PatternRewriter& rewriter) { - if (!isa(op->getParentOp()) && - !isa(op->getParentOp())) { - return WalkResult::skip(); - } - + WalkResult handleYield(scf::YieldOp op, Layout& layout, SWAPHistory& history, + PatternRewriter& rewriter) const { /// Uncompute SWAPs. - RoutingDriverBase::insertSWAPs(llvm::to_vector(llvm::reverse(history)), - layout, op.getLoc(), rewriter); + this->insertSWAPs(llvm::to_vector(llvm::reverse(history)), layout, + op.getLoc(), rewriter); return WalkResult::advance(); } diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/RoutingDriverBase.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/RoutingDriverBase.h index b27c90a7c5..f86eb169b4 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/RoutingDriverBase.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/RoutingDriverBase.h @@ -16,6 +16,7 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" #include +#include #include #include #include @@ -98,10 +99,15 @@ inline void replaceAllUsesInRegionAndChildrenExcept(Value oldValue, }); } +struct Statistics { + llvm::Statistic* numSwaps; +}; + class RoutingDriverBase { public: - explicit RoutingDriverBase(std::unique_ptr arch) - : arch(std::move(arch)) {} + explicit RoutingDriverBase(std::unique_ptr arch, + const Statistics& stats) + : arch(std::move(arch)), stats(stats) {} virtual ~RoutingDriverBase() = default; @@ -123,8 +129,8 @@ class RoutingDriverBase { * @brief Insert SWAPs at the rewriter's insertion point and update the * layout. */ - static void insertSWAPs(ArrayRef swaps, Layout& layout, - Location anchor, PatternRewriter& rewriter) { + void insertSWAPs(ArrayRef swaps, Layout& layout, + Location anchor, PatternRewriter& rewriter) const { for (const auto [hw0, hw1] : swaps) { const Value in0 = layout.lookupHardwareValue(hw0); const Value in1 = layout.lookupHardwareValue(hw1); @@ -150,6 +156,8 @@ class RoutingDriverBase { layout.remapQubitValue(in0, out0); layout.remapQubitValue(in1, out1); } + + (*stats.numSwaps) += swaps.size(); } /** @@ -162,5 +170,7 @@ class RoutingDriverBase { } std::unique_ptr arch; + + Statistics stats; }; } // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingPass.cpp index 8c71d44332..c94d3cf1b2 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingPass.cpp @@ -81,16 +81,18 @@ struct RoutingPassSC final : impl::RoutingPassSCBase { private: [[nodiscard]] std::unique_ptr getMapper(std::unique_ptr arch) { + Statistics stats{.numSwaps = &numSwaps}; + switch (static_cast(method)) { case RoutingMethod::Naive: { LLVM_DEBUG({ llvm::dbgs() << "getMapper: method=naive\n"; }); - return std::make_unique(std::move(arch)); + return std::make_unique(std::move(arch), stats); } case RoutingMethod::AStar: { LLVM_DEBUG({ llvm::dbgs() << "getRouter: method=astar\n"; }); const HeuristicWeights weights(alpha, lambda, nlookahead); - return std::make_unique(std::move(arch), weights, - nlookahead); + return std::make_unique(weights, nlookahead, std::move(arch), + stats); } } From 7f9a8cab2d8ee4935bf5a9a5f3a5dad936cca471 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 11 Nov 2025 08:49:40 +0100 Subject: [PATCH 08/56] Improve a-star scheduler --- .../Transforms/Transpilation/AStarDriver.h | 458 ++++++++---------- .../Transpilation/AStarHeuristicRouter.h | 5 +- .../Transforms/Transpilation/NaiveDriver.h | 2 - 3 files changed, 214 insertions(+), 251 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h index 67b9b17d12..80c9dd25f2 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h @@ -18,6 +18,7 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/RoutingDriverBase.h" #include "mlir/Interfaces/ControlFlowInterfaces.h" +#include #include #include #include @@ -36,32 +37,49 @@ using namespace mlir; struct Layer { /// The program indices of the two-qubit gates within this layer. - SmallVector twoQubitIndices; - /// One-qubit ops. Scheduled before the two-qubit ops. + SmallVector gates; + /// The first two-qubit op in the layer. + /// Used as front-anchor to trigger the routing. + Operation* front{}; + //// @returns true iff the layer contains gates. + [[nodiscard]] bool empty() const { return gates.empty(); } +}; + +struct Schedule { + /// A vector of layers. + SmallVector layers; + /// The scheduled ops. SmallVector ops; - /// Two-qubit ops. Schedule before the two-qubit block ops. - SmallVector twoQubitOps; - /// One and two-qubit ops released by the scheduling of the two-qubit ops. - /// Scheduled before any barrierLikeOp. - SmallVector blockOps; - /// The op scheduled last in the layer. - /// This is an extra pointer to deal with barriers, if's, and for's. - SmallVector last; - - /// @returns the scheduled operations in the layer. - [[nodiscard]] auto getOps() { - return concat(ops, twoQubitOps, blockOps, last); - } }; -using Schedule = SmallVector; +class SlidingWindow { +public: + explicit SlidingWindow(ArrayRef layers, const std::size_t nlookahead) + : layers(layers), nlookahead(nlookahead) {} + + /// @returns the current window of layers. + [[nodiscard]] ArrayRef getCurrent() const { + if (layers.empty()) { + return {}; + } + + const auto remaining = layers.size() - offset; + const auto count = std::min(1 + nlookahead, remaining); + return layers.slice(offset, count); + } + + /// Advance to the next window. + void advance() { ++offset; } + +private: + ArrayRef layers; + std::size_t nlookahead; + std::size_t offset{}; +}; class Scheduler { /// The region where the schedule ops reside. Region* region; - /// Counts the amount of occurrences for a given op. - /// Currently used for all (>=2)-qubit ops. - DenseMap occurrences; public: explicit Scheduler(Region* region) : region(region) {} @@ -69,24 +87,32 @@ class Scheduler { /** * @brief Starting from the given layout, schedule all operations and divide * the circuit into parallelly executable layers. + * + * It schedules `scf.for` and `scf.if` operations (`RegionBranchOpInterface`) + * but does not recursively schedule the operation within these ops. + * * @returns the schedule. */ [[nodiscard]] Schedule schedule(Layout layout) { - occurrences.clear(); - + SyncMap syncMap; Schedule schedule; - SmallVector qubits(layout.getHardwareQubits()); // worklist. + SmallVector qubits(layout.getHardwareQubits()); + while (!qubits.empty()) { - const auto res = getNextLayer(qubits, layout); - schedule.emplace_back(res.layer); - qubits = res.qubits; + Layer layer; + qubits = advanceQubits(qubits, layer, layout, syncMap, schedule.ops); + + /// Only add non-empty layers to the schedule. + if (!layer.empty()) { + schedule.layers.emplace_back(layer); + } } LLVM_DEBUG({ llvm::dbgs() << "schedule: layers=\n"; - for (const auto [i, layer] : llvm::enumerate(schedule)) { + for (const auto [i, layer] : llvm::enumerate(schedule.layers)) { llvm::dbgs() << '\t' << i << "= "; - for (const auto [prog0, prog1] : layer.twoQubitIndices) { + for (const auto [prog0, prog1] : layer.gates) { llvm::dbgs() << "(" << prog0 << "," << prog1 << "), "; } llvm::dbgs() << '\n'; @@ -97,135 +123,119 @@ class Scheduler { } private: - struct NextLayerResult { - /// The next layer. - Layer layer; - /// The updated worklist of qubits for the next iteration. - SmallVector qubits; + class SyncMap { + /// Counts the amount of occurrences for a given op. + DenseMap occurrences; + /// The operations (value) that depend on the release of another operation + /// (key). This is used to advance passed `mqtopt.barrier`, `scf.for`, and + /// `scf.if` ops, which, from a routing perspective, act like identities. + DenseMap> pending; + + public: + /// Increase the occurrence count of the given op and return true iff the + /// operation can be scheduled. An operation can be scheduled whenever we've + /// seen it as many times as it has inputs. + bool sync(Operation* op) { + return ++occurrences[op] == op->getNumResults(); + } + /// Returns a reference to the vector of pending operations for a given op. + SmallVector& getPending(Operation* op) { return pending[op]; } }; - struct AdvanceResult { + struct OneQubitAdvanceResult { /// The advanced qubit value on the wire. - Value q = nullptr; - /// The (>=2)-qubit op which has q as argument. - Operation* op = nullptr; - /// All one-qubit ops visited until the (>=2)-qubit op. - SmallVector ops; - /// @returns true iff the advancement found a (>=2)-qubit op. - explicit operator bool() const { return op != nullptr; } + Value q; + /// The two-qubit unitary which has q as argument. + Operation* op{}; }; - struct BlockAdvanceResult { - /// The final qubit values in the two-qubit block. - ValuePair outs; - /// All operations visited in the two-qubit block. - SmallVector ops; - }; - - /** - * @returns todo - */ - [[nodiscard]] NextLayerResult getNextLayer(ArrayRef qubits, - Layout& layout) { - NextLayerResult next; - next.qubits.reserve(qubits.size()); + SmallVector advanceQubits(ArrayRef qubits, Layer& layer, + Layout& layout, SyncMap& syncMap, + SmallVector& chain) { + SmallVector next; for (const Value q : qubits) { if (q.use_empty()) { continue; } - const auto res = advanceOneQubitOnWire(q); - - /// Append one-qubit op chain to layer. - next.layer.ops.append(res.ops); + const auto res = advanceQubit(q, chain); - /// If no (>=2)-qubit op has been found continue. - if (!res) { + /// Continue, if no (>=2)-qubit op has been found. + if (res.op == nullptr) { continue; } - /// Otherwise map the current to the advanced qubit value. + /// Otherwise, map the current to the advanced qubit value. if (q != res.q) { layout.remapQubitValue(q, res.q); } - /// Handle the found (>=2)-qubit op based on its type: - - if (auto barrier = dyn_cast(res.op)) { - /// Once we've seen all inputs of the barrier, we can release and - /// forwards its qubit values. - if (++occurrences[barrier] == barrier->getNumResults()) { - for (const auto [in, out] : - llvm::zip_equal(barrier.getInQubits(), barrier.getOutQubits())) { - layout.remapQubitValue(in, out); - } - next.layer.last.push_back(barrier); - } - continue; - } - - if (auto u = dyn_cast(res.op)) { - if (++occurrences[u] == 2) { - const auto ins = getIns(u); - const auto outs = getOuts(u); - - next.layer.twoQubitOps.emplace_back(u); - next.layer.twoQubitIndices.emplace_back( - layout.lookupProgramIndex(ins.first), - layout.lookupProgramIndex(ins.second)); - - layout.remapQubitValue(ins.first, outs.first); - layout.remapQubitValue(ins.second, outs.second); - - next.qubits.push_back(outs.first); - next.qubits.push_back(outs.second); + /// Handle the found (>=2)-qubit op based on its type. + TypeSwitch(res.op) + /// MQT + .Case([&](BarrierOp op) { + for (const auto [in, out] : + llvm::zip_equal(op.getInQubits(), op.getOutQubits())) { + if (in == res.q) { + layout.remapQubitValue(res.q, out); + next.append(advanceQubits(ArrayRef(Value(out)), layer, layout, + syncMap, syncMap.getPending(op))); + return; + } + } - // const auto blockResult = skipTwoQubitBlock(outs, layout); - // next.layer.blockOps.append(blockResult.ops); + if (syncMap.sync(op)) { + chain.push_back(op); + chain.append(syncMap.getPending(op)); + } + }) + .Case([&](UnitaryInterface op) { + if (syncMap.sync(op)) { + /// Add two-qubit op to schedule. + chain.push_back(op); - // if (blockResult.outs.first != nullptr && - // !blockResult.outs.first.use_empty()) { - // next.qubits.push_back(blockResult.outs.first); - // } + /// Setup front anchor. + if (layer.front == nullptr) { + layer.front = op; + } - // if (blockResult.outs.second != nullptr && - // !blockResult.outs.second.use_empty()) { - // next.qubits.push_back(blockResult.outs.second); - // } - } - continue; - } + /// Remap values. + const auto ins = getIns(op); + const auto outs = getOuts(op); - if (auto loop = dyn_cast(res.op)) { - if (++occurrences[loop] == loop->getNumResults()) { - for (const auto [in, out] : - llvm::zip_equal(loop.getInitArgs(), loop.getResults())) { - layout.remapQubitValue(in, out); - next.qubits.push_back(out); - } - next.layer.last.push_back(loop); - } - continue; - } + layout.remapQubitValue(ins.first, outs.first); + layout.remapQubitValue(ins.second, outs.second); - if (auto cond = dyn_cast(res.op)) { - if (++occurrences[cond] == cond->getNumResults()) { - for (const auto [in, out] : - llvm::zip_equal(layout.getHardwareQubits(), cond.getResults())) { - layout.remapQubitValue(in, out); - next.qubits.push_back(out); - } - next.layer.last.push_back(cond); - } - continue; - } + /// Add gate indices to the current layer. + layer.gates.emplace_back(layout.lookupProgramIndex(outs.first), + layout.lookupProgramIndex(outs.second)); - if (auto yield = dyn_cast(res.op)) { - if (++occurrences[yield] == yield.getResults().size()) { - next.layer.last.push_back(yield); - } - } + next.push_back(outs.first); + next.push_back(outs.second); + } + }) + /// SCF + .Case([&](RegionBranchOpInterface op) { + /// Probably have to remap the layout here. + const Value out = op->getResult(layout.lookupHardwareIndex(res.q)); + layout.remapQubitValue(res.q, out); + next.append(advanceQubits(ArrayRef(out), layer, layout, syncMap, + syncMap.getPending(op))); + + if (syncMap.sync(op)) { + chain.push_back(op); + chain.append(syncMap.getPending(op)); + } + }) + .Case([&](scf::YieldOp op) { /* nothing to do. */ }) + .Default([&]([[maybe_unused]] Operation* op) { + LLVM_DEBUG({ + llvm::dbgs() << "unknown operation in def-use chain: "; + op->dump(); + }); + llvm_unreachable("unknown operation in def-use chain"); + }); } return next; @@ -234,54 +244,8 @@ class Scheduler { /** * @returns todo */ - BlockAdvanceResult skipTwoQubitBlock(const ValuePair outs, Layout& layout) { - BlockAdvanceResult blockResult; - - std::array gates; - std::array heads{outs.first, outs.second}; - - while (true) { - bool stop = false; - for (const auto [i, q] : llvm::enumerate(heads)) { - const auto res = advanceOneQubitOnWire(q); - blockResult.ops.append(res.ops); - if (!res) { - heads[i] = nullptr; - stop = true; - break; - } - - if (q != res.q) { - layout.remapQubitValue(q, res.q); - } - - heads[i] = res.q; - gates[i] = dyn_cast(res.op); - } - - if (stop || gates[0] != gates[1]) { - break; - } - - blockResult.ops.push_back(gates[0]); - - const ValuePair ins = getIns(gates[0]); - const ValuePair outs = getOuts(gates[0]); - layout.remapQubitValue(ins.first, outs.first); - layout.remapQubitValue(ins.second, outs.second); - heads = {outs.first, outs.second}; - } - - blockResult.outs = std::make_pair(heads[0], heads[1]); - - return blockResult; - } - - /** - * @returns todo - */ - AdvanceResult advanceOneQubitOnWire(const Value q) { - AdvanceResult res; + OneQubitAdvanceResult advanceQubit(Value q, SmallVector& chain) { + OneQubitAdvanceResult res; res.q = q; while (true) { @@ -298,23 +262,22 @@ class Scheduler { TypeSwitch(user) /// MQT - .Case([&](BarrierOp op) { res.op = op; }) .Case([&](UnitaryInterface op) { - if (isTwoQubitGate(op)) { + if (op->getNumResults() > 1) { res.op = op; - return; // Found a two-qubit gate, stop advancing head. + return; } - // Otherwise, advance head. + res.q = op.getOutQubits().front(); - res.ops.push_back(user); /// Only add one-qubit gates. + chain.push_back(user); }) .Case([&](ResetOp op) { res.q = op.getOutQubit(); - res.ops.push_back(user); + chain.push_back(user); }) .Case([&](MeasureOp op) { res.q = op.getOutQubit(); - res.ops.push_back(user); + chain.push_back(user); }) /// SCF .Case( @@ -371,63 +334,50 @@ class AStarDriver final : public RoutingDriverBase { LogicalResult rewrite(Region& region, Layout& layout, SWAPHistory& history, PatternRewriter& rewriter) const { - - /// Generate schedule for region. - + /// Generate schedule. Scheduler scheduler(®ion); Schedule schedule = scheduler.schedule(layout); /// Iterate over schedule in sliding windows of size 1 + nlookahead. + SlidingWindow window(schedule.layers, nlookahead_); Operation* prev{}; + for (Operation* curr : schedule.ops) { - for (Schedule::iterator it = schedule.begin(); it != schedule.end(); ++it) { - const Schedule::iterator end = - std::min(it + 1 + nlookahead_, schedule.end()); - const auto window = llvm::make_range(it, end); - const auto ops = window.begin()->getOps(); - - for (Operation* curr : ops) { - - /// Impose a strict ordering of the operations. Note that - /// we reorder the operation before we insert any swaps. - - if (prev != nullptr && prev != curr && curr->isBeforeInBlock(prev)) { - rewriter.moveOpAfter(curr, prev); - } - - const OpBuilder::InsertionGuard guard(rewriter); - rewriter.setInsertionPoint(curr); - - const auto res = - TypeSwitch(curr) - /// mqtopt Dialect - .Case([&](UnitaryInterface op) { - return handleUnitary(op, layout, window, history, rewriter); - }) - .Case( - [&](ResetOp op) { return handleReset(op, layout); }) - .Case( - [&](MeasureOp op) { return handleMeasure(op, layout); }) - /// scf Dialect - .Case([&](scf::ForOp op) { - return handleFor(op, layout, rewriter); - }) - .Case([&](scf::IfOp op) { - return handleIf(op, layout, rewriter); - }) - .Case([&](scf::YieldOp op) { - return handleYield(op, layout, history, rewriter); - }) - /// Skip the rest. - .Default([](auto) { return WalkResult::skip(); }); - - if (res.wasInterrupted()) { - return failure(); - } + if (prev != nullptr && prev != curr) { + rewriter.moveOpAfter(curr, prev); + } - prev = curr; + const OpBuilder::InsertionGuard guard(rewriter); + rewriter.setInsertionPoint(curr); + + const auto res = + TypeSwitch(curr) + /// mqtopt Dialect + .Case([&](UnitaryInterface op) { + return handleUnitary(op, layout, window, history, rewriter); + }) + .Case( + [&](ResetOp op) { return handleReset(op, layout); }) + .Case( + [&](MeasureOp op) { return handleMeasure(op, layout); }) + /// scf Dialect + .Case([&](scf::ForOp op) { + return handleFor(op, layout, rewriter); + }) + .Case( + [&](scf::IfOp op) { return handleIf(op, layout, rewriter); }) + .Case([&](scf::YieldOp op) { + return handleYield(op, layout, history, rewriter); + }) + /// Skip the rest. + .Default([](auto) { return WalkResult::skip(); }); + + if (res.wasInterrupted()) { + return failure(); } + + prev = curr; } return success(); @@ -510,8 +460,8 @@ class AStarDriver final : public RoutingDriverBase { LLVM_DEBUG(llvm::dbgs() << "handleYield\n"); /// Uncompute SWAPs. - this->insertSWAPs(llvm::to_vector(llvm::reverse(history)), layout, - op.getLoc(), rewriter); + this->insertSWAPs(to_vector(llvm::reverse(history)), layout, op.getLoc(), + rewriter); return WalkResult::advance(); } @@ -521,8 +471,7 @@ class AStarDriver final : public RoutingDriverBase { * architecture by inserting SWAPs. */ WalkResult handleUnitary(UnitaryInterface op, Layout& layout, - const ArrayRef window, - SWAPHistory& history, + SlidingWindow& window, SWAPHistory& history, PatternRewriter& rewriter) const { LLVM_DEBUG(llvm::dbgs() << "handleUnitary: gate= " << op->getName() << '\n'); @@ -530,14 +479,12 @@ class AStarDriver final : public RoutingDriverBase { /// If this is the first two-qubit op in the layer, route the layer /// and remap afterwards. - if (!window.front().twoQubitOps.empty() && - op.getOperation() == window.front().twoQubitOps.front()) { - const auto layers = to_vector(map_range(window, [](const Layer& layer) { - return ArrayRef(layer.twoQubitIndices); - })); - const auto swaps = router_.route(layers, layout, *arch); - insertSWAPs(swaps, layout, op->getLoc(), rewriter); - history.append(swaps); + const auto curr = window.getCurrent(); + + /// Current op is front-anchor: route. + if (!curr.empty() && op == curr.front().front) { + route(curr, layout, op.getLoc(), history, rewriter); + window.advance(); } for (const auto [in, out] : @@ -571,6 +518,25 @@ class AStarDriver final : public RoutingDriverBase { return WalkResult::advance(); } + /** + * @brief Use A*-search to make the gates in the front layer (layers.front()) + * executable. + */ + void route(const ArrayRef layers, Layout& layout, Location location, + SWAPHistory& history, PatternRewriter& rewriter) const { + /// Find SWAPs. + SmallVector> layerIndices; + layerIndices.reserve(layers.size()); + for (const auto& layer : layers) { + layerIndices.push_back(layer.gates); + } + const auto swaps = router_.route(layerIndices, layout, *arch); + /// Append SWAPs to history. + history.append(swaps); + /// Insert SWAPs. + RoutingDriverBase::insertSWAPs(swaps, layout, location, rewriter); + } + AStarHeuristicRouter router_; std::size_t nlookahead_; }; diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h index d9370cab37..1227f79bec 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h @@ -14,8 +14,6 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" -#include "llvm/ADT/ArrayRef.h" - #include #include #include @@ -142,7 +140,8 @@ struct AStarHeuristicRouter final { using MinQueue = std::priority_queue, std::greater<>>; public: - [[nodiscard]] RouterResult route(Layers layers, const ThinLayout& layout, + [[nodiscard]] RouterResult route(const Layers layers, + const ThinLayout& layout, const Architecture& arch) const { Node root(layout); diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveDriver.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveDriver.h index a9bedcc9d2..8168bb191c 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveDriver.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveDriver.h @@ -278,10 +278,8 @@ class NaiveDriver final : public RoutingDriverBase { for (std::size_t i = 0; i < path.size() - 2; ++i) { swaps.emplace_back(path[i], path[i + 1]); } - /// Append SWAPs to history. history.append(swaps); - /// Insert SWAPs. RoutingDriverBase::insertSWAPs(swaps, layout, op.getLoc(), rewriter); } From f9106f68ac52f2fd87cf2a711910ce0137465867 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 12 Nov 2025 11:21:35 +0100 Subject: [PATCH 09/56] Implement fix-and-repair approach --- .../Transforms/Transpilation/AStarDriver.h | 632 ++++++------------ .../Transpilation/AStarHeuristicRouter.h | 3 +- 2 files changed, 207 insertions(+), 428 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h index 80c9dd25f2..12775f7416 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h @@ -10,15 +10,21 @@ #pragma once +#include "mlir/Analysis/TopologicalSortUtils.h" #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/RoutingDriverBase.h" +#include "mlir/IR/Dominance.h" #include "mlir/Interfaces/ControlFlowInterfaces.h" +#include "llvm/Support/ErrorHandling.h" + +#include #include +#include #include #include #include @@ -35,272 +41,104 @@ namespace mqt::ir::opt { using namespace mlir; -struct Layer { - /// The program indices of the two-qubit gates within this layer. - SmallVector gates; - /// The first two-qubit op in the layer. - /// Used as front-anchor to trigger the routing. - Operation* front{}; - //// @returns true iff the layer contains gates. - [[nodiscard]] bool empty() const { return gates.empty(); } -}; - -struct Schedule { - /// A vector of layers. - SmallVector layers; - /// The scheduled ops. - SmallVector ops; -}; - -class SlidingWindow { +class WireIterator { public: - explicit SlidingWindow(ArrayRef layers, const std::size_t nlookahead) - : layers(layers), nlookahead(nlookahead) {} + using difference_type = std::ptrdiff_t; + using value_type = Operation*; - /// @returns the current window of layers. - [[nodiscard]] ArrayRef getCurrent() const { - if (layers.empty()) { - return {}; - } + explicit WireIterator(Value q = nullptr) : q(q) { setNextOp(); } + + Operation* operator*() const { return currOp; } - const auto remaining = layers.size() - offset; - const auto count = std::min(1 + nlookahead, remaining); - return layers.slice(offset, count); + WireIterator& operator++() { + setNextQubit(); + setNextOp(); + return *this; } - /// Advance to the next window. - void advance() { ++offset; } + void operator++(int) { ++*this; } + bool operator==(const WireIterator& other) const { return other.q == q; } private: - ArrayRef layers; - std::size_t nlookahead; - std::size_t offset{}; -}; - -class Scheduler { - /// The region where the schedule ops reside. - Region* region; - -public: - explicit Scheduler(Region* region) : region(region) {} - - /** - * @brief Starting from the given layout, schedule all operations and divide - * the circuit into parallelly executable layers. - * - * It schedules `scf.for` and `scf.if` operations (`RegionBranchOpInterface`) - * but does not recursively schedule the operation within these ops. - * - * @returns the schedule. - */ - [[nodiscard]] Schedule schedule(Layout layout) { - SyncMap syncMap; - Schedule schedule; - SmallVector qubits(layout.getHardwareQubits()); - - while (!qubits.empty()) { - Layer layer; - qubits = advanceQubits(qubits, layer, layout, syncMap, schedule.ops); - - /// Only add non-empty layers to the schedule. - if (!layer.empty()) { - schedule.layers.emplace_back(layer); - } + void setNextOp() { + if (q == nullptr) { + return; } - - LLVM_DEBUG({ - llvm::dbgs() << "schedule: layers=\n"; - for (const auto [i, layer] : llvm::enumerate(schedule.layers)) { - llvm::dbgs() << '\t' << i << "= "; - for (const auto [prog0, prog1] : layer.gates) { - llvm::dbgs() << "(" << prog0 << "," << prog1 << "), "; - } - llvm::dbgs() << '\n'; - } - }); - - return schedule; - } - -private: - class SyncMap { - /// Counts the amount of occurrences for a given op. - DenseMap occurrences; - /// The operations (value) that depend on the release of another operation - /// (key). This is used to advance passed `mqtopt.barrier`, `scf.for`, and - /// `scf.if` ops, which, from a routing perspective, act like identities. - DenseMap> pending; - - public: - /// Increase the occurrence count of the given op and return true iff the - /// operation can be scheduled. An operation can be scheduled whenever we've - /// seen it as many times as it has inputs. - bool sync(Operation* op) { - return ++occurrences[op] == op->getNumResults(); + if (q.use_empty()) { + q = nullptr; + currOp = nullptr; + return; } - /// Returns a reference to the vector of pending operations for a given op. - SmallVector& getPending(Operation* op) { return pending[op]; } - }; - - struct OneQubitAdvanceResult { - /// The advanced qubit value on the wire. - Value q; - /// The two-qubit unitary which has q as argument. - Operation* op{}; - }; - - SmallVector advanceQubits(ArrayRef qubits, Layer& layer, - Layout& layout, SyncMap& syncMap, - SmallVector& chain) { - SmallVector next; - - for (const Value q : qubits) { - if (q.use_empty()) { - continue; - } - - const auto res = advanceQubit(q, chain); - /// Continue, if no (>=2)-qubit op has been found. - if (res.op == nullptr) { - continue; - } - - /// Otherwise, map the current to the advanced qubit value. - if (q != res.q) { - layout.remapQubitValue(q, res.q); - } - - /// Handle the found (>=2)-qubit op based on its type. - TypeSwitch(res.op) - /// MQT - .Case([&](BarrierOp op) { - for (const auto [in, out] : - llvm::zip_equal(op.getInQubits(), op.getOutQubits())) { - if (in == res.q) { - layout.remapQubitValue(res.q, out); - next.append(advanceQubits(ArrayRef(Value(out)), layer, layout, - syncMap, syncMap.getPending(op))); - return; - } - } - - if (syncMap.sync(op)) { - chain.push_back(op); - chain.append(syncMap.getPending(op)); - } - }) - .Case([&](UnitaryInterface op) { - if (syncMap.sync(op)) { - /// Add two-qubit op to schedule. - chain.push_back(op); - - /// Setup front anchor. - if (layer.front == nullptr) { - layer.front = op; - } - - /// Remap values. - const auto ins = getIns(op); - const auto outs = getOuts(op); - - layout.remapQubitValue(ins.first, outs.first); - layout.remapQubitValue(ins.second, outs.second); - - /// Add gate indices to the current layer. - layer.gates.emplace_back(layout.lookupProgramIndex(outs.first), - layout.lookupProgramIndex(outs.second)); - - next.push_back(outs.first); - next.push_back(outs.second); - } - }) - /// SCF - .Case([&](RegionBranchOpInterface op) { - /// Probably have to remap the layout here. - const Value out = op->getResult(layout.lookupHardwareIndex(res.q)); - layout.remapQubitValue(res.q, out); - next.append(advanceQubits(ArrayRef(out), layer, layout, syncMap, - syncMap.getPending(op))); - - if (syncMap.sync(op)) { - chain.push_back(op); - chain.append(syncMap.getPending(op)); - } - }) - .Case([&](scf::YieldOp op) { /* nothing to do. */ }) - .Default([&]([[maybe_unused]] Operation* op) { - LLVM_DEBUG({ - llvm::dbgs() << "unknown operation in def-use chain: "; - op->dump(); - }); - llvm_unreachable("unknown operation in def-use chain"); - }); + currOp = getUserInRegion(q, q.getParentRegion()); + if (currOp == nullptr) { + /// Must be a branching op: + currOp = q.getUsers().begin()->getParentOp(); + assert(isa(currOp)); } - - return next; } - /** - * @returns todo - */ - OneQubitAdvanceResult advanceQubit(Value q, SmallVector& chain) { - OneQubitAdvanceResult res; - res.q = q; - - while (true) { - if (res.q.use_empty()) { - break; - } - - Operation* user = getUserInRegion(res.q, region); - if (user == nullptr) { - /// Must be a branching op: - user = res.q.getUsers().begin()->getParentOp(); - assert(isa(user)); - } - - TypeSwitch(user) - /// MQT - .Case([&](UnitaryInterface op) { - if (op->getNumResults() > 1) { - res.op = op; + void setNextQubit() { + TypeSwitch(currOp) + /// MQT + .Case([&](UnitaryInterface op) { + for (const auto& [in, out] : + llvm::zip_equal(op.getAllInQubits(), op.getAllOutQubits())) { + if (q == in) { + q = out; return; } - - res.q = op.getOutQubits().front(); - chain.push_back(user); - }) - .Case([&](ResetOp op) { - res.q = op.getOutQubit(); - chain.push_back(user); - }) - .Case([&](MeasureOp op) { - res.q = op.getOutQubit(); - chain.push_back(user); - }) - /// SCF - .Case( - [&](RegionBranchOpInterface op) { res.op = op; }) - .Case([&](scf::YieldOp op) { res.op = op; }) - .Default([&]([[maybe_unused]] Operation* op) { - LLVM_DEBUG({ - llvm::dbgs() << "unknown operation in def-use chain: "; - op->dump(); - }); - llvm_unreachable("unknown operation in def-use chain"); + } + + llvm_unreachable("unknown qubit value in def-use chain"); + }) + .Case([&](ResetOp op) { q = op.getOutQubit(); }) + .Case([&](MeasureOp op) { q = op.getOutQubit(); }) + /// SCF + .Case([&](scf::ForOp op) { + for (const auto& [in, out] : + llvm::zip_equal(op.getInitArgs(), op.getResults())) { + if (q == in) { + q = out; + return; + } + } + + llvm_unreachable("unknown qubit value in def-use chain"); + }) + .Case([&](scf::YieldOp op) { + /// End of region. Invalidate iterator. + q = nullptr; + currOp = nullptr; + }) + .Default([&]([[maybe_unused]] Operation* op) { + LLVM_DEBUG({ + llvm::dbgs() << "unknown operation in def-use chain: "; + op->dump(); }); + llvm_unreachable("unknown operation in def-use chain"); + }); + } - if (res.op != nullptr) { - break; - } - } + Value q; + Operation* currOp{}; +}; - return res; - } +static_assert(std::input_iterator); + +struct Layer { + /// All unitary ops contained in this layer. + SmallVector ops; + /// The program indices of the gates in this layer. + SmallVector gates; }; class AStarDriver final : public RoutingDriverBase { + /// A vector of layers. + using LayerVec = SmallVector; + /// Hold and release map. + using HoldMap = DenseMap; + /// A vector of SWAP gate indices. using SWAPHistory = SmallVector; public: @@ -334,207 +172,149 @@ class AStarDriver final : public RoutingDriverBase { LogicalResult rewrite(Region& region, Layout& layout, SWAPHistory& history, PatternRewriter& rewriter) const { - /// Generate schedule. - Scheduler scheduler(®ion); - Schedule schedule = scheduler.schedule(layout); - - /// Iterate over schedule in sliding windows of size 1 + nlookahead. - SlidingWindow window(schedule.layers, nlookahead_); - - Operation* prev{}; - for (Operation* curr : schedule.ops) { - - if (prev != nullptr && prev != curr) { - rewriter.moveOpAfter(curr, prev); - } - - const OpBuilder::InsertionGuard guard(rewriter); - rewriter.setInsertionPoint(curr); - - const auto res = - TypeSwitch(curr) - /// mqtopt Dialect - .Case([&](UnitaryInterface op) { - return handleUnitary(op, layout, window, history, rewriter); - }) - .Case( - [&](ResetOp op) { return handleReset(op, layout); }) - .Case( - [&](MeasureOp op) { return handleMeasure(op, layout); }) - /// scf Dialect - .Case([&](scf::ForOp op) { - return handleFor(op, layout, rewriter); - }) - .Case( - [&](scf::IfOp op) { return handleIf(op, layout, rewriter); }) - .Case([&](scf::YieldOp op) { - return handleYield(op, layout, history, rewriter); - }) - /// Skip the rest. - .Default([](auto) { return WalkResult::skip(); }); - - if (res.wasInterrupted()) { - return failure(); - } - - prev = curr; + /// Find layers. + LayerVec layers = schedule(layout); + /// Route the layers. Might break SSA Dominance. + route(layout, layers, rewriter); + /// Repair any SSA Dominance Issues. + for (Block& block : region.getBlocks()) { + sortTopologically(&block); } - return success(); } - /** - * @brief Copy the layout and recursively map the loop body. - */ - WalkResult handleFor(scf::ForOp op, Layout& layout, - PatternRewriter& rewriter) const { - LLVM_DEBUG(llvm::dbgs() << "handleFor: recurse for loop body\n"); - - /// Copy layout. - Layout forLayout(layout); - - /// Forward out-of-loop and in-loop values. - const auto initArgs = op.getInitArgs().take_front(arch->nqubits()); - const auto results = op.getResults().take_front(arch->nqubits()); - const auto iterArgs = op.getRegionIterArgs().take_front(arch->nqubits()); - for (const auto [arg, res, iter] : llvm::zip(initArgs, results, iterArgs)) { - layout.remapQubitValue(arg, res); - forLayout.remapQubitValue(arg, iter); - } + void route(const Layout& layout, LayerVec& layers, + PatternRewriter& rewriter) const { + Layout routingLayout(layout); + LayerVec::iterator end = layers.end(); + for (LayerVec::iterator it = layers.begin(); it != end; ++it) { + LayerVec::iterator lookaheadIt = std::min(end, it + 1 + nlookahead_); + + auto& front = *it; /// == window.front() + auto window = llvm::make_range(it, lookaheadIt); + auto windowLayerGates = to_vector(llvm::map_range( + window, [](const Layer& layer) { return ArrayRef(layer.gates); })); + + Operation* anchor{}; /// First op in textual IR order. + for (Operation* op : front.ops) { + if (anchor == nullptr || op->isBeforeInBlock(anchor)) { + anchor = op; + } + } - /// Recursively handle loop region. - SWAPHistory history; - return rewrite(op.getRegion(), forLayout, history, rewriter); - } + assert(anchor != nullptr && "expected to find anchor"); + llvm::dbgs() << "schedule: anchor= " << *anchor << '\n'; - /** - * @brief Copy the layout for each branch and recursively map the branches. - */ - WalkResult handleIf(scf::IfOp op, Layout& layout, - PatternRewriter& rewriter) const { - - /// Recursively handle each branch region. - - LLVM_DEBUG(llvm::dbgs() << "handleIf: recurse for then\n"); - Layout ifLayout(layout); - SWAPHistory ifHistory; - const auto ifRes = - rewrite(op.getThenRegion(), ifLayout, ifHistory, rewriter); - if (ifRes.failed()) { - return ifRes; - } + const auto swaps = router_.route(windowLayerGates, routingLayout, *arch); + /// history.append(swaps); - LLVM_DEBUG(llvm::dbgs() << "handleIf: recurse for else\n"); - Layout elseLayout(layout); - SWAPHistory elseHistory; - const auto elseRes = - rewrite(op.getElseRegion(), elseLayout, elseHistory, rewriter); - if (elseRes.failed()) { - return elseRes; - } + rewriter.setInsertionPoint(anchor); + insertSWAPs(swaps, routingLayout, anchor->getLoc(), rewriter); - /// Forward out-of-if values. - const auto results = op->getResults().take_front(arch->nqubits()); - for (const auto [hw, res] : llvm::enumerate(results)) { - const Value q = layout.lookupHardwareValue(hw); - layout.remapQubitValue(q, res); + for (Operation* op : front.ops) { + if (auto u = dyn_cast(op)) { + for (const auto& [in, out] : + llvm::zip_equal(u.getAllInQubits(), u.getAllOutQubits())) { + routingLayout.remapQubitValue(in, out); + } + continue; + } + llvm_unreachable("TODO."); + } } - - return WalkResult::advance(); } - /** - * @brief Indicates the end of a region defined by a scf op. - * - * Restores layout by uncomputation and replaces (invalid) yield. - * - * Using uncompute has the advantages of (1) being intuitive and - * (2) preserving the optimality of the original SWAP sequence. - * Essentially the better the routing algorithm the better the - * uncompute. Moreover, this has the nice property that routing - * a 'for' of 'if' region always requires 2 * #(SWAPs required for region) - * additional SWAPS. - */ - WalkResult handleYield(scf::YieldOp op, Layout& layout, SWAPHistory& history, - PatternRewriter& rewriter) const { - LLVM_DEBUG(llvm::dbgs() << "handleYield\n"); - - /// Uncompute SWAPs. - this->insertSWAPs(to_vector(llvm::reverse(history)), layout, op.getLoc(), - rewriter); - - return WalkResult::advance(); - } + static LayerVec schedule(const Layout& layout) { + LayerVec layers; + HoldMap onHold; + Layout schedulingLayout(layout); + SmallVector wires(llvm::map_range( + layout.getHardwareQubits(), [](Value q) { return WireIterator(q); })); - /** - * @brief Ensures the executability of two-qubit gates on the given target - * architecture by inserting SWAPs. - */ - WalkResult handleUnitary(UnitaryInterface op, Layout& layout, - SlidingWindow& window, SWAPHistory& history, - PatternRewriter& rewriter) const { - LLVM_DEBUG(llvm::dbgs() - << "handleUnitary: gate= " << op->getName() << '\n'); - - /// If this is the first two-qubit op in the layer, route the layer - /// and remap afterwards. - - const auto curr = window.getCurrent(); - - /// Current op is front-anchor: route. - if (!curr.empty() && op == curr.front().front) { - route(curr, layout, op.getLoc(), history, rewriter); - window.advance(); - } + do { + const auto [layer, next] = + collectLayerAndAdvance(wires, schedulingLayout, onHold); - for (const auto [in, out] : - llvm::zip(op.getAllInQubits(), op.getAllOutQubits())) { - layout.remapQubitValue(in, out); - } + /// Early exit if there are no more gates to route. + if (layer.gates.empty()) { + break; + } - if (isa(op)) { - const auto outs = getOuts(op); - layout.swap(outs.first, outs.second); - history.push_back({layout.lookupHardwareIndex(outs.first), - layout.lookupHardwareIndex(outs.second)}); - } + layers.emplace_back(layer); + wires = next; - return WalkResult::advance(); - } + } while (!wires.empty()); - /** - * @brief Update layout. - */ - static WalkResult handleReset(ResetOp op, Layout& layout) { - layout.remapQubitValue(op.getInQubit(), op.getOutQubit()); - return WalkResult::advance(); - } + LLVM_DEBUG({ + llvm::dbgs() << "schedule: layers=\n"; + for (const auto [i, layer] : llvm::enumerate(layers)) { + llvm::dbgs() << '\t' << i << "= "; + for (const auto [prog0, prog1] : layer.gates) { + llvm::dbgs() << "(" << prog0 << "," << prog1 << "), "; + } + llvm::dbgs() << '\n'; + } + }); - /** - * @brief Update layout. - */ - static WalkResult handleMeasure(MeasureOp op, Layout& layout) { - layout.remapQubitValue(op.getInQubit(), op.getOutQubit()); - return WalkResult::advance(); + return layers; } - /** - * @brief Use A*-search to make the gates in the front layer (layers.front()) - * executable. - */ - void route(const ArrayRef layers, Layout& layout, Location location, - SWAPHistory& history, PatternRewriter& rewriter) const { - /// Find SWAPs. - SmallVector> layerIndices; - layerIndices.reserve(layers.size()); - for (const auto& layer : layers) { - layerIndices.push_back(layer.gates); + static std::pair> + collectLayerAndAdvance(ArrayRef wires, Layout& schedulingLayout, + DenseMap& onHold) { + /// The collected layer. + Layer layer; + /// A vector of iterators for the next iteration. + SmallVector next; + next.reserve(wires.size()); + + for (WireIterator it : wires) { + while (it != WireIterator()) { + Operation* op = *it; + + if (!isa(op)) { + layer.ops.push_back(op); + ++it; + continue; + } + + auto u = cast(op); + if (!isTwoQubitGate(u)) { + /// Add unitary to layer operations. + layer.ops.push_back(op); + /// Forward scheduling layout. + schedulingLayout.remapQubitValue(u.getInQubits().front(), + u.getOutQubits().front()); + ++it; + continue; + } + + if (onHold.contains(u)) { + const auto ins = getIns(u); + const auto outs = getOuts(u); + + /// Release iterators for next iteration. + next.push_back(onHold.lookup(u)); + next.push_back(++it); + /// Only add ready two-qubit gates to the layer. + layer.ops.push_back(op); + layer.gates.emplace_back( + schedulingLayout.lookupProgramIndex(ins.first), + schedulingLayout.lookupProgramIndex(ins.second)); + /// Forward scheduling layout. + schedulingLayout.remapQubitValue(ins.first, outs.first); + schedulingLayout.remapQubitValue(ins.second, outs.second); + } else { + /// Emplace the next iterator after the two-qubit + /// gate for a later release. + onHold.try_emplace(u, ++it); + } + + break; + } } - const auto swaps = router_.route(layerIndices, layout, *arch); - /// Append SWAPs to history. - history.append(swaps); - /// Insert SWAPs. - RoutingDriverBase::insertSWAPs(swaps, layout, location, rewriter); + + return {layer, next}; } AStarHeuristicRouter router_; diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h index 1227f79bec..feecea38c8 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h @@ -140,8 +140,7 @@ struct AStarHeuristicRouter final { using MinQueue = std::priority_queue, std::greater<>>; public: - [[nodiscard]] RouterResult route(const Layers layers, - const ThinLayout& layout, + [[nodiscard]] RouterResult route(Layers layers, const ThinLayout& layout, const Architecture& arch) const { Node root(layout); From a4f43fefcd88624ce148a09ea5361e1990c7bfdb Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 12 Nov 2025 16:18:35 +0100 Subject: [PATCH 10/56] Split naive and astar router into separate passes --- .../mlir/Dialect/MQTOpt/Transforms/Passes.h | 1 - .../mlir/Dialect/MQTOpt/Transforms/Passes.td | 20 +- .../Transforms/Transpilation/AStarDriver.h | 9 +- .../MQTOpt/Transforms/Transpilation/Common.h | 31 ++ .../Transforms/Transpilation/NaiveDriver.h | 287 ------------ .../Transpilation/RoutingDriverBase.h | 176 ------- .../Transforms/Transpilation/Common.cpp | 47 ++ .../Transpilation/sc/AStarRoutingPass.cpp | 146 ++++++ .../Transpilation/sc/NaiveRoutingPass.cpp | 429 ++++++++++++++++++ .../Transpilation/sc/RoutingPass.cpp | 113 ----- .../Transforms/Transpilation/basics.mlir | 8 +- .../Transforms/Transpilation/grover_5.mlir | 8 +- .../Transpilation/invalid-arch-option.mlir | 3 +- .../Transpilation/missing-arch-option.mlir | 3 +- 14 files changed, 684 insertions(+), 597 deletions(-) delete mode 100644 mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveDriver.h delete mode 100644 mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/RoutingDriverBase.h create mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp create mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp delete mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingPass.cpp diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h index 04b64642d3..c747005fa8 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h @@ -24,7 +24,6 @@ class RewritePatternSet; namespace mqt::ir::opt { enum class PlacementStrategy : std::uint8_t { Random, Identity }; -enum class RoutingMethod : std::uint8_t { Naive, AStar }; #define GEN_PASS_DECL #include "mlir/Dialect/MQTOpt/Transforms/Passes.h.inc" // IWYU pragma: export diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td index 4c38e7d98f..52ba12f728 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td @@ -107,16 +107,26 @@ def PlacementPassSC : Pass<"placement-sc", "mlir::ModuleOp"> { ]; } -def RoutingPassSC : Pass<"route-sc", "mlir::ModuleOp"> { +def NaiveRoutingPassSC : Pass<"route-naive-sc", "mlir::ModuleOp"> { + 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. + }]; + let options = [ + Option<"archName", "arch", "std::string", "", + "The name of the targeted architecture.">, + ]; + let statistics = [ + Statistic<"numSwaps", "num-additional-swaps", "The number of additional SWAPs"> + ]; +} + +def AStarRoutingPassSC : Pass<"route-astar-sc", "mlir::ModuleOp"> { 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. }]; let options = [ - Option<"method", "method", "RoutingMethod", "RoutingMethod::AStar", - "The routing method to use.", [{llvm::cl::values( - clEnumValN(RoutingMethod::Naive, "naive", "Swap along shortest paths"), - clEnumValN(RoutingMethod::AStar, "astar", "A*-search-based routing algorithm"))}]>, Option<"archName", "arch", "std::string", "", "The name of the targeted architecture.">, Option<"nlookahead", "nlookahead", "std::size_t", "1", diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h index 12775f7416..d3fa0de522 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h @@ -17,8 +17,6 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/RoutingDriverBase.h" -#include "mlir/IR/Dominance.h" -#include "mlir/Interfaces/ControlFlowInterfaces.h" #include "llvm/Support/ErrorHandling.h" @@ -30,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -174,9 +173,9 @@ class AStarDriver final : public RoutingDriverBase { PatternRewriter& rewriter) const { /// Find layers. LayerVec layers = schedule(layout); - /// Route the layers. Might break SSA Dominance. + /// Route the layers. Might break SSA dominance. route(layout, layers, rewriter); - /// Repair any SSA Dominance Issues. + /// Repair any SSA dominance issues. for (Block& block : region.getBlocks()) { sortTopologically(&block); } @@ -190,7 +189,7 @@ class AStarDriver final : public RoutingDriverBase { for (LayerVec::iterator it = layers.begin(); it != end; ++it) { LayerVec::iterator lookaheadIt = std::min(end, it + 1 + nlookahead_); - auto& front = *it; /// == window.front() + auto& front = *it; // == window.front() auto window = llvm::make_range(it, lookaheadIt); auto windowLayerGates = to_vector(llvm::map_range( window, [](const Layer& layer) { return ArrayRef(layer.gates); })); diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h index 4a9dc7ab6a..f773d5570c 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h @@ -80,4 +80,35 @@ using QubitIndexPair = std::pair; */ [[nodiscard]] mlir::Operation* getUserInRegion(mlir::Value v, mlir::Region* region); + +/** + * @brief Create and return SWAPOp for two qubits. + * + * Expects the rewriter to be set to the correct position. + * + * @param location The Location to attach to the created op. + * @param in0 First input qubit SSA value. + * @param in1 Second input qubit SSA value. + * @param rewriter A PatternRewriter. + * @return The created SWAPOp. + */ +[[nodiscard]] SWAPOp createSwap(mlir::Location location, mlir::Value in0, + mlir::Value in1, + mlir::PatternRewriter& rewriter); + +/** + * @brief Replace all uses of a value within a region and its nested regions, + * except for a specific operation. + * + * @param oldValue The value to replace. + * @param newValue The new value to use. + * @param region The region in which to perform replacements. + * @param exceptOp Operation to exclude from replacements. + * @param rewriter The pattern rewriter. + */ +void replaceAllUsesInRegionAndChildrenExcept(mlir::Value oldValue, + mlir::Value newValue, + mlir::Region* region, + mlir::Operation* exceptOp, + mlir::PatternRewriter& rewriter); } // namespace mqt::ir::opt diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveDriver.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveDriver.h deleted file mode 100644 index 8168bb191c..0000000000 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveDriver.h +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#pragma once - -#include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/RoutingDriverBase.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DEBUG_TYPE "route-sc" - -namespace mqt::ir::opt { - -using namespace mlir; - -class NaiveDriver final : public RoutingDriverBase { - using SWAPHistory = SmallVector; - -public: - using RoutingDriverBase::RoutingDriverBase; - -private: - LogicalResult rewrite(func::FuncOp func, PatternRewriter& rewriter) override { - LLVM_DEBUG(llvm::dbgs() << "handleFunc: " << func.getSymName() << '\n'); - - if (!isEntryPoint(func)) { - LLVM_DEBUG(llvm::dbgs() << "\tskip non entry\n"); - return success(); // Ignore non entry_point functions for now. - } - - Layout layout(arch->nqubits()); - SWAPHistory history; - return rewrite(func.getBody(), layout, history, rewriter); - } - - LogicalResult rewrite(Region& region, Layout& layout, SWAPHistory& history, - PatternRewriter& rewriter) const { - for (Operation& curr : region.getOps()) { - const OpBuilder::InsertionGuard guard(rewriter); - rewriter.setInsertionPoint(&curr); - - const auto res = - TypeSwitch(&curr) - /// mqtopt Dialect - .Case([&](UnitaryInterface op) { - return handleUnitary(op, layout, history, rewriter); - }) - .Case( - [&](QubitOp op) { return handleQubit(op, layout); }) - .Case( - [&](ResetOp op) { return handleReset(op, layout); }) - .Case( - [&](MeasureOp op) { return handleMeasure(op, layout); }) - /// built-in Dialect - .Case([&]([[maybe_unused]] ModuleOp op) { - return WalkResult::advance(); - }) - /// func Dialect - .Case([&]([[maybe_unused]] func::ReturnOp op) { - return WalkResult::advance(); - }) - /// scf Dialect - .Case([&](scf::ForOp op) { - return handleFor(op, layout, rewriter); - }) - .Case( - [&](scf::IfOp op) { return handleIf(op, layout, rewriter); }) - .Case([&](scf::YieldOp op) { - return handleYield(op, layout, history, rewriter); - }) - /// Skip the rest. - .Default([](auto) { return WalkResult::skip(); }); - - if (res.wasInterrupted()) { - return failure(); - } - } - - return success(); - } - - /** - * @brief Copy the layout and recursively map the loop body. - */ - WalkResult handleFor(scf::ForOp op, Layout& layout, - PatternRewriter& rewriter) const { - /// Copy layout. - Layout forLayout(layout); - - /// Forward out-of-loop and in-loop values. - const auto initArgs = op.getInitArgs().take_front(arch->nqubits()); - const auto results = op.getResults().take_front(arch->nqubits()); - const auto iterArgs = op.getRegionIterArgs().take_front(arch->nqubits()); - for (const auto [arg, res, iter] : llvm::zip(initArgs, results, iterArgs)) { - layout.remapQubitValue(arg, res); - forLayout.remapQubitValue(arg, iter); - } - - /// Recursively handle loop region. - SWAPHistory history; - return rewrite(op.getRegion(), forLayout, history, rewriter); - } - - /** - * @brief Copy the layout for each branch and recursively map the branches. - */ - WalkResult handleIf(scf::IfOp op, Layout& layout, - PatternRewriter& rewriter) const { - /// Recursively handle each branch region. - Layout ifLayout(layout); - SWAPHistory ifHistory; - const auto ifRes = - rewrite(op.getThenRegion(), ifLayout, ifHistory, rewriter); - if (ifRes.failed()) { - return ifRes; - } - - Layout elseLayout(layout); - SWAPHistory elseHistory; - const auto elseRes = - rewrite(op.getElseRegion(), elseLayout, elseHistory, rewriter); - if (elseRes.failed()) { - return elseRes; - } - - /// Forward out-of-if values. - const auto results = op->getResults().take_front(arch->nqubits()); - for (const auto [hw, res] : llvm::enumerate(results)) { - const Value q = layout.lookupHardwareValue(hw); - layout.remapQubitValue(q, res); - } - - return WalkResult::advance(); - } - - /** - * @brief Indicates the end of a region defined by a scf op. - * - * Restores layout by uncomputation and replaces (invalid) yield. - * - * Using uncompute has the advantages of (1) being intuitive and - * (2) preserving the optimality of the original SWAP sequence. - * Essentially the better the routing algorithm the better the - * uncompute. Moreover, this has the nice property that routing - * a 'for' of 'if' region always requires 2 * #(SWAPs required for region) - * additional SWAPS. - */ - WalkResult handleYield(scf::YieldOp op, Layout& layout, SWAPHistory& history, - PatternRewriter& rewriter) const { - /// Uncompute SWAPs. - this->insertSWAPs(llvm::to_vector(llvm::reverse(history)), layout, - op.getLoc(), rewriter); - - return WalkResult::advance(); - } - - /** - * @brief Add hardware qubit with respective program & hardware index to - * layout. - * - * Thanks to the placement pass, we can apply the identity layout here. - */ - static WalkResult handleQubit(QubitOp op, Layout& layout) { - const std::size_t index = op.getIndex(); - layout.add(index, index, op.getQubit()); - return WalkResult::advance(); - } - - /** - * @brief Ensures the executability of two-qubit gates on the given target - * architecture by inserting SWAPs. - */ - WalkResult handleUnitary(UnitaryInterface op, Layout& layout, - SWAPHistory& history, - PatternRewriter& rewriter) const { - const std::vector inQubits = op.getAllInQubits(); - const std::vector outQubits = op.getAllOutQubits(); - const std::size_t nacts = inQubits.size(); - - // Global-phase or zero-qubit unitary: Nothing to do. - if (nacts == 0) { - return WalkResult::advance(); - } - - if (isa(op)) { - for (const auto [in, out] : llvm::zip(inQubits, outQubits)) { - layout.remapQubitValue(in, out); - } - return WalkResult::advance(); - } - - /// Expect two-qubit gate decomposition. - if (nacts > 2) { - return op->emitOpError() << "acts on more than two qubits"; - } - - /// Single-qubit: Forward mapping. - if (nacts == 1) { - layout.remapQubitValue(inQubits[0], outQubits[0]); - return WalkResult::advance(); - } - - if (!isExecutable(op, layout)) { - route(op, layout, history, rewriter); - } - - const auto [execIn0, execIn1] = getIns(op); - const auto [execOut0, execOut1] = getOuts(op); - - LLVM_DEBUG({ - llvm::dbgs() << llvm::format("handleUnitary: gate= p%d:h%d, p%d:h%d\n", - layout.lookupProgramIndex(execIn0), - layout.lookupHardwareIndex(execIn0), - layout.lookupProgramIndex(execIn1), - layout.lookupHardwareIndex(execIn1)); - }); - - if (isa(op)) { - layout.swap(execIn0, execIn1); - history.push_back({layout.lookupHardwareIndex(execIn0), - layout.lookupHardwareIndex(execIn1)}); - } - - layout.remapQubitValue(execIn0, execOut0); - layout.remapQubitValue(execIn1, execOut1); - - return WalkResult::advance(); - } - - /** - * @brief Update layout. - */ - static WalkResult handleReset(ResetOp op, Layout& layout) { - layout.remapQubitValue(op.getInQubit(), op.getOutQubit()); - return WalkResult::advance(); - } - - /** - * @brief Update layout. - */ - static WalkResult handleMeasure(MeasureOp op, Layout& layout) { - layout.remapQubitValue(op.getInQubit(), op.getOutQubit()); - return WalkResult::advance(); - } - - /** - * @brief Use shortest path swapping to make the given unitary executable. - * @details Optimized for an avg. SWAP count of 16. - */ - void route(UnitaryInterface op, Layout& layout, SWAPHistory& history, - PatternRewriter& rewriter) const { - /// Find SWAPs. - SmallVector swaps; - const auto ins = getIns(op); - const auto hw0 = layout.lookupHardwareIndex(ins.first); - const auto hw1 = layout.lookupHardwareIndex(ins.second); - const auto path = arch->shortestPathBetween(hw0, hw1); - for (std::size_t i = 0; i < path.size() - 2; ++i) { - swaps.emplace_back(path[i], path[i + 1]); - } - /// Append SWAPs to history. - history.append(swaps); - /// Insert SWAPs. - RoutingDriverBase::insertSWAPs(swaps, layout, op.getLoc(), rewriter); - } -}; -} // namespace mqt::ir::opt diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/RoutingDriverBase.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/RoutingDriverBase.h deleted file mode 100644 index f86eb169b4..0000000000 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/RoutingDriverBase.h +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#pragma once - -#include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DEBUG_TYPE "route-sc" - -namespace mqt::ir::opt { - -using namespace mlir; - -/** - * @brief Create and return SWAPOp for two qubits. - * - * Expects the rewriter to be set to the correct position. - * - * @param location The Location to attach to the created op. - * @param in0 First input qubit SSA value. - * @param in1 Second input qubit SSA value. - * @param rewriter A PatternRewriter. - * @return The created SWAPOp. - */ -[[nodiscard]] inline SWAPOp createSwap(Location location, Value in0, Value in1, - PatternRewriter& rewriter) { - const SmallVector resultTypes{in0.getType(), in1.getType()}; - const SmallVector inQubits{in0, in1}; - - return rewriter.create( - /* location = */ location, - /* out_qubits = */ resultTypes, - /* pos_ctrl_out_qubits = */ TypeRange{}, - /* neg_ctrl_out_qubits = */ TypeRange{}, - /* static_params = */ nullptr, - /* params_mask = */ nullptr, - /* params = */ ValueRange{}, - /* in_qubits = */ inQubits, - /* pos_ctrl_in_qubits = */ ValueRange{}, - /* neg_ctrl_in_qubits = */ ValueRange{}); -} - -/** - * @brief Replace all uses of a value within a region and its nested regions, - * except for a specific operation. - * - * @param oldValue The value to replace - * @param newValue The new value to use - * @param region The region in which to perform replacements - * @param exceptOp Operation to exclude from replacements - * @param rewriter The pattern rewriter - */ -inline void replaceAllUsesInRegionAndChildrenExcept(Value oldValue, - Value newValue, - Region* region, - Operation* exceptOp, - PatternRewriter& rewriter) { - if (oldValue == newValue) { - return; - } - - rewriter.replaceUsesWithIf(oldValue, newValue, [&](OpOperand& use) { - Operation* user = use.getOwner(); - if (user == exceptOp) { - return false; - } - - // For other blocks, check if in region tree - Region* userRegion = user->getParentRegion(); - while (userRegion) { - if (userRegion == region) { - return true; - } - userRegion = userRegion->getParentRegion(); - } - return false; - }); -} - -struct Statistics { - llvm::Statistic* numSwaps; -}; - -class RoutingDriverBase { -public: - explicit RoutingDriverBase(std::unique_ptr arch, - const Statistics& stats) - : arch(std::move(arch)), stats(stats) {} - - virtual ~RoutingDriverBase() = default; - - [[nodiscard]] virtual LogicalResult rewrite(func::FuncOp func, - PatternRewriter& rewriter) = 0; - - [[nodiscard]] LogicalResult rewrite(ModuleOp module) { - PatternRewriter rewriter(module->getContext()); - for (auto func : module.getOps()) { - if (rewrite(func, rewriter).failed()) { - return failure(); - } - } - return success(); - } - -protected: - /** - * @brief Insert SWAPs at the rewriter's insertion point and update the - * layout. - */ - void insertSWAPs(ArrayRef swaps, Layout& layout, - Location anchor, PatternRewriter& rewriter) const { - for (const auto [hw0, hw1] : swaps) { - const Value in0 = layout.lookupHardwareValue(hw0); - const Value in1 = layout.lookupHardwareValue(hw1); - [[maybe_unused]] const auto [prog0, prog1] = - layout.getProgramIndices(hw0, hw1); - - LLVM_DEBUG({ - llvm::dbgs() << llvm::format( - "route: swap= p%d:h%d, p%d:h%d <- p%d:h%d, p%d:h%d\n", prog1, hw0, - prog0, hw1, prog0, hw0, prog1, hw1); - }); - - auto swap = createSwap(anchor, in0, in1, rewriter); - const auto [out0, out1] = getOuts(swap); - - rewriter.setInsertionPointAfter(swap); - replaceAllUsesInRegionAndChildrenExcept( - in0, out1, swap->getParentRegion(), swap, rewriter); - replaceAllUsesInRegionAndChildrenExcept( - in1, out0, swap->getParentRegion(), swap, rewriter); - - layout.swap(in0, in1); - layout.remapQubitValue(in0, out0); - layout.remapQubitValue(in1, out1); - } - - (*stats.numSwaps) += swaps.size(); - } - - /** - * @returns true iff @p op is executable on the targeted architecture. - */ - [[nodiscard]] bool isExecutable(UnitaryInterface op, Layout& layout) const { - const auto [in0, in1] = getIns(op); - return arch->areAdjacent(layout.lookupHardwareIndex(in0), - layout.lookupHardwareIndex(in1)); - } - - std::unique_ptr arch; - - Statistics stats; -}; -} // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp index c0432a7639..afdfa83f0b 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -91,4 +92,50 @@ bool isTwoQubitGate(UnitaryInterface op) { } return nullptr; } + +[[nodiscard]] SWAPOp createSwap(mlir::Location location, mlir::Value in0, + mlir::Value in1, + mlir::PatternRewriter& rewriter) { + const mlir::SmallVector resultTypes{in0.getType(), in1.getType()}; + const mlir::SmallVector inQubits{in0, in1}; + + return rewriter.create( + /* location = */ location, + /* out_qubits = */ resultTypes, + /* pos_ctrl_out_qubits = */ mlir::TypeRange{}, + /* neg_ctrl_out_qubits = */ mlir::TypeRange{}, + /* static_params = */ nullptr, + /* params_mask = */ nullptr, + /* params = */ mlir::ValueRange{}, + /* in_qubits = */ inQubits, + /* pos_ctrl_in_qubits = */ mlir::ValueRange{}, + /* neg_ctrl_in_qubits = */ mlir::ValueRange{}); +} + +void replaceAllUsesInRegionAndChildrenExcept(mlir::Value oldValue, + mlir::Value newValue, + mlir::Region* region, + mlir::Operation* exceptOp, + mlir::PatternRewriter& rewriter) { + if (oldValue == newValue) { + return; + } + + rewriter.replaceUsesWithIf(oldValue, newValue, [&](mlir::OpOperand& use) { + mlir::Operation* user = use.getOwner(); + if (user == exceptOp) { + return false; + } + + // For other blocks, check if in region tree + mlir::Region* userRegion = user->getParentRegion(); + while (userRegion) { + if (userRegion == region) { + return true; + } + userRegion = userRegion->getParentRegion(); + } + return false; + }); +} } // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp new file mode 100644 index 0000000000..c1c5c13ad1 --- /dev/null +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/MQTOpt/Transforms/Passes.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEBUG_TYPE "route-sc" + +namespace mqt::ir::opt { + +#define GEN_PASS_DEF_ASTARROUTINGPASSSC +#include "mlir/Dialect/MQTOpt/Transforms/Passes.h.inc" + +namespace { +using namespace mlir; + +/// @brief A composite datastructure for LLVM Statistics. +struct Statistics { + llvm::Statistic* numSwaps; +}; + +/** + * @brief Insert SWAP ops at the rewriter's insertion point. + * + * @param location The location of the inserted SWAP ops. + * @param swaps The hardware indices of the SWAPs. + * @param layout The current layout. + * @param rewriter The pattern rewriter. + */ +void insertSWAPs(Location location, ArrayRef swaps, + Layout& layout, PatternRewriter& rewriter) { + for (const auto [hw0, hw1] : swaps) { + const Value in0 = layout.lookupHardwareValue(hw0); + const Value in1 = layout.lookupHardwareValue(hw1); + [[maybe_unused]] const auto [prog0, prog1] = + layout.getProgramIndices(hw0, hw1); + + LLVM_DEBUG({ + llvm::dbgs() << llvm::format( + "route: swap= p%d:h%d, p%d:h%d <- p%d:h%d, p%d:h%d\n", prog1, hw0, + prog0, hw1, prog0, hw0, prog1, hw1); + }); + + auto swap = createSwap(location, in0, in1, rewriter); + const auto [out0, out1] = getOuts(swap); + + rewriter.setInsertionPointAfter(swap); + replaceAllUsesInRegionAndChildrenExcept(in0, out1, swap->getParentRegion(), + swap, rewriter); + replaceAllUsesInRegionAndChildrenExcept(in1, out0, swap->getParentRegion(), + swap, rewriter); + + layout.swap(in0, in1); + layout.remapQubitValue(in0, out0); + layout.remapQubitValue(in1, out1); + } +} + +/** + * @brief Given a layout, validate if the two-qubit unitary op is executable on + * the targeted architecture. + * + * @param op The two-qubit unitary. + * @param layout The current layout. + * @param arch The targeted architecture. + */ +[[nodiscard]] bool isExecutable(UnitaryInterface op, const Layout& layout, + const Architecture& arch) { + const auto ins = getIns(op); + return arch.areAdjacent(layout.lookupHardwareIndex(ins.first), + layout.lookupHardwareIndex(ins.second)); +} + +/** + * @brief This pass ensures that the connectivity constraints of the target + * architecture are met. + */ +struct AStarRoutingPassSC final + : impl::AStarRoutingPassSCBase { + using AStarRoutingPassSCBase::AStarRoutingPassSCBase; + + void runOnOperation() override { + if (preflight().failed()) { + signalPassFailure(); + return; + } + + auto arch = getArchitecture(archName); + if (!arch) { + emitError(UnknownLoc::get(&getContext())) + << "unsupported architecture '" << archName << "'"; + signalPassFailure(); + return; + } + + Statistics stats{.numSwaps = &numSwaps}; + } + +private: + LogicalResult preflight() { + if (archName.empty()) { + return emitError(UnknownLoc::get(&getContext()), + "required option 'arch' not provided"); + } + + return success(); + } +}; + +} // namespace +} // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp new file mode 100644 index 0000000000..b4a4f1a1fc --- /dev/null +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp @@ -0,0 +1,429 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/MQTOpt/Transforms/Passes.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEBUG_TYPE "route-sc" + +namespace mqt::ir::opt { + +#define GEN_PASS_DEF_NAIVEROUTINGPASSSC +#include "mlir/Dialect/MQTOpt/Transforms/Passes.h.inc" + +namespace { +using namespace mlir; + +/// @brief A composite datastructure for LLVM Statistics. +struct Statistics { + llvm::Statistic* numSwaps; +}; + +/// @brief Commonly passed parameters for the routing functions. +struct RoutingContext { + /// @brief The targeted architecture. + std::unique_ptr arch; + /// @brief LLVM/MLIR statistics. + Statistics stats; + /// @brief A pattern rewriter. + PatternRewriter rewriter; +}; + +/** + * @brief Insert SWAP ops at the rewriter's insertion point. + * + * @param location The location of the inserted SWAP ops. + * @param swaps The hardware indices of the SWAPs. + * @param layout The current layout. + * @param rewriter The pattern rewriter. + */ +void insertSWAPs(Location location, ArrayRef swaps, + Layout& layout, PatternRewriter& rewriter) { + for (const auto [hw0, hw1] : swaps) { + const Value in0 = layout.lookupHardwareValue(hw0); + const Value in1 = layout.lookupHardwareValue(hw1); + [[maybe_unused]] const auto [prog0, prog1] = + layout.getProgramIndices(hw0, hw1); + + LLVM_DEBUG({ + llvm::dbgs() << llvm::format( + "route: swap= p%d:h%d, p%d:h%d <- p%d:h%d, p%d:h%d\n", prog1, hw0, + prog0, hw1, prog0, hw0, prog1, hw1); + }); + + auto swap = createSwap(location, in0, in1, rewriter); + const auto [out0, out1] = getOuts(swap); + + rewriter.setInsertionPointAfter(swap); + replaceAllUsesInRegionAndChildrenExcept(in0, out1, swap->getParentRegion(), + swap, rewriter); + replaceAllUsesInRegionAndChildrenExcept(in1, out0, swap->getParentRegion(), + swap, rewriter); + + layout.swap(in0, in1); + layout.remapQubitValue(in0, out0); + layout.remapQubitValue(in1, out1); + } +} + +/** + * @brief Given a layout, validate if the two-qubit unitary op is executable on + * the targeted architecture. + * + * @param op The two-qubit unitary. + * @param layout The current layout. + * @param arch The targeted architecture. + */ +[[nodiscard]] bool isExecutable(UnitaryInterface op, const Layout& layout, + const Architecture& arch) { + const auto ins = getIns(op); + return arch.areAdjacent(layout.lookupHardwareIndex(ins.first), + layout.lookupHardwareIndex(ins.second)); +} + +LogicalResult processRegion(Region& region, Layout& layout, + SmallVector& history, + RoutingContext& ctx); + +/** + * @brief Copy the layout and recursively map the loop body. + */ +WalkResult handleFor(scf::ForOp op, Layout& layout, RoutingContext& ctx) { + /// Copy layout. + Layout forLayout(layout); + + /// Forward out-of-loop and in-loop values. + const auto initArgs = op.getInitArgs().take_front(ctx.arch->nqubits()); + const auto results = op.getResults().take_front(ctx.arch->nqubits()); + const auto iterArgs = op.getRegionIterArgs().take_front(ctx.arch->nqubits()); + for (const auto [arg, res, iter] : llvm::zip(initArgs, results, iterArgs)) { + layout.remapQubitValue(arg, res); + forLayout.remapQubitValue(arg, iter); + } + + /// Recursively handle loop region. + SmallVector history; + return processRegion(op.getRegion(), forLayout, history, ctx); +} + +/** + * @brief Copy the layout for each branch and recursively map the branches. + */ +WalkResult handleIf(scf::IfOp op, Layout& layout, RoutingContext& ctx) { + /// Recursively handle each branch region. + Layout ifLayout(layout); + SmallVector ifHistory; + const auto ifRes = + processRegion(op.getThenRegion(), ifLayout, ifHistory, ctx); + if (ifRes.failed()) { + return ifRes; + } + + Layout elseLayout(layout); + SmallVector elseHistory; + const auto elseRes = + processRegion(op.getElseRegion(), elseLayout, elseHistory, ctx); + if (elseRes.failed()) { + return elseRes; + } + + /// Forward out-of-if values. + const auto results = op->getResults().take_front(ctx.arch->nqubits()); + for (const auto [hw, res] : llvm::enumerate(results)) { + const Value q = layout.lookupHardwareValue(hw); + layout.remapQubitValue(q, res); + } + + return WalkResult::advance(); +} + +/** + * @brief Indicates the end of a region defined by a scf op. + * + * Restores layout by uncomputation and replaces (invalid) yield. + * + * Using uncompute has the advantages of (1) being intuitive and + * (2) preserving the optimality of the original SWAP sequence. + * Essentially the better the routing algorithm the better the + * uncompute. Moreover, this has the nice property that routing + * a 'for' of 'if' region always requires 2 * #(SWAPs required for region) + * additional SWAPS. + */ +WalkResult handleYield(scf::YieldOp op, Layout& layout, + ArrayRef history, RoutingContext& ctx) { + /// Uncompute SWAPs. + insertSWAPs(op.getLoc(), llvm::to_vector(llvm::reverse(history)), layout, + ctx.rewriter); + /// Count SWAPs. + *(ctx.stats.numSwaps) += history.size(); + return WalkResult::advance(); +} + +/** + * @brief Add hardware qubit with respective program & hardware index to + * layout. + * + * Thanks to the placement pass, we can apply the identity layout here. + */ +WalkResult handleQubit(QubitOp op, Layout& layout) { + const std::size_t index = op.getIndex(); + layout.add(index, index, op.getQubit()); + return WalkResult::advance(); +} + +/** + * @brief Use shortest path swapping to make the given unitary executable. + * @details Optimized for an avg. SWAP count of 16. + */ +void findAndInsertSWAPs(UnitaryInterface op, Layout& layout, + SmallVector& history, + RoutingContext& ctx) { + /// Find SWAPs. + SmallVector swaps; + const auto ins = getIns(op); + const auto hw0 = layout.lookupHardwareIndex(ins.first); + const auto hw1 = layout.lookupHardwareIndex(ins.second); + const auto path = ctx.arch->shortestPathBetween(hw0, hw1); + for (std::size_t i = 0; i < path.size() - 2; ++i) { + swaps.emplace_back(path[i], path[i + 1]); + } + + /// Append SWAPs to history. + history.append(swaps); + + /// Insert SWAPs. + insertSWAPs(op.getLoc(), swaps, layout, ctx.rewriter); + + /// Count SWAPs. + *(ctx.stats.numSwaps) += swaps.size(); +} + +/** + * @brief Ensures the executability of two-qubit gates on the given target + * architecture by inserting SWAPs. + */ +WalkResult handleUnitary(UnitaryInterface op, Layout& layout, + SmallVector& history, + RoutingContext& ctx) { + const std::vector inQubits = op.getAllInQubits(); + const std::vector outQubits = op.getAllOutQubits(); + const std::size_t nacts = inQubits.size(); + + // Global-phase or zero-qubit unitary: Nothing to do. + if (nacts == 0) { + return WalkResult::advance(); + } + + if (isa(op)) { + for (const auto [in, out] : llvm::zip(inQubits, outQubits)) { + layout.remapQubitValue(in, out); + } + return WalkResult::advance(); + } + + /// Expect two-qubit gate decomposition. + if (nacts > 2) { + return op->emitOpError() << "acts on more than two qubits"; + } + + /// Single-qubit: Forward mapping. + if (nacts == 1) { + layout.remapQubitValue(inQubits[0], outQubits[0]); + return WalkResult::advance(); + } + + if (!isExecutable(op, layout, *ctx.arch)) { + findAndInsertSWAPs(op, layout, history, ctx); + } + + const auto [execIn0, execIn1] = getIns(op); + const auto [execOut0, execOut1] = getOuts(op); + + LLVM_DEBUG({ + llvm::dbgs() << llvm::format("handleUnitary: gate= p%d:h%d, p%d:h%d\n", + layout.lookupProgramIndex(execIn0), + layout.lookupHardwareIndex(execIn0), + layout.lookupProgramIndex(execIn1), + layout.lookupHardwareIndex(execIn1)); + }); + + if (isa(op)) { + layout.swap(execIn0, execIn1); + history.push_back({layout.lookupHardwareIndex(execIn0), + layout.lookupHardwareIndex(execIn1)}); + } + + layout.remapQubitValue(execIn0, execOut0); + layout.remapQubitValue(execIn1, execOut1); + + return WalkResult::advance(); +} + +/** + * @brief Update layout. + */ +WalkResult handleReset(ResetOp op, Layout& layout) { + layout.remapQubitValue(op.getInQubit(), op.getOutQubit()); + return WalkResult::advance(); +} + +/** + * @brief Update layout. + */ +WalkResult handleMeasure(MeasureOp op, Layout& layout) { + layout.remapQubitValue(op.getInQubit(), op.getOutQubit()); + return WalkResult::advance(); +} + +LogicalResult processRegion(Region& region, Layout& layout, + SmallVector& history, + RoutingContext& ctx) { + for (Operation& curr : region.getOps()) { + const OpBuilder::InsertionGuard guard(ctx.rewriter); + ctx.rewriter.setInsertionPoint(&curr); + + const auto res = + TypeSwitch(&curr) + /// mqtopt Dialect + .Case([&](UnitaryInterface op) { + return handleUnitary(op, layout, history, ctx); + }) + .Case([&](QubitOp op) { return handleQubit(op, layout); }) + .Case([&](ResetOp op) { return handleReset(op, layout); }) + .Case( + [&](MeasureOp op) { return handleMeasure(op, layout); }) + /// built-in Dialect + .Case([&]([[maybe_unused]] ModuleOp op) { + return WalkResult::advance(); + }) + /// func Dialect + .Case([&]([[maybe_unused]] func::ReturnOp op) { + return WalkResult::advance(); + }) + /// scf Dialect + .Case( + [&](scf::ForOp op) { return handleFor(op, layout, ctx); }) + .Case( + [&](scf::IfOp op) { return handleIf(op, layout, ctx); }) + .Case([&](scf::YieldOp op) { + return handleYield(op, layout, history, ctx); + }) + /// Skip the rest. + .Default([](auto) { return WalkResult::skip(); }); + + if (res.wasInterrupted()) { + return failure(); + } + } + + return success(); +} + +LogicalResult processFunction(func::FuncOp func, RoutingContext& ctx) { + LLVM_DEBUG(llvm::dbgs() << "handleFunc: " << func.getSymName() << '\n'); + + if (!isEntryPoint(func)) { + LLVM_DEBUG(llvm::dbgs() << "\tskip non entry\n"); + return success(); // Ignore non entry_point functions for now. + } + + Layout layout(ctx.arch->nqubits()); + SmallVector history; + return processRegion(func.getBody(), layout, history, ctx); +} + +/** + * @brief Naively route the given module for the targeted architecture. + * + * @param module The module to route. + * @param arch The targeted architecture. + */ +LogicalResult route(ModuleOp module, std::unique_ptr arch, + Statistics& stats) { + RoutingContext ctx{.arch = std::move(arch), + .stats = stats, + .rewriter = PatternRewriter(module->getContext())}; + for (auto func : module.getOps()) { + if (processFunction(func, ctx).failed()) { + return failure(); + } + } + return success(); +} + +/** + * @brief This pass ensures that the connectivity constraints of the target + * architecture are met. + */ +struct NaiveRoutingPassSC final + : impl::NaiveRoutingPassSCBase { + using NaiveRoutingPassSCBase::NaiveRoutingPassSCBase; + + void runOnOperation() override { + if (preflight().failed()) { + signalPassFailure(); + return; + } + + auto arch = getArchitecture(archName); + if (!arch) { + emitError(UnknownLoc::get(&getContext())) + << "unsupported architecture '" << archName << "'"; + signalPassFailure(); + return; + } + + Statistics stats{.numSwaps = &numSwaps}; + if (route(getOperation(), std::move(arch), stats).failed()) { + signalPassFailure(); + }; + } + +private: + LogicalResult preflight() { + if (archName.empty()) { + return emitError(UnknownLoc::get(&getContext()), + "required option 'arch' not provided"); + } + + return success(); + } +}; + +} // namespace +} // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingPass.cpp deleted file mode 100644 index c94d3cf1b2..0000000000 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingPass.cpp +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "mlir/Dialect/MQTOpt/Transforms/Passes.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/NaiveDriver.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/RoutingDriverBase.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DEBUG_TYPE "route-sc" - -namespace mqt::ir::opt { - -#define GEN_PASS_DEF_ROUTINGPASSSC -#include "mlir/Dialect/MQTOpt/Transforms/Passes.h.inc" - -namespace { -using namespace mlir; - -/** - * @brief This pass ensures that the connectivity constraints of the target - * architecture are met. - */ -struct RoutingPassSC final : impl::RoutingPassSCBase { - using RoutingPassSCBase::RoutingPassSCBase; - - void runOnOperation() override { - if (preflight().failed()) { - signalPassFailure(); - return; - } - - auto arch = getArchitecture(archName); - if (!arch) { - emitError(UnknownLoc::get(&getContext())) - << "unsupported architecture '" << archName << "'"; - signalPassFailure(); - return; - } - - auto mapper = getMapper(std::move(arch)); - if (mapper->rewrite(getOperation()).failed()) { - signalPassFailure(); - } - } - -private: - [[nodiscard]] std::unique_ptr - getMapper(std::unique_ptr arch) { - Statistics stats{.numSwaps = &numSwaps}; - - switch (static_cast(method)) { - case RoutingMethod::Naive: { - LLVM_DEBUG({ llvm::dbgs() << "getMapper: method=naive\n"; }); - return std::make_unique(std::move(arch), stats); - } - case RoutingMethod::AStar: { - LLVM_DEBUG({ llvm::dbgs() << "getRouter: method=astar\n"; }); - const HeuristicWeights weights(alpha, lambda, nlookahead); - return std::make_unique(weights, nlookahead, std::move(arch), - stats); - } - } - - llvm_unreachable("Unknown method"); - } - - LogicalResult preflight() { - if (archName.empty()) { - return emitError(UnknownLoc::get(&getContext()), - "required option 'arch' not provided"); - } - - return success(); - } -}; - -} // namespace -} // namespace mqt::ir::opt diff --git a/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/basics.mlir b/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/basics.mlir index a1ab6ad969..9d38b88bcd 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/basics.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/basics.mlir @@ -8,10 +8,10 @@ // Instead of applying checks, the routing verifier pass ensures the validity of this program. -// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(canonicalize, placement-sc{strategy=identity arch=MQTTest}, route-sc{method=naive arch=MQTTest},verify-routing-sc{arch=MQTTest})" -verify-diagnostics | FileCheck %s -// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(canonicalize, placement-sc{strategy=identity arch=MQTTest}, route-sc{method=astar arch=MQTTest},verify-routing-sc{arch=MQTTest})" -verify-diagnostics | FileCheck %s -// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(canonicalize, placement-sc{strategy=identity arch=IBMFalcon}, route-sc{method=naive arch=IBMFalcon},verify-routing-sc{arch=IBMFalcon})" -verify-diagnostics | FileCheck %s -// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(canonicalize, placement-sc{strategy=identity arch=IBMFalcon}, route-sc{method=astar arch=IBMFalcon},verify-routing-sc{arch=IBMFalcon})" -verify-diagnostics | FileCheck %s +// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(canonicalize, placement-sc{strategy=identity arch=MQTTest}, route-naive-sc{arch=MQTTest},verify-routing-sc{arch=MQTTest})" -verify-diagnostics | FileCheck %s +// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(canonicalize, placement-sc{strategy=identity arch=MQTTest}, route-astar-sc{arch=MQTTest},verify-routing-sc{arch=MQTTest})" -verify-diagnostics | FileCheck %s +// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(canonicalize, placement-sc{strategy=identity arch=IBMFalcon}, route-naive-sc{arch=IBMFalcon},verify-routing-sc{arch=IBMFalcon})" -verify-diagnostics | FileCheck %s +// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(canonicalize, placement-sc{strategy=identity arch=IBMFalcon}, route-astar-sc{astar arch=IBMFalcon},verify-routing-sc{arch=IBMFalcon})" -verify-diagnostics | FileCheck %s module { // CHECK-LABEL: func.func @entrySABRE diff --git a/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/grover_5.mlir b/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/grover_5.mlir index 66149b883b..f17fe699d2 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/grover_5.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/grover_5.mlir @@ -8,10 +8,10 @@ // Instead of applying checks, the routing verifier pass ensures the validity of this program. -// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(placement-sc{strategy=identity arch=MQTTest}, route-sc{method=naive arch=MQTTest},verify-routing-sc{arch=MQTTest})" -verify-diagnostics | FileCheck %s -// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(placement-sc{strategy=identity arch=MQTTest}, route-sc{method=astar arch=MQTTest},verify-routing-sc{arch=MQTTest})" -verify-diagnostics | FileCheck %s -// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(placement-sc{strategy=identity arch=IBMFalcon}, route-sc{method=naive arch=IBMFalcon},verify-routing-sc{arch=IBMFalcon})" -verify-diagnostics | FileCheck %s -// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(placement-sc{strategy=identity arch=IBMFalcon}, route-sc{method=astar arch=IBMFalcon},verify-routing-sc{arch=IBMFalcon})" -verify-diagnostics | FileCheck %s +// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(placement-sc{strategy=identity arch=MQTTest}, route-naive-sc{arch=MQTTest},verify-routing-sc{arch=MQTTest})" -verify-diagnostics | FileCheck %s +// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(placement-sc{strategy=identity arch=MQTTest}, route-astar-sc{arch=MQTTest},verify-routing-sc{arch=MQTTest})" -verify-diagnostics | FileCheck %s +// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(placement-sc{strategy=identity arch=IBMFalcon}, route-naive-sc{arch=IBMFalcon},verify-routing-sc{arch=IBMFalcon})" -verify-diagnostics | FileCheck %s +// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(placement-sc{strategy=identity arch=IBMFalcon}, route-astar-sc{arch=IBMFalcon},verify-routing-sc{arch=IBMFalcon})" -verify-diagnostics | FileCheck %s module { // CHECK-LABEL: func.func @main diff --git a/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/invalid-arch-option.mlir b/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/invalid-arch-option.mlir index 111c894576..2b6da0a4d7 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/invalid-arch-option.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/invalid-arch-option.mlir @@ -9,7 +9,8 @@ // Instead of applying checks, the routing verifier pass ensures the validity of this program. // RUN: quantum-opt %s --placement-sc="arch=invalid-127" -verify-diagnostics -// RUN: quantum-opt %s --route-sc="arch=invalid-127" -verify-diagnostics +// RUN: quantum-opt %s --route-naive-sc="arch=invalid-127" -verify-diagnostics +// RUN: quantum-opt %s --route-astar-sc="arch=invalid-127" -verify-diagnostics // RUN: quantum-opt %s --verify-routing-sc="arch=invalid-127" -verify-diagnostics // expected-error@unknown {{unsupported architecture}} diff --git a/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/missing-arch-option.mlir b/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/missing-arch-option.mlir index 9f11ee4c23..74cb256b48 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/missing-arch-option.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/missing-arch-option.mlir @@ -9,7 +9,8 @@ // Instead of applying checks, the routing verifier pass ensures the validity of this program. // RUN: quantum-opt %s --placement-sc -verify-diagnostics -// RUN: quantum-opt %s --route-sc -verify-diagnostics +// RUN: quantum-opt %s --route-naive-sc -verify-diagnostics +// RUN: quantum-opt %s --route-astar-sc -verify-diagnostics // RUN: quantum-opt %s --verify-routing-sc -verify-diagnostics // expected-error@unknown {{required option 'arch' not provided}} From 8531ed7256dd64c870ecb75f09bb616d3f8a6b36 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 12 Nov 2025 16:38:55 +0100 Subject: [PATCH 11/56] Add astar functionality to the respective pass --- .../Transforms/Transpilation/AStarDriver.h | 322 ------------------ .../Transforms/Transpilation/WireIterator.h | 105 ++++++ .../Transpilation/sc/AStarRoutingPass.cpp | 241 ++++++++++++- .../Transpilation/sc/NaiveRoutingPass.cpp | 3 +- 4 files changed, 338 insertions(+), 333 deletions(-) delete mode 100644 mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h create mode 100644 mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h deleted file mode 100644 index d3fa0de522..0000000000 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarDriver.h +++ /dev/null @@ -1,322 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#pragma once - -#include "mlir/Analysis/TopologicalSortUtils.h" -#include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/RoutingDriverBase.h" - -#include "llvm/Support/ErrorHandling.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DEBUG_TYPE "route-sc" - -namespace mqt::ir::opt { - -using namespace mlir; - -class WireIterator { -public: - using difference_type = std::ptrdiff_t; - using value_type = Operation*; - - explicit WireIterator(Value q = nullptr) : q(q) { setNextOp(); } - - Operation* operator*() const { return currOp; } - - WireIterator& operator++() { - setNextQubit(); - setNextOp(); - return *this; - } - - void operator++(int) { ++*this; } - bool operator==(const WireIterator& other) const { return other.q == q; } - -private: - void setNextOp() { - if (q == nullptr) { - return; - } - if (q.use_empty()) { - q = nullptr; - currOp = nullptr; - return; - } - - currOp = getUserInRegion(q, q.getParentRegion()); - if (currOp == nullptr) { - /// Must be a branching op: - currOp = q.getUsers().begin()->getParentOp(); - assert(isa(currOp)); - } - } - - void setNextQubit() { - TypeSwitch(currOp) - /// MQT - .Case([&](UnitaryInterface op) { - for (const auto& [in, out] : - llvm::zip_equal(op.getAllInQubits(), op.getAllOutQubits())) { - if (q == in) { - q = out; - return; - } - } - - llvm_unreachable("unknown qubit value in def-use chain"); - }) - .Case([&](ResetOp op) { q = op.getOutQubit(); }) - .Case([&](MeasureOp op) { q = op.getOutQubit(); }) - /// SCF - .Case([&](scf::ForOp op) { - for (const auto& [in, out] : - llvm::zip_equal(op.getInitArgs(), op.getResults())) { - if (q == in) { - q = out; - return; - } - } - - llvm_unreachable("unknown qubit value in def-use chain"); - }) - .Case([&](scf::YieldOp op) { - /// End of region. Invalidate iterator. - q = nullptr; - currOp = nullptr; - }) - .Default([&]([[maybe_unused]] Operation* op) { - LLVM_DEBUG({ - llvm::dbgs() << "unknown operation in def-use chain: "; - op->dump(); - }); - llvm_unreachable("unknown operation in def-use chain"); - }); - } - - Value q; - Operation* currOp{}; -}; - -static_assert(std::input_iterator); - -struct Layer { - /// All unitary ops contained in this layer. - SmallVector ops; - /// The program indices of the gates in this layer. - SmallVector gates; -}; - -class AStarDriver final : public RoutingDriverBase { - /// A vector of layers. - using LayerVec = SmallVector; - /// Hold and release map. - using HoldMap = DenseMap; - /// A vector of SWAP gate indices. - using SWAPHistory = SmallVector; - -public: - AStarDriver(const HeuristicWeights& weights, std::size_t nlookahead, - std::unique_ptr arch, const Statistics& stats) - : RoutingDriverBase(std::move(arch), stats), router_(weights), - nlookahead_(nlookahead) {} - -private: - LogicalResult rewrite(func::FuncOp func, PatternRewriter& rewriter) override { - LLVM_DEBUG(llvm::dbgs() << "handleFunc: " << func.getSymName() << '\n'); - - if (!isEntryPoint(func)) { - LLVM_DEBUG(llvm::dbgs() << "\tskip non entry\n"); - return success(); // Ignore non entry_point functions for now. - } - - /// Find all static qubits and initialize layout. - /// In a circuit diagram this corresponds to finding the very - /// start of each circuit wire. - - Layout layout(arch->nqubits()); - for_each(func.getOps(), [&](QubitOp op) { - const std::size_t index = op.getIndex(); - layout.add(index, index, op.getQubit()); - }); - - SWAPHistory history; - return rewrite(func.getBody(), layout, history, rewriter); - } - - LogicalResult rewrite(Region& region, Layout& layout, SWAPHistory& history, - PatternRewriter& rewriter) const { - /// Find layers. - LayerVec layers = schedule(layout); - /// Route the layers. Might break SSA dominance. - route(layout, layers, rewriter); - /// Repair any SSA dominance issues. - for (Block& block : region.getBlocks()) { - sortTopologically(&block); - } - return success(); - } - - void route(const Layout& layout, LayerVec& layers, - PatternRewriter& rewriter) const { - Layout routingLayout(layout); - LayerVec::iterator end = layers.end(); - for (LayerVec::iterator it = layers.begin(); it != end; ++it) { - LayerVec::iterator lookaheadIt = std::min(end, it + 1 + nlookahead_); - - auto& front = *it; // == window.front() - auto window = llvm::make_range(it, lookaheadIt); - auto windowLayerGates = to_vector(llvm::map_range( - window, [](const Layer& layer) { return ArrayRef(layer.gates); })); - - Operation* anchor{}; /// First op in textual IR order. - for (Operation* op : front.ops) { - if (anchor == nullptr || op->isBeforeInBlock(anchor)) { - anchor = op; - } - } - - assert(anchor != nullptr && "expected to find anchor"); - llvm::dbgs() << "schedule: anchor= " << *anchor << '\n'; - - const auto swaps = router_.route(windowLayerGates, routingLayout, *arch); - /// history.append(swaps); - - rewriter.setInsertionPoint(anchor); - insertSWAPs(swaps, routingLayout, anchor->getLoc(), rewriter); - - for (Operation* op : front.ops) { - if (auto u = dyn_cast(op)) { - for (const auto& [in, out] : - llvm::zip_equal(u.getAllInQubits(), u.getAllOutQubits())) { - routingLayout.remapQubitValue(in, out); - } - continue; - } - llvm_unreachable("TODO."); - } - } - } - - static LayerVec schedule(const Layout& layout) { - LayerVec layers; - HoldMap onHold; - Layout schedulingLayout(layout); - SmallVector wires(llvm::map_range( - layout.getHardwareQubits(), [](Value q) { return WireIterator(q); })); - - do { - const auto [layer, next] = - collectLayerAndAdvance(wires, schedulingLayout, onHold); - - /// Early exit if there are no more gates to route. - if (layer.gates.empty()) { - break; - } - - layers.emplace_back(layer); - wires = next; - - } while (!wires.empty()); - - LLVM_DEBUG({ - llvm::dbgs() << "schedule: layers=\n"; - for (const auto [i, layer] : llvm::enumerate(layers)) { - llvm::dbgs() << '\t' << i << "= "; - for (const auto [prog0, prog1] : layer.gates) { - llvm::dbgs() << "(" << prog0 << "," << prog1 << "), "; - } - llvm::dbgs() << '\n'; - } - }); - - return layers; - } - - static std::pair> - collectLayerAndAdvance(ArrayRef wires, Layout& schedulingLayout, - DenseMap& onHold) { - /// The collected layer. - Layer layer; - /// A vector of iterators for the next iteration. - SmallVector next; - next.reserve(wires.size()); - - for (WireIterator it : wires) { - while (it != WireIterator()) { - Operation* op = *it; - - if (!isa(op)) { - layer.ops.push_back(op); - ++it; - continue; - } - - auto u = cast(op); - if (!isTwoQubitGate(u)) { - /// Add unitary to layer operations. - layer.ops.push_back(op); - /// Forward scheduling layout. - schedulingLayout.remapQubitValue(u.getInQubits().front(), - u.getOutQubits().front()); - ++it; - continue; - } - - if (onHold.contains(u)) { - const auto ins = getIns(u); - const auto outs = getOuts(u); - - /// Release iterators for next iteration. - next.push_back(onHold.lookup(u)); - next.push_back(++it); - /// Only add ready two-qubit gates to the layer. - layer.ops.push_back(op); - layer.gates.emplace_back( - schedulingLayout.lookupProgramIndex(ins.first), - schedulingLayout.lookupProgramIndex(ins.second)); - /// Forward scheduling layout. - schedulingLayout.remapQubitValue(ins.first, outs.first); - schedulingLayout.remapQubitValue(ins.second, outs.second); - } else { - /// Emplace the next iterator after the two-qubit - /// gate for a later release. - onHold.try_emplace(u, ++it); - } - - break; - } - } - - return {layer, next}; - } - - AStarHeuristicRouter router_; - std::size_t nlookahead_; -}; -} // namespace mqt::ir::opt diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h new file mode 100644 index 0000000000..451d3342b1 --- /dev/null +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" + +#include +#include +#include +#include +#include +#include + +namespace mqt::ir::opt { +using namespace mlir; + +class WireIterator { +public: + using difference_type = std::ptrdiff_t; + using value_type = Operation*; + + explicit WireIterator(Value q = nullptr) : q(q) { setNextOp(); } + + Operation* operator*() const { return currOp; } + + WireIterator& operator++() { + setNextQubit(); + setNextOp(); + return *this; + } + + void operator++(int) { ++*this; } + bool operator==(const WireIterator& other) const { return other.q == q; } + +private: + void setNextOp() { + if (q == nullptr) { + return; + } + if (q.use_empty()) { + q = nullptr; + currOp = nullptr; + return; + } + + currOp = getUserInRegion(q, q.getParentRegion()); + if (currOp == nullptr) { + /// Must be a branching op: + currOp = q.getUsers().begin()->getParentOp(); + assert(isa(currOp)); + } + } + + void setNextQubit() { + TypeSwitch(currOp) + /// MQT + .Case([&](UnitaryInterface op) { + for (const auto& [in, out] : + llvm::zip_equal(op.getAllInQubits(), op.getAllOutQubits())) { + if (q == in) { + q = out; + return; + } + } + + llvm_unreachable("unknown qubit value in def-use chain"); + }) + .Case([&](ResetOp op) { q = op.getOutQubit(); }) + .Case([&](MeasureOp op) { q = op.getOutQubit(); }) + /// SCF + .Case([&](scf::ForOp op) { + for (const auto& [in, out] : + llvm::zip_equal(op.getInitArgs(), op.getResults())) { + if (q == in) { + q = out; + return; + } + } + + llvm_unreachable("unknown qubit value in def-use chain"); + }) + .Case([&](scf::YieldOp) { + /// End of region. Invalidate iterator. + q = nullptr; + currOp = nullptr; + }) + .Default([&]([[maybe_unused]] Operation* op) { + llvm_unreachable("unknown operation in def-use chain"); + }); + } + + Value q; + Operation* currOp{}; +}; + +static_assert(std::input_iterator); +} // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index c1c5c13ad1..5f4f1496ac 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -9,9 +9,11 @@ */ #include "mlir/Dialect/MQTOpt/Transforms/Passes.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h" #include #include @@ -22,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -39,7 +42,7 @@ #include #include -#define DEBUG_TYPE "route-sc" +#define DEBUG_TYPE "route-astar-sc" namespace mqt::ir::opt { @@ -49,11 +52,49 @@ namespace mqt::ir::opt { namespace { using namespace mlir; +/// @brief Hold and release map. +using HoldMap = DenseMap; + +/// @brief A vector of SWAP gate indices. +using SWAPHistory = SmallVector; + /// @brief A composite datastructure for LLVM Statistics. struct Statistics { llvm::Statistic* numSwaps; }; +/// @brief A composite datastructure for pass parameters. +struct Params { + /// @brief The amount of lookahead layers. + std::size_t nlookahead; + float alpha; + float lambda; +}; + +/// @brief Commonly passed parameters for the routing functions. +struct RoutingContext { + /// @brief The targeted architecture. + std::unique_ptr arch; + /// @brief LLVM/MLIR statistics. + Statistics stats; + /// @brief A pattern rewriter. + PatternRewriter rewriter; + /// @brief The A*-search based router. + AStarHeuristicRouter router; + /// @brief The amount of lookahead layers. + std::size_t nlookahead; +}; + +struct Layer { + /// @brief All unitary ops contained in this layer. + SmallVector ops; + /// @brief The program indices of the gates in this layer. + SmallVector gates; +}; + +/// @brief A vector of layers. +using LayerVec = SmallVector; + /** * @brief Insert SWAP ops at the rewriter's insertion point. * @@ -91,19 +132,195 @@ void insertSWAPs(Location location, ArrayRef swaps, } } +std::pair> +collectLayerAndAdvance(ArrayRef wires, Layout& schedulingLayout, + DenseMap& onHold) { + /// The collected layer. + Layer layer; + /// A vector of iterators for the next iteration. + SmallVector next; + next.reserve(wires.size()); + + for (WireIterator it : wires) { + while (it != WireIterator()) { + Operation* op = *it; + + if (!isa(op)) { + layer.ops.push_back(op); + ++it; + continue; + } + + auto u = cast(op); + if (!isTwoQubitGate(u)) { + /// Add unitary to layer operations. + layer.ops.push_back(op); + /// Forward scheduling layout. + schedulingLayout.remapQubitValue(u.getInQubits().front(), + u.getOutQubits().front()); + ++it; + continue; + } + + if (onHold.contains(u)) { + const auto ins = getIns(u); + const auto outs = getOuts(u); + + /// Release iterators for next iteration. + next.push_back(onHold.lookup(u)); + next.push_back(++it); + /// Only add ready two-qubit gates to the layer. + layer.ops.push_back(op); + layer.gates.emplace_back( + schedulingLayout.lookupProgramIndex(ins.first), + schedulingLayout.lookupProgramIndex(ins.second)); + /// Forward scheduling layout. + schedulingLayout.remapQubitValue(ins.first, outs.first); + schedulingLayout.remapQubitValue(ins.second, outs.second); + } else { + /// Emplace the next iterator after the two-qubit + /// gate for a later release. + onHold.try_emplace(u, ++it); + } + + break; + } + } + + return {layer, next}; +} + +LayerVec schedule(const Layout& layout) { + LayerVec layers; + HoldMap onHold; + Layout schedulingLayout(layout); + SmallVector wires(llvm::map_range( + layout.getHardwareQubits(), [](Value q) { return WireIterator(q); })); + + do { + const auto [layer, next] = + collectLayerAndAdvance(wires, schedulingLayout, onHold); + + /// Early exit if there are no more gates to route. + if (layer.gates.empty()) { + break; + } + + layers.emplace_back(layer); + wires = next; + + } while (!wires.empty()); + + LLVM_DEBUG({ + llvm::dbgs() << "schedule: layers=\n"; + for (const auto [i, layer] : llvm::enumerate(layers)) { + llvm::dbgs() << '\t' << i << "= "; + for (const auto [prog0, prog1] : layer.gates) { + llvm::dbgs() << "(" << prog0 << "," << prog1 << "), "; + } + llvm::dbgs() << '\n'; + } + }); + + return layers; +} + +void route(const Layout& layout, LayerVec& layers, RoutingContext& ctx) { + Layout routingLayout(layout); + LayerVec::iterator end = layers.end(); + for (LayerVec::iterator it = layers.begin(); it != end; ++it) { + LayerVec::iterator lookaheadIt = std::min(end, it + 1 + ctx.nlookahead); + + auto& front = *it; // == window.front() + auto window = llvm::make_range(it, lookaheadIt); + auto windowLayerGates = to_vector(llvm::map_range( + window, [](const Layer& layer) { return ArrayRef(layer.gates); })); + + Operation* anchor{}; /// First op in textual IR order. + for (Operation* op : front.ops) { + if (anchor == nullptr || op->isBeforeInBlock(anchor)) { + anchor = op; + } + } + + assert(anchor != nullptr && "expected to find anchor"); + llvm::dbgs() << "schedule: anchor= " << *anchor << '\n'; + + const auto swaps = + ctx.router.route(windowLayerGates, routingLayout, *ctx.arch); + /// history.append(swaps); + + ctx.rewriter.setInsertionPoint(anchor); + insertSWAPs(anchor->getLoc(), swaps, routingLayout, ctx.rewriter); + + for (Operation* op : front.ops) { + if (auto u = dyn_cast(op)) { + for (const auto& [in, out] : + llvm::zip_equal(u.getAllInQubits(), u.getAllOutQubits())) { + routingLayout.remapQubitValue(in, out); + } + continue; + } + llvm_unreachable("TODO."); + } + } +} + +LogicalResult rewrite(Region& region, Layout& layout, RoutingContext& ctx) { + /// Find layers. + LayerVec layers = schedule(layout); + /// Route the layers. Might break SSA dominance. + route(layout, layers, ctx); + /// Repair any SSA dominance issues. + for (Block& block : region.getBlocks()) { + sortTopologically(&block); + } + return success(); +} + +LogicalResult processFunction(func::FuncOp func, RoutingContext& ctx) { + LLVM_DEBUG(llvm::dbgs() << "handleFunc: " << func.getSymName() << '\n'); + + if (!isEntryPoint(func)) { + LLVM_DEBUG(llvm::dbgs() << "\tskip non entry\n"); + return success(); // Ignore non entry_point functions for now. + } + + /// Find all static qubits and initialize layout. + /// In a circuit diagram this corresponds to finding the very + /// start of each circuit wire. + + Layout layout(ctx.arch->nqubits()); + for_each(func.getOps(), [&](QubitOp op) { + const std::size_t index = op.getIndex(); + layout.add(index, index, op.getQubit()); + }); + + return rewrite(func.getBody(), layout, ctx); +} + /** - * @brief Given a layout, validate if the two-qubit unitary op is executable on - * the targeted architecture. + * @brief Route the given module for the targeted architecture using A*-search. * - * @param op The two-qubit unitary. - * @param layout The current layout. + * @param module The module to route. * @param arch The targeted architecture. + * @param stats The composite statistics datastructure. */ -[[nodiscard]] bool isExecutable(UnitaryInterface op, const Layout& layout, - const Architecture& arch) { - const auto ins = getIns(op); - return arch.areAdjacent(layout.lookupHardwareIndex(ins.first), - layout.lookupHardwareIndex(ins.second)); +LogicalResult route(ModuleOp module, std::unique_ptr arch, + Params& params, Statistics& stats) { + const HeuristicWeights weights(params.alpha, params.lambda, + params.nlookahead); + RoutingContext ctx{.arch = std::move(arch), + .stats = stats, + .rewriter = PatternRewriter(module->getContext()), + .router = AStarHeuristicRouter(weights), + .nlookahead = params.nlookahead}; + for (auto func : module.getOps()) { + if (processFunction(func, ctx).failed()) { + return failure(); + } + } + return success(); } /** @@ -129,6 +346,10 @@ struct AStarRoutingPassSC final } Statistics stats{.numSwaps = &numSwaps}; + Params params{.nlookahead = nlookahead, .alpha = alpha, .lambda = lambda}; + if (route(getOperation(), std::move(arch), params, stats).failed()) { + signalPassFailure(); + }; } private: diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp index b4a4f1a1fc..e58510d60b 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp @@ -39,7 +39,7 @@ #include #include -#define DEBUG_TYPE "route-sc" +#define DEBUG_TYPE "route-naive-sc" namespace mqt::ir::opt { @@ -372,6 +372,7 @@ LogicalResult processFunction(func::FuncOp func, RoutingContext& ctx) { * * @param module The module to route. * @param arch The targeted architecture. + * @param stats The composite statistics datastructure. */ LogicalResult route(ModuleOp module, std::unique_ptr arch, Statistics& stats) { From dc908e1407955c47ffd4a23c0841272e85f60499 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 13 Nov 2025 07:27:59 +0100 Subject: [PATCH 12/56] Update schedule function to handle scf.for and scf.if as well --- .../MQTOpt/Transforms/Transpilation/Layout.h | 7 + .../Transpilation/sc/AStarRoutingPass.cpp | 165 +++++++++++++----- 2 files changed, 125 insertions(+), 47 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h index f87cabd1cb..bb5de43248 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h @@ -105,6 +105,13 @@ class [[nodiscard]] ThinLayout { 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. diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index 5f4f1496ac..eb5b829f5f 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -8,12 +8,14 @@ * Licensed under the MIT License */ +#include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include "mlir/Dialect/MQTOpt/Transforms/Passes.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h" +#include "mlir/Interfaces/ControlFlowInterfaces.h" #include #include @@ -41,6 +43,8 @@ #include #include #include +#include +#include #define DEBUG_TYPE "route-astar-sc" @@ -52,9 +56,6 @@ namespace mqt::ir::opt { namespace { using namespace mlir; -/// @brief Hold and release map. -using HoldMap = DenseMap; - /// @brief A vector of SWAP gate indices. using SWAPHistory = SmallVector; @@ -95,6 +96,37 @@ struct Layer { /// @brief A vector of layers. using LayerVec = SmallVector; +/// @brief Map to handle multi-qubit gates when traversing the def-use chain. +class SynchronizationMap { + DenseMap> onHold; + DenseMap refCount; + +public: + /// @returns true iff. the operation is contained in the map. + bool contains(Operation* op) { return onHold.contains(op); } + /// @brief Add op with respective iterator and ref count to map. + void add(Operation* op, WireIterator it, const std::size_t cnt) { + onHold.try_emplace(op, SmallVector{it}); + /// Decrease the cnt by one because the op was visited when adding. + refCount.try_emplace(op, cnt - 1); + } + + std::optional> visit(Operation* op, + WireIterator it) { + assert(refCount.contains(op) && "expected sync map to contain op"); + + /// Add iterator for later release. + onHold[op].push_back(it); + + /// Release iterators whenever the ref count reaches zero. + if (--refCount[op] == 0) { + return onHold[op]; + } + + return std::nullopt; + } +}; + /** * @brief Insert SWAP ops at the rewriter's insertion point. * @@ -134,7 +166,7 @@ void insertSWAPs(Location location, ArrayRef swaps, std::pair> collectLayerAndAdvance(ArrayRef wires, Layout& schedulingLayout, - DenseMap& onHold) { + SynchronizationMap& sync) { /// The collected layer. Layer layer; /// A vector of iterators for the next iteration. @@ -145,45 +177,84 @@ collectLayerAndAdvance(ArrayRef wires, Layout& schedulingLayout, while (it != WireIterator()) { Operation* op = *it; - if (!isa(op)) { - layer.ops.push_back(op); - ++it; - continue; + /// A barrier may be a UnitaryInterface, but also requires + /// synchronization. + if (auto barrier = dyn_cast(op)) { + if (!sync.contains(barrier)) { + sync.add(barrier, ++it, barrier.getInQubits().size()); + break; + } + + if (const auto iterators = sync.visit(barrier, ++it)) { + layer.ops.push_back(barrier); + next.append(iterators.value()); + } + + break; } - auto u = cast(op); - if (!isTwoQubitGate(u)) { - /// Add unitary to layer operations. - layer.ops.push_back(op); - /// Forward scheduling layout. - schedulingLayout.remapQubitValue(u.getInQubits().front(), - u.getOutQubits().front()); - ++it; - continue; + if (auto u = dyn_cast(op)) { + if (!isTwoQubitGate(u)) { + /// Add unitary to layer operations. + layer.ops.push_back(op); + + /// Forward scheduling layout. + schedulingLayout.remapQubitValue(u.getInQubits().front(), + u.getOutQubits().front()); + ++it; + continue; + } + + if (!sync.contains(u)) { + /// Add the next iterator after the two-qubit + /// gate for a later release. + sync.add(u, ++it, 2); + break; + } + + if (const auto iterators = sync.visit(u, ++it)) { + const auto ins = getIns(u); + const auto outs = getOuts(u); + + /// Release iterators for next iteration. + next.append(iterators.value()); + + /// Only add ready two-qubit gates to the layer. + layer.ops.push_back(u); + layer.gates.emplace_back( + schedulingLayout.lookupProgramIndex(ins.first), + schedulingLayout.lookupProgramIndex(ins.second)); + + /// Forward scheduling layout. + schedulingLayout.remapQubitValue(ins.first, outs.first); + schedulingLayout.remapQubitValue(ins.second, outs.second); + } + + break; } - if (onHold.contains(u)) { - const auto ins = getIns(u); - const auto outs = getOuts(u); - - /// Release iterators for next iteration. - next.push_back(onHold.lookup(u)); - next.push_back(++it); - /// Only add ready two-qubit gates to the layer. - layer.ops.push_back(op); - layer.gates.emplace_back( - schedulingLayout.lookupProgramIndex(ins.first), - schedulingLayout.lookupProgramIndex(ins.second)); - /// Forward scheduling layout. - schedulingLayout.remapQubitValue(ins.first, outs.first); - schedulingLayout.remapQubitValue(ins.second, outs.second); - } else { - /// Emplace the next iterator after the two-qubit - /// gate for a later release. - onHold.try_emplace(u, ++it); + /// RegionBranchOpInterface = scf.for, scf.if, ... + if (auto branch = dyn_cast(op)) { + if (!sync.contains(branch)) { + /// This assumes that branching ops always take and return all + /// hardware qubits. + sync.add(branch, ++it, schedulingLayout.getNumQubits()); + break; + } + + if (const auto iterators = sync.visit(branch, ++it)) { + layer.ops.push_back(branch); + next.append(iterators.value()); + } + + break; } - break; + /// Anything else is either a measure or reset op. + assert(isa(op) || + isa(op) && "expect measure or reset"); + layer.ops.push_back(op); + ++it; } } @@ -192,14 +263,14 @@ collectLayerAndAdvance(ArrayRef wires, Layout& schedulingLayout, LayerVec schedule(const Layout& layout) { LayerVec layers; - HoldMap onHold; + SynchronizationMap sync; Layout schedulingLayout(layout); SmallVector wires(llvm::map_range( layout.getHardwareQubits(), [](Value q) { return WireIterator(q); })); do { const auto [layer, next] = - collectLayerAndAdvance(wires, schedulingLayout, onHold); + collectLayerAndAdvance(wires, schedulingLayout, sync); /// Early exit if there are no more gates to route. if (layer.gates.empty()) { @@ -225,7 +296,8 @@ LayerVec schedule(const Layout& layout) { return layers; } -void route(const Layout& layout, LayerVec& layers, RoutingContext& ctx) { +void routeEachLayer(LayerVec& layers, const Layout& layout, + RoutingContext& ctx) { Layout routingLayout(layout); LayerVec::iterator end = layers.end(); for (LayerVec::iterator it = layers.begin(); it != end; ++it) { @@ -266,11 +338,12 @@ void route(const Layout& layout, LayerVec& layers, RoutingContext& ctx) { } } -LogicalResult rewrite(Region& region, Layout& layout, RoutingContext& ctx) { +LogicalResult processRegion(Region& region, Layout& layout, + RoutingContext& ctx) { /// Find layers. LayerVec layers = schedule(layout); - /// Route the layers. Might break SSA dominance. - route(layout, layers, ctx); + /// Route each of the layers. Might violate SSA dominance. + routeEachLayer(layers, layout, ctx); /// Repair any SSA dominance issues. for (Block& block : region.getBlocks()) { sortTopologically(&block); @@ -286,9 +359,7 @@ LogicalResult processFunction(func::FuncOp func, RoutingContext& ctx) { return success(); // Ignore non entry_point functions for now. } - /// Find all static qubits and initialize layout. - /// In a circuit diagram this corresponds to finding the very - /// start of each circuit wire. + /// Find all hardware (static) qubits and initialize layout. Layout layout(ctx.arch->nqubits()); for_each(func.getOps(), [&](QubitOp op) { @@ -296,7 +367,7 @@ LogicalResult processFunction(func::FuncOp func, RoutingContext& ctx) { layout.add(index, index, op.getQubit()); }); - return rewrite(func.getBody(), layout, ctx); + return processRegion(func.getBody(), layout, ctx); } /** From 24aba99cbef8304b45f2349a1354e8a4e4ea4784 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 13 Nov 2025 07:37:58 +0100 Subject: [PATCH 13/56] Compute anchor when adding operations to layer --- .../Transpilation/sc/AStarRoutingPass.cpp | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index eb5b829f5f..d5a9ff2432 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -87,10 +87,19 @@ struct RoutingContext { }; struct Layer { - /// @brief All unitary ops contained in this layer. + /// @brief All ops contained in this layer. SmallVector ops; /// @brief The program indices of the gates in this layer. SmallVector gates; + /// @brief The first op in ops in textual IR order. + Operation* anchor{}; + /// @brief Add op to ops and reset anchor if necessary. + void addOp(Operation* op) { + ops.emplace_back(op); + if (anchor == nullptr || op->isBeforeInBlock(anchor)) { + anchor = op; + } + } }; /// @brief A vector of layers. @@ -186,7 +195,7 @@ collectLayerAndAdvance(ArrayRef wires, Layout& schedulingLayout, } if (const auto iterators = sync.visit(barrier, ++it)) { - layer.ops.push_back(barrier); + layer.addOp(barrier); next.append(iterators.value()); } @@ -196,7 +205,7 @@ collectLayerAndAdvance(ArrayRef wires, Layout& schedulingLayout, if (auto u = dyn_cast(op)) { if (!isTwoQubitGate(u)) { /// Add unitary to layer operations. - layer.ops.push_back(op); + layer.addOp(op); /// Forward scheduling layout. schedulingLayout.remapQubitValue(u.getInQubits().front(), @@ -220,7 +229,7 @@ collectLayerAndAdvance(ArrayRef wires, Layout& schedulingLayout, next.append(iterators.value()); /// Only add ready two-qubit gates to the layer. - layer.ops.push_back(u); + layer.addOp(u); layer.gates.emplace_back( schedulingLayout.lookupProgramIndex(ins.first), schedulingLayout.lookupProgramIndex(ins.second)); @@ -243,7 +252,7 @@ collectLayerAndAdvance(ArrayRef wires, Layout& schedulingLayout, } if (const auto iterators = sync.visit(branch, ++it)) { - layer.ops.push_back(branch); + layer.addOp(branch); next.append(iterators.value()); } @@ -253,7 +262,7 @@ collectLayerAndAdvance(ArrayRef wires, Layout& schedulingLayout, /// Anything else is either a measure or reset op. assert(isa(op) || isa(op) && "expect measure or reset"); - layer.ops.push_back(op); + layer.addOp(op); ++it; } } @@ -289,7 +298,7 @@ LayerVec schedule(const Layout& layout) { for (const auto [prog0, prog1] : layer.gates) { llvm::dbgs() << "(" << prog0 << "," << prog1 << "), "; } - llvm::dbgs() << '\n'; + llvm::dbgs() << " anchor= " << layer.anchor->getLoc() << '\n'; } }); @@ -308,24 +317,15 @@ void routeEachLayer(LayerVec& layers, const Layout& layout, auto windowLayerGates = to_vector(llvm::map_range( window, [](const Layer& layer) { return ArrayRef(layer.gates); })); - Operation* anchor{}; /// First op in textual IR order. - for (Operation* op : front.ops) { - if (anchor == nullptr || op->isBeforeInBlock(anchor)) { - anchor = op; - } - } - - assert(anchor != nullptr && "expected to find anchor"); - llvm::dbgs() << "schedule: anchor= " << *anchor << '\n'; - const auto swaps = ctx.router.route(windowLayerGates, routingLayout, *ctx.arch); /// history.append(swaps); - ctx.rewriter.setInsertionPoint(anchor); - insertSWAPs(anchor->getLoc(), swaps, routingLayout, ctx.rewriter); + ctx.rewriter.setInsertionPoint(front.anchor); + insertSWAPs(front.anchor->getLoc(), swaps, routingLayout, ctx.rewriter); for (Operation* op : front.ops) { + /// Handle unitaries incl. barriers. if (auto u = dyn_cast(op)) { for (const auto& [in, out] : llvm::zip_equal(u.getAllInQubits(), u.getAllOutQubits())) { From fdbbbb408650f14f57dd8b2d396619b83a51469c Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 14 Nov 2025 08:51:19 +0100 Subject: [PATCH 14/56] Implement branching logic --- .../MQTOpt/Transforms/Transpilation/Layout.h | 34 ++ .../Transforms/Transpilation/WireIterator.h | 58 ++- .../Transpilation/sc/AStarRoutingPass.cpp | 357 +++++++++++++----- .../Transpilation/sc/NaiveRoutingPass.cpp | 76 ++-- .../Transforms/Transpilation/basics.mlir | 6 +- 5 files changed, 380 insertions(+), 151 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h index bb5de43248..447b8c6379 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h @@ -269,6 +269,40 @@ class [[nodiscard]] Layout : public ThinLayout { */ SmallVector qubits_; }; + +/** + * @brief Remap all input to output qubits for the given unitary op. + * + * @param op The unitary op. + * @param layout The current layout. + */ +inline void remap(UnitaryInterface op, Layout& layout) { + for (const auto& [in, out] : + llvm::zip_equal(op.getAllInQubits(), op.getAllOutQubits())) { + layout.remapQubitValue(in, out); + } +} + +/** + * @brief Remap input to output qubit for the given reset op. + * + * @param op The reset op. + * @param layout The current layout. + */ +inline void remap(ResetOp op, Layout& layout) { + layout.remapQubitValue(op.getInQubit(), op.getOutQubit()); +} + +/** + * @brief Remap input to output qubit for the given measure op. + * + * @param op The measure op. + * @param layout The current layout. + */ +inline void remap(MeasureOp op, Layout& layout) { + layout.remapQubitValue(op.getInQubit(), op.getOutQubit()); +} + } // namespace mqt::ir::opt namespace llvm { diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h index 451d3342b1..9a36624c01 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h @@ -15,19 +15,32 @@ #include #include #include +#include +#include #include #include #include +#include +#include namespace mqt::ir::opt { using namespace mlir; +/** + * @brief A non-recursive input_iterator traversing the def-use chain of a + * single qubit (on a wire). + * + * It does not visit nested regions (nested ops). + */ class WireIterator { public: using difference_type = std::ptrdiff_t; using value_type = Operation*; - explicit WireIterator(Value q = nullptr) : q(q) { setNextOp(); } + explicit WireIterator(Value q = nullptr, Region* region = nullptr) + : q(q), region(region) { + setNextOp(); + } Operation* operator*() const { return currOp; } @@ -45,13 +58,15 @@ class WireIterator { if (q == nullptr) { return; } + if (q.use_empty()) { q = nullptr; currOp = nullptr; return; } - currOp = getUserInRegion(q, q.getParentRegion()); + currOp = + getUserInRegion(q, region != nullptr ? region : q.getParentRegion()); if (currOp == nullptr) { /// Must be a branching op: currOp = q.getUsers().begin()->getParentOp(); @@ -61,7 +76,6 @@ class WireIterator { void setNextQubit() { TypeSwitch(currOp) - /// MQT .Case([&](UnitaryInterface op) { for (const auto& [in, out] : llvm::zip_equal(op.getAllInQubits(), op.getAllOutQubits())) { @@ -75,7 +89,6 @@ class WireIterator { }) .Case([&](ResetOp op) { q = op.getOutQubit(); }) .Case([&](MeasureOp op) { q = op.getOutQubit(); }) - /// SCF .Case([&](scf::ForOp op) { for (const auto& [in, out] : llvm::zip_equal(op.getInitArgs(), op.getResults())) { @@ -87,17 +100,50 @@ class WireIterator { llvm_unreachable("unknown qubit value in def-use chain"); }) + .Case([&](scf::IfOp op) { + /// Find yielded value by using a recursive WireIterator for the THEN + /// region. + WireIterator itThen(q, &op.getThenRegion()); + for (; itThen != WireIterator(); ++itThen) { + if (scf::YieldOp yield = dyn_cast(*itThen)) { + for (const auto [yielded, res] : + llvm::zip(yield.getResults(), op->getResults())) { + if (itThen.q == yielded) { + q = res; + return; + } + } + } + } + + /// Otherwise it must be in the ELSE region. + WireIterator itElse(q, &op.getElseRegion()); + for (; itElse != WireIterator(); ++itElse) { + if (scf::YieldOp yield = dyn_cast(*itElse)) { + for (const auto [yielded, res] : + llvm::zip(yield.getResults(), op->getResults())) { + if (itElse.q == yielded) { + q = res; + return; + } + } + } + } + + llvm_unreachable("must find yielded value."); + }) .Case([&](scf::YieldOp) { /// End of region. Invalidate iterator. q = nullptr; currOp = nullptr; }) - .Default([&]([[maybe_unused]] Operation* op) { - llvm_unreachable("unknown operation in def-use chain"); + .Default([&](Operation*) { + throw std::runtime_error("unhandled / invalid op in def-use chain"); }); } Value q; + Region* region; Operation* currOp{}; }; diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index d5a9ff2432..f6857a8dda 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -15,7 +15,6 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h" -#include "mlir/Interfaces/ControlFlowInterfaces.h" #include #include @@ -26,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -56,9 +57,6 @@ namespace mqt::ir::opt { namespace { using namespace mlir; -/// @brief A vector of SWAP gate indices. -using SWAPHistory = SmallVector; - /// @brief A composite datastructure for LLVM Statistics. struct Statistics { llvm::Statistic* numSwaps; @@ -100,6 +98,8 @@ struct Layer { anchor = op; } } + /// @returns true iff the layer contains gates to route. + [[nodiscard]] bool hasRoutableGates() const { return !gates.empty(); } }; /// @brief A vector of layers. @@ -107,12 +107,17 @@ using LayerVec = SmallVector; /// @brief Map to handle multi-qubit gates when traversing the def-use chain. class SynchronizationMap { + /// @brief Maps operations to to-be-released iterators. DenseMap> onHold; + + /// @brief Maps operations to ref counts. An op can be released whenever the + /// count reaches zero. DenseMap refCount; public: /// @returns true iff. the operation is contained in the map. bool contains(Operation* op) { return onHold.contains(op); } + /// @brief Add op with respective iterator and ref count to map. void add(Operation* op, WireIterator it, const std::size_t cnt) { onHold.try_emplace(op, SmallVector{it}); @@ -120,6 +125,7 @@ class SynchronizationMap { refCount.try_emplace(op, cnt - 1); } + /// @brief Decrement ref count of op and potentially release its iterators. std::optional> visit(Operation* op, WireIterator it) { assert(refCount.contains(op) && "expected sync map to contain op"); @@ -136,6 +142,10 @@ class SynchronizationMap { } }; +LogicalResult processRegion(Region& region, Layout& layout, + SmallVector& history, + RoutingContext& ctx); + /** * @brief Insert SWAP ops at the rewriter's insertion point. * @@ -174,7 +184,7 @@ void insertSWAPs(Location location, ArrayRef swaps, } std::pair> -collectLayerAndAdvance(ArrayRef wires, Layout& schedulingLayout, +collectLayerAndAdvance(ArrayRef wires, Layout& layout, SynchronizationMap& sync) { /// The collected layer. Layer layer; @@ -184,170 +194,326 @@ collectLayerAndAdvance(ArrayRef wires, Layout& schedulingLayout, for (WireIterator it : wires) { while (it != WireIterator()) { - Operation* op = *it; + Operation* curr = *it; /// A barrier may be a UnitaryInterface, but also requires /// synchronization. - if (auto barrier = dyn_cast(op)) { - if (!sync.contains(barrier)) { - sync.add(barrier, ++it, barrier.getInQubits().size()); + if (auto op = dyn_cast(curr)) { + if (!sync.contains(op)) { + sync.add(op, ++it, op.getInQubits().size()); break; } - if (const auto iterators = sync.visit(barrier, ++it)) { - layer.addOp(barrier); + if (const auto iterators = sync.visit(op, ++it)) { + layer.addOp(op); next.append(iterators.value()); + remap(op, layout); // Remap values on release. } break; } - if (auto u = dyn_cast(op)) { - if (!isTwoQubitGate(u)) { - /// Add unitary to layer operations. - layer.addOp(op); - - /// Forward scheduling layout. - schedulingLayout.remapQubitValue(u.getInQubits().front(), - u.getOutQubits().front()); + if (auto op = dyn_cast(curr)) { + if (!isTwoQubitGate(op)) { + layer.addOp(curr); // Add 1Q-op to layer operations. + remap(op, layout); // Remap scheduling layout. ++it; continue; } - if (!sync.contains(u)) { + if (!sync.contains(op)) { /// Add the next iterator after the two-qubit /// gate for a later release. - sync.add(u, ++it, 2); + sync.add(op, ++it, 2); break; } - if (const auto iterators = sync.visit(u, ++it)) { - const auto ins = getIns(u); - const auto outs = getOuts(u); + if (const auto iterators = sync.visit(op, ++it)) { + const auto ins = getIns(op); + + /// Only add ready two-qubit gates to the layer. + layer.addOp(op); + layer.gates.emplace_back(layout.lookupProgramIndex(ins.first), + layout.lookupProgramIndex(ins.second)); + remap(op, layout); // Remap scheduling layout. /// Release iterators for next iteration. next.append(iterators.value()); - - /// Only add ready two-qubit gates to the layer. - layer.addOp(u); - layer.gates.emplace_back( - schedulingLayout.lookupProgramIndex(ins.first), - schedulingLayout.lookupProgramIndex(ins.second)); - - /// Forward scheduling layout. - schedulingLayout.remapQubitValue(ins.first, outs.first); - schedulingLayout.remapQubitValue(ins.second, outs.second); } break; } - /// RegionBranchOpInterface = scf.for, scf.if, ... - if (auto branch = dyn_cast(op)) { - if (!sync.contains(branch)) { - /// This assumes that branching ops always take and return all - /// hardware qubits. - sync.add(branch, ++it, schedulingLayout.getNumQubits()); + if (auto op = dyn_cast(curr)) { + remap(op, layout); + layer.addOp(curr); + ++it; + continue; + } + + if (auto op = dyn_cast(curr)) { + remap(op, layout); + layer.addOp(curr); + ++it; + continue; + } + + if (auto op = dyn_cast(curr)) { + + if (!sync.contains(op)) { + /// This assumes that branch ops always returns all hardware qubits. + sync.add(op, ++it, layout.getNumQubits()); break; } - if (const auto iterators = sync.visit(branch, ++it)) { - layer.addOp(branch); + if (const auto iterators = sync.visit(op, ++it)) { + layer.addOp(op); + + /// Remap values on release. + for (const auto& [in, out] : + llvm::zip_equal(layout.getHardwareQubits(), op->getResults())) { + layout.remapQubitValue(in, out); + } next.append(iterators.value()); } + break; + } + + if (auto yield = dyn_cast(curr)) { + if (!sync.contains(yield)) { + /// This assumes that yield always returns all hardware qubits. + sync.add(yield, ++it, layout.getNumQubits()); + break; + } + + if (const auto iterators = sync.visit(yield, ++it)) { + layer.addOp(yield); + } break; } - /// Anything else is either a measure or reset op. - assert(isa(op) || - isa(op) && "expect measure or reset"); - layer.addOp(op); - ++it; + /// Anything else is a bug in the program. + assert(false && "unhandled operation"); } } return {layer, next}; } -LayerVec schedule(const Layout& layout) { +/** + * @brief Given a layout, divide the circuit into layers and schedule the ops in + * their respective layer. + * + * @param layout A copy of the current layout. + * @returns a vector of layers. + */ +LayerVec schedule(Layout layout, Region& region) { LayerVec layers; SynchronizationMap sync; - Layout schedulingLayout(layout); - SmallVector wires(llvm::map_range( - layout.getHardwareQubits(), [](Value q) { return WireIterator(q); })); + SmallVector wires( + llvm::map_range(layout.getHardwareQubits(), + [&](Value q) { return WireIterator(q, ®ion); })); do { - const auto [layer, next] = - collectLayerAndAdvance(wires, schedulingLayout, sync); - - /// Early exit if there are no more gates to route. - if (layer.gates.empty()) { + const auto [layer, next] = collectLayerAndAdvance(wires, layout, sync); + if (layer.ops.empty()) { break; } - layers.emplace_back(layer); wires = next; - } while (!wires.empty()); LLVM_DEBUG({ llvm::dbgs() << "schedule: layers=\n"; for (const auto [i, layer] : llvm::enumerate(layers)) { - llvm::dbgs() << '\t' << i << "= "; - for (const auto [prog0, prog1] : layer.gates) { - llvm::dbgs() << "(" << prog0 << "," << prog1 << "), "; + llvm::dbgs() << '\t' << i << ": "; + llvm::dbgs() << "#ops= " << layer.ops.size() << ", "; + llvm::dbgs() << "gates= "; + if (layer.hasRoutableGates()) { + for (const auto [prog0, prog1] : layer.gates) { + llvm::dbgs() << "(" << prog0 << "," << prog1 << "), "; + } + } else { + llvm::dbgs() << "(), "; } - llvm::dbgs() << " anchor= " << layer.anchor->getLoc() << '\n'; + if (layer.anchor) { + llvm::dbgs() << "anchor= " << layer.anchor->getLoc() << ", "; + } + llvm::dbgs() << '\n'; } }); return layers; } -void routeEachLayer(LayerVec& layers, const Layout& layout, - RoutingContext& ctx) { - Layout routingLayout(layout); - LayerVec::iterator end = layers.end(); - for (LayerVec::iterator it = layers.begin(); it != end; ++it) { - LayerVec::iterator lookaheadIt = std::min(end, it + 1 + ctx.nlookahead); - - auto& front = *it; // == window.front() - auto window = llvm::make_range(it, lookaheadIt); - auto windowLayerGates = to_vector(llvm::map_range( - window, [](const Layer& layer) { return ArrayRef(layer.gates); })); - - const auto swaps = - ctx.router.route(windowLayerGates, routingLayout, *ctx.arch); - /// history.append(swaps); - - ctx.rewriter.setInsertionPoint(front.anchor); - insertSWAPs(front.anchor->getLoc(), swaps, routingLayout, ctx.rewriter); - - for (Operation* op : front.ops) { - /// Handle unitaries incl. barriers. - if (auto u = dyn_cast(op)) { - for (const auto& [in, out] : - llvm::zip_equal(u.getAllInQubits(), u.getAllOutQubits())) { - routingLayout.remapQubitValue(in, out); - } - continue; +/** + * @brief Copy the layout and recursively process the loop body. + */ +WalkResult handle(scf::ForOp op, Layout& layout, RoutingContext& ctx) { + /// Copy layout. + Layout forLayout(layout); + + /// Forward out-of-loop and in-loop values. + const auto initArgs = op.getInitArgs().take_front(ctx.arch->nqubits()); + const auto results = op.getResults().take_front(ctx.arch->nqubits()); + const auto iterArgs = op.getRegionIterArgs().take_front(ctx.arch->nqubits()); + for (const auto [arg, res, iter] : llvm::zip(initArgs, results, iterArgs)) { + layout.remapQubitValue(arg, res); + forLayout.remapQubitValue(arg, iter); + } + + /// Recursively handle loop region. + SmallVector history; + return processRegion(op.getRegion(), forLayout, history, ctx); +} + +/** + * @brief Copy the layout for each branch and recursively map the branches. + */ +WalkResult handle(scf::IfOp op, Layout& layout, RoutingContext& ctx) { + /// Recursively handle each branch region. + Layout ifLayout(layout); + SmallVector ifHistory; + + const auto ifRes = + processRegion(op.getThenRegion(), ifLayout, ifHistory, ctx); + if (ifRes.failed()) { + return ifRes; + } + + Layout elseLayout(layout); + SmallVector elseHistory; + const auto elseRes = + processRegion(op.getElseRegion(), elseLayout, elseHistory, ctx); + if (elseRes.failed()) { + return elseRes; + } + + /// Forward out-of-if values. + const auto results = op->getResults().take_front(ctx.arch->nqubits()); + for (const auto [in, out] : llvm::zip(layout.getHardwareQubits(), results)) { + layout.remapQubitValue(in, out); + } + + return WalkResult::advance(); +} + +/** + * @brief Indicates the end of a region defined by a scf op. + * + * Restores layout by uncomputation. + */ +WalkResult handle(scf::YieldOp op, Layout& layout, + ArrayRef history, RoutingContext& ctx) { + /// Uncompute SWAPs. + insertSWAPs(op.getLoc(), llvm::to_vector(llvm::reverse(history)), layout, + ctx.rewriter); + /// Count SWAPs. + *(ctx.stats.numSwaps) += history.size(); + return WalkResult::advance(); +} + +/** + * @brief Remap all input to output qubits for the given unitary op. SWAP + * indices if the unitary is a SWAP. + */ +WalkResult handle(UnitaryInterface op, Layout& layout, + SmallVector& history) { + remap(op, layout); + if (isa(op)) { + const auto outs = getOuts(op); + layout.swap(outs.first, outs.second); + history.push_back({layout.lookupHardwareIndex(outs.first), + layout.lookupHardwareIndex(outs.second)}); + } + return WalkResult::advance(); +} + +/** + * @brief Route each layer by iterating a sliding window of (1 + nlookahead) + * layers. + * + * @param layers The layers. + * @param layout A copy of the current layout. + * @param ctx The routing context. + */ +LogicalResult routeEachLayer(const LayerVec& layers, Layout layout, + SmallVector& history, + RoutingContext& ctx) { + LayerVec::const_iterator end = layers.end(); + for (LayerVec::const_iterator it = layers.begin(); it != end; ++it) { + LayerVec::const_iterator lookaheadIt = + std::min(end, it + 1 + ctx.nlookahead); + + const auto& front = *it; // == window.front() + + if (front.hasRoutableGates()) { + ctx.rewriter.setInsertionPoint(front.anchor); + + /// Find SWAPs for front layer with nlookahead layers. + const auto window = llvm::make_range(it, lookaheadIt); + const auto windowLayerGates = to_vector(llvm::map_range( + window, [](const Layer& layer) { return ArrayRef(layer.gates); })); + const auto swaps = ctx.router.route(windowLayerGates, layout, *ctx.arch); + + /// Append SWAPs to history. + history.append(swaps); + + /// Insert SWAPs. + insertSWAPs(front.anchor->getLoc(), swaps, layout, ctx.rewriter); + + /// Count SWAPs. + *(ctx.stats.numSwaps) += swaps.size(); + } + + for (Operation* curr : front.ops) { + const auto res = TypeSwitch(curr) + .Case([&](UnitaryInterface op) { + return handle(op, layout, history); + }) + .Case([&](ResetOp op) { + remap(op, layout); + return WalkResult::advance(); + }) + .Case([&](MeasureOp op) { + remap(op, layout); + return WalkResult::advance(); + }) + .Case([&](scf::ForOp op) { + return handle(op, layout, ctx); + }) + .Case([&](scf::IfOp op) { + return handle(op, layout, ctx); + }) + .Case([&](scf::YieldOp op) { + return handle(op, layout, history, ctx); + }) + .Default([](auto) { return WalkResult::skip(); }); + if (res.wasInterrupted()) { + return failure(); } - llvm_unreachable("TODO."); } } + + return success(); } LogicalResult processRegion(Region& region, Layout& layout, + SmallVector& history, RoutingContext& ctx) { - /// Find layers. - LayerVec layers = schedule(layout); - /// Route each of the layers. Might violate SSA dominance. - routeEachLayer(layers, layout, ctx); + /// Find and route each of the layers. Might violate SSA dominance. + const auto res = + routeEachLayer(schedule(layout, region), layout, history, ctx); + if (res.failed()) { + return res; + } + /// Repair any SSA dominance issues. for (Block& block : region.getBlocks()) { sortTopologically(&block); } + return success(); } @@ -367,7 +533,8 @@ LogicalResult processFunction(func::FuncOp func, RoutingContext& ctx) { layout.add(index, index, op.getQubit()); }); - return processRegion(func.getBody(), layout, ctx); + SmallVector history; + return processRegion(func.getBody(), layout, history, ctx); } /** diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp index e58510d60b..bd13e77215 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp @@ -64,6 +64,10 @@ struct RoutingContext { PatternRewriter rewriter; }; +LogicalResult processRegion(Region& region, Layout& layout, + SmallVector& history, + RoutingContext& ctx); + /** * @brief Insert SWAP ops at the rewriter's insertion point. * @@ -116,14 +120,10 @@ void insertSWAPs(Location location, ArrayRef swaps, layout.lookupHardwareIndex(ins.second)); } -LogicalResult processRegion(Region& region, Layout& layout, - SmallVector& history, - RoutingContext& ctx); - /** - * @brief Copy the layout and recursively map the loop body. + * @brief Copy the layout and recursively process the loop body. */ -WalkResult handleFor(scf::ForOp op, Layout& layout, RoutingContext& ctx) { +WalkResult handle(scf::ForOp op, Layout& layout, RoutingContext& ctx) { /// Copy layout. Layout forLayout(layout); @@ -144,7 +144,7 @@ WalkResult handleFor(scf::ForOp op, Layout& layout, RoutingContext& ctx) { /** * @brief Copy the layout for each branch and recursively map the branches. */ -WalkResult handleIf(scf::IfOp op, Layout& layout, RoutingContext& ctx) { +WalkResult handle(scf::IfOp op, Layout& layout, RoutingContext& ctx) { /// Recursively handle each branch region. Layout ifLayout(layout); SmallVector ifHistory; @@ -164,9 +164,8 @@ WalkResult handleIf(scf::IfOp op, Layout& layout, RoutingContext& ctx) { /// Forward out-of-if values. const auto results = op->getResults().take_front(ctx.arch->nqubits()); - for (const auto [hw, res] : llvm::enumerate(results)) { - const Value q = layout.lookupHardwareValue(hw); - layout.remapQubitValue(q, res); + for (const auto [in, out] : llvm::zip(layout.getHardwareQubits(), results)) { + layout.remapQubitValue(in, out); } return WalkResult::advance(); @@ -175,17 +174,10 @@ WalkResult handleIf(scf::IfOp op, Layout& layout, RoutingContext& ctx) { /** * @brief Indicates the end of a region defined by a scf op. * - * Restores layout by uncomputation and replaces (invalid) yield. - * - * Using uncompute has the advantages of (1) being intuitive and - * (2) preserving the optimality of the original SWAP sequence. - * Essentially the better the routing algorithm the better the - * uncompute. Moreover, this has the nice property that routing - * a 'for' of 'if' region always requires 2 * #(SWAPs required for region) - * additional SWAPS. + * Restores layout by uncomputation. */ -WalkResult handleYield(scf::YieldOp op, Layout& layout, - ArrayRef history, RoutingContext& ctx) { +WalkResult handle(scf::YieldOp op, Layout& layout, + ArrayRef history, RoutingContext& ctx) { /// Uncompute SWAPs. insertSWAPs(op.getLoc(), llvm::to_vector(llvm::reverse(history)), layout, ctx.rewriter); @@ -200,7 +192,7 @@ WalkResult handleYield(scf::YieldOp op, Layout& layout, * * Thanks to the placement pass, we can apply the identity layout here. */ -WalkResult handleQubit(QubitOp op, Layout& layout) { +WalkResult handle(QubitOp op, Layout& layout) { const std::size_t index = op.getIndex(); layout.add(index, index, op.getQubit()); return WalkResult::advance(); @@ -237,9 +229,8 @@ void findAndInsertSWAPs(UnitaryInterface op, Layout& layout, * @brief Ensures the executability of two-qubit gates on the given target * architecture by inserting SWAPs. */ -WalkResult handleUnitary(UnitaryInterface op, Layout& layout, - SmallVector& history, - RoutingContext& ctx) { +WalkResult handle(UnitaryInterface op, Layout& layout, + SmallVector& history, RoutingContext& ctx) { const std::vector inQubits = op.getAllInQubits(); const std::vector outQubits = op.getAllOutQubits(); const std::size_t nacts = inQubits.size(); @@ -294,22 +285,6 @@ WalkResult handleUnitary(UnitaryInterface op, Layout& layout, return WalkResult::advance(); } -/** - * @brief Update layout. - */ -WalkResult handleReset(ResetOp op, Layout& layout) { - layout.remapQubitValue(op.getInQubit(), op.getOutQubit()); - return WalkResult::advance(); -} - -/** - * @brief Update layout. - */ -WalkResult handleMeasure(MeasureOp op, Layout& layout) { - layout.remapQubitValue(op.getInQubit(), op.getOutQubit()); - return WalkResult::advance(); -} - LogicalResult processRegion(Region& region, Layout& layout, SmallVector& history, RoutingContext& ctx) { @@ -321,12 +296,17 @@ LogicalResult processRegion(Region& region, Layout& layout, TypeSwitch(&curr) /// mqtopt Dialect .Case([&](UnitaryInterface op) { - return handleUnitary(op, layout, history, ctx); + return handle(op, layout, history, ctx); + }) + .Case([&](QubitOp op) { return handle(op, layout); }) + .Case([&](ResetOp op) { + remap(op, layout); + return WalkResult::advance(); + }) + .Case([&](MeasureOp op) { + remap(op, layout); + return WalkResult::advance(); }) - .Case([&](QubitOp op) { return handleQubit(op, layout); }) - .Case([&](ResetOp op) { return handleReset(op, layout); }) - .Case( - [&](MeasureOp op) { return handleMeasure(op, layout); }) /// built-in Dialect .Case([&]([[maybe_unused]] ModuleOp op) { return WalkResult::advance(); @@ -337,11 +317,11 @@ LogicalResult processRegion(Region& region, Layout& layout, }) /// scf Dialect .Case( - [&](scf::ForOp op) { return handleFor(op, layout, ctx); }) + [&](scf::ForOp op) { return handle(op, layout, ctx); }) .Case( - [&](scf::IfOp op) { return handleIf(op, layout, ctx); }) + [&](scf::IfOp op) { return handle(op, layout, ctx); }) .Case([&](scf::YieldOp op) { - return handleYield(op, layout, history, ctx); + return handle(op, layout, history, ctx); }) /// Skip the rest. .Default([](auto) { return WalkResult::skip(); }); diff --git a/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/basics.mlir b/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/basics.mlir index 9d38b88bcd..2b8980130b 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/basics.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/basics.mlir @@ -11,7 +11,7 @@ // RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(canonicalize, placement-sc{strategy=identity arch=MQTTest}, route-naive-sc{arch=MQTTest},verify-routing-sc{arch=MQTTest})" -verify-diagnostics | FileCheck %s // RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(canonicalize, placement-sc{strategy=identity arch=MQTTest}, route-astar-sc{arch=MQTTest},verify-routing-sc{arch=MQTTest})" -verify-diagnostics | FileCheck %s // RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(canonicalize, placement-sc{strategy=identity arch=IBMFalcon}, route-naive-sc{arch=IBMFalcon},verify-routing-sc{arch=IBMFalcon})" -verify-diagnostics | FileCheck %s -// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(canonicalize, placement-sc{strategy=identity arch=IBMFalcon}, route-astar-sc{astar arch=IBMFalcon},verify-routing-sc{arch=IBMFalcon})" -verify-diagnostics | FileCheck %s +// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(canonicalize, placement-sc{strategy=identity arch=IBMFalcon}, route-astar-sc{arch=IBMFalcon},verify-routing-sc{arch=IBMFalcon})" -verify-diagnostics | FileCheck %s module { // CHECK-LABEL: func.func @entrySABRE @@ -223,7 +223,9 @@ module { scf.yield %q0_3, %q1_2 : !mqtopt.Qubit, !mqtopt.Qubit } - mqtopt.deallocQubit %q0_3 + %q0_4 = mqtopt.x() %q0_3 : !mqtopt.Qubit + + mqtopt.deallocQubit %q0_4 mqtopt.deallocQubit %q1_2 return From 27f23b154e8b1581821333127aa2e901b2ab8a6f Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 14 Nov 2025 09:10:32 +0100 Subject: [PATCH 15/56] Improve documentation of WireIterator --- .../Transforms/Transpilation/WireIterator.h | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h index 9a36624c01..4f78371178 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h @@ -27,10 +27,20 @@ namespace mqt::ir::opt { using namespace mlir; /** - * @brief A non-recursive input_iterator traversing the def-use chain of a - * single qubit (on a wire). + * @brief A non-recursive input_iterator traversing the def-use chain of a qubit + * wire. * - * It does not visit nested regions (nested ops). + * The iterator follows the flow of a qubit through a sequence of quantum + * operations in a given region. It respects the semantics of the respective + * quantum operation including control flow constructs (scf::ForOp and + * scf::IfOp). + * + * It does not visit operations within nested regions. These include the loop + * body of the scf::ForOp and the THEN and ELSE branches of the scf::IfOp. From + * the iterator's perspective these act like regular gates. As a consequence, + * an input qubit is mapped to the respective output qubit. For example, finding + * which result of an scf::IfOp corresponds to a qubit passed into one of its + * regions. */ class WireIterator { public: @@ -51,6 +61,7 @@ class WireIterator { } void operator++(int) { ++*this; } + bool operator==(const WireIterator& other) const { return other.q == q; } private: From 40b005e07165dc9b334bc89f0a00221385c322e9 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 14 Nov 2025 09:18:00 +0100 Subject: [PATCH 16/56] Fix linting --- .../MQTOpt/Transforms/Transpilation/Common.cpp | 2 ++ .../Transpilation/sc/AStarRoutingPass.cpp | 15 ++++++++++----- .../Transpilation/sc/NaiveRoutingPass.cpp | 5 ++++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp index afdfa83f0b..c947d43b08 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp @@ -19,7 +19,9 @@ #include #include #include +#include #include +#include #include namespace mqt::ir::opt { diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index f6857a8dda..a8a61f4fd1 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -16,23 +16,25 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h" +#include #include #include #include #include #include #include +#include #include #include #include #include +#include #include #include #include #include #include #include -#include #include #include #include @@ -441,10 +443,13 @@ WalkResult handle(UnitaryInterface op, Layout& layout, LogicalResult routeEachLayer(const LayerVec& layers, Layout layout, SmallVector& history, RoutingContext& ctx) { + const auto nhorizion = static_cast(1 + ctx.nlookahead); + LayerVec::const_iterator end = layers.end(); - for (LayerVec::const_iterator it = layers.begin(); it != end; ++it) { + LayerVec::const_iterator it = layers.begin(); + for (; it != end; std::advance(it, 1)) { LayerVec::const_iterator lookaheadIt = - std::min(end, it + 1 + ctx.nlookahead); + std::min(end, std::next(it, nhorizion)); const auto& front = *it; // == window.front() @@ -467,8 +472,8 @@ LogicalResult routeEachLayer(const LayerVec& layers, Layout layout, *(ctx.stats.numSwaps) += swaps.size(); } - for (Operation* curr : front.ops) { - const auto res = TypeSwitch(curr) + for (const Operation* curr : front.ops) { + const auto res = TypeSwitch(curr) .Case([&](UnitaryInterface op) { return handle(op, layout, history); }) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp index bd13e77215..ea8ed5f62c 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp @@ -8,6 +8,7 @@ * Licensed under the MIT License */ +#include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include "mlir/Dialect/MQTOpt/Transforms/Passes.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" @@ -22,12 +23,12 @@ #include #include #include +#include #include #include #include #include #include -#include #include #include #include @@ -38,6 +39,8 @@ #include #include #include +#include +#include #define DEBUG_TYPE "route-naive-sc" From 8772335e7f07abf485be19ef3e0ef2c3a206f21c Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 14 Nov 2025 09:26:06 +0100 Subject: [PATCH 17/56] Add missing header --- .../MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index a8a61f4fd1..ac085f6e58 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include From 993163aea9e4a7cc23c371bcc7fa61d782edaf60 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 14 Nov 2025 10:22:31 +0100 Subject: [PATCH 18/56] Improve documentation --- .../mlir/Dialect/MQTOpt/Transforms/Passes.td | 13 +-- .../Transpilation/sc/AStarRoutingPass.cpp | 73 +++++++------- .../Transpilation/sc/NaiveRoutingPass.cpp | 94 +++++++++---------- .../Transpilation/sc/PlacementPass.cpp | 2 +- .../sc/RoutingVerificationPass.cpp | 4 +- 5 files changed, 92 insertions(+), 94 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td index 52ba12f728..76eb8f3af3 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td @@ -96,7 +96,7 @@ def QuantumSinkPass : Pass<"quantum-sink", "mlir::ModuleOp"> { //===----------------------------------------------------------------------===// def PlacementPassSC : Pass<"placement-sc", "mlir::ModuleOp"> { - let summary = "This pass maps dynamic qubits to static qubits on superconducting quantum devices using initial placement strategies."; + let summary = "This pass maps program qubits to hardware qubits on superconducting quantum devices using initial placement strategies."; let options = [ Option<"strategy", "strategy", "PlacementStrategy", "PlacementStrategy::Random", "The initial placement strategy to use.", [{llvm::cl::values( @@ -108,9 +108,9 @@ def PlacementPassSC : Pass<"placement-sc", "mlir::ModuleOp"> { } def NaiveRoutingPassSC : Pass<"route-naive-sc", "mlir::ModuleOp"> { - let summary = "This pass ensures that a program meets the connectivity constraints of a given architecture."; + let summary = "This pass ensures that all two-qubit gates are executable on the target architecture."; let description = [{ - This pass inserts SWAP operations to ensure two-qubit gates are executable on a given target architecture. + Simple pre-order traversal of the IR that routes any non-executable gates by inserting SWAPs along the shortest path. }]; let options = [ Option<"archName", "arch", "std::string", "", @@ -122,9 +122,10 @@ def NaiveRoutingPassSC : Pass<"route-naive-sc", "mlir::ModuleOp"> { } def AStarRoutingPassSC : Pass<"route-astar-sc", "mlir::ModuleOp"> { - let summary = "This pass ensures that a program meets the connectivity constraints of a given architecture."; + let summary = "This pass ensures that all two-qubit gates are executable on the target architecture."; let description = [{ - This pass inserts SWAP operations to ensure two-qubit gates are executable on a given target architecture. + Routes the program by dividing the circuit into layers of parallel two-qubit gates and iteratively searches and + inserts SWAPs for each layer using A*-search. }]; let options = [ Option<"archName", "arch", "std::string", "", @@ -142,7 +143,7 @@ def AStarRoutingPassSC : Pass<"route-astar-sc", "mlir::ModuleOp"> { } def RoutingVerificationSCPass : Pass<"verify-routing-sc", "mlir::ModuleOp"> { - let summary = "This pass verifies that a program meets the connectivity constraints of a given architecture."; + let summary = "This pass verifies that all two-qubit gates are executable on the target architecture."; let description = [{ This pass ensures that all two-qubit gates are executable on the target's architecture. }]; diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index ac085f6e58..12ae381724 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -69,7 +69,9 @@ struct Statistics { struct Params { /// @brief The amount of lookahead layers. std::size_t nlookahead; + /// @brief The alpha factor in the heuristic function. float alpha; + /// @brief The lambda decay factor in the heuristic function. float lambda; }; @@ -186,6 +188,11 @@ void insertSWAPs(Location location, ArrayRef swaps, } } +/** + * @brief Advance each wire until (>=2)-qubit gates are found, collect the + * indices of the respective two-qubit gates, and prepare iterators for next + * iteration. + */ std::pair> collectLayerAndAdvance(ArrayRef wires, Layout& layout, SynchronizationMap& sync) { @@ -262,7 +269,6 @@ collectLayerAndAdvance(ArrayRef wires, Layout& layout, } if (auto op = dyn_cast(curr)) { - if (!sync.contains(op)) { /// This assumes that branch ops always returns all hardware qubits. sync.add(op, ++it, layout.getNumQubits()); @@ -307,9 +313,6 @@ collectLayerAndAdvance(ArrayRef wires, Layout& layout, /** * @brief Given a layout, divide the circuit into layers and schedule the ops in * their respective layer. - * - * @param layout A copy of the current layout. - * @returns a vector of layers. */ LayerVec schedule(Layout layout, Region& region) { LayerVec layers; @@ -372,7 +375,7 @@ WalkResult handle(scf::ForOp op, Layout& layout, RoutingContext& ctx) { } /** - * @brief Copy the layout for each branch and recursively map the branches. + * @brief Copy the layout for each branch and recursively process the branches. */ WalkResult handle(scf::IfOp op, Layout& layout, RoutingContext& ctx) { /// Recursively handle each branch region. @@ -436,10 +439,6 @@ WalkResult handle(UnitaryInterface op, Layout& layout, /** * @brief Route each layer by iterating a sliding window of (1 + nlookahead) * layers. - * - * @param layers The layers. - * @param layout A copy of the current layout. - * @param ctx The routing context. */ LogicalResult routeEachLayer(const LayerVec& layers, Layout layout, SmallVector& history, @@ -505,6 +504,12 @@ LogicalResult routeEachLayer(const LayerVec& layers, Layout layout, return success(); } +/** + * @brief Schedule and route the given region. + * + * Since this might break SSA Dominance, sort the blocks in the given region + * topologically. + */ LogicalResult processRegion(Region& region, Layout& layout, SmallVector& history, RoutingContext& ctx) { @@ -523,32 +528,9 @@ LogicalResult processRegion(Region& region, Layout& layout, return success(); } -LogicalResult processFunction(func::FuncOp func, RoutingContext& ctx) { - LLVM_DEBUG(llvm::dbgs() << "handleFunc: " << func.getSymName() << '\n'); - - if (!isEntryPoint(func)) { - LLVM_DEBUG(llvm::dbgs() << "\tskip non entry\n"); - return success(); // Ignore non entry_point functions for now. - } - - /// Find all hardware (static) qubits and initialize layout. - - Layout layout(ctx.arch->nqubits()); - for_each(func.getOps(), [&](QubitOp op) { - const std::size_t index = op.getIndex(); - layout.add(index, index, op.getQubit()); - }); - - SmallVector history; - return processRegion(func.getBody(), layout, history, ctx); -} - /** * @brief Route the given module for the targeted architecture using A*-search. - * - * @param module The module to route. - * @param arch The targeted architecture. - * @param stats The composite statistics datastructure. + * Processes each entry_point function separately. */ LogicalResult route(ModuleOp module, std::unique_ptr arch, Params& params, Statistics& stats) { @@ -560,16 +542,33 @@ LogicalResult route(ModuleOp module, std::unique_ptr arch, .router = AStarHeuristicRouter(weights), .nlookahead = params.nlookahead}; for (auto func : module.getOps()) { - if (processFunction(func, ctx).failed()) { - return failure(); + LLVM_DEBUG(llvm::dbgs() << "handleFunc: " << func.getSymName() << '\n'); + + if (!isEntryPoint(func)) { + LLVM_DEBUG(llvm::dbgs() << "\tskip non entry\n"); + return success(); // Ignore non entry_point functions for now. + } + + /// Find all hardware (static) qubits and initialize layout. + Layout layout(ctx.arch->nqubits()); + for_each(func.getOps(), [&](QubitOp op) { + const std::size_t index = op.getIndex(); + layout.add(index, index, op.getQubit()); + }); + + SmallVector history; + const auto res = processRegion(func.getBody(), layout, history, ctx); + if (res.failed()) { + return res; } } return success(); } /** - * @brief This pass ensures that the connectivity constraints of the target - * architecture are met. + * @brief Routes the program by dividing the circuit into layers of parallel + * two-qubit gates and iteratively searches and inserts SWAPs for each layer + * using A*-search. */ struct AStarRoutingPassSC final : impl::AStarRoutingPassSCBase { diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp index ea8ed5f62c..1a457ed371 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp @@ -123,6 +123,33 @@ void insertSWAPs(Location location, ArrayRef swaps, layout.lookupHardwareIndex(ins.second)); } +/** + * @brief Use shortest path swapping to make the given unitary executable. + * @details Optimized for an avg. SWAP count of 16. + */ +void findAndInsertSWAPs(UnitaryInterface op, Layout& layout, + SmallVector& history, + RoutingContext& ctx) { + /// Find SWAPs. + SmallVector swaps; + const auto ins = getIns(op); + const auto hw0 = layout.lookupHardwareIndex(ins.first); + const auto hw1 = layout.lookupHardwareIndex(ins.second); + const auto path = ctx.arch->shortestPathBetween(hw0, hw1); + for (std::size_t i = 0; i < path.size() - 2; ++i) { + swaps.emplace_back(path[i], path[i + 1]); + } + + /// Append SWAPs to history. + history.append(swaps); + + /// Insert SWAPs. + insertSWAPs(op.getLoc(), swaps, layout, ctx.rewriter); + + /// Count SWAPs. + *(ctx.stats.numSwaps) += swaps.size(); +} + /** * @brief Copy the layout and recursively process the loop body. */ @@ -201,33 +228,6 @@ WalkResult handle(QubitOp op, Layout& layout) { return WalkResult::advance(); } -/** - * @brief Use shortest path swapping to make the given unitary executable. - * @details Optimized for an avg. SWAP count of 16. - */ -void findAndInsertSWAPs(UnitaryInterface op, Layout& layout, - SmallVector& history, - RoutingContext& ctx) { - /// Find SWAPs. - SmallVector swaps; - const auto ins = getIns(op); - const auto hw0 = layout.lookupHardwareIndex(ins.first); - const auto hw1 = layout.lookupHardwareIndex(ins.second); - const auto path = ctx.arch->shortestPathBetween(hw0, hw1); - for (std::size_t i = 0; i < path.size() - 2; ++i) { - swaps.emplace_back(path[i], path[i + 1]); - } - - /// Append SWAPs to history. - history.append(swaps); - - /// Insert SWAPs. - insertSWAPs(op.getLoc(), swaps, layout, ctx.rewriter); - - /// Count SWAPs. - *(ctx.stats.numSwaps) += swaps.size(); -} - /** * @brief Ensures the executability of two-qubit gates on the given target * architecture by inserting SWAPs. @@ -288,6 +288,10 @@ WalkResult handle(UnitaryInterface op, Layout& layout, return WalkResult::advance(); } +/** + * @brief Traverse the given region pre-order and insert SWAPs for any + * non-executable gate. + */ LogicalResult processRegion(Region& region, Layout& layout, SmallVector& history, RoutingContext& ctx) { @@ -337,25 +341,9 @@ LogicalResult processRegion(Region& region, Layout& layout, return success(); } -LogicalResult processFunction(func::FuncOp func, RoutingContext& ctx) { - LLVM_DEBUG(llvm::dbgs() << "handleFunc: " << func.getSymName() << '\n'); - - if (!isEntryPoint(func)) { - LLVM_DEBUG(llvm::dbgs() << "\tskip non entry\n"); - return success(); // Ignore non entry_point functions for now. - } - - Layout layout(ctx.arch->nqubits()); - SmallVector history; - return processRegion(func.getBody(), layout, history, ctx); -} - /** * @brief Naively route the given module for the targeted architecture. - * - * @param module The module to route. - * @param arch The targeted architecture. - * @param stats The composite statistics datastructure. + * Processes each entry_point function separately. */ LogicalResult route(ModuleOp module, std::unique_ptr arch, Statistics& stats) { @@ -363,16 +351,26 @@ LogicalResult route(ModuleOp module, std::unique_ptr arch, .stats = stats, .rewriter = PatternRewriter(module->getContext())}; for (auto func : module.getOps()) { - if (processFunction(func, ctx).failed()) { - return failure(); + LLVM_DEBUG(llvm::dbgs() << "handleFunc: " << func.getSymName() << '\n'); + + if (!isEntryPoint(func)) { + LLVM_DEBUG(llvm::dbgs() << "\tskip non entry\n"); + return success(); // Ignore non entry_point functions for now. + } + + Layout layout(ctx.arch->nqubits()); + SmallVector history; + const auto res = processRegion(func.getBody(), layout, history, ctx); + if (res.failed()) { + return res; } } return success(); } /** - * @brief This pass ensures that the connectivity constraints of the target - * architecture are met. + * @brief Simple pre-order traversal of the IR that routes any non-executable + * gates by inserting SWAPs along the shortest path. */ struct NaiveRoutingPassSC final : impl::NaiveRoutingPassSCBase { diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp index 76b036738a..d259029904 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp @@ -421,7 +421,7 @@ LogicalResult run(ModuleOp module, MLIRContext* mlirCtx, } /** - * @brief This pass maps dynamic qubits to static qubits on superconducting + * @brief This pass maps program qubits to hardware qubits on superconducting * quantum devices using initial placement strategies. */ struct PlacementPassSC final : impl::PlacementPassSCBase { diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp index f09a8a3210..d7d405b9a0 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp @@ -219,8 +219,8 @@ WalkResult handleMeasure(MeasureOp op, VerificationContext& ctx) { } /** - * @brief This pass verifies that the constraints of a target architecture are - * met. + * @brief This pass verifies that all two-qubit gates are executable on the + * target architecture. */ struct RoutingVerificationPassSC final : impl::RoutingVerificationSCPassBase { From 78d4d6734b102325cfaf85b87eb670fb636f30ad Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 14 Nov 2025 10:23:57 +0100 Subject: [PATCH 19/56] Remove closed map in A* for now --- .../Transpilation/AStarHeuristicRouter.h | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h index feecea38c8..10a2b04ae0 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h @@ -52,7 +52,6 @@ struct AStarHeuristicRouter final { : weights_(std::move(weights)) {} private: - using ClosedMap = DenseMap; using Layer = ArrayRef; using Layers = ArrayRef; @@ -153,9 +152,6 @@ struct AStarHeuristicRouter final { MinQueue frontier{}; frontier.emplace(root); - /// Initialize visited map. - ClosedMap visited; - /// Iterative searching and expanding. while (!frontier.empty()) { Node curr = frontier.top(); @@ -165,16 +161,6 @@ struct AStarHeuristicRouter final { return curr.sequence; } - /// Don't revisit layouts that were discovered with a lower depth. - const auto [it, inserted] = - visited.try_emplace(curr.layout, curr.depth()); - if (!inserted) { - if (it->second <= curr.depth()) { - continue; - } - it->second = curr.sequence.size(); - } - /// Expand frontier with all neighbouring SWAPs in the current front. expand(frontier, curr, layers, arch); } From 7dcccac5eb88cba0325b4e24662a98291150d9e1 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Sat, 15 Nov 2025 13:02:27 +0100 Subject: [PATCH 20/56] Add skip-two-qubit-block function --- .../Transpilation/sc/AStarRoutingPass.cpp | 67 +++++++++++++++++-- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index 12ae381724..1fea136fb0 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -188,6 +188,61 @@ void insertSWAPs(Location location, ArrayRef swaps, } } +SmallVector +advanceUntilEndOfTwoQubitBlock(ArrayRef wires, Layout& layout, + Layer& layer) { + assert(wires.size() == 2 && "expected two wires"); + + WireIterator it0 = wires[0]; + WireIterator it1 = wires[1]; + WireIterator end; + while (it0 != end && it1 != end) { + Operation* op0 = *it0; + if (!isa(op0) || isa(op0)) { + break; + } + + Operation* op1 = *it1; + if (!isa(op1) || isa(op1)) { + break; + } + + UnitaryInterface u0 = cast(op0); + + /// Advance for single qubit gate on wire 0. + if (!isTwoQubitGate(u0)) { + layer.addOp(u0); // Add 1Q-op to layer operations. + remap(u0, layout); // Remap scheduling layout. + ++it0; + continue; + } + + UnitaryInterface u1 = cast(op1); + + /// Advance for single qubit gate on wire 1. + if (!isTwoQubitGate(u1)) { + layer.addOp(u1); // Add 1Q-op to layer operations. + remap(u1, layout); // Remap scheduling layout. + ++it1; + continue; + } + + /// Stop if the wires reach different two qubit gates. + if (op0 != op1) { + break; + } + + /// Remap and advance if u0 == u1. + layer.addOp(u1); + remap(u1, layout); + + ++it0; + ++it1; + } + + return {it0, it1}; +} + /** * @brief Advance each wire until (>=2)-qubit gates are found, collect the * indices of the respective two-qubit gates, and prepare iterators for next @@ -248,7 +303,8 @@ collectLayerAndAdvance(ArrayRef wires, Layout& layout, remap(op, layout); // Remap scheduling layout. /// Release iterators for next iteration. - next.append(iterators.value()); + next.append( + advanceUntilEndOfTwoQubitBlock(iterators.value(), layout, layer)); } break; @@ -521,9 +577,9 @@ LogicalResult processRegion(Region& region, Layout& layout, } /// Repair any SSA dominance issues. - for (Block& block : region.getBlocks()) { - sortTopologically(&block); - } + // for (Block& block : region.getBlocks()) { + // sortTopologically(&block); + // } return success(); } @@ -551,9 +607,12 @@ LogicalResult route(ModuleOp module, std::unique_ptr arch, /// Find all hardware (static) qubits and initialize layout. Layout layout(ctx.arch->nqubits()); + SmallVector circuit; + circuit.reserve(ctx.arch->nqubits()); for_each(func.getOps(), [&](QubitOp op) { const std::size_t index = op.getIndex(); layout.add(index, index, op.getQubit()); + circuit.emplace_back(op.getQubit(), op.getQubit()); }); SmallVector history; From beb0b81dae4efbaafb57e11ad51b10efad9dd961 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Sun, 16 Nov 2025 09:34:38 +0100 Subject: [PATCH 21/56] Add A* fallback mechanism. --- .../Transpilation/AStarHeuristicRouter.h | 28 +++--- .../Transpilation/sc/AStarRoutingPass.cpp | 89 +++++++++++++++---- 2 files changed, 89 insertions(+), 28 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h index 10a2b04ae0..6da851157d 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h @@ -14,19 +14,18 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" +#include "llvm/Support/Debug.h" + #include #include +#include #include +#include #include #include namespace mqt::ir::opt { -/** - * @brief A vector of SWAPs. - */ -using RouterResult = SmallVector; - /** * @brief Specifies the weights for different terms in the cost function f. */ @@ -56,7 +55,7 @@ struct AStarHeuristicRouter final { using Layers = ArrayRef; struct Node { - SmallVector sequence; + SmallVector sequence; ThinLayout layout; float f; @@ -139,8 +138,9 @@ struct AStarHeuristicRouter final { using MinQueue = std::priority_queue, std::greater<>>; public: - [[nodiscard]] RouterResult route(Layers layers, const ThinLayout& layout, - const Architecture& arch) const { + [[nodiscard]] std::optional> + route(Layers layers, const ThinLayout& layout, const Architecture& arch, + std::size_t maxIterations = 20001UL) { Node root(layout); /// Early exit. No SWAPs required: @@ -153,7 +153,7 @@ struct AStarHeuristicRouter final { frontier.emplace(root); /// Iterative searching and expanding. - while (!frontier.empty()) { + while (!frontier.empty() && maxIterations > 0) { Node curr = frontier.top(); frontier.pop(); @@ -163,9 +163,10 @@ struct AStarHeuristicRouter final { /// Expand frontier with all neighbouring SWAPs in the current front. expand(frontier, curr, layers, arch); + maxIterations--; } - return {}; + return std::nullopt; } private: @@ -173,15 +174,15 @@ struct AStarHeuristicRouter final { * @brief Expand frontier with all neighbouring SWAPs in the current front. */ void expand(MinQueue& frontier, const Node& parent, Layers layers, - const Architecture& arch) const { - llvm::SmallDenseSet swaps{}; + const Architecture& arch) { + expansionSet.clear(); for (const QubitIndexPair gate : layers.front()) { 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 QubitIndexPair swap = std::minmax(hw0, hw1); - if (!swaps.insert(swap).second) { + if (!expansionSet.insert(swap).second) { continue; } @@ -191,6 +192,7 @@ struct AStarHeuristicRouter final { } } + llvm::SmallDenseSet expansionSet{}; HeuristicWeights weights_; }; } // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index 1fea136fb0..b396cade04 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -188,6 +189,10 @@ void insertSWAPs(Location location, ArrayRef swaps, } } +//===----------------------------------------------------------------------===// +// Scheduling +//===----------------------------------------------------------------------===// + SmallVector advanceUntilEndOfTwoQubitBlock(ArrayRef wires, Layout& layout, Layer& layer) { @@ -409,6 +414,10 @@ LayerVec schedule(Layout layout, Region& region) { return layers; } +//===----------------------------------------------------------------------===// +// Routing +//===----------------------------------------------------------------------===// + /** * @brief Copy the layout and recursively process the loop body. */ @@ -492,6 +501,65 @@ WalkResult handle(UnitaryInterface op, Layout& layout, return WalkResult::advance(); } +/** + * @brief Find and insert SWAPs using A*-search. + * + * If A*-search fails use the LightSABRE fallback mechanism: Search + * the the gate which acts on the qubit index pair with the minimum distance in + * the coupling graph of the targeted architecture. Route this gate naively, + * remove it from the front layer, and restart the A*-search. + */ +void findAndInsertSWAPs(ArrayRef> layers, + Location location, Layout& layout, + SmallVector& history, + RoutingContext& ctx) { + /// Mutable copy of the front layer. + SmallVector workingFront(layers.front()); + + SmallVector> workingLayers; + workingLayers.reserve(layers.size()); + workingLayers.push_back(workingFront); // Non-owning view of workingFront. + for (auto layer : layers.drop_front()) { + workingLayers.push_back(layer); + } + + while (!workingFront.empty()) { + if (const auto swaps = ctx.router.route(workingLayers, layout, *ctx.arch)) { + history.append(*swaps); + insertSWAPs(location, *swaps, layout, ctx.rewriter); + *(ctx.stats.numSwaps) += swaps->size(); + return; + } + + QubitIndexPair bestGate; + std::size_t bestIdx = 0; + std::size_t minDist = std::numeric_limits::max(); + for (const auto [i, gate] : llvm::enumerate(workingFront)) { + const auto hw0 = layout.getHardwareIndex(gate.first); + const auto hw1 = layout.getHardwareIndex(gate.second); + const auto dist = ctx.arch->distanceBetween(hw0, hw1); + if (dist < minDist) { + bestIdx = i; + minDist = dist; + bestGate = std::make_pair(hw0, hw1); + } + } + + workingFront.erase(workingFront.begin() + static_cast(bestIdx)); + + SmallVector swaps; + const auto path = + ctx.arch->shortestPathBetween(bestGate.first, bestGate.second); + for (std::size_t i = 0; i < path.size() - 2; ++i) { + swaps.emplace_back(path[i], path[i + 1]); + } + + history.append(swaps); + insertSWAPs(location, swaps, layout, ctx.rewriter); + *(ctx.stats.numSwaps) += swaps.size(); + } +} + /** * @brief Route each layer by iterating a sliding window of (1 + nlookahead) * layers. @@ -516,16 +584,9 @@ LogicalResult routeEachLayer(const LayerVec& layers, Layout layout, const auto window = llvm::make_range(it, lookaheadIt); const auto windowLayerGates = to_vector(llvm::map_range( window, [](const Layer& layer) { return ArrayRef(layer.gates); })); - const auto swaps = ctx.router.route(windowLayerGates, layout, *ctx.arch); - - /// Append SWAPs to history. - history.append(swaps); - /// Insert SWAPs. - insertSWAPs(front.anchor->getLoc(), swaps, layout, ctx.rewriter); - - /// Count SWAPs. - *(ctx.stats.numSwaps) += swaps.size(); + findAndInsertSWAPs(windowLayerGates, front.anchor->getLoc(), layout, + history, ctx); } for (const Operation* curr : front.ops) { @@ -569,6 +630,7 @@ LogicalResult routeEachLayer(const LayerVec& layers, Layout layout, LogicalResult processRegion(Region& region, Layout& layout, SmallVector& history, RoutingContext& ctx) { + schedule(layout, region); /// Find and route each of the layers. Might violate SSA dominance. const auto res = routeEachLayer(schedule(layout, region), layout, history, ctx); @@ -577,9 +639,9 @@ LogicalResult processRegion(Region& region, Layout& layout, } /// Repair any SSA dominance issues. - // for (Block& block : region.getBlocks()) { - // sortTopologically(&block); - // } + for (Block& block : region.getBlocks()) { + sortTopologically(&block); + } return success(); } @@ -607,12 +669,9 @@ LogicalResult route(ModuleOp module, std::unique_ptr arch, /// Find all hardware (static) qubits and initialize layout. Layout layout(ctx.arch->nqubits()); - SmallVector circuit; - circuit.reserve(ctx.arch->nqubits()); for_each(func.getOps(), [&](QubitOp op) { const std::size_t index = op.getIndex(); layout.add(index, index, op.getQubit()); - circuit.emplace_back(op.getQubit(), op.getQubit()); }); SmallVector history; From ab8b7a7259b30f24b6554f2e6e74cf736dd0594d Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Sun, 16 Nov 2025 10:30:34 +0100 Subject: [PATCH 22/56] Remove layout from scheduling --- .../Transpilation/sc/AStarRoutingPass.cpp | 97 ++++++++----------- 1 file changed, 43 insertions(+), 54 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index b396cade04..fb93b27ab5 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -111,10 +111,16 @@ struct Layer { /// @brief A vector of layers. using LayerVec = SmallVector; +struct Wire { + Wire(WireIterator it, QubitIndex index) : it(it), index(index) {} + WireIterator it; + QubitIndex index; +}; + /// @brief Map to handle multi-qubit gates when traversing the def-use chain. class SynchronizationMap { /// @brief Maps operations to to-be-released iterators. - DenseMap> onHold; + DenseMap> onHold; /// @brief Maps operations to ref counts. An op can be released whenever the /// count reaches zero. @@ -124,20 +130,19 @@ class SynchronizationMap { /// @returns true iff. the operation is contained in the map. bool contains(Operation* op) { return onHold.contains(op); } - /// @brief Add op with respective iterator and ref count to map. - void add(Operation* op, WireIterator it, const std::size_t cnt) { - onHold.try_emplace(op, SmallVector{it}); + /// @brief Add op with respective wire and ref count to map. + void add(Operation* op, Wire wire, const std::size_t cnt) { + onHold.try_emplace(op, SmallVector{wire}); /// Decrease the cnt by one because the op was visited when adding. refCount.try_emplace(op, cnt - 1); } /// @brief Decrement ref count of op and potentially release its iterators. - std::optional> visit(Operation* op, - WireIterator it) { + std::optional> visit(Operation* op, Wire wire) { assert(refCount.contains(op) && "expected sync map to contain op"); /// Add iterator for later release. - onHold[op].push_back(it); + onHold[op].push_back(wire); /// Release iterators whenever the ref count reaches zero. if (--refCount[op] == 0) { @@ -193,14 +198,13 @@ void insertSWAPs(Location location, ArrayRef swaps, // Scheduling //===----------------------------------------------------------------------===// -SmallVector -advanceUntilEndOfTwoQubitBlock(ArrayRef wires, Layout& layout, - Layer& layer) { +SmallVector advanceUntilEndOfTwoQubitBlock(ArrayRef wires, + Layer& layer) { assert(wires.size() == 2 && "expected two wires"); - WireIterator it0 = wires[0]; - WireIterator it1 = wires[1]; WireIterator end; + auto [it0, index0] = wires[0]; + auto [it1, index1] = wires[1]; while (it0 != end && it1 != end) { Operation* op0 = *it0; if (!isa(op0) || isa(op0)) { @@ -216,8 +220,7 @@ advanceUntilEndOfTwoQubitBlock(ArrayRef wires, Layout& layout, /// Advance for single qubit gate on wire 0. if (!isTwoQubitGate(u0)) { - layer.addOp(u0); // Add 1Q-op to layer operations. - remap(u0, layout); // Remap scheduling layout. + layer.addOp(u0); // Add 1Q-op to layer operations. ++it0; continue; } @@ -226,8 +229,7 @@ advanceUntilEndOfTwoQubitBlock(ArrayRef wires, Layout& layout, /// Advance for single qubit gate on wire 1. if (!isTwoQubitGate(u1)) { - layer.addOp(u1); // Add 1Q-op to layer operations. - remap(u1, layout); // Remap scheduling layout. + layer.addOp(u1); // Add 1Q-op to layer operations. ++it1; continue; } @@ -239,13 +241,12 @@ advanceUntilEndOfTwoQubitBlock(ArrayRef wires, Layout& layout, /// Remap and advance if u0 == u1. layer.addOp(u1); - remap(u1, layout); ++it0; ++it1; } - return {it0, it1}; + return {Wire(it0, index0), Wire(it1, index1)}; } /** @@ -253,16 +254,16 @@ advanceUntilEndOfTwoQubitBlock(ArrayRef wires, Layout& layout, * indices of the respective two-qubit gates, and prepare iterators for next * iteration. */ -std::pair> -collectLayerAndAdvance(ArrayRef wires, Layout& layout, - SynchronizationMap& sync) { +std::pair> +collectLayerAndAdvance(ArrayRef wires, SynchronizationMap& sync, + const std::size_t nqubits) { /// The collected layer. Layer layer; /// A vector of iterators for the next iteration. - SmallVector next; + SmallVector next; next.reserve(wires.size()); - for (WireIterator it : wires) { + for (auto [it, index] : wires) { while (it != WireIterator()) { Operation* curr = *it; @@ -270,14 +271,13 @@ collectLayerAndAdvance(ArrayRef wires, Layout& layout, /// synchronization. if (auto op = dyn_cast(curr)) { if (!sync.contains(op)) { - sync.add(op, ++it, op.getInQubits().size()); + sync.add(op, Wire(++it, index), op.getInQubits().size()); break; } - if (const auto iterators = sync.visit(op, ++it)) { + if (const auto iterators = sync.visit(op, Wire(++it, index))) { layer.addOp(op); next.append(iterators.value()); - remap(op, layout); // Remap values on release. } break; @@ -286,7 +286,6 @@ collectLayerAndAdvance(ArrayRef wires, Layout& layout, if (auto op = dyn_cast(curr)) { if (!isTwoQubitGate(op)) { layer.addOp(curr); // Add 1Q-op to layer operations. - remap(op, layout); // Remap scheduling layout. ++it; continue; } @@ -294,36 +293,30 @@ collectLayerAndAdvance(ArrayRef wires, Layout& layout, if (!sync.contains(op)) { /// Add the next iterator after the two-qubit /// gate for a later release. - sync.add(op, ++it, 2); + sync.add(op, Wire(++it, index), 2); break; } - if (const auto iterators = sync.visit(op, ++it)) { - const auto ins = getIns(op); - + if (const auto iterators = sync.visit(op, Wire(++it, index))) { /// Only add ready two-qubit gates to the layer. layer.addOp(op); - layer.gates.emplace_back(layout.lookupProgramIndex(ins.first), - layout.lookupProgramIndex(ins.second)); - remap(op, layout); // Remap scheduling layout. + layer.gates.emplace_back((*iterators)[0].index, + (*iterators)[1].index); /// Release iterators for next iteration. - next.append( - advanceUntilEndOfTwoQubitBlock(iterators.value(), layout, layer)); + next.append(advanceUntilEndOfTwoQubitBlock(iterators.value(), layer)); } break; } if (auto op = dyn_cast(curr)) { - remap(op, layout); layer.addOp(curr); ++it; continue; } if (auto op = dyn_cast(curr)) { - remap(op, layout); layer.addOp(curr); ++it; continue; @@ -332,18 +325,12 @@ collectLayerAndAdvance(ArrayRef wires, Layout& layout, if (auto op = dyn_cast(curr)) { if (!sync.contains(op)) { /// This assumes that branch ops always returns all hardware qubits. - sync.add(op, ++it, layout.getNumQubits()); + sync.add(op, Wire(++it, index), nqubits); break; } - if (const auto iterators = sync.visit(op, ++it)) { + if (const auto iterators = sync.visit(op, Wire(++it, index))) { layer.addOp(op); - - /// Remap values on release. - for (const auto& [in, out] : - llvm::zip_equal(layout.getHardwareQubits(), op->getResults())) { - layout.remapQubitValue(in, out); - } next.append(iterators.value()); } break; @@ -352,11 +339,11 @@ collectLayerAndAdvance(ArrayRef wires, Layout& layout, if (auto yield = dyn_cast(curr)) { if (!sync.contains(yield)) { /// This assumes that yield always returns all hardware qubits. - sync.add(yield, ++it, layout.getNumQubits()); + sync.add(yield, Wire(++it, index), nqubits); break; } - if (const auto iterators = sync.visit(yield, ++it)) { + if (const auto iterators = sync.visit(yield, Wire(++it, index))) { layer.addOp(yield); } @@ -375,15 +362,18 @@ collectLayerAndAdvance(ArrayRef wires, Layout& layout, * @brief Given a layout, divide the circuit into layers and schedule the ops in * their respective layer. */ -LayerVec schedule(Layout layout, Region& region) { +LayerVec schedule(const Layout& layout, Region& region) { LayerVec layers; SynchronizationMap sync; - SmallVector wires( - llvm::map_range(layout.getHardwareQubits(), - [&](Value q) { return WireIterator(q, ®ion); })); + SmallVector wires; + wires.reserve(layout.getNumQubits()); + for (auto [hw, q] : llvm::enumerate(layout.getHardwareQubits())) { + wires.emplace_back(WireIterator(q, ®ion), layout.getProgramIndex(hw)); + } do { - const auto [layer, next] = collectLayerAndAdvance(wires, layout, sync); + const auto [layer, next] = + collectLayerAndAdvance(wires, sync, layout.getNumQubits()); if (layer.ops.empty()) { break; } @@ -630,7 +620,6 @@ LogicalResult routeEachLayer(const LayerVec& layers, Layout layout, LogicalResult processRegion(Region& region, Layout& layout, SmallVector& history, RoutingContext& ctx) { - schedule(layout, region); /// Find and route each of the layers. Might violate SSA dominance. const auto res = routeEachLayer(schedule(layout, region), layout, history, ctx); From 11bf8d7783502708f96b3ba0eecd9087b6e41e28 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Sun, 16 Nov 2025 13:58:04 +0100 Subject: [PATCH 23/56] Use TypeSwitch --- .../Transpilation/AStarHeuristicRouter.h | 2 +- .../Transpilation/sc/AStarRoutingPass.cpp | 191 +++++++++--------- 2 files changed, 99 insertions(+), 94 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h index 6da851157d..e37f99aa06 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h @@ -140,7 +140,7 @@ struct AStarHeuristicRouter final { public: [[nodiscard]] std::optional> route(Layers layers, const ThinLayout& layout, const Architecture& arch, - std::size_t maxIterations = 20001UL) { + std::size_t maxIterations = 5001UL) { Node root(layout); /// Early exit. No SWAPs required: diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index fb93b27ab5..edea7b83b5 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -92,7 +92,7 @@ struct RoutingContext { struct Layer { /// @brief All ops contained in this layer. - SmallVector ops; + SmallVector ops; /// @brief The program indices of the gates in this layer. SmallVector gates; /// @brief The first op in ops in textual IR order. @@ -109,7 +109,7 @@ struct Layer { }; /// @brief A vector of layers. -using LayerVec = SmallVector; +using LayerVec = SmallVector; struct Wire { Wire(WireIterator it, QubitIndex index) : it(it), index(index) {} @@ -128,7 +128,7 @@ class SynchronizationMap { public: /// @returns true iff. the operation is contained in the map. - bool contains(Operation* op) { return onHold.contains(op); } + bool contains(Operation* op) const { return onHold.contains(op); } /// @brief Add op with respective wire and ref count to map. void add(Operation* op, Wire wire, const std::size_t cnt) { @@ -265,93 +265,97 @@ collectLayerAndAdvance(ArrayRef wires, SynchronizationMap& sync, for (auto [it, index] : wires) { while (it != WireIterator()) { - Operation* curr = *it; - - /// A barrier may be a UnitaryInterface, but also requires - /// synchronization. - if (auto op = dyn_cast(curr)) { - if (!sync.contains(op)) { - sync.add(op, Wire(++it, index), op.getInQubits().size()); - break; - } - - if (const auto iterators = sync.visit(op, Wire(++it, index))) { - layer.addOp(op); - next.append(iterators.value()); - } - + const bool stop = + TypeSwitch(*it) + .Case([&](BarrierOp op) { + /// A barrier may be a UnitaryInterface, but also requires + /// synchronization. + if (!sync.contains(op)) { + sync.add(op, Wire(++it, index), op.getInQubits().size()); + return true; + } + + if (const auto iterators = sync.visit(op, Wire(++it, index))) { + layer.addOp(op); + next.append(std::make_move_iterator(iterators->begin()), + std::make_move_iterator(iterators->end())); + } + return true; + }) + .Case([&](UnitaryInterface op) { + if (!isTwoQubitGate(op)) { + layer.addOp(op); // Add 1Q-op to layer operations. + ++it; + return false; + } + + if (!sync.contains(op)) { + /// Add the next iterator after the two-qubit + /// gate for a later release. + sync.add(op, Wire(++it, index), 2); + return true; + } + + if (const auto iterators = sync.visit(op, Wire(++it, index))) { + /// Only add ready two-qubit gates to the layer. + layer.addOp(op); + layer.gates.emplace_back((*iterators)[0].index, + (*iterators)[1].index); + + /// Release iterators for next iteration. + next.append( + advanceUntilEndOfTwoQubitBlock(iterators.value(), layer)); + } + return true; + }) + .Case([&](ResetOp op) { + layer.addOp(op); + ++it; + return false; + }) + .Case([&](MeasureOp op) { + layer.addOp(op); + ++it; + return false; + }) + .Case([&](RegionBranchOpInterface op) { + if (!sync.contains(op)) { + /// This assumes that branch ops always returns all hardware + /// qubits. + sync.add(op, Wire(++it, index), nqubits); + return true; + } + + if (const auto iterators = sync.visit(op, Wire(++it, index))) { + layer.addOp(op); + next.append(std::make_move_iterator(iterators->begin()), + std::make_move_iterator(iterators->end())); + } + return true; + }) + .Case([&](scf::YieldOp yield) { + if (!sync.contains(yield)) { + /// This assumes that yield always returns all hardware + /// qubits. + sync.add(yield, Wire(++it, index), nqubits); + return true; + } + + if (const auto iterators = + sync.visit(yield, Wire(++it, index))) { + layer.addOp(yield); + } + + return true; + }) + .Default([](auto) { + llvm_unreachable("unhandled operation"); + return true; + }); + + if (stop) { break; } - - if (auto op = dyn_cast(curr)) { - if (!isTwoQubitGate(op)) { - layer.addOp(curr); // Add 1Q-op to layer operations. - ++it; - continue; - } - - if (!sync.contains(op)) { - /// Add the next iterator after the two-qubit - /// gate for a later release. - sync.add(op, Wire(++it, index), 2); - break; - } - - if (const auto iterators = sync.visit(op, Wire(++it, index))) { - /// Only add ready two-qubit gates to the layer. - layer.addOp(op); - layer.gates.emplace_back((*iterators)[0].index, - (*iterators)[1].index); - - /// Release iterators for next iteration. - next.append(advanceUntilEndOfTwoQubitBlock(iterators.value(), layer)); - } - - break; - } - - if (auto op = dyn_cast(curr)) { - layer.addOp(curr); - ++it; - continue; - } - - if (auto op = dyn_cast(curr)) { - layer.addOp(curr); - ++it; - continue; - } - - if (auto op = dyn_cast(curr)) { - if (!sync.contains(op)) { - /// This assumes that branch ops always returns all hardware qubits. - sync.add(op, Wire(++it, index), nqubits); - break; - } - - if (const auto iterators = sync.visit(op, Wire(++it, index))) { - layer.addOp(op); - next.append(iterators.value()); - } - break; - } - - if (auto yield = dyn_cast(curr)) { - if (!sync.contains(yield)) { - /// This assumes that yield always returns all hardware qubits. - sync.add(yield, Wire(++it, index), nqubits); - break; - } - - if (const auto iterators = sync.visit(yield, Wire(++it, index))) { - layer.addOp(yield); - } - - break; - } - - /// Anything else is a bug in the program. - assert(false && "unhandled operation"); } } @@ -371,7 +375,7 @@ LayerVec schedule(const Layout& layout, Region& region) { wires.emplace_back(WireIterator(q, ®ion), layout.getProgramIndex(hw)); } - do { + while (!wires.empty()) { const auto [layer, next] = collectLayerAndAdvance(wires, sync, layout.getNumQubits()); if (layer.ops.empty()) { @@ -379,7 +383,7 @@ LayerVec schedule(const Layout& layout, Region& region) { } layers.emplace_back(layer); wires = next; - } while (!wires.empty()); + }; LLVM_DEBUG({ llvm::dbgs() << "schedule: layers=\n"; @@ -620,6 +624,7 @@ LogicalResult routeEachLayer(const LayerVec& layers, Layout layout, LogicalResult processRegion(Region& region, Layout& layout, SmallVector& history, RoutingContext& ctx) { + std::ignore = schedule(layout, region); /// Find and route each of the layers. Might violate SSA dominance. const auto res = routeEachLayer(schedule(layout, region), layout, history, ctx); @@ -628,9 +633,9 @@ LogicalResult processRegion(Region& region, Layout& layout, } /// Repair any SSA dominance issues. - for (Block& block : region.getBlocks()) { - sortTopologically(&block); - } + // for (Block& block : region.getBlocks()) { + // sortTopologically(&block); + // } return success(); } From 882909ce546cdc1c2bca42e66146624da1b833a9 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 18 Nov 2025 09:32:44 +0100 Subject: [PATCH 24/56] Clean up pull-request --- .../{AStarHeuristicRouter.h => Router.h} | 58 ++- .../Transforms/Transpilation/Schedule.h | 66 +++ .../Transforms/Transpilation/WireIterator.h | 7 +- .../Transforms/Transpilation/Schedule.cpp | 283 ++++++++++++ .../Transpilation/sc/AStarRoutingPass.cpp | 419 ++---------------- 5 files changed, 413 insertions(+), 420 deletions(-) rename mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/{AStarHeuristicRouter.h => Router.h} (78%) create mode 100644 mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Schedule.h create mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Schedule.cpp diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h similarity index 78% rename from mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h rename to mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h index e37f99aa06..25a27c966a 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h @@ -13,22 +13,20 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" - -#include "llvm/Support/Debug.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Schedule.h" #include #include #include #include -#include #include #include namespace mqt::ir::opt { -/** - * @brief Specifies the weights for different terms in the cost function f. - */ +using namespace mlir; + +/// @brief Specifies the weights for different terms in the cost function f. struct HeuristicWeights { float alpha; SmallVector lambdas; @@ -43,17 +41,11 @@ struct HeuristicWeights { } }; -/** - * @brief Use A*-search to make all gates executable. - */ struct AStarHeuristicRouter final { explicit AStarHeuristicRouter(HeuristicWeights weights) : weights_(std::move(weights)) {} private: - using Layer = ArrayRef; - using Layers = ArrayRef; - struct Node { SmallVector sequence; ThinLayout layout; @@ -69,8 +61,9 @@ struct AStarHeuristicRouter final { * @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, QubitIndexPair swap, Layers layers, - const Architecture& arch, const HeuristicWeights& weights) + Node(const Node& parent, QubitIndexPair swap, + ArrayRef window, const Architecture& arch, + const HeuristicWeights& weights) : sequence(parent.sequence), layout(parent.layout), f(0) { /// Apply node-specific swap to given layout. layout.swap(layout.getProgramIndex(swap.first), @@ -80,14 +73,15 @@ struct AStarHeuristicRouter final { sequence.push_back(swap); /// Evaluate cost function. - f = g(weights) + h(layers, arch, weights); // NOLINT + f = g(weights) + h(window, arch, weights); // NOLINT } /** * @brief Return true if the current sequence of SWAPs makes all gates * executable. */ - [[nodiscard]] bool isGoal(Layer layer, const Architecture& arch) const { + [[nodiscard]] bool isGoal(const Schedule::GateLayer& layer, + const Architecture& arch) const { return std::ranges::all_of(layer, [&](const QubitIndexPair gate) { return arch.areAdjacent(layout.getHardwareIndex(gate.first), layout.getHardwareIndex(gate.second)); @@ -120,10 +114,11 @@ struct AStarHeuristicRouter final { * its hardware qubits. Intuitively, this is the number of SWAPs that a * naive router would insert to route the layers. */ - [[nodiscard]] float h(Layers layers, const Architecture& arch, + [[nodiscard]] float h(ArrayRef window, + const Architecture& arch, const HeuristicWeights& weights) const { float nn{0}; - for (const auto [i, layer] : llvm::enumerate(layers)) { + for (const auto [i, layer] : llvm::enumerate(window)) { for (const auto [prog0, prog1] : layer) { const auto [hw0, hw1] = layout.getHardwareIndices(prog0, prog1); const std::size_t dist = arch.distanceBetween(hw0, hw1); @@ -139,13 +134,13 @@ struct AStarHeuristicRouter final { public: [[nodiscard]] std::optional> - route(Layers layers, const ThinLayout& layout, const Architecture& arch, - std::size_t maxIterations = 5001UL) { + route(ArrayRef window, const ThinLayout& layout, + const Architecture& arch) { Node root(layout); /// Early exit. No SWAPs required: - if (root.isGoal(layers.front(), arch)) { - return {}; + if (root.isGoal(window.front(), arch)) { + return SmallVector{}; } /// Initialize queue. @@ -153,17 +148,16 @@ struct AStarHeuristicRouter final { frontier.emplace(root); /// Iterative searching and expanding. - while (!frontier.empty() && maxIterations > 0) { + while (!frontier.empty()) { Node curr = frontier.top(); frontier.pop(); - if (curr.isGoal(layers.front(), arch)) { + if (curr.isGoal(window.front(), arch)) { return curr.sequence; } /// Expand frontier with all neighbouring SWAPs in the current front. - expand(frontier, curr, layers, arch); - maxIterations--; + expand(frontier, curr, window, arch); } return std::nullopt; @@ -173,10 +167,10 @@ struct AStarHeuristicRouter final { /** * @brief Expand frontier with all neighbouring SWAPs in the current front. */ - void expand(MinQueue& frontier, const Node& parent, Layers layers, - const Architecture& arch) { - expansionSet.clear(); - for (const QubitIndexPair gate : layers.front()) { + void expand(MinQueue& frontier, const Node& parent, + ArrayRef window, const Architecture& arch) { + llvm::SmallDenseSet expansionSet{}; + for (const QubitIndexPair gate : window.front()) { for (const auto prog : {gate.first, gate.second}) { const auto hw0 = parent.layout.getHardwareIndex(prog); for (const auto hw1 : arch.neighboursOf(hw0)) { @@ -186,13 +180,13 @@ struct AStarHeuristicRouter final { continue; } - frontier.emplace(parent, swap, layers, arch, weights_); + frontier.emplace(parent, swap, window, arch, weights_); } } } } - llvm::SmallDenseSet expansionSet{}; HeuristicWeights weights_; }; + } // namespace mqt::ir::opt diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Schedule.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Schedule.h new file mode 100644 index 0000000000..8dd54be8ce --- /dev/null +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Schedule.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" + +#include + +#define DEBUG_TYPE "route-astar-sc" + +namespace mqt::ir::opt { + +struct Schedule { + using GateLayer = SmallVector; + using GateLayers = SmallVector; + + struct OpLayer { + /// @brief All ops contained inside this layer. + SmallVector ops; + /// @brief The first op in ops in textual IR order. + Operation* anchor{}; + + /// @brief Add op to ops and reset anchor if necessary. + void addOp(Operation* op) { + ops.emplace_back(op); + if (anchor == nullptr || op->isBeforeInBlock(anchor)) { + anchor = op; + } + } + + [[nodiscard]] bool empty() const { return ops.empty(); } + }; + + using OpLayers = SmallVector; + + /// @brief Vector of layers containing the program indices of the two-qubit + /// gates inside it. + GateLayers gateLayers; + + /// @brief Vector of layers containing the ops inside it. + OpLayers opLayers; + + /// @returns a window of gate layers from (start, start + nlookahead). + MutableArrayRef getWindow(std::size_t start, + std::size_t nlookahead); + +#ifndef NDEBUG + LLVM_DUMP_METHOD void dump(llvm::raw_ostream& os = llvm::dbgs()) const; +#endif +}; + +/** + * @brief Given a layout, divide the circuit into layers and schedule the ops in + * their respective layer. + */ +Schedule getSchedule(const Layout& layout, Region& region); +} // namespace mqt::ir::opt diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h index 4f78371178..9a71a281d7 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h @@ -44,11 +44,14 @@ using namespace mlir; */ class WireIterator { public: + static constexpr WireIterator end() { return {}; } + using difference_type = std::ptrdiff_t; using value_type = Operation*; - explicit WireIterator(Value q = nullptr, Region* region = nullptr) - : q(q), region(region) { + constexpr WireIterator() : q(nullptr), region(nullptr) {} + + explicit WireIterator(Value q, Region* region) : q(q), region(region) { setNextOp(); } diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Schedule.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Schedule.cpp new file mode 100644 index 0000000000..582342e3c6 --- /dev/null +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Schedule.cpp @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Schedule.h" + +#include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h" + +#include "llvm/ADT/ArrayRef.h" + +#include +#include + +namespace mqt::ir::opt { + +namespace { +struct Wire { + Wire(const WireIterator& it, QubitIndex index) : it(it), index(index) {} + + WireIterator it; + QubitIndex index; +}; + +/// @brief Map to handle multi-qubit gates when traversing the def-use chain. +class SynchronizationMap { + /// @brief Maps operations to to-be-released iterators. + DenseMap> onHold; + + /// @brief Maps operations to ref counts. An op can be released whenever the + /// count reaches zero. + DenseMap refCount; + +public: + /// @returns true iff. the operation is contained in the map. + bool contains(Operation* op) const { return onHold.contains(op); } + + /// @brief Add op with respective wire and ref count to map. + void add(Operation* op, Wire wire, const std::size_t cnt) { + onHold.try_emplace(op, SmallVector{wire}); + /// Decrease the cnt by one because the op was visited when adding. + refCount.try_emplace(op, cnt - 1); + } + + /// @brief Decrement ref count of op and potentially release its iterators. + std::optional> visit(Operation* op, Wire wire) { + assert(refCount.contains(op) && "expected sync map to contain op"); + + /// Add iterator for later release. + onHold[op].push_back(wire); + + /// Release iterators whenever the ref count reaches zero. + if (--refCount[op] == 0) { + return onHold[op]; + } + + return std::nullopt; + } +}; + +SmallVector skipTwoQubitBlock(ArrayRef wires, + Schedule::OpLayer& opLayer) { + assert(wires.size() == 2 && "expected two wires"); + + WireIterator end; + auto [it0, index0] = wires[0]; + auto [it1, index1] = wires[1]; + while (it0 != end && it1 != end) { + Operation* op0 = *it0; + if (!isa(op0) || isa(op0)) { + break; + } + + Operation* op1 = *it1; + if (!isa(op1) || isa(op1)) { + break; + } + + UnitaryInterface u0 = cast(op0); + + /// Advance for single qubit gate on wire 0. + if (!isTwoQubitGate(u0)) { + opLayer.addOp(u0); + ++it0; + continue; + } + + UnitaryInterface u1 = cast(op1); + + /// Advance for single qubit gate on wire 1. + if (!isTwoQubitGate(u1)) { + opLayer.addOp(u1); + ++it1; + continue; + } + + /// Stop if the wires reach different two qubit gates. + if (op0 != op1) { + break; + } + + /// Advance if u0 == u1. + opLayer.addOp(u1); + + ++it0; + ++it1; + } + + return {Wire(it0, index0), Wire(it1, index1)}; +} + +/** + * @brief Advance each wire until (>=2)-qubit gates are found, collect the + * indices of the respective two-qubit gates, and prepare iterators for next + * iteration. + */ +void collectLayerAndAdvance(ArrayRef wires, SynchronizationMap& sync, + Schedule& s, const std::size_t nqubits, + SmallVector& next) { + Schedule::GateLayer gateLayer; + Schedule::OpLayer opLayer; + + for (auto [it, index] : wires) { + while (it != WireIterator::end()) { + const bool stop = + TypeSwitch(*it) + .Case([&](UnitaryInterface op) { + const auto nins = op.getInQubits().size() + + op.getPosCtrlInQubits().size() + + op.getNegCtrlInQubits().size(); + + /// Skip over one-qubit gates. Note: Might be a BarrierOp. + if (nins == 1) { + opLayer.addOp(op); + ++it; + return false; + } + + /// Otherwise, add it to the sync map. + if (!sync.contains(op)) { + sync.add(op, Wire(++it, index), nins); + return true; + } + + if (const auto iterators = sync.visit(op, Wire(++it, index))) { + opLayer.addOp(op); + + if (!isa(op)) { // Is ready two-qubit unitary? + gateLayer.emplace_back((*iterators)[0].index, + (*iterators)[1].index); + next.append(skipTwoQubitBlock(*iterators, opLayer)); + } else { + next.append(*iterators); + } + } + + return true; + }) + .Case([&](ResetOp op) { + opLayer.addOp(op); + ++it; + return false; + }) + .Case([&](MeasureOp op) { + opLayer.addOp(op); + ++it; + return false; + }) + .Case([&](scf::YieldOp yield) { + if (!sync.contains(yield)) { + sync.add(yield, Wire(++it, index), nqubits); + return true; + } + + if (const auto iterators = + sync.visit(yield, Wire(++it, index))) { + opLayer.addOp(yield); + } + + return true; + }) + .Case([&](RegionBranchOpInterface op) { + if (!sync.contains(op)) { + sync.add(op, Wire(++it, index), nqubits); + return true; + } + + if (const auto iterators = sync.visit(op, Wire(++it, index))) { + opLayer.addOp(op); + next.append(*iterators); + } + return true; + }) + .Default([](auto) { + llvm_unreachable("unhandled operation"); + return true; + }); + + if (stop) { + break; + } + } + } + + /// If there is no gates to route, merge the last layer with this one and + /// keep the anchor the same. Otherwise, add the layer. + if (gateLayer.empty()) { + s.gateLayers.back().append(gateLayer); + s.opLayers.back().ops.append(opLayer.ops); + } else { + s.gateLayers.emplace_back(gateLayer); + s.opLayers.emplace_back(opLayer); + } +} +} // namespace + +MutableArrayRef +Schedule::getWindow(const std::size_t start, const std::size_t nlookahead) { + const size_t sz = gateLayers.size(); + const size_t len = std::min(1 + nlookahead, sz - start); + return MutableArrayRef(gateLayers).slice(start, len); +} + +#ifndef NDEBUG +LLVM_DUMP_METHOD void Schedule::dump(llvm::raw_ostream& os) const { + os << "schedule: gate layers=\n"; + for (const auto [i, layer] : llvm::enumerate(gateLayers)) { + os << '\t' << i << ": "; + os << "gates= "; + if (!layer.empty()) { + for (const auto [prog0, prog1] : layer) { + os << "(" << prog0 << "," << prog1 << "), "; + } + } else { + os << "(), "; + } + os << '\n'; + } + + os << "schedule: op layers=\n"; + for (const auto [i, layer] : llvm::enumerate(opLayers)) { + os << '\t' << i << ": "; + os << "#ops= " << layer.ops.size(); + if (!layer.ops.empty()) { + os << " anchor= " << layer.anchor->getLoc(); + } + os << '\n'; + } +} +#endif + +Schedule getSchedule(const Layout& layout, Region& region) { + Schedule s; + SynchronizationMap sync; + + SmallVector curr; + SmallVector next; + + const auto nqubits = layout.getNumQubits(); + curr.reserve(nqubits); + next.reserve(nqubits); + + for (auto [hw, q] : llvm::enumerate(layout.getHardwareQubits())) { + curr.emplace_back(WireIterator(q, ®ion), layout.getProgramIndex(hw)); + } + + while (!curr.empty()) { + collectLayerAndAdvance(curr, sync, s, nqubits, next); + curr.swap(next); + next.clear(); + }; + + LLVM_DEBUG(s.dump()); + + return s; +} +} // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index edea7b83b5..b3c7b0eccd 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -10,16 +10,14 @@ #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include "mlir/Dialect/MQTOpt/Transforms/Passes.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/AStarHeuristicRouter.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Schedule.h" -#include #include #include -#include #include #include #include @@ -48,7 +46,6 @@ #include #include #include -#include #include #define DEBUG_TYPE "route-astar-sc" @@ -90,69 +87,6 @@ struct RoutingContext { std::size_t nlookahead; }; -struct Layer { - /// @brief All ops contained in this layer. - SmallVector ops; - /// @brief The program indices of the gates in this layer. - SmallVector gates; - /// @brief The first op in ops in textual IR order. - Operation* anchor{}; - /// @brief Add op to ops and reset anchor if necessary. - void addOp(Operation* op) { - ops.emplace_back(op); - if (anchor == nullptr || op->isBeforeInBlock(anchor)) { - anchor = op; - } - } - /// @returns true iff the layer contains gates to route. - [[nodiscard]] bool hasRoutableGates() const { return !gates.empty(); } -}; - -/// @brief A vector of layers. -using LayerVec = SmallVector; - -struct Wire { - Wire(WireIterator it, QubitIndex index) : it(it), index(index) {} - WireIterator it; - QubitIndex index; -}; - -/// @brief Map to handle multi-qubit gates when traversing the def-use chain. -class SynchronizationMap { - /// @brief Maps operations to to-be-released iterators. - DenseMap> onHold; - - /// @brief Maps operations to ref counts. An op can be released whenever the - /// count reaches zero. - DenseMap refCount; - -public: - /// @returns true iff. the operation is contained in the map. - bool contains(Operation* op) const { return onHold.contains(op); } - - /// @brief Add op with respective wire and ref count to map. - void add(Operation* op, Wire wire, const std::size_t cnt) { - onHold.try_emplace(op, SmallVector{wire}); - /// Decrease the cnt by one because the op was visited when adding. - refCount.try_emplace(op, cnt - 1); - } - - /// @brief Decrement ref count of op and potentially release its iterators. - std::optional> visit(Operation* op, Wire wire) { - assert(refCount.contains(op) && "expected sync map to contain op"); - - /// Add iterator for later release. - onHold[op].push_back(wire); - - /// Release iterators whenever the ref count reaches zero. - if (--refCount[op] == 0) { - return onHold[op]; - } - - return std::nullopt; - } -}; - LogicalResult processRegion(Region& region, Layout& layout, SmallVector& history, RoutingContext& ctx); @@ -175,8 +109,8 @@ void insertSWAPs(Location location, ArrayRef swaps, LLVM_DEBUG({ llvm::dbgs() << llvm::format( - "route: swap= p%d:h%d, p%d:h%d <- p%d:h%d, p%d:h%d\n", prog1, hw0, - prog0, hw1, prog0, hw0, prog1, hw1); + "insertSWAPs: swap= p%d:h%d, p%d:h%d <- p%d:h%d, p%d:h%d\n", prog1, + hw0, prog0, hw1, prog0, hw0, prog1, hw1); }); auto swap = createSwap(location, in0, in1, rewriter); @@ -194,224 +128,6 @@ void insertSWAPs(Location location, ArrayRef swaps, } } -//===----------------------------------------------------------------------===// -// Scheduling -//===----------------------------------------------------------------------===// - -SmallVector advanceUntilEndOfTwoQubitBlock(ArrayRef wires, - Layer& layer) { - assert(wires.size() == 2 && "expected two wires"); - - WireIterator end; - auto [it0, index0] = wires[0]; - auto [it1, index1] = wires[1]; - while (it0 != end && it1 != end) { - Operation* op0 = *it0; - if (!isa(op0) || isa(op0)) { - break; - } - - Operation* op1 = *it1; - if (!isa(op1) || isa(op1)) { - break; - } - - UnitaryInterface u0 = cast(op0); - - /// Advance for single qubit gate on wire 0. - if (!isTwoQubitGate(u0)) { - layer.addOp(u0); // Add 1Q-op to layer operations. - ++it0; - continue; - } - - UnitaryInterface u1 = cast(op1); - - /// Advance for single qubit gate on wire 1. - if (!isTwoQubitGate(u1)) { - layer.addOp(u1); // Add 1Q-op to layer operations. - ++it1; - continue; - } - - /// Stop if the wires reach different two qubit gates. - if (op0 != op1) { - break; - } - - /// Remap and advance if u0 == u1. - layer.addOp(u1); - - ++it0; - ++it1; - } - - return {Wire(it0, index0), Wire(it1, index1)}; -} - -/** - * @brief Advance each wire until (>=2)-qubit gates are found, collect the - * indices of the respective two-qubit gates, and prepare iterators for next - * iteration. - */ -std::pair> -collectLayerAndAdvance(ArrayRef wires, SynchronizationMap& sync, - const std::size_t nqubits) { - /// The collected layer. - Layer layer; - /// A vector of iterators for the next iteration. - SmallVector next; - next.reserve(wires.size()); - - for (auto [it, index] : wires) { - while (it != WireIterator()) { - const bool stop = - TypeSwitch(*it) - .Case([&](BarrierOp op) { - /// A barrier may be a UnitaryInterface, but also requires - /// synchronization. - if (!sync.contains(op)) { - sync.add(op, Wire(++it, index), op.getInQubits().size()); - return true; - } - - if (const auto iterators = sync.visit(op, Wire(++it, index))) { - layer.addOp(op); - next.append(std::make_move_iterator(iterators->begin()), - std::make_move_iterator(iterators->end())); - } - return true; - }) - .Case([&](UnitaryInterface op) { - if (!isTwoQubitGate(op)) { - layer.addOp(op); // Add 1Q-op to layer operations. - ++it; - return false; - } - - if (!sync.contains(op)) { - /// Add the next iterator after the two-qubit - /// gate for a later release. - sync.add(op, Wire(++it, index), 2); - return true; - } - - if (const auto iterators = sync.visit(op, Wire(++it, index))) { - /// Only add ready two-qubit gates to the layer. - layer.addOp(op); - layer.gates.emplace_back((*iterators)[0].index, - (*iterators)[1].index); - - /// Release iterators for next iteration. - next.append( - advanceUntilEndOfTwoQubitBlock(iterators.value(), layer)); - } - return true; - }) - .Case([&](ResetOp op) { - layer.addOp(op); - ++it; - return false; - }) - .Case([&](MeasureOp op) { - layer.addOp(op); - ++it; - return false; - }) - .Case([&](RegionBranchOpInterface op) { - if (!sync.contains(op)) { - /// This assumes that branch ops always returns all hardware - /// qubits. - sync.add(op, Wire(++it, index), nqubits); - return true; - } - - if (const auto iterators = sync.visit(op, Wire(++it, index))) { - layer.addOp(op); - next.append(std::make_move_iterator(iterators->begin()), - std::make_move_iterator(iterators->end())); - } - return true; - }) - .Case([&](scf::YieldOp yield) { - if (!sync.contains(yield)) { - /// This assumes that yield always returns all hardware - /// qubits. - sync.add(yield, Wire(++it, index), nqubits); - return true; - } - - if (const auto iterators = - sync.visit(yield, Wire(++it, index))) { - layer.addOp(yield); - } - - return true; - }) - .Default([](auto) { - llvm_unreachable("unhandled operation"); - return true; - }); - - if (stop) { - break; - } - } - } - - return {layer, next}; -} - -/** - * @brief Given a layout, divide the circuit into layers and schedule the ops in - * their respective layer. - */ -LayerVec schedule(const Layout& layout, Region& region) { - LayerVec layers; - SynchronizationMap sync; - SmallVector wires; - wires.reserve(layout.getNumQubits()); - for (auto [hw, q] : llvm::enumerate(layout.getHardwareQubits())) { - wires.emplace_back(WireIterator(q, ®ion), layout.getProgramIndex(hw)); - } - - while (!wires.empty()) { - const auto [layer, next] = - collectLayerAndAdvance(wires, sync, layout.getNumQubits()); - if (layer.ops.empty()) { - break; - } - layers.emplace_back(layer); - wires = next; - }; - - LLVM_DEBUG({ - llvm::dbgs() << "schedule: layers=\n"; - for (const auto [i, layer] : llvm::enumerate(layers)) { - llvm::dbgs() << '\t' << i << ": "; - llvm::dbgs() << "#ops= " << layer.ops.size() << ", "; - llvm::dbgs() << "gates= "; - if (layer.hasRoutableGates()) { - for (const auto [prog0, prog1] : layer.gates) { - llvm::dbgs() << "(" << prog0 << "," << prog1 << "), "; - } - } else { - llvm::dbgs() << "(), "; - } - if (layer.anchor) { - llvm::dbgs() << "anchor= " << layer.anchor->getLoc() << ", "; - } - llvm::dbgs() << '\n'; - } - }); - - return layers; -} - -//===----------------------------------------------------------------------===// -// Routing -//===----------------------------------------------------------------------===// - /** * @brief Copy the layout and recursively process the loop body. */ @@ -497,95 +213,51 @@ WalkResult handle(UnitaryInterface op, Layout& layout, /** * @brief Find and insert SWAPs using A*-search. - * - * If A*-search fails use the LightSABRE fallback mechanism: Search - * the the gate which acts on the qubit index pair with the minimum distance in - * the coupling graph of the targeted architecture. Route this gate naively, - * remove it from the front layer, and restart the A*-search. */ -void findAndInsertSWAPs(ArrayRef> layers, - Location location, Layout& layout, +void findAndInsertSWAPs(MutableArrayRef window, + Operation* anchor, Layout& layout, SmallVector& history, RoutingContext& ctx) { - /// Mutable copy of the front layer. - SmallVector workingFront(layers.front()); - - SmallVector> workingLayers; - workingLayers.reserve(layers.size()); - workingLayers.push_back(workingFront); // Non-owning view of workingFront. - for (auto layer : layers.drop_front()) { - workingLayers.push_back(layer); - } + ctx.rewriter.setInsertionPoint(anchor); - while (!workingFront.empty()) { - if (const auto swaps = ctx.router.route(workingLayers, layout, *ctx.arch)) { + if (const auto swaps = ctx.router.route(window, layout, *ctx.arch)) { + if (!swaps->empty()) { history.append(*swaps); - insertSWAPs(location, *swaps, layout, ctx.rewriter); + insertSWAPs(anchor->getLoc(), *swaps, layout, ctx.rewriter); *(ctx.stats.numSwaps) += swaps->size(); - return; - } - - QubitIndexPair bestGate; - std::size_t bestIdx = 0; - std::size_t minDist = std::numeric_limits::max(); - for (const auto [i, gate] : llvm::enumerate(workingFront)) { - const auto hw0 = layout.getHardwareIndex(gate.first); - const auto hw1 = layout.getHardwareIndex(gate.second); - const auto dist = ctx.arch->distanceBetween(hw0, hw1); - if (dist < minDist) { - bestIdx = i; - minDist = dist; - bestGate = std::make_pair(hw0, hw1); - } - } - - workingFront.erase(workingFront.begin() + static_cast(bestIdx)); - - SmallVector swaps; - const auto path = - ctx.arch->shortestPathBetween(bestGate.first, bestGate.second); - for (std::size_t i = 0; i < path.size() - 2; ++i) { - swaps.emplace_back(path[i], path[i + 1]); } - - history.append(swaps); - insertSWAPs(location, swaps, layout, ctx.rewriter); - *(ctx.stats.numSwaps) += swaps.size(); + return; } + + throw std::runtime_error("A* router failed to find a valid SWAP sequence"); } /** - * @brief Route each layer by iterating a sliding window of (1 + nlookahead) - * layers. + * @brief Schedule and route the given region. + * + * Since this might break SSA Dominance, sort the blocks in the given region + * topologically. */ -LogicalResult routeEachLayer(const LayerVec& layers, Layout layout, - SmallVector& history, - RoutingContext& ctx) { - const auto nhorizion = static_cast(1 + ctx.nlookahead); - - LayerVec::const_iterator end = layers.end(); - LayerVec::const_iterator it = layers.begin(); - for (; it != end; std::advance(it, 1)) { - LayerVec::const_iterator lookaheadIt = - std::min(end, std::next(it, nhorizion)); - - const auto& front = *it; // == window.front() - - if (front.hasRoutableGates()) { - ctx.rewriter.setInsertionPoint(front.anchor); +LogicalResult processRegion(Region& region, Layout& layout, + SmallVector& history, + RoutingContext& ctx) { + /// Find and route each of the layers. Might violate SSA dominance. + auto schedule = getSchedule(layout, region); - /// Find SWAPs for front layer with nlookahead layers. - const auto window = llvm::make_range(it, lookaheadIt); - const auto windowLayerGates = to_vector(llvm::map_range( - window, [](const Layer& layer) { return ArrayRef(layer.gates); })); + for (std::size_t i = 0; i < schedule.gateLayers.size(); ++i) { + auto window = schedule.getWindow(i, ctx.nlookahead); + auto opLayer = schedule.opLayers[i]; - findAndInsertSWAPs(windowLayerGates, front.anchor->getLoc(), layout, - history, ctx); - } + findAndInsertSWAPs(window, opLayer.anchor, layout, history, ctx); - for (const Operation* curr : front.ops) { - const auto res = TypeSwitch(curr) + for (Operation* curr : opLayer.ops) { + ctx.rewriter.setInsertionPoint(curr); + const auto res = TypeSwitch(curr) .Case([&](UnitaryInterface op) { + if (i + 1 < schedule.gateLayers.size()) { + ctx.rewriter.moveOpBefore( + op, schedule.opLayers[i + 1].anchor); + } return handle(op, layout, history); }) .Case([&](ResetOp op) { @@ -615,31 +287,6 @@ LogicalResult routeEachLayer(const LayerVec& layers, Layout layout, return success(); } -/** - * @brief Schedule and route the given region. - * - * Since this might break SSA Dominance, sort the blocks in the given region - * topologically. - */ -LogicalResult processRegion(Region& region, Layout& layout, - SmallVector& history, - RoutingContext& ctx) { - std::ignore = schedule(layout, region); - /// Find and route each of the layers. Might violate SSA dominance. - const auto res = - routeEachLayer(schedule(layout, region), layout, history, ctx); - if (res.failed()) { - return res; - } - - /// Repair any SSA dominance issues. - // for (Block& block : region.getBlocks()) { - // sortTopologically(&block); - // } - - return success(); -} - /** * @brief Route the given module for the targeted architecture using A*-search. * Processes each entry_point function separately. From c201372df261ac94f4de91389eceffe040071dd1 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 19 Nov 2025 13:22:40 +0100 Subject: [PATCH 25/56] Add Unit abstraction --- .../MQTOpt/Transforms/Transpilation/Router.h | 20 +- .../Transforms/Transpilation/Schedule.h | 66 --- .../MQTOpt/Transforms/Transpilation/Unit.h | 98 ++++ .../Transforms/Transpilation/WireIterator.h | 11 +- .../Transforms/Transpilation/Architecture.cpp | 217 +++++++++ .../Transforms/Transpilation/Schedule.cpp | 283 ------------ .../MQTOpt/Transforms/Transpilation/Unit.cpp | 430 ++++++++++++++++++ .../Transpilation/sc/AStarRoutingPass.cpp | 229 +--------- 8 files changed, 786 insertions(+), 568 deletions(-) delete mode 100644 mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Schedule.h create mode 100644 mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h delete mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Schedule.cpp create mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Unit.cpp diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h index 25a27c966a..be3d061e32 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h @@ -13,7 +13,6 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Schedule.h" #include #include @@ -26,6 +25,9 @@ namespace mqt::ir::opt { using namespace mlir; +using GateLayer = SmallVector; +using GateLayers = SmallVector; + /// @brief Specifies the weights for different terms in the cost function f. struct HeuristicWeights { float alpha; @@ -61,9 +63,8 @@ struct AStarHeuristicRouter final { * @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, QubitIndexPair swap, - ArrayRef window, const Architecture& arch, - const HeuristicWeights& weights) + Node(const Node& parent, QubitIndexPair swap, ArrayRef window, + const Architecture& arch, const HeuristicWeights& weights) : sequence(parent.sequence), layout(parent.layout), f(0) { /// Apply node-specific swap to given layout. layout.swap(layout.getProgramIndex(swap.first), @@ -80,7 +81,7 @@ struct AStarHeuristicRouter final { * @brief Return true if the current sequence of SWAPs makes all gates * executable. */ - [[nodiscard]] bool isGoal(const Schedule::GateLayer& layer, + [[nodiscard]] bool isGoal(const GateLayer& layer, const Architecture& arch) const { return std::ranges::all_of(layer, [&](const QubitIndexPair gate) { return arch.areAdjacent(layout.getHardwareIndex(gate.first), @@ -114,8 +115,7 @@ struct AStarHeuristicRouter final { * its hardware qubits. Intuitively, this is the number of SWAPs that a * naive router would insert to route the layers. */ - [[nodiscard]] float h(ArrayRef window, - const Architecture& arch, + [[nodiscard]] float h(ArrayRef window, const Architecture& arch, const HeuristicWeights& weights) const { float nn{0}; for (const auto [i, layer] : llvm::enumerate(window)) { @@ -134,8 +134,8 @@ struct AStarHeuristicRouter final { public: [[nodiscard]] std::optional> - route(ArrayRef window, const ThinLayout& layout, - const Architecture& arch) { + route(ArrayRef window, const ThinLayout& layout, + const Architecture& arch) const { Node root(layout); /// Early exit. No SWAPs required: @@ -168,7 +168,7 @@ struct AStarHeuristicRouter final { * @brief Expand frontier with all neighbouring SWAPs in the current front. */ void expand(MinQueue& frontier, const Node& parent, - ArrayRef window, const Architecture& arch) { + ArrayRef window, const Architecture& arch) const { llvm::SmallDenseSet expansionSet{}; for (const QubitIndexPair gate : window.front()) { for (const auto prog : {gate.first, gate.second}) { diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Schedule.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Schedule.h deleted file mode 100644 index 8dd54be8ce..0000000000 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Schedule.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#pragma once - -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" - -#include - -#define DEBUG_TYPE "route-astar-sc" - -namespace mqt::ir::opt { - -struct Schedule { - using GateLayer = SmallVector; - using GateLayers = SmallVector; - - struct OpLayer { - /// @brief All ops contained inside this layer. - SmallVector ops; - /// @brief The first op in ops in textual IR order. - Operation* anchor{}; - - /// @brief Add op to ops and reset anchor if necessary. - void addOp(Operation* op) { - ops.emplace_back(op); - if (anchor == nullptr || op->isBeforeInBlock(anchor)) { - anchor = op; - } - } - - [[nodiscard]] bool empty() const { return ops.empty(); } - }; - - using OpLayers = SmallVector; - - /// @brief Vector of layers containing the program indices of the two-qubit - /// gates inside it. - GateLayers gateLayers; - - /// @brief Vector of layers containing the ops inside it. - OpLayers opLayers; - - /// @returns a window of gate layers from (start, start + nlookahead). - MutableArrayRef getWindow(std::size_t start, - std::size_t nlookahead); - -#ifndef NDEBUG - LLVM_DUMP_METHOD void dump(llvm::raw_ostream& os = llvm::dbgs()) const; -#endif -}; - -/** - * @brief Given a layout, divide the circuit into layers and schedule the ops in - * their respective layer. - */ -Schedule getSchedule(const Layout& layout, Region& region); -} // namespace mqt::ir::opt diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h new file mode 100644 index 0000000000..2cca549689 --- /dev/null +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h" + +#include +#include +#include +#include + +#define DEBUG_TYPE "route-astar-sc" + +namespace mqt::ir::opt { + +using namespace mlir; + +struct Wire { + Wire(const WireIterator& it, QubitIndex index) : it(it), index(index) {} + + WireIterator it; + QubitIndex index; +}; + +using GateLayer = SmallVector; +using GateLayers = SmallVector; + +struct OpLayer { + /// @brief All ops contained inside this layer. + SmallVector ops; + /// @brief The first op in ops in textual IR order. + Operation* anchor{}; + + /// @brief Add op to ops and reset anchor if necessary. + void addOp(Operation* op) { + ops.emplace_back(op); + if (anchor == nullptr || op->isBeforeInBlock(anchor)) { + anchor = op; + } + } + + [[nodiscard]] bool empty() const { return ops.empty(); } +}; + +using OpLayers = SmallVector; + +struct Schedule { + /// @returns a window of gate layers from (start, start + nlookahead). + [[nodiscard]] MutableArrayRef window(std::size_t start, + std::size_t nlookahead) const; + +#ifndef NDEBUG + /// @brief Debug dump of the scheduled unit. + LLVM_DUMP_METHOD void dump(llvm::raw_ostream& os = llvm::dbgs()) const; +#endif + + /// @brief Layers of program indices of the two-qubit gates. + GateLayers gateLayers; + + /// @brief Layers of the ops inside it. + OpLayers opLayers; + + /// @brief Pointer to the next dividing operation. + Operation* next{}; +}; + +class Unit { +public: + Unit(Layout layout, Region* region, bool restore = false) + : layout(std::move(layout)), region(region), restore(restore) {} + + /// @brief Schedule the unit. + void schedule(); + /// @brief Route the unit. + void route(const AStarHeuristicRouter& router, std::size_t nlookahead, + const Architecture& arch, PatternRewriter& rewriter); + /// @brief If possible, advance to the next unit. + [[nodiscard]] SmallVector advance(); + +private: + Schedule s; + Layout layout; + Region* region; + bool restore; +}; + +} // namespace mqt::ir::opt diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h index 9a71a281d7..245dde2f78 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h @@ -12,6 +12,8 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" +#include "llvm/ADT/STLExtras.h" + #include #include #include @@ -91,8 +93,13 @@ class WireIterator { void setNextQubit() { TypeSwitch(currOp) .Case([&](UnitaryInterface op) { - for (const auto& [in, out] : - llvm::zip_equal(op.getAllInQubits(), op.getAllOutQubits())) { + const auto inRng = + llvm::concat(op.getInQubits(), op.getPosCtrlInQubits(), + op.getNegCtrlInQubits()); + const auto outRng = + llvm::concat(op.getOutQubits(), op.getPosCtrlOutQubits(), + op.getNegCtrlOutQubits()); + for (const auto& [in, out] : llvm::zip_equal(inRng, outRng)) { if (q == in) { q = out; return; diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Architecture.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Architecture.cpp index 74d3dcaa4f..df895696d7 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Architecture.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Architecture.cpp @@ -148,6 +148,223 @@ std::unique_ptr getArchitecture(const llvm::StringRef name) { return std::make_unique("IBM-Falcon", 127, COUPLING); } + if (name == "MQTGrid4") { + static const Architecture::CouplingSet COUPLING{ + {0, 4}, {4, 0}, {0, 1}, {1, 0}, {1, 5}, {5, 1}, {1, 2}, + {2, 1}, {2, 6}, {6, 2}, {2, 3}, {3, 2}, {3, 7}, {7, 3}, + {4, 8}, {8, 4}, {4, 5}, {5, 4}, {5, 9}, {9, 5}, {5, 6}, + {6, 5}, {6, 10}, {10, 6}, {6, 7}, {7, 6}, {7, 11}, {11, 7}, + {8, 12}, {12, 8}, {8, 9}, {9, 8}, {9, 13}, {13, 9}, {9, 10}, + {10, 9}, {10, 14}, {14, 10}, {10, 11}, {11, 10}, {11, 15}, {15, 11}, + {12, 13}, {13, 12}, {13, 14}, {14, 13}, {14, 15}, {15, 14}}; + + return std::make_unique("MQTGrid4", 16, COUPLING); + } + + if (name == "MQTGrid8") { + static const Architecture::CouplingSet COUPLING{ + {0, 8}, {8, 0}, {0, 1}, {1, 0}, {1, 9}, {9, 1}, {1, 2}, + {2, 1}, {2, 10}, {10, 2}, {2, 3}, {3, 2}, {3, 11}, {11, 3}, + {3, 4}, {4, 3}, {4, 12}, {12, 4}, {4, 5}, {5, 4}, {5, 13}, + {13, 5}, {5, 6}, {6, 5}, {6, 14}, {14, 6}, {6, 7}, {7, 6}, + {7, 15}, {15, 7}, {8, 16}, {16, 8}, {8, 9}, {9, 8}, {9, 17}, + {17, 9}, {9, 10}, {10, 9}, {10, 18}, {18, 10}, {10, 11}, {11, 10}, + {11, 19}, {19, 11}, {11, 12}, {12, 11}, {12, 20}, {20, 12}, {12, 13}, + {13, 12}, {13, 21}, {21, 13}, {13, 14}, {14, 13}, {14, 22}, {22, 14}, + {14, 15}, {15, 14}, {15, 23}, {23, 15}, {16, 24}, {24, 16}, {16, 17}, + {17, 16}, {17, 25}, {25, 17}, {17, 18}, {18, 17}, {18, 26}, {26, 18}, + {18, 19}, {19, 18}, {19, 27}, {27, 19}, {19, 20}, {20, 19}, {20, 28}, + {28, 20}, {20, 21}, {21, 20}, {21, 29}, {29, 21}, {21, 22}, {22, 21}, + {22, 30}, {30, 22}, {22, 23}, {23, 22}, {23, 31}, {31, 23}, {24, 32}, + {32, 24}, {24, 25}, {25, 24}, {25, 33}, {33, 25}, {25, 26}, {26, 25}, + {26, 34}, {34, 26}, {26, 27}, {27, 26}, {27, 35}, {35, 27}, {27, 28}, + {28, 27}, {28, 36}, {36, 28}, {28, 29}, {29, 28}, {29, 37}, {37, 29}, + {29, 30}, {30, 29}, {30, 38}, {38, 30}, {30, 31}, {31, 30}, {31, 39}, + {39, 31}, {32, 40}, {40, 32}, {32, 33}, {33, 32}, {33, 41}, {41, 33}, + {33, 34}, {34, 33}, {34, 42}, {42, 34}, {34, 35}, {35, 34}, {35, 43}, + {43, 35}, {35, 36}, {36, 35}, {36, 44}, {44, 36}, {36, 37}, {37, 36}, + {37, 45}, {45, 37}, {37, 38}, {38, 37}, {38, 46}, {46, 38}, {38, 39}, + {39, 38}, {39, 47}, {47, 39}, {40, 48}, {48, 40}, {40, 41}, {41, 40}, + {41, 49}, {49, 41}, {41, 42}, {42, 41}, {42, 50}, {50, 42}, {42, 43}, + {43, 42}, {43, 51}, {51, 43}, {43, 44}, {44, 43}, {44, 52}, {52, 44}, + {44, 45}, {45, 44}, {45, 53}, {53, 45}, {45, 46}, {46, 45}, {46, 54}, + {54, 46}, {46, 47}, {47, 46}, {47, 55}, {55, 47}, {48, 56}, {56, 48}, + {48, 49}, {49, 48}, {49, 57}, {57, 49}, {49, 50}, {50, 49}, {50, 58}, + {58, 50}, {50, 51}, {51, 50}, {51, 59}, {59, 51}, {51, 52}, {52, 51}, + {52, 60}, {60, 52}, {52, 53}, {53, 52}, {53, 61}, {61, 53}, {53, 54}, + {54, 53}, {54, 62}, {62, 54}, {54, 55}, {55, 54}, {55, 63}, {63, 55}, + {56, 57}, {57, 56}, {57, 58}, {58, 57}, {58, 59}, {59, 58}, {59, 60}, + {60, 59}, {60, 61}, {61, 60}, {61, 62}, {62, 61}, {62, 63}, {63, 62}}; + + return std::make_unique("MQTGrid8", 64, COUPLING); + } + + if (name == "MQTGrid16") { + static const Architecture::CouplingSet COUPLING{ + {0, 16}, {16, 0}, {0, 1}, {1, 0}, {1, 17}, {17, 1}, + {1, 2}, {2, 1}, {2, 18}, {18, 2}, {2, 3}, {3, 2}, + {3, 19}, {19, 3}, {3, 4}, {4, 3}, {4, 20}, {20, 4}, + {4, 5}, {5, 4}, {5, 21}, {21, 5}, {5, 6}, {6, 5}, + {6, 22}, {22, 6}, {6, 7}, {7, 6}, {7, 23}, {23, 7}, + {7, 8}, {8, 7}, {8, 24}, {24, 8}, {8, 9}, {9, 8}, + {9, 25}, {25, 9}, {9, 10}, {10, 9}, {10, 26}, {26, 10}, + {10, 11}, {11, 10}, {11, 27}, {27, 11}, {11, 12}, {12, 11}, + {12, 28}, {28, 12}, {12, 13}, {13, 12}, {13, 29}, {29, 13}, + {13, 14}, {14, 13}, {14, 30}, {30, 14}, {14, 15}, {15, 14}, + {15, 31}, {31, 15}, {16, 32}, {32, 16}, {16, 17}, {17, 16}, + {17, 33}, {33, 17}, {17, 18}, {18, 17}, {18, 34}, {34, 18}, + {18, 19}, {19, 18}, {19, 35}, {35, 19}, {19, 20}, {20, 19}, + {20, 36}, {36, 20}, {20, 21}, {21, 20}, {21, 37}, {37, 21}, + {21, 22}, {22, 21}, {22, 38}, {38, 22}, {22, 23}, {23, 22}, + {23, 39}, {39, 23}, {23, 24}, {24, 23}, {24, 40}, {40, 24}, + {24, 25}, {25, 24}, {25, 41}, {41, 25}, {25, 26}, {26, 25}, + {26, 42}, {42, 26}, {26, 27}, {27, 26}, {27, 43}, {43, 27}, + {27, 28}, {28, 27}, {28, 44}, {44, 28}, {28, 29}, {29, 28}, + {29, 45}, {45, 29}, {29, 30}, {30, 29}, {30, 46}, {46, 30}, + {30, 31}, {31, 30}, {31, 47}, {47, 31}, {32, 48}, {48, 32}, + {32, 33}, {33, 32}, {33, 49}, {49, 33}, {33, 34}, {34, 33}, + {34, 50}, {50, 34}, {34, 35}, {35, 34}, {35, 51}, {51, 35}, + {35, 36}, {36, 35}, {36, 52}, {52, 36}, {36, 37}, {37, 36}, + {37, 53}, {53, 37}, {37, 38}, {38, 37}, {38, 54}, {54, 38}, + {38, 39}, {39, 38}, {39, 55}, {55, 39}, {39, 40}, {40, 39}, + {40, 56}, {56, 40}, {40, 41}, {41, 40}, {41, 57}, {57, 41}, + {41, 42}, {42, 41}, {42, 58}, {58, 42}, {42, 43}, {43, 42}, + {43, 59}, {59, 43}, {43, 44}, {44, 43}, {44, 60}, {60, 44}, + {44, 45}, {45, 44}, {45, 61}, {61, 45}, {45, 46}, {46, 45}, + {46, 62}, {62, 46}, {46, 47}, {47, 46}, {47, 63}, {63, 47}, + {48, 64}, {64, 48}, {48, 49}, {49, 48}, {49, 65}, {65, 49}, + {49, 50}, {50, 49}, {50, 66}, {66, 50}, {50, 51}, {51, 50}, + {51, 67}, {67, 51}, {51, 52}, {52, 51}, {52, 68}, {68, 52}, + {52, 53}, {53, 52}, {53, 69}, {69, 53}, {53, 54}, {54, 53}, + {54, 70}, {70, 54}, {54, 55}, {55, 54}, {55, 71}, {71, 55}, + {55, 56}, {56, 55}, {56, 72}, {72, 56}, {56, 57}, {57, 56}, + {57, 73}, {73, 57}, {57, 58}, {58, 57}, {58, 74}, {74, 58}, + {58, 59}, {59, 58}, {59, 75}, {75, 59}, {59, 60}, {60, 59}, + {60, 76}, {76, 60}, {60, 61}, {61, 60}, {61, 77}, {77, 61}, + {61, 62}, {62, 61}, {62, 78}, {78, 62}, {62, 63}, {63, 62}, + {63, 79}, {79, 63}, {64, 80}, {80, 64}, {64, 65}, {65, 64}, + {65, 81}, {81, 65}, {65, 66}, {66, 65}, {66, 82}, {82, 66}, + {66, 67}, {67, 66}, {67, 83}, {83, 67}, {67, 68}, {68, 67}, + {68, 84}, {84, 68}, {68, 69}, {69, 68}, {69, 85}, {85, 69}, + {69, 70}, {70, 69}, {70, 86}, {86, 70}, {70, 71}, {71, 70}, + {71, 87}, {87, 71}, {71, 72}, {72, 71}, {72, 88}, {88, 72}, + {72, 73}, {73, 72}, {73, 89}, {89, 73}, {73, 74}, {74, 73}, + {74, 90}, {90, 74}, {74, 75}, {75, 74}, {75, 91}, {91, 75}, + {75, 76}, {76, 75}, {76, 92}, {92, 76}, {76, 77}, {77, 76}, + {77, 93}, {93, 77}, {77, 78}, {78, 77}, {78, 94}, {94, 78}, + {78, 79}, {79, 78}, {79, 95}, {95, 79}, {80, 96}, {96, 80}, + {80, 81}, {81, 80}, {81, 97}, {97, 81}, {81, 82}, {82, 81}, + {82, 98}, {98, 82}, {82, 83}, {83, 82}, {83, 99}, {99, 83}, + {83, 84}, {84, 83}, {84, 100}, {100, 84}, {84, 85}, {85, 84}, + {85, 101}, {101, 85}, {85, 86}, {86, 85}, {86, 102}, {102, 86}, + {86, 87}, {87, 86}, {87, 103}, {103, 87}, {87, 88}, {88, 87}, + {88, 104}, {104, 88}, {88, 89}, {89, 88}, {89, 105}, {105, 89}, + {89, 90}, {90, 89}, {90, 106}, {106, 90}, {90, 91}, {91, 90}, + {91, 107}, {107, 91}, {91, 92}, {92, 91}, {92, 108}, {108, 92}, + {92, 93}, {93, 92}, {93, 109}, {109, 93}, {93, 94}, {94, 93}, + {94, 110}, {110, 94}, {94, 95}, {95, 94}, {95, 111}, {111, 95}, + {96, 112}, {112, 96}, {96, 97}, {97, 96}, {97, 113}, {113, 97}, + {97, 98}, {98, 97}, {98, 114}, {114, 98}, {98, 99}, {99, 98}, + {99, 115}, {115, 99}, {99, 100}, {100, 99}, {100, 116}, {116, 100}, + {100, 101}, {101, 100}, {101, 117}, {117, 101}, {101, 102}, {102, 101}, + {102, 118}, {118, 102}, {102, 103}, {103, 102}, {103, 119}, {119, 103}, + {103, 104}, {104, 103}, {104, 120}, {120, 104}, {104, 105}, {105, 104}, + {105, 121}, {121, 105}, {105, 106}, {106, 105}, {106, 122}, {122, 106}, + {106, 107}, {107, 106}, {107, 123}, {123, 107}, {107, 108}, {108, 107}, + {108, 124}, {124, 108}, {108, 109}, {109, 108}, {109, 125}, {125, 109}, + {109, 110}, {110, 109}, {110, 126}, {126, 110}, {110, 111}, {111, 110}, + {111, 127}, {127, 111}, {112, 128}, {128, 112}, {112, 113}, {113, 112}, + {113, 129}, {129, 113}, {113, 114}, {114, 113}, {114, 130}, {130, 114}, + {114, 115}, {115, 114}, {115, 131}, {131, 115}, {115, 116}, {116, 115}, + {116, 132}, {132, 116}, {116, 117}, {117, 116}, {117, 133}, {133, 117}, + {117, 118}, {118, 117}, {118, 134}, {134, 118}, {118, 119}, {119, 118}, + {119, 135}, {135, 119}, {119, 120}, {120, 119}, {120, 136}, {136, 120}, + {120, 121}, {121, 120}, {121, 137}, {137, 121}, {121, 122}, {122, 121}, + {122, 138}, {138, 122}, {122, 123}, {123, 122}, {123, 139}, {139, 123}, + {123, 124}, {124, 123}, {124, 140}, {140, 124}, {124, 125}, {125, 124}, + {125, 141}, {141, 125}, {125, 126}, {126, 125}, {126, 142}, {142, 126}, + {126, 127}, {127, 126}, {127, 143}, {143, 127}, {128, 144}, {144, 128}, + {128, 129}, {129, 128}, {129, 145}, {145, 129}, {129, 130}, {130, 129}, + {130, 146}, {146, 130}, {130, 131}, {131, 130}, {131, 147}, {147, 131}, + {131, 132}, {132, 131}, {132, 148}, {148, 132}, {132, 133}, {133, 132}, + {133, 149}, {149, 133}, {133, 134}, {134, 133}, {134, 150}, {150, 134}, + {134, 135}, {135, 134}, {135, 151}, {151, 135}, {135, 136}, {136, 135}, + {136, 152}, {152, 136}, {136, 137}, {137, 136}, {137, 153}, {153, 137}, + {137, 138}, {138, 137}, {138, 154}, {154, 138}, {138, 139}, {139, 138}, + {139, 155}, {155, 139}, {139, 140}, {140, 139}, {140, 156}, {156, 140}, + {140, 141}, {141, 140}, {141, 157}, {157, 141}, {141, 142}, {142, 141}, + {142, 158}, {158, 142}, {142, 143}, {143, 142}, {143, 159}, {159, 143}, + {144, 160}, {160, 144}, {144, 145}, {145, 144}, {145, 161}, {161, 145}, + {145, 146}, {146, 145}, {146, 162}, {162, 146}, {146, 147}, {147, 146}, + {147, 163}, {163, 147}, {147, 148}, {148, 147}, {148, 164}, {164, 148}, + {148, 149}, {149, 148}, {149, 165}, {165, 149}, {149, 150}, {150, 149}, + {150, 166}, {166, 150}, {150, 151}, {151, 150}, {151, 167}, {167, 151}, + {151, 152}, {152, 151}, {152, 168}, {168, 152}, {152, 153}, {153, 152}, + {153, 169}, {169, 153}, {153, 154}, {154, 153}, {154, 170}, {170, 154}, + {154, 155}, {155, 154}, {155, 171}, {171, 155}, {155, 156}, {156, 155}, + {156, 172}, {172, 156}, {156, 157}, {157, 156}, {157, 173}, {173, 157}, + {157, 158}, {158, 157}, {158, 174}, {174, 158}, {158, 159}, {159, 158}, + {159, 175}, {175, 159}, {160, 176}, {176, 160}, {160, 161}, {161, 160}, + {161, 177}, {177, 161}, {161, 162}, {162, 161}, {162, 178}, {178, 162}, + {162, 163}, {163, 162}, {163, 179}, {179, 163}, {163, 164}, {164, 163}, + {164, 180}, {180, 164}, {164, 165}, {165, 164}, {165, 181}, {181, 165}, + {165, 166}, {166, 165}, {166, 182}, {182, 166}, {166, 167}, {167, 166}, + {167, 183}, {183, 167}, {167, 168}, {168, 167}, {168, 184}, {184, 168}, + {168, 169}, {169, 168}, {169, 185}, {185, 169}, {169, 170}, {170, 169}, + {170, 186}, {186, 170}, {170, 171}, {171, 170}, {171, 187}, {187, 171}, + {171, 172}, {172, 171}, {172, 188}, {188, 172}, {172, 173}, {173, 172}, + {173, 189}, {189, 173}, {173, 174}, {174, 173}, {174, 190}, {190, 174}, + {174, 175}, {175, 174}, {175, 191}, {191, 175}, {176, 192}, {192, 176}, + {176, 177}, {177, 176}, {177, 193}, {193, 177}, {177, 178}, {178, 177}, + {178, 194}, {194, 178}, {178, 179}, {179, 178}, {179, 195}, {195, 179}, + {179, 180}, {180, 179}, {180, 196}, {196, 180}, {180, 181}, {181, 180}, + {181, 197}, {197, 181}, {181, 182}, {182, 181}, {182, 198}, {198, 182}, + {182, 183}, {183, 182}, {183, 199}, {199, 183}, {183, 184}, {184, 183}, + {184, 200}, {200, 184}, {184, 185}, {185, 184}, {185, 201}, {201, 185}, + {185, 186}, {186, 185}, {186, 202}, {202, 186}, {186, 187}, {187, 186}, + {187, 203}, {203, 187}, {187, 188}, {188, 187}, {188, 204}, {204, 188}, + {188, 189}, {189, 188}, {189, 205}, {205, 189}, {189, 190}, {190, 189}, + {190, 206}, {206, 190}, {190, 191}, {191, 190}, {191, 207}, {207, 191}, + {192, 208}, {208, 192}, {192, 193}, {193, 192}, {193, 209}, {209, 193}, + {193, 194}, {194, 193}, {194, 210}, {210, 194}, {194, 195}, {195, 194}, + {195, 211}, {211, 195}, {195, 196}, {196, 195}, {196, 212}, {212, 196}, + {196, 197}, {197, 196}, {197, 213}, {213, 197}, {197, 198}, {198, 197}, + {198, 214}, {214, 198}, {198, 199}, {199, 198}, {199, 215}, {215, 199}, + {199, 200}, {200, 199}, {200, 216}, {216, 200}, {200, 201}, {201, 200}, + {201, 217}, {217, 201}, {201, 202}, {202, 201}, {202, 218}, {218, 202}, + {202, 203}, {203, 202}, {203, 219}, {219, 203}, {203, 204}, {204, 203}, + {204, 220}, {220, 204}, {204, 205}, {205, 204}, {205, 221}, {221, 205}, + {205, 206}, {206, 205}, {206, 222}, {222, 206}, {206, 207}, {207, 206}, + {207, 223}, {223, 207}, {208, 224}, {224, 208}, {208, 209}, {209, 208}, + {209, 225}, {225, 209}, {209, 210}, {210, 209}, {210, 226}, {226, 210}, + {210, 211}, {211, 210}, {211, 227}, {227, 211}, {211, 212}, {212, 211}, + {212, 228}, {228, 212}, {212, 213}, {213, 212}, {213, 229}, {229, 213}, + {213, 214}, {214, 213}, {214, 230}, {230, 214}, {214, 215}, {215, 214}, + {215, 231}, {231, 215}, {215, 216}, {216, 215}, {216, 232}, {232, 216}, + {216, 217}, {217, 216}, {217, 233}, {233, 217}, {217, 218}, {218, 217}, + {218, 234}, {234, 218}, {218, 219}, {219, 218}, {219, 235}, {235, 219}, + {219, 220}, {220, 219}, {220, 236}, {236, 220}, {220, 221}, {221, 220}, + {221, 237}, {237, 221}, {221, 222}, {222, 221}, {222, 238}, {238, 222}, + {222, 223}, {223, 222}, {223, 239}, {239, 223}, {224, 240}, {240, 224}, + {224, 225}, {225, 224}, {225, 241}, {241, 225}, {225, 226}, {226, 225}, + {226, 242}, {242, 226}, {226, 227}, {227, 226}, {227, 243}, {243, 227}, + {227, 228}, {228, 227}, {228, 244}, {244, 228}, {228, 229}, {229, 228}, + {229, 245}, {245, 229}, {229, 230}, {230, 229}, {230, 246}, {246, 230}, + {230, 231}, {231, 230}, {231, 247}, {247, 231}, {231, 232}, {232, 231}, + {232, 248}, {248, 232}, {232, 233}, {233, 232}, {233, 249}, {249, 233}, + {233, 234}, {234, 233}, {234, 250}, {250, 234}, {234, 235}, {235, 234}, + {235, 251}, {251, 235}, {235, 236}, {236, 235}, {236, 252}, {252, 236}, + {236, 237}, {237, 236}, {237, 253}, {253, 237}, {237, 238}, {238, 237}, + {238, 254}, {254, 238}, {238, 239}, {239, 238}, {239, 255}, {255, 239}, + {240, 241}, {241, 240}, {241, 242}, {242, 241}, {242, 243}, {243, 242}, + {243, 244}, {244, 243}, {244, 245}, {245, 244}, {245, 246}, {246, 245}, + {246, 247}, {247, 246}, {247, 248}, {248, 247}, {248, 249}, {249, 248}, + {249, 250}, {250, 249}, {250, 251}, {251, 250}, {251, 252}, {252, 251}, + {252, 253}, {253, 252}, {253, 254}, {254, 253}, {254, 255}, {255, 254}}; + + return std::make_unique("MQTGrid16", 256, COUPLING); + } + return nullptr; } }; // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Schedule.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Schedule.cpp deleted file mode 100644 index 582342e3c6..0000000000 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Schedule.cpp +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Schedule.h" - -#include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h" - -#include "llvm/ADT/ArrayRef.h" - -#include -#include - -namespace mqt::ir::opt { - -namespace { -struct Wire { - Wire(const WireIterator& it, QubitIndex index) : it(it), index(index) {} - - WireIterator it; - QubitIndex index; -}; - -/// @brief Map to handle multi-qubit gates when traversing the def-use chain. -class SynchronizationMap { - /// @brief Maps operations to to-be-released iterators. - DenseMap> onHold; - - /// @brief Maps operations to ref counts. An op can be released whenever the - /// count reaches zero. - DenseMap refCount; - -public: - /// @returns true iff. the operation is contained in the map. - bool contains(Operation* op) const { return onHold.contains(op); } - - /// @brief Add op with respective wire and ref count to map. - void add(Operation* op, Wire wire, const std::size_t cnt) { - onHold.try_emplace(op, SmallVector{wire}); - /// Decrease the cnt by one because the op was visited when adding. - refCount.try_emplace(op, cnt - 1); - } - - /// @brief Decrement ref count of op and potentially release its iterators. - std::optional> visit(Operation* op, Wire wire) { - assert(refCount.contains(op) && "expected sync map to contain op"); - - /// Add iterator for later release. - onHold[op].push_back(wire); - - /// Release iterators whenever the ref count reaches zero. - if (--refCount[op] == 0) { - return onHold[op]; - } - - return std::nullopt; - } -}; - -SmallVector skipTwoQubitBlock(ArrayRef wires, - Schedule::OpLayer& opLayer) { - assert(wires.size() == 2 && "expected two wires"); - - WireIterator end; - auto [it0, index0] = wires[0]; - auto [it1, index1] = wires[1]; - while (it0 != end && it1 != end) { - Operation* op0 = *it0; - if (!isa(op0) || isa(op0)) { - break; - } - - Operation* op1 = *it1; - if (!isa(op1) || isa(op1)) { - break; - } - - UnitaryInterface u0 = cast(op0); - - /// Advance for single qubit gate on wire 0. - if (!isTwoQubitGate(u0)) { - opLayer.addOp(u0); - ++it0; - continue; - } - - UnitaryInterface u1 = cast(op1); - - /// Advance for single qubit gate on wire 1. - if (!isTwoQubitGate(u1)) { - opLayer.addOp(u1); - ++it1; - continue; - } - - /// Stop if the wires reach different two qubit gates. - if (op0 != op1) { - break; - } - - /// Advance if u0 == u1. - opLayer.addOp(u1); - - ++it0; - ++it1; - } - - return {Wire(it0, index0), Wire(it1, index1)}; -} - -/** - * @brief Advance each wire until (>=2)-qubit gates are found, collect the - * indices of the respective two-qubit gates, and prepare iterators for next - * iteration. - */ -void collectLayerAndAdvance(ArrayRef wires, SynchronizationMap& sync, - Schedule& s, const std::size_t nqubits, - SmallVector& next) { - Schedule::GateLayer gateLayer; - Schedule::OpLayer opLayer; - - for (auto [it, index] : wires) { - while (it != WireIterator::end()) { - const bool stop = - TypeSwitch(*it) - .Case([&](UnitaryInterface op) { - const auto nins = op.getInQubits().size() + - op.getPosCtrlInQubits().size() + - op.getNegCtrlInQubits().size(); - - /// Skip over one-qubit gates. Note: Might be a BarrierOp. - if (nins == 1) { - opLayer.addOp(op); - ++it; - return false; - } - - /// Otherwise, add it to the sync map. - if (!sync.contains(op)) { - sync.add(op, Wire(++it, index), nins); - return true; - } - - if (const auto iterators = sync.visit(op, Wire(++it, index))) { - opLayer.addOp(op); - - if (!isa(op)) { // Is ready two-qubit unitary? - gateLayer.emplace_back((*iterators)[0].index, - (*iterators)[1].index); - next.append(skipTwoQubitBlock(*iterators, opLayer)); - } else { - next.append(*iterators); - } - } - - return true; - }) - .Case([&](ResetOp op) { - opLayer.addOp(op); - ++it; - return false; - }) - .Case([&](MeasureOp op) { - opLayer.addOp(op); - ++it; - return false; - }) - .Case([&](scf::YieldOp yield) { - if (!sync.contains(yield)) { - sync.add(yield, Wire(++it, index), nqubits); - return true; - } - - if (const auto iterators = - sync.visit(yield, Wire(++it, index))) { - opLayer.addOp(yield); - } - - return true; - }) - .Case([&](RegionBranchOpInterface op) { - if (!sync.contains(op)) { - sync.add(op, Wire(++it, index), nqubits); - return true; - } - - if (const auto iterators = sync.visit(op, Wire(++it, index))) { - opLayer.addOp(op); - next.append(*iterators); - } - return true; - }) - .Default([](auto) { - llvm_unreachable("unhandled operation"); - return true; - }); - - if (stop) { - break; - } - } - } - - /// If there is no gates to route, merge the last layer with this one and - /// keep the anchor the same. Otherwise, add the layer. - if (gateLayer.empty()) { - s.gateLayers.back().append(gateLayer); - s.opLayers.back().ops.append(opLayer.ops); - } else { - s.gateLayers.emplace_back(gateLayer); - s.opLayers.emplace_back(opLayer); - } -} -} // namespace - -MutableArrayRef -Schedule::getWindow(const std::size_t start, const std::size_t nlookahead) { - const size_t sz = gateLayers.size(); - const size_t len = std::min(1 + nlookahead, sz - start); - return MutableArrayRef(gateLayers).slice(start, len); -} - -#ifndef NDEBUG -LLVM_DUMP_METHOD void Schedule::dump(llvm::raw_ostream& os) const { - os << "schedule: gate layers=\n"; - for (const auto [i, layer] : llvm::enumerate(gateLayers)) { - os << '\t' << i << ": "; - os << "gates= "; - if (!layer.empty()) { - for (const auto [prog0, prog1] : layer) { - os << "(" << prog0 << "," << prog1 << "), "; - } - } else { - os << "(), "; - } - os << '\n'; - } - - os << "schedule: op layers=\n"; - for (const auto [i, layer] : llvm::enumerate(opLayers)) { - os << '\t' << i << ": "; - os << "#ops= " << layer.ops.size(); - if (!layer.ops.empty()) { - os << " anchor= " << layer.anchor->getLoc(); - } - os << '\n'; - } -} -#endif - -Schedule getSchedule(const Layout& layout, Region& region) { - Schedule s; - SynchronizationMap sync; - - SmallVector curr; - SmallVector next; - - const auto nqubits = layout.getNumQubits(); - curr.reserve(nqubits); - next.reserve(nqubits); - - for (auto [hw, q] : llvm::enumerate(layout.getHardwareQubits())) { - curr.emplace_back(WireIterator(q, ®ion), layout.getProgramIndex(hw)); - } - - while (!curr.empty()) { - collectLayerAndAdvance(curr, sync, s, nqubits, next); - curr.swap(next); - next.clear(); - }; - - LLVM_DEBUG(s.dump()); - - return s; -} -} // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Unit.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Unit.cpp new file mode 100644 index 0000000000..cd9d6371a2 --- /dev/null +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Unit.cpp @@ -0,0 +1,430 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h" + +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h" +#include "mlir/Dialect/SCF/IR/SCF.h" +#include "mlir/IR/PatternMatch.h" + +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/ErrorHandling.h" + +#include +#include +#include +#include +#include + +namespace mqt::ir::opt { +namespace { + +/** + * @brief Insert SWAP ops at the rewriter's insertion point. + * + * @param location The location of the inserted SWAP ops. + * @param swaps The hardware indices of the SWAPs. + * @param layout The current layout. + * @param rewriter The pattern rewriter. + */ +void insertSWAPs(Location location, ArrayRef swaps, + Layout& layout, PatternRewriter& rewriter) { + for (const auto [hw0, hw1] : swaps) { + const Value in0 = layout.lookupHardwareValue(hw0); + const Value in1 = layout.lookupHardwareValue(hw1); + [[maybe_unused]] const auto [prog0, prog1] = + layout.getProgramIndices(hw0, hw1); + + LLVM_DEBUG({ + llvm::dbgs() << llvm::format( + "insertSWAPs: swap= p%d:h%d, p%d:h%d <- p%d:h%d, p%d:h%d\n", prog1, + hw0, prog0, hw1, prog0, hw0, prog1, hw1); + }); + + auto swap = createSwap(location, in0, in1, rewriter); + const auto [out0, out1] = getOuts(swap); + + rewriter.setInsertionPointAfter(swap); + replaceAllUsesInRegionAndChildrenExcept(in0, out1, swap->getParentRegion(), + swap, rewriter); + replaceAllUsesInRegionAndChildrenExcept(in1, out0, swap->getParentRegion(), + swap, rewriter); + + layout.swap(in0, in1); + layout.remapQubitValue(in0, out0); + layout.remapQubitValue(in1, out1); + } +} + +/// @brief Map to handle multi-qubit gates when traversing the def-use chain. +class SynchronizationMap { + /// @brief Maps operations to to-be-released iterators. + DenseMap> onHold; + + /// @brief Maps operations to ref counts. An op can be released whenever the + /// count reaches zero. + DenseMap refCount; + +public: + /// @returns true iff. the operation is contained in the map. + bool contains(Operation* op) const { return onHold.contains(op); } + + /// @brief Add op with respective wire and ref count to map. + void add(Operation* op, Wire wire, const std::size_t cnt) { + onHold.try_emplace(op, SmallVector{wire}); + /// Decrease the cnt by one because the op was visited when adding. + refCount.try_emplace(op, cnt - 1); + } + + /// @brief Decrement ref count of op and potentially release its iterators. + std::optional> visit(Operation* op, Wire wire) { + assert(refCount.contains(op) && "expected sync map to contain op"); + + /// Add iterator for later release. + onHold[op].push_back(wire); + + /// Release iterators whenever the ref count reaches zero. + if (--refCount[op] == 0) { + return onHold[op]; + } + + return std::nullopt; + } +}; + +SmallVector skipTwoQubitBlock(ArrayRef wires, OpLayer& opLayer) { + assert(wires.size() == 2 && "expected two wires"); + + WireIterator end; + auto [it0, index0] = wires[0]; + auto [it1, index1] = wires[1]; + while (it0 != end && it1 != end) { + Operation* op0 = *it0; + if (!isa(op0) || isa(op0)) { + break; + } + + Operation* op1 = *it1; + if (!isa(op1) || isa(op1)) { + break; + } + + UnitaryInterface u0 = cast(op0); + + /// Advance for single qubit gate on wire 0. + if (!isTwoQubitGate(u0)) { + opLayer.addOp(u0); + ++it0; + continue; + } + + UnitaryInterface u1 = cast(op1); + + /// Advance for single qubit gate on wire 1. + if (!isTwoQubitGate(u1)) { + opLayer.addOp(u1); + ++it1; + continue; + } + + /// Stop if the wires reach different two qubit gates. + if (op0 != op1) { + break; + } + + /// Advance if u0 == u1. + opLayer.addOp(u1); + + ++it0; + ++it1; + } + + return {Wire(it0, index0), Wire(it1, index1)}; +} +} // namespace + +MutableArrayRef +Schedule::window(const std::size_t start, const std::size_t nlookahead) const { + const size_t sz = gateLayers.size(); + const size_t len = std::min(1 + nlookahead, sz - start); + return MutableArrayRef(gateLayers).slice(start, len); +} + +#ifndef NDEBUG +LLVM_DUMP_METHOD void Schedule::dump(llvm::raw_ostream& os) const { + os << "schedule: gate layers=\n"; + for (const auto [i, layer] : llvm::enumerate(gateLayers)) { + os << '\t' << i << ": "; + os << "gates= "; + if (!layer.empty()) { + for (const auto [prog0, prog1] : layer) { + os << "(" << prog0 << "," << prog1 << "), "; + } + } else { + os << "(), "; + } + os << '\n'; + } + + os << "schedule: op layers=\n"; + for (const auto [i, layer] : llvm::enumerate(opLayers)) { + os << '\t' << i << ": "; + os << "#ops= " << layer.ops.size(); + if (!layer.ops.empty()) { + os << " anchor= " << layer.anchor->getLoc(); + } + os << '\n'; + } + if (next != nullptr) { + os << "schedule: followUp= " << next->getLoc() << '\n'; + } +} +#endif + +void Unit::schedule() { + SynchronizationMap sync; + + SmallVector curr; + SmallVector next; + curr.reserve(layout.getNumQubits()); + next.reserve(layout.getNumQubits()); + + for (const auto q : layout.getHardwareQubits()) { + curr.emplace_back(WireIterator(q, region), layout.lookupProgramIndex(q)); + } + + while (true) { + + /// Advance each wire until (>=2)-qubit gates are found, collect the indices + /// of the respective two-qubit gates, and prepare iterators for next + /// iteration. + + GateLayer gateLayer; + OpLayer opLayer; + + bool haltOnWire{}; + + for (auto [it, index] : curr) { + while (it != WireIterator::end()) { + haltOnWire = + TypeSwitch(*it) + .Case([&](UnitaryInterface op) { + const auto nins = op.getInQubits().size() + + op.getPosCtrlInQubits().size() + + op.getNegCtrlInQubits().size(); + + /// Skip over one-qubit gates. Note: Might be a BarrierOp. + if (nins == 1) { + opLayer.addOp(op); + ++it; + return false; + } + + /// Otherwise, add it to the sync map. + if (!sync.contains(op)) { + sync.add(op, Wire(++it, index), nins); + return true; + } + + if (const auto iterators = + sync.visit(op, Wire(++it, index))) { + opLayer.addOp(op); + + if (!isa(op)) { // Is ready two-qubit unitary? + gateLayer.emplace_back((*iterators)[0].index, + (*iterators)[1].index); + next.append(skipTwoQubitBlock(*iterators, opLayer)); + } else { + next.append(*iterators); + } + } + + return true; + }) + .Case([&](ResetOp op) { + opLayer.addOp(op); + ++it; + return false; + }) + .Case([&](MeasureOp op) { + opLayer.addOp(op); + ++it; + return false; + }) + .Case([&](scf::YieldOp yield) { + if (!sync.contains(yield)) { + sync.add(yield, Wire(++it, index), layout.getNumQubits()); + return true; + } + + if (const auto iterators = + sync.visit(yield, Wire(++it, index))) { + opLayer.addOp(yield); + } + + return true; + }) + .Case([&](RegionBranchOpInterface op) { + if (!sync.contains(op)) { + sync.add(op, Wire(++it, index), layout.getNumQubits()); + return true; + } + + if (const auto iterators = + sync.visit(op, Wire(++it, index))) { + s.next = op; + } + return true; + }) + .Default([](auto) { + llvm_unreachable("unhandled operation"); + return true; + }); + + if (haltOnWire) { + break; + } + } + + if (s.next != nullptr) { + break; + } + } + + /// If there is no gates to route, merge the last layer with this one and + /// keep the anchor the same. Otherwise, add the layer. + + if (gateLayer.empty()) { + if (!opLayer.empty()) { + if (s.opLayers.empty()) { + s.gateLayers.emplace_back(); + s.opLayers.emplace_back(); + } + s.opLayers.back().ops.append(opLayer.ops); + if (s.opLayers.back().anchor == nullptr) { + s.opLayers.back().anchor = opLayer.anchor; + } + } + + } else if (!opLayer.empty()) { + s.gateLayers.emplace_back(gateLayer); + s.opLayers.emplace_back(opLayer); + } + + /// Prepare next iteration or stop. + curr.swap(next); + next.clear(); + + if (curr.empty() || s.next != nullptr) { + break; + } + }; + + LLVM_DEBUG(s.dump()); +} + +void Unit::route(const AStarHeuristicRouter& router, std::size_t nlookahead, + const Architecture& arch, PatternRewriter& rewriter) { + SmallVector history; + for (std::size_t i = 0; i < s.gateLayers.size(); ++i) { + const auto opLayer = s.opLayers[i]; + + rewriter.setInsertionPoint(opLayer.anchor); + const auto swaps = router.route(s.window(i, nlookahead), layout, arch); + if (!swaps) { + throw std::runtime_error("A* failed to find a valid SWAP sequence"); + } + + if (!swaps->empty()) { + history.append(*swaps); + insertSWAPs(opLayer.anchor->getLoc(), *swaps, layout, rewriter); + /// *(ctx.stats.numSwaps) += swaps->size(); + } + + for (Operation* curr : opLayer.ops) { + rewriter.setInsertionPoint(curr); + + /// Re-order to fix any SSA Dominance issues. + if (i + 1 < s.gateLayers.size()) { + rewriter.moveOpBefore(curr, s.opLayers[i + 1].anchor); + } + + TypeSwitch(curr) + .Case([&](UnitaryInterface op) { + if (isa(op)) { + const auto ins = getIns(op); + layout.swap(ins.first, ins.second); + history.push_back({layout.lookupHardwareIndex(ins.first), + layout.lookupHardwareIndex(ins.second)}); + } + remap(op, layout); + }) + .Case([&](ResetOp op) { remap(op, layout); }) + .Case([&](MeasureOp op) { remap(op, layout); }) + .Case([&](scf::YieldOp op) { + if (restore) { + rewriter.setInsertionPointAfter(op->getPrevNode()); + insertSWAPs(op.getLoc(), llvm::to_vector(llvm::reverse(history)), + layout, rewriter); + } + }) + .Default( + [](auto) { llvm_unreachable("unhandled 'curr' operation"); }); + } + } +} + +SmallVector Unit::advance() { + if (s.next == nullptr) { + return {}; + } + + SmallVector next; + + TypeSwitch(s.next) + .Case([&](scf::ForOp op) { + /// Copy layout. + Layout forLayout(layout); + + /// Forward out-of-loop and in-loop values. + const auto nqubits = layout.getNumQubits(); + const auto initArgs = op.getInitArgs().take_front(nqubits); + const auto results = op.getResults().take_front(nqubits); + const auto iterArgs = op.getRegionIterArgs().take_front(nqubits); + for (const auto [arg, res, iter] : + llvm::zip(initArgs, results, iterArgs)) { + layout.remapQubitValue(arg, res); + forLayout.remapQubitValue(arg, iter); + } + + next.emplace_back(std::move(layout), region, restore); + next.emplace_back(std::move(forLayout), &op.getRegion(), true); + }) + .Case([&](scf::IfOp op) { + next.emplace_back(layout, &op.getThenRegion(), true); + next.emplace_back(layout, &op.getElseRegion(), true); + + /// Forward results. + const auto results = op->getResults().take_front(layout.getNumQubits()); + for (const auto [in, out] : + llvm::zip(layout.getHardwareQubits(), results)) { + layout.remapQubitValue(in, out); + } + + next.emplace_back(layout, region, restore); + }) + .Default( + [](auto) { throw std::runtime_error("invalid 'next' operation"); }); + + return next; +} + +} // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index b3c7b0eccd..fd8818cea7 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -14,11 +14,10 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Schedule.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h" #include #include -#include #include #include #include @@ -46,6 +45,7 @@ #include #include #include +#include #include #define DEBUG_TYPE "route-astar-sc" @@ -87,200 +87,22 @@ struct RoutingContext { std::size_t nlookahead; }; -LogicalResult processRegion(Region& region, Layout& layout, - SmallVector& history, - RoutingContext& ctx); - -/** - * @brief Insert SWAP ops at the rewriter's insertion point. - * - * @param location The location of the inserted SWAP ops. - * @param swaps The hardware indices of the SWAPs. - * @param layout The current layout. - * @param rewriter The pattern rewriter. - */ -void insertSWAPs(Location location, ArrayRef swaps, - Layout& layout, PatternRewriter& rewriter) { - for (const auto [hw0, hw1] : swaps) { - const Value in0 = layout.lookupHardwareValue(hw0); - const Value in1 = layout.lookupHardwareValue(hw1); - [[maybe_unused]] const auto [prog0, prog1] = - layout.getProgramIndices(hw0, hw1); - - LLVM_DEBUG({ - llvm::dbgs() << llvm::format( - "insertSWAPs: swap= p%d:h%d, p%d:h%d <- p%d:h%d, p%d:h%d\n", prog1, - hw0, prog0, hw1, prog0, hw0, prog1, hw1); - }); - - auto swap = createSwap(location, in0, in1, rewriter); - const auto [out0, out1] = getOuts(swap); - - rewriter.setInsertionPointAfter(swap); - replaceAllUsesInRegionAndChildrenExcept(in0, out1, swap->getParentRegion(), - swap, rewriter); - replaceAllUsesInRegionAndChildrenExcept(in1, out0, swap->getParentRegion(), - swap, rewriter); - - layout.swap(in0, in1); - layout.remapQubitValue(in0, out0); - layout.remapQubitValue(in1, out1); - } -} - -/** - * @brief Copy the layout and recursively process the loop body. - */ -WalkResult handle(scf::ForOp op, Layout& layout, RoutingContext& ctx) { - /// Copy layout. - Layout forLayout(layout); - - /// Forward out-of-loop and in-loop values. - const auto initArgs = op.getInitArgs().take_front(ctx.arch->nqubits()); - const auto results = op.getResults().take_front(ctx.arch->nqubits()); - const auto iterArgs = op.getRegionIterArgs().take_front(ctx.arch->nqubits()); - for (const auto [arg, res, iter] : llvm::zip(initArgs, results, iterArgs)) { - layout.remapQubitValue(arg, res); - forLayout.remapQubitValue(arg, iter); - } - - /// Recursively handle loop region. - SmallVector history; - return processRegion(op.getRegion(), forLayout, history, ctx); -} - -/** - * @brief Copy the layout for each branch and recursively process the branches. - */ -WalkResult handle(scf::IfOp op, Layout& layout, RoutingContext& ctx) { - /// Recursively handle each branch region. - Layout ifLayout(layout); - SmallVector ifHistory; - - const auto ifRes = - processRegion(op.getThenRegion(), ifLayout, ifHistory, ctx); - if (ifRes.failed()) { - return ifRes; - } - - Layout elseLayout(layout); - SmallVector elseHistory; - const auto elseRes = - processRegion(op.getElseRegion(), elseLayout, elseHistory, ctx); - if (elseRes.failed()) { - return elseRes; - } - - /// Forward out-of-if values. - const auto results = op->getResults().take_front(ctx.arch->nqubits()); - for (const auto [in, out] : llvm::zip(layout.getHardwareQubits(), results)) { - layout.remapQubitValue(in, out); - } - - return WalkResult::advance(); -} - -/** - * @brief Indicates the end of a region defined by a scf op. - * - * Restores layout by uncomputation. - */ -WalkResult handle(scf::YieldOp op, Layout& layout, - ArrayRef history, RoutingContext& ctx) { - /// Uncompute SWAPs. - insertSWAPs(op.getLoc(), llvm::to_vector(llvm::reverse(history)), layout, - ctx.rewriter); - /// Count SWAPs. - *(ctx.stats.numSwaps) += history.size(); - return WalkResult::advance(); -} - -/** - * @brief Remap all input to output qubits for the given unitary op. SWAP - * indices if the unitary is a SWAP. - */ -WalkResult handle(UnitaryInterface op, Layout& layout, - SmallVector& history) { - remap(op, layout); - if (isa(op)) { - const auto outs = getOuts(op); - layout.swap(outs.first, outs.second); - history.push_back({layout.lookupHardwareIndex(outs.first), - layout.lookupHardwareIndex(outs.second)}); - } - return WalkResult::advance(); -} - -/** - * @brief Find and insert SWAPs using A*-search. - */ -void findAndInsertSWAPs(MutableArrayRef window, - Operation* anchor, Layout& layout, - SmallVector& history, - RoutingContext& ctx) { - ctx.rewriter.setInsertionPoint(anchor); - - if (const auto swaps = ctx.router.route(window, layout, *ctx.arch)) { - if (!swaps->empty()) { - history.append(*swaps); - insertSWAPs(anchor->getLoc(), *swaps, layout, ctx.rewriter); - *(ctx.stats.numSwaps) += swaps->size(); - } - return; - } - - throw std::runtime_error("A* router failed to find a valid SWAP sequence"); -} - -/** - * @brief Schedule and route the given region. - * - * Since this might break SSA Dominance, sort the blocks in the given region - * topologically. - */ -LogicalResult processRegion(Region& region, Layout& layout, - SmallVector& history, - RoutingContext& ctx) { - /// Find and route each of the layers. Might violate SSA dominance. - auto schedule = getSchedule(layout, region); - - for (std::size_t i = 0; i < schedule.gateLayers.size(); ++i) { - auto window = schedule.getWindow(i, ctx.nlookahead); - auto opLayer = schedule.opLayers[i]; - - findAndInsertSWAPs(window, opLayer.anchor, layout, history, ctx); - - for (Operation* curr : opLayer.ops) { - ctx.rewriter.setInsertionPoint(curr); - const auto res = TypeSwitch(curr) - .Case([&](UnitaryInterface op) { - if (i + 1 < schedule.gateLayers.size()) { - ctx.rewriter.moveOpBefore( - op, schedule.opLayers[i + 1].anchor); - } - return handle(op, layout, history); - }) - .Case([&](ResetOp op) { - remap(op, layout); - return WalkResult::advance(); - }) - .Case([&](MeasureOp op) { - remap(op, layout); - return WalkResult::advance(); - }) - .Case([&](scf::ForOp op) { - return handle(op, layout, ctx); - }) - .Case([&](scf::IfOp op) { - return handle(op, layout, ctx); - }) - .Case([&](scf::YieldOp op) { - return handle(op, layout, history, ctx); - }) - .Default([](auto) { return WalkResult::skip(); }); - if (res.wasInterrupted()) { - return failure(); - } +LogicalResult processFunction(func::FuncOp func, RoutingContext& ctx) { + /// Collect entry layout. + Layout layout(ctx.arch->nqubits()); + for_each(func.getOps(), [&](QubitOp op) { + layout.add(op.getIndex(), op.getIndex(), op.getQubit()); + }); + + std::queue units; + units.emplace(std::move(layout), &func.getBody()); + + for (; !units.empty(); units.pop()) { + Unit& unit = units.front(); + unit.schedule(); + unit.route(ctx.router, ctx.nlookahead, *ctx.arch, ctx.rewriter); + for (auto next : unit.advance()) { + units.emplace(next); } } @@ -300,6 +122,7 @@ LogicalResult route(ModuleOp module, std::unique_ptr arch, .rewriter = PatternRewriter(module->getContext()), .router = AStarHeuristicRouter(weights), .nlookahead = params.nlookahead}; + for (auto func : module.getOps()) { LLVM_DEBUG(llvm::dbgs() << "handleFunc: " << func.getSymName() << '\n'); @@ -308,19 +131,11 @@ LogicalResult route(ModuleOp module, std::unique_ptr arch, return success(); // Ignore non entry_point functions for now. } - /// Find all hardware (static) qubits and initialize layout. - Layout layout(ctx.arch->nqubits()); - for_each(func.getOps(), [&](QubitOp op) { - const std::size_t index = op.getIndex(); - layout.add(index, index, op.getQubit()); - }); - - SmallVector history; - const auto res = processRegion(func.getBody(), layout, history, ctx); - if (res.failed()) { - return res; + if (failed(processFunction(func, ctx))) { + return failure(); } } + return success(); } From 69a47347e5b7a52e3c3289518f8632d6b685caae Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 21 Nov 2025 09:38:03 +0100 Subject: [PATCH 26/56] Implement LayeredUnit --- .../Transforms/Transpilation/LayeredUnit.h | 154 ++++++++++++ .../MQTOpt/Transforms/Transpilation/Router.h | 4 +- .../MQTOpt/Transforms/Transpilation/Unit.h | 87 ++----- .../{Unit.cpp => LayeredUnit.cpp} | 230 +++++------------- .../Transpilation/sc/AStarRoutingPass.cpp | 226 ++++++++++------- 5 files changed, 372 insertions(+), 329 deletions(-) create mode 100644 mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.h rename mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/{Unit.cpp => LayeredUnit.cpp} (60%) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.h new file mode 100644 index 0000000000..4844e04d65 --- /dev/null +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.h @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h" + +#include +#include +#include +#include +#include + +#define DEBUG_TYPE "route-astar-sc" + +namespace mqt::ir::opt { + +struct Wire { + Wire(const WireIterator& it, QubitIndex index) : it(it), index(index) {} + + WireIterator it; + QubitIndex index; +}; + +using GateLayer = SmallVector; + +struct OpLayer { + /// @brief All ops contained inside this layer. + SmallVector ops; + /// @brief The first op in ops in textual IR order. + Operation* anchor{}; + + /// @brief Add op to ops and reset anchor if necessary. + void addOp(Operation* op) { + ops.emplace_back(op); + if (anchor == nullptr || op->isBeforeInBlock(anchor)) { + anchor = op; + } + } + + [[nodiscard]] bool empty() const { return ops.empty(); } +}; + +struct WindowView { + ArrayRef gateLayers; + const OpLayer* opLayer{}; + Operation* nextAnchor{}; +}; + +class SlidingWindow { +public: + struct Iterator { + using value_type = WindowView; + using difference_type = std::ptrdiff_t; + using iterator_category = std::forward_iterator_tag; + + Iterator() = default; + Iterator(const SlidingWindow* window, std::size_t pos) + : window(window), pos(pos) {} + + WindowView operator*() const { + WindowView w; + const auto sz = window->opLayers->size(); + const auto len = std::min(1 + window->nlookahead, sz - pos); + w.gateLayers = ArrayRef(*window->gateLayers).slice(pos, len); + w.opLayer = &(*window->opLayers)[pos]; + if (pos + 1 < window->gateLayers->size()) { + w.nextAnchor = (*window->opLayers)[pos + 1].anchor; + } + return w; + } + + Iterator& operator++() { + ++pos; + return *this; + } + + Iterator operator++(int) { + auto tmp = *this; + ++*this; + return tmp; + } + + bool operator==(const Iterator& other) const { + return pos == other.pos && window == other.window; + } + + private: + const SlidingWindow* window; + std::size_t pos; + }; + + explicit SlidingWindow(const SmallVector& gateLayers, + const SmallVector& opLayers, + const std::size_t nlookahead) + : gateLayers(&gateLayers), opLayers(&opLayers), nlookahead(nlookahead) {} + + [[nodiscard]] Iterator begin() const { return {this, 0}; } + [[nodiscard]] Iterator end() const { return {this, opLayers->size()}; } + + static_assert(std::forward_iterator); + +private: + const SmallVector* gateLayers; + const SmallVector* opLayers; + std::size_t nlookahead; +}; + +class LayeredUnit : public Unit { +public: + static LayeredUnit fromEntryPointFunction(mlir::func::FuncOp func, + const std::size_t nqubits) { + Layout layout(nqubits); + for_each(func.getOps(), [&](QubitOp op) { + layout.add(op.getIndex(), op.getIndex(), op.getQubit()); + }); + return {std::move(layout), &func.getBody()}; + } + + LayeredUnit(Layout layout, Region* region, bool restore = false) + : Unit(std::move(layout), region, restore) { + init(layout_, region); + } + + [[nodiscard]] SmallVector next(); + + [[nodiscard]] SlidingWindow slidingWindow(std::size_t nlookahead) const; + +#ifndef NDEBUG + /// @brief Debug dump of the layered unit. + LLVM_DUMP_METHOD void dump(llvm::raw_ostream& os = llvm::dbgs()) const; +#endif + +private: + /// @brief Compute (schedule) the layers of the unit. + void init(const Layout& layout, Region* region); + + /// @brief Layers of program indices of the two-qubit gates. + SmallVector gateLayers; + + /// @brief Layers of the ops inside it. + SmallVector opLayers; +}; +} // namespace mqt::ir::opt diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h index be3d061e32..fd0d137b7a 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h @@ -12,6 +12,7 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" #include @@ -25,9 +26,6 @@ namespace mqt::ir::opt { using namespace mlir; -using GateLayer = SmallVector; -using GateLayers = SmallVector; - /// @brief Specifies the weights for different terms in the cost function f. struct HeuristicWeights { float alpha; diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h index 2cca549689..55bc52bbd9 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h @@ -10,89 +10,34 @@ #pragma once -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h" -#include -#include -#include #include #define DEBUG_TYPE "route-astar-sc" namespace mqt::ir::opt { -using namespace mlir; - -struct Wire { - Wire(const WireIterator& it, QubitIndex index) : it(it), index(index) {} - - WireIterator it; - QubitIndex index; -}; - -using GateLayer = SmallVector; -using GateLayers = SmallVector; - -struct OpLayer { - /// @brief All ops contained inside this layer. - SmallVector ops; - /// @brief The first op in ops in textual IR order. - Operation* anchor{}; - - /// @brief Add op to ops and reset anchor if necessary. - void addOp(Operation* op) { - ops.emplace_back(op); - if (anchor == nullptr || op->isBeforeInBlock(anchor)) { - anchor = op; - } - } - - [[nodiscard]] bool empty() const { return ops.empty(); } -}; - -using OpLayers = SmallVector; - -struct Schedule { - /// @returns a window of gate layers from (start, start + nlookahead). - [[nodiscard]] MutableArrayRef window(std::size_t start, - std::size_t nlookahead) const; - -#ifndef NDEBUG - /// @brief Debug dump of the scheduled unit. - LLVM_DUMP_METHOD void dump(llvm::raw_ostream& os = llvm::dbgs()) const; -#endif - - /// @brief Layers of program indices of the two-qubit gates. - GateLayers gateLayers; - - /// @brief Layers of the ops inside it. - OpLayers opLayers; - - /// @brief Pointer to the next dividing operation. - Operation* next{}; -}; - class Unit { public: - Unit(Layout layout, Region* region, bool restore = false) - : layout(std::move(layout)), region(region), restore(restore) {} + Unit(Layout layout, mlir::Region* region, bool restore = false) + : layout_(std::move(layout)), region_(region), restore_(restore) {} - /// @brief Schedule the unit. - void schedule(); - /// @brief Route the unit. - void route(const AStarHeuristicRouter& router, std::size_t nlookahead, - const Architecture& arch, PatternRewriter& rewriter); - /// @brief If possible, advance to the next unit. - [[nodiscard]] SmallVector advance(); + /// @returns the managed layout. + [[nodiscard]] Layout& layout() { return layout_; } -private: - Schedule s; - Layout layout; - Region* region; - bool restore; + /// @returns true iff. the unit has to be restored. + [[nodiscard]] bool restore() const { return restore_; } + +protected: + /// @brief The layout this unit manages. + Layout layout_; + /// @brief The region this unit belongs to. + mlir::Region* region_; + /// @brief Pointer to the next dividing operation. + mlir::Operation* divider_{}; + /// @brief Whether to uncompute the inserted SWAP sequence. + bool restore_; }; } // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Unit.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp similarity index 60% rename from mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Unit.cpp rename to mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp index cd9d6371a2..cc27659ce2 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Unit.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp @@ -8,62 +8,23 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h" -#include "mlir/Dialect/SCF/IR/SCF.h" -#include "mlir/IR/PatternMatch.h" - -#include "llvm/ADT/SmallVector.h" -#include "llvm/Support/ErrorHandling.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" #include +#include #include +#include #include #include +#include #include namespace mqt::ir::opt { namespace { -/** - * @brief Insert SWAP ops at the rewriter's insertion point. - * - * @param location The location of the inserted SWAP ops. - * @param swaps The hardware indices of the SWAPs. - * @param layout The current layout. - * @param rewriter The pattern rewriter. - */ -void insertSWAPs(Location location, ArrayRef swaps, - Layout& layout, PatternRewriter& rewriter) { - for (const auto [hw0, hw1] : swaps) { - const Value in0 = layout.lookupHardwareValue(hw0); - const Value in1 = layout.lookupHardwareValue(hw1); - [[maybe_unused]] const auto [prog0, prog1] = - layout.getProgramIndices(hw0, hw1); - - LLVM_DEBUG({ - llvm::dbgs() << llvm::format( - "insertSWAPs: swap= p%d:h%d, p%d:h%d <- p%d:h%d, p%d:h%d\n", prog1, - hw0, prog0, hw1, prog0, hw0, prog1, hw1); - }); - - auto swap = createSwap(location, in0, in1, rewriter); - const auto [out0, out1] = getOuts(swap); - - rewriter.setInsertionPointAfter(swap); - replaceAllUsesInRegionAndChildrenExcept(in0, out1, swap->getParentRegion(), - swap, rewriter); - replaceAllUsesInRegionAndChildrenExcept(in1, out0, swap->getParentRegion(), - swap, rewriter); - - layout.swap(in0, in1); - layout.remapQubitValue(in0, out0); - layout.remapQubitValue(in1, out1); - } -} - /// @brief Map to handle multi-qubit gates when traversing the def-use chain. class SynchronizationMap { /// @brief Maps operations to to-be-released iterators. @@ -151,15 +112,57 @@ SmallVector skipTwoQubitBlock(ArrayRef wires, OpLayer& opLayer) { } } // namespace -MutableArrayRef -Schedule::window(const std::size_t start, const std::size_t nlookahead) const { - const size_t sz = gateLayers.size(); - const size_t len = std::min(1 + nlookahead, sz - start); - return MutableArrayRef(gateLayers).slice(start, len); +SmallVector LayeredUnit::next() { + if (divider_ == nullptr) { + return {}; + } + + SmallVector units; + TypeSwitch(divider_) + .Case([&](scf::ForOp op) { + /// Copy layout. + Layout forLayout(layout_); + + /// Forward out-of-loop and in-loop values. + const auto nqubits = layout_.getNumQubits(); + const auto initArgs = op.getInitArgs().take_front(nqubits); + const auto results = op.getResults().take_front(nqubits); + const auto iterArgs = op.getRegionIterArgs().take_front(nqubits); + for (const auto [arg, res, iter] : + llvm::zip(initArgs, results, iterArgs)) { + layout_.remapQubitValue(arg, res); + forLayout.remapQubitValue(arg, iter); + } + + units.emplace_back(std::move(layout_), region_, restore_); + units.emplace_back(std::move(forLayout), &op.getRegion(), true); + }) + .Case([&](scf::IfOp op) { + units.emplace_back(layout_, &op.getThenRegion(), true); + units.emplace_back(layout_, &op.getElseRegion(), true); + + /// Forward results. + const auto results = + op->getResults().take_front(layout_.getNumQubits()); + for (const auto [in, out] : + llvm::zip(layout_.getHardwareQubits(), results)) { + layout_.remapQubitValue(in, out); + } + + units.emplace_back(layout_, region_, restore_); + }) + .Default( + [](auto) { throw std::runtime_error("invalid 'next' operation"); }); + + return units; +} + +SlidingWindow LayeredUnit::slidingWindow(std::size_t nlookahead) const { + return SlidingWindow(gateLayers, opLayers, nlookahead); } #ifndef NDEBUG -LLVM_DUMP_METHOD void Schedule::dump(llvm::raw_ostream& os) const { +LLVM_DUMP_METHOD void LayeredUnit::dump(llvm::raw_ostream& os) const { os << "schedule: gate layers=\n"; for (const auto [i, layer] : llvm::enumerate(gateLayers)) { os << '\t' << i << ": "; @@ -183,13 +186,13 @@ LLVM_DUMP_METHOD void Schedule::dump(llvm::raw_ostream& os) const { } os << '\n'; } - if (next != nullptr) { - os << "schedule: followUp= " << next->getLoc() << '\n'; + if (divider_ != nullptr) { + os << "schedule: followUp= " << divider_->getLoc() << '\n'; } } #endif -void Unit::schedule() { +void LayeredUnit::init(const Layout& layout, Region* region) { SynchronizationMap sync; SmallVector curr; @@ -280,7 +283,7 @@ void Unit::schedule() { if (const auto iterators = sync.visit(op, Wire(++it, index))) { - s.next = op; + divider_ = op; } return true; }) @@ -294,7 +297,7 @@ void Unit::schedule() { } } - if (s.next != nullptr) { + if (divider_ != nullptr) { break; } } @@ -304,127 +307,28 @@ void Unit::schedule() { if (gateLayer.empty()) { if (!opLayer.empty()) { - if (s.opLayers.empty()) { - s.gateLayers.emplace_back(); - s.opLayers.emplace_back(); + if (opLayers.empty()) { + gateLayers.emplace_back(); + opLayers.emplace_back(); } - s.opLayers.back().ops.append(opLayer.ops); - if (s.opLayers.back().anchor == nullptr) { - s.opLayers.back().anchor = opLayer.anchor; + opLayers.back().ops.append(opLayer.ops); + if (opLayers.back().anchor == nullptr) { + opLayers.back().anchor = opLayer.anchor; } } } else if (!opLayer.empty()) { - s.gateLayers.emplace_back(gateLayer); - s.opLayers.emplace_back(opLayer); + gateLayers.emplace_back(gateLayer); + opLayers.emplace_back(opLayer); } /// Prepare next iteration or stop. curr.swap(next); next.clear(); - if (curr.empty() || s.next != nullptr) { + if (curr.empty() || divider_ != nullptr) { break; } }; - - LLVM_DEBUG(s.dump()); } - -void Unit::route(const AStarHeuristicRouter& router, std::size_t nlookahead, - const Architecture& arch, PatternRewriter& rewriter) { - SmallVector history; - for (std::size_t i = 0; i < s.gateLayers.size(); ++i) { - const auto opLayer = s.opLayers[i]; - - rewriter.setInsertionPoint(opLayer.anchor); - const auto swaps = router.route(s.window(i, nlookahead), layout, arch); - if (!swaps) { - throw std::runtime_error("A* failed to find a valid SWAP sequence"); - } - - if (!swaps->empty()) { - history.append(*swaps); - insertSWAPs(opLayer.anchor->getLoc(), *swaps, layout, rewriter); - /// *(ctx.stats.numSwaps) += swaps->size(); - } - - for (Operation* curr : opLayer.ops) { - rewriter.setInsertionPoint(curr); - - /// Re-order to fix any SSA Dominance issues. - if (i + 1 < s.gateLayers.size()) { - rewriter.moveOpBefore(curr, s.opLayers[i + 1].anchor); - } - - TypeSwitch(curr) - .Case([&](UnitaryInterface op) { - if (isa(op)) { - const auto ins = getIns(op); - layout.swap(ins.first, ins.second); - history.push_back({layout.lookupHardwareIndex(ins.first), - layout.lookupHardwareIndex(ins.second)}); - } - remap(op, layout); - }) - .Case([&](ResetOp op) { remap(op, layout); }) - .Case([&](MeasureOp op) { remap(op, layout); }) - .Case([&](scf::YieldOp op) { - if (restore) { - rewriter.setInsertionPointAfter(op->getPrevNode()); - insertSWAPs(op.getLoc(), llvm::to_vector(llvm::reverse(history)), - layout, rewriter); - } - }) - .Default( - [](auto) { llvm_unreachable("unhandled 'curr' operation"); }); - } - } -} - -SmallVector Unit::advance() { - if (s.next == nullptr) { - return {}; - } - - SmallVector next; - - TypeSwitch(s.next) - .Case([&](scf::ForOp op) { - /// Copy layout. - Layout forLayout(layout); - - /// Forward out-of-loop and in-loop values. - const auto nqubits = layout.getNumQubits(); - const auto initArgs = op.getInitArgs().take_front(nqubits); - const auto results = op.getResults().take_front(nqubits); - const auto iterArgs = op.getRegionIterArgs().take_front(nqubits); - for (const auto [arg, res, iter] : - llvm::zip(initArgs, results, iterArgs)) { - layout.remapQubitValue(arg, res); - forLayout.remapQubitValue(arg, iter); - } - - next.emplace_back(std::move(layout), region, restore); - next.emplace_back(std::move(forLayout), &op.getRegion(), true); - }) - .Case([&](scf::IfOp op) { - next.emplace_back(layout, &op.getThenRegion(), true); - next.emplace_back(layout, &op.getElseRegion(), true); - - /// Forward results. - const auto results = op->getResults().take_front(layout.getNumQubits()); - for (const auto [in, out] : - llvm::zip(layout.getHardwareQubits(), results)) { - layout.remapQubitValue(in, out); - } - - next.emplace_back(layout, region, restore); - }) - .Default( - [](auto) { throw std::runtime_error("invalid 'next' operation"); }); - - return next; -} - } // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index fd8818cea7..4463014015 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -12,9 +12,8 @@ #include "mlir/Dialect/MQTOpt/Transforms/Passes.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h" #include #include @@ -46,7 +45,6 @@ #include #include #include -#include #define DEBUG_TYPE "route-astar-sc" @@ -58,85 +56,41 @@ namespace mqt::ir::opt { namespace { using namespace mlir; -/// @brief A composite datastructure for LLVM Statistics. -struct Statistics { - llvm::Statistic* numSwaps; -}; - -/// @brief A composite datastructure for pass parameters. -struct Params { - /// @brief The amount of lookahead layers. - std::size_t nlookahead; - /// @brief The alpha factor in the heuristic function. - float alpha; - /// @brief The lambda decay factor in the heuristic function. - float lambda; -}; - -/// @brief Commonly passed parameters for the routing functions. -struct RoutingContext { - /// @brief The targeted architecture. - std::unique_ptr arch; - /// @brief LLVM/MLIR statistics. - Statistics stats; - /// @brief A pattern rewriter. - PatternRewriter rewriter; - /// @brief The A*-search based router. - AStarHeuristicRouter router; - /// @brief The amount of lookahead layers. - std::size_t nlookahead; -}; - -LogicalResult processFunction(func::FuncOp func, RoutingContext& ctx) { - /// Collect entry layout. - Layout layout(ctx.arch->nqubits()); - for_each(func.getOps(), [&](QubitOp op) { - layout.add(op.getIndex(), op.getIndex(), op.getQubit()); - }); - - std::queue units; - units.emplace(std::move(layout), &func.getBody()); - - for (; !units.empty(); units.pop()) { - Unit& unit = units.front(); - unit.schedule(); - unit.route(ctx.router, ctx.nlookahead, *ctx.arch, ctx.rewriter); - for (auto next : unit.advance()) { - units.emplace(next); - } - } - - return success(); -} - /** - * @brief Route the given module for the targeted architecture using A*-search. - * Processes each entry_point function separately. + * @brief Insert SWAP ops at the rewriter's insertion point. + * + * @param location The location of the inserted SWAP ops. + * @param swaps The hardware indices of the SWAPs. + * @param layout The current layout. + * @param rewriter The pattern rewriter. */ -LogicalResult route(ModuleOp module, std::unique_ptr arch, - Params& params, Statistics& stats) { - const HeuristicWeights weights(params.alpha, params.lambda, - params.nlookahead); - RoutingContext ctx{.arch = std::move(arch), - .stats = stats, - .rewriter = PatternRewriter(module->getContext()), - .router = AStarHeuristicRouter(weights), - .nlookahead = params.nlookahead}; - - for (auto func : module.getOps()) { - LLVM_DEBUG(llvm::dbgs() << "handleFunc: " << func.getSymName() << '\n'); - - if (!isEntryPoint(func)) { - LLVM_DEBUG(llvm::dbgs() << "\tskip non entry\n"); - return success(); // Ignore non entry_point functions for now. - } - - if (failed(processFunction(func, ctx))) { - return failure(); - } +void insertSWAPs(Location location, ArrayRef swaps, + Layout& layout, PatternRewriter& rewriter) { + for (const auto [hw0, hw1] : swaps) { + const Value in0 = layout.lookupHardwareValue(hw0); + const Value in1 = layout.lookupHardwareValue(hw1); + [[maybe_unused]] const auto [prog0, prog1] = + layout.getProgramIndices(hw0, hw1); + + LLVM_DEBUG({ + llvm::dbgs() << llvm::format( + "route: swap= p%d:h%d, p%d:h%d <- p%d:h%d, p%d:h%d\n", prog1, hw0, + prog0, hw1, prog0, hw0, prog1, hw1); + }); + + auto swap = createSwap(location, in0, in1, rewriter); + const auto [out0, out1] = getOuts(swap); + + rewriter.setInsertionPointAfter(swap); + replaceAllUsesInRegionAndChildrenExcept(in0, out1, swap->getParentRegion(), + swap, rewriter); + replaceAllUsesInRegionAndChildrenExcept(in1, out0, swap->getParentRegion(), + swap, rewriter); + + layout.swap(in0, in1); + layout.remapQubitValue(in0, out0); + layout.remapQubitValue(in1, out1); } - - return success(); } /** @@ -149,31 +103,119 @@ struct AStarRoutingPassSC final using AStarRoutingPassSCBase::AStarRoutingPassSCBase; void runOnOperation() override { - if (preflight().failed()) { + if (failed(preflight())) { signalPassFailure(); return; } - auto arch = getArchitecture(archName); - if (!arch) { - emitError(UnknownLoc::get(&getContext())) - << "unsupported architecture '" << archName << "'"; + if (failed(route())) { signalPassFailure(); return; - } - - Statistics stats{.numSwaps = &numSwaps}; - Params params{.nlookahead = nlookahead, .alpha = alpha, .lambda = lambda}; - if (route(getOperation(), std::move(arch), params, stats).failed()) { - signalPassFailure(); }; } private: + /** + * @brief Route the given module for the targeted architecture using + * A*-search. Processes each entry_point function separately. + */ + LogicalResult route() { + ModuleOp module(getOperation()); + PatternRewriter rewriter(module->getContext()); + AStarHeuristicRouter router(HeuristicWeights(alpha, lambda, nlookahead)); + std::unique_ptr arch(getArchitecture(archName)); + + if (!arch) { + Location location = UnknownLoc::get(&getContext()); + emitError(location) << "unsupported architecture '" << archName << "'"; + return failure(); + } + + for (auto func : module.getOps()) { + LLVM_DEBUG(llvm::dbgs() << "handleFunc: " << func.getSymName() << '\n'); + + if (!isEntryPoint(func)) { + LLVM_DEBUG(llvm::dbgs() << "\tskip non entry\n"); + return success(); // Ignore non entry_point functions for now. + } + + /// Iteratively process each unit in the function. + std::queue units; + units.emplace(LayeredUnit::fromEntryPointFunction(func, arch->nqubits())); + for (; !units.empty(); units.pop()) { + LayeredUnit& unit = units.front(); + + SmallVector history; + for (const auto window : unit.slidingWindow(nlookahead)) { + Operation* anchor = window.opLayer->anchor; + ArrayRef layers = window.gateLayers; + ArrayRef ops = window.opLayer->ops; + + /// Find and insert SWAPs. + rewriter.setInsertionPoint(anchor); + const auto swaps = router.route(layers, unit.layout(), *arch); + if (!swaps) { + Location loc = UnknownLoc::get(&getContext()); + return emitError(loc, "A* failed to find a valid SWAP sequence"); + } + + if (!swaps->empty()) { + history.append(*swaps); + insertSWAPs(anchor->getLoc(), *swaps, unit.layout(), rewriter); + numSwaps += swaps->size(); + } + + /// Process all operations contained in the layer. + for (Operation* curr : ops) { + rewriter.setInsertionPoint(curr); + + /// Re-order to fix any SSA Dominance issues. + if (window.nextAnchor != nullptr) { + rewriter.moveOpBefore(curr, window.nextAnchor); + } + + /// Forward layout. + TypeSwitch(curr) + .Case([&](UnitaryInterface op) { + if (isa(op)) { + const auto ins = getIns(op); + unit.layout().swap(ins.first, ins.second); + history.push_back( + {unit.layout().lookupHardwareIndex(ins.first), + unit.layout().lookupHardwareIndex(ins.second)}); + } + remap(op, unit.layout()); + }) + .Case([&](ResetOp op) { remap(op, unit.layout()); }) + .Case( + [&](MeasureOp op) { remap(op, unit.layout()); }) + .Case([&](scf::YieldOp op) { + if (unit.restore()) { + rewriter.setInsertionPointAfter(op->getPrevNode()); + insertSWAPs(op.getLoc(), + llvm::to_vector(llvm::reverse(history)), + unit.layout(), rewriter); + } + }) + .Default([](auto) { + llvm_unreachable("unhandled 'curr' operation"); + }); + } + } + + for (auto next : unit.next()) { + units.emplace(next); + } + } + } + + return success(); + } + LogicalResult preflight() { if (archName.empty()) { - return emitError(UnknownLoc::get(&getContext()), - "required option 'arch' not provided"); + Location loc = UnknownLoc::get(&getContext()); + return emitError(loc, "required option 'arch' not provided"); } return success(); From befa5f74bc18819f513bd3894c1e4ffa310e9087 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 21 Nov 2025 11:00:59 +0100 Subject: [PATCH 27/56] Implement SequentialUnit --- .../Transforms/Transpilation/LayeredUnit.h | 7 - .../MQTOpt/Transforms/Transpilation/Layout.h | 31 +- .../MQTOpt/Transforms/Transpilation/Router.h | 19 +- .../Transforms/Transpilation/SequentialUnit.h | 48 +++ .../MQTOpt/Transforms/Transpilation/Unit.h | 2 - .../Transpilation/SequentialUnit.cpp | 93 +++++ .../Transpilation/sc/NaiveRoutingPass.cpp | 360 +++++------------- 7 files changed, 259 insertions(+), 301 deletions(-) create mode 100644 mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.h create mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.h index 4844e04d65..1bf5b90fc5 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.h @@ -21,8 +21,6 @@ #include #include -#define DEBUG_TYPE "route-astar-sc" - namespace mqt::ir::opt { struct Wire { @@ -133,11 +131,9 @@ class LayeredUnit : public Unit { } [[nodiscard]] SmallVector next(); - [[nodiscard]] SlidingWindow slidingWindow(std::size_t nlookahead) const; #ifndef NDEBUG - /// @brief Debug dump of the layered unit. LLVM_DUMP_METHOD void dump(llvm::raw_ostream& os = llvm::dbgs()) const; #endif @@ -145,10 +141,7 @@ class LayeredUnit : public Unit { /// @brief Compute (schedule) the layers of the unit. void init(const Layout& layout, Region* region); - /// @brief Layers of program indices of the two-qubit gates. SmallVector gateLayers; - - /// @brief Layers of the ops inside it. SmallVector opLayers; }; } // namespace mqt::ir::opt diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h index 447b8c6379..cc67c58bd0 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h @@ -19,7 +19,6 @@ #include namespace mqt::ir::opt { -using namespace mlir; /** * @brief A qubit layout that maps program and hardware indices without storing @@ -116,12 +115,12 @@ class [[nodiscard]] ThinLayout { /** * @brief Maps a program qubit index to its hardware index. */ - SmallVector programToHardware_; + mlir::SmallVector programToHardware_; /** * @brief Maps a hardware qubit index to its program index. */ - SmallVector hardwareToProgram_; + mlir::SmallVector hardwareToProgram_; private: friend struct llvm::DenseMapInfo; @@ -144,7 +143,7 @@ class [[nodiscard]] Layout : public ThinLayout { * @param hw The hardware index. * @param q The SSA value associated with the indices. */ - void add(QubitIndex prog, QubitIndex hw, Value q) { + void add(QubitIndex prog, QubitIndex hw, mlir::Value q) { ThinLayout::add(prog, hw); qubits_[hw] = q; valueToMapping_.try_emplace(q, prog, hw); @@ -155,7 +154,7 @@ class [[nodiscard]] Layout : public ThinLayout { * @param q The SSA Value representing the qubit. * @return The hardware index where this qubit currently resides. */ - [[nodiscard]] QubitIndex lookupHardwareIndex(const Value q) const { + [[nodiscard]] QubitIndex lookupHardwareIndex(const mlir::Value q) const { const auto it = valueToMapping_.find(q); assert(it != valueToMapping_.end() && "lookupHardwareIndex: unknown value"); return it->second.hw; @@ -167,7 +166,7 @@ class [[nodiscard]] Layout : public ThinLayout { * @return The SSA value currently representing the qubit at the hardware * location. */ - [[nodiscard]] Value lookupHardwareValue(const QubitIndex hw) const { + [[nodiscard]] mlir::Value lookupHardwareValue(const QubitIndex hw) const { assert(hw < qubits_.size() && "lookupHardwareValue: hardware index out of bounds"); return qubits_[hw]; @@ -178,7 +177,7 @@ class [[nodiscard]] Layout : public ThinLayout { * @param q The SSA Value representing the qubit. * @return The program index where this qubit currently resides. */ - [[nodiscard]] QubitIndex lookupProgramIndex(const Value q) const { + [[nodiscard]] QubitIndex lookupProgramIndex(const mlir::Value q) const { const auto it = valueToMapping_.find(q); assert(it != valueToMapping_.end() && "lookupProgramIndex: unknown value"); return it->second.prog; @@ -190,7 +189,7 @@ class [[nodiscard]] Layout : public ThinLayout { * @return The SSA value currently representing the qubit at the program * location. */ - [[nodiscard]] Value lookupProgramValue(const QubitIndex prog) const { + [[nodiscard]] mlir::Value lookupProgramValue(const QubitIndex prog) const { assert(prog < this->programToHardware_.size() && "lookupProgramValue: program index out of bounds"); return qubits_[this->programToHardware_[prog]]; @@ -201,14 +200,14 @@ class [[nodiscard]] Layout : public ThinLayout { * @param q The SSA Value representing the qubit. * @return True if the layout contains the qubit, false otherwise. */ - [[nodiscard]] bool contains(const Value q) const { + [[nodiscard]] bool contains(const mlir::Value q) const { return valueToMapping_.contains(q); } /** * @brief Replace an old SSA value with a new one. */ - void remapQubitValue(const Value in, const Value out) { + void remapQubitValue(const mlir::Value in, const mlir::Value out) { const auto it = valueToMapping_.find(in); assert(it != valueToMapping_.end() && "remapQubitValue: unknown input value"); @@ -227,7 +226,7 @@ class [[nodiscard]] Layout : public ThinLayout { * @brief Swap the locations of two program qubits. This is the effect of a * SWAP gate. */ - void swap(const Value q0, const Value q1) { + void swap(const mlir::Value q0, const mlir::Value q1) { auto it0 = valueToMapping_.find(q0); auto it1 = valueToMapping_.find(q1); assert(it0 != valueToMapping_.end() && it1 != valueToMapping_.end() && @@ -244,14 +243,16 @@ class [[nodiscard]] Layout : public ThinLayout { /** * @brief Return the current layout. */ - ArrayRef getCurrentLayout() const { + mlir::ArrayRef getCurrentLayout() const { return this->programToHardware_; } /** * @brief Return the SSA values for hardware indices from 0...nqubits. */ - [[nodiscard]] ArrayRef getHardwareQubits() const { return qubits_; } + [[nodiscard]] mlir::ArrayRef getHardwareQubits() const { + return qubits_; + } private: struct QubitInfo { @@ -262,12 +263,12 @@ class [[nodiscard]] Layout : public ThinLayout { /** * @brief Maps an SSA value to its `QubitInfo`. */ - DenseMap valueToMapping_; + mlir::DenseMap valueToMapping_; /** * @brief Maps hardware qubit indices to SSA values. */ - SmallVector qubits_; + mlir::SmallVector qubits_; }; /** diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h index fd0d137b7a..1d1d247e72 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h @@ -24,7 +24,21 @@ namespace mqt::ir::opt { -using namespace mlir; +class NaiveRouter { +public: + [[nodiscard]] static SmallVector + route(QubitIndexPair gate, const ThinLayout& layout, + const Architecture& arch) { + SmallVector swaps; + const auto hw0 = layout.getHardwareIndex(gate.first); + const auto hw1 = layout.getHardwareIndex(gate.second); + const auto path = arch.shortestPathBetween(hw0, hw1); + for (std::size_t i = 0; i < path.size() - 2; ++i) { + swaps.emplace_back(path[i], path[i + 1]); + } + return swaps; + } +}; /// @brief Specifies the weights for different terms in the cost function f. struct HeuristicWeights { @@ -41,7 +55,8 @@ struct HeuristicWeights { } }; -struct AStarHeuristicRouter final { +class AStarHeuristicRouter { +public: explicit AStarHeuristicRouter(HeuristicWeights weights) : weights_(std::move(weights)) {} diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.h new file mode 100644 index 0000000000..bfe68f36b8 --- /dev/null +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h" + +#include +#include +#include +#include + +namespace mqt::ir::opt { +class SequentialUnit : public Unit { +public: + static SequentialUnit fromEntryPointFunction(mlir::func::FuncOp func, + const std::size_t nqubits) { + Layout layout(nqubits); + for_each(func.getOps(), [&](QubitOp op) { + layout.add(op.getIndex(), op.getIndex(), op.getQubit()); + }); + return {std::move(layout), &func.getBody()}; + } + + SequentialUnit(Layout layout, mlir::Region* region, + mlir::Region::OpIterator start, bool restore = false); + + SequentialUnit(Layout layout, mlir::Region* region, bool restore = false) + : SequentialUnit(std::move(layout), region, region->op_begin(), restore) { + } + + [[nodiscard]] mlir::SmallVector next(); + [[nodiscard]] mlir::Region::OpIterator begin() const { return start_; } + [[nodiscard]] mlir::Region::OpIterator end() const { return end_; } + +private: + mlir::Region::OpIterator start_; + mlir::Region::OpIterator end_; +}; +} // namespace mqt::ir::opt diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h index 55bc52bbd9..3af9d23f75 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h @@ -14,8 +14,6 @@ #include -#define DEBUG_TYPE "route-astar-sc" - namespace mqt::ir::opt { class Unit { diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp new file mode 100644 index 0000000000..ebca9f0af9 --- /dev/null +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.h" + +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h" + +#include +#include +#include +#include + +namespace mqt::ir::opt { + +SequentialUnit::SequentialUnit(Layout layout, mlir::Region* region, + mlir::Region::OpIterator start, bool restore) + : Unit(std::move(layout), region, restore), start_(start), + end_(region->op_end()) { + mlir::Region::OpIterator it = start_; + for (; it != end_; ++it) { + mlir::Operation* op = &*it; + if (isa(op)) { + divider_ = op; + break; + } + } + end_ = it; +} + +[[nodiscard]] mlir::SmallVector SequentialUnit::next() { + if (divider_ == nullptr) { + return {}; + } + + mlir::SmallVector units; + mlir::TypeSwitch(divider_) + .Case([&](mlir::scf::ForOp op) { + /// Copy layout. + Layout forLayout(layout_); + + /// Forward out-of-loop and in-loop values. + const auto nqubits = layout_.getNumQubits(); + const auto initArgs = op.getInitArgs().take_front(nqubits); + const auto results = op.getResults().take_front(nqubits); + const auto iterArgs = op.getRegionIterArgs().take_front(nqubits); + for (const auto [arg, res, iter] : + llvm::zip(initArgs, results, iterArgs)) { + layout_.remapQubitValue(arg, res); + forLayout.remapQubitValue(arg, iter); + } + + units.emplace_back(std::move(layout_), region_, std::next(end_), + restore_); + units.emplace_back(std::move(forLayout), &op.getRegion(), true); + }) + .Case([&](mlir::scf::IfOp op) { + units.emplace_back(layout_, &op.getThenRegion(), true); + units.emplace_back(layout_, &op.getElseRegion(), true); + + /// Forward results. + const auto results = + op->getResults().take_front(layout_.getNumQubits()); + for (const auto [in, out] : + llvm::zip(layout_.getHardwareQubits(), results)) { + layout_.remapQubitValue(in, out); + } + + units.emplace_back(std::move(layout_), region_, std::next(end_), + restore_); + }) + .Default( + [](auto) { throw std::runtime_error("invalid 'next' operation"); }); + + return units; +} +} // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp index 1a457ed371..661511fc1a 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp @@ -13,6 +13,8 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.h" #include #include @@ -40,7 +42,6 @@ #include #include #include -#include #define DEBUG_TYPE "route-naive-sc" @@ -52,25 +53,6 @@ namespace mqt::ir::opt { namespace { using namespace mlir; -/// @brief A composite datastructure for LLVM Statistics. -struct Statistics { - llvm::Statistic* numSwaps; -}; - -/// @brief Commonly passed parameters for the routing functions. -struct RoutingContext { - /// @brief The targeted architecture. - std::unique_ptr arch; - /// @brief LLVM/MLIR statistics. - Statistics stats; - /// @brief A pattern rewriter. - PatternRewriter rewriter; -}; - -LogicalResult processRegion(Region& region, Layout& layout, - SmallVector& history, - RoutingContext& ctx); - /** * @brief Insert SWAP ops at the rewriter's insertion point. * @@ -123,251 +105,6 @@ void insertSWAPs(Location location, ArrayRef swaps, layout.lookupHardwareIndex(ins.second)); } -/** - * @brief Use shortest path swapping to make the given unitary executable. - * @details Optimized for an avg. SWAP count of 16. - */ -void findAndInsertSWAPs(UnitaryInterface op, Layout& layout, - SmallVector& history, - RoutingContext& ctx) { - /// Find SWAPs. - SmallVector swaps; - const auto ins = getIns(op); - const auto hw0 = layout.lookupHardwareIndex(ins.first); - const auto hw1 = layout.lookupHardwareIndex(ins.second); - const auto path = ctx.arch->shortestPathBetween(hw0, hw1); - for (std::size_t i = 0; i < path.size() - 2; ++i) { - swaps.emplace_back(path[i], path[i + 1]); - } - - /// Append SWAPs to history. - history.append(swaps); - - /// Insert SWAPs. - insertSWAPs(op.getLoc(), swaps, layout, ctx.rewriter); - - /// Count SWAPs. - *(ctx.stats.numSwaps) += swaps.size(); -} - -/** - * @brief Copy the layout and recursively process the loop body. - */ -WalkResult handle(scf::ForOp op, Layout& layout, RoutingContext& ctx) { - /// Copy layout. - Layout forLayout(layout); - - /// Forward out-of-loop and in-loop values. - const auto initArgs = op.getInitArgs().take_front(ctx.arch->nqubits()); - const auto results = op.getResults().take_front(ctx.arch->nqubits()); - const auto iterArgs = op.getRegionIterArgs().take_front(ctx.arch->nqubits()); - for (const auto [arg, res, iter] : llvm::zip(initArgs, results, iterArgs)) { - layout.remapQubitValue(arg, res); - forLayout.remapQubitValue(arg, iter); - } - - /// Recursively handle loop region. - SmallVector history; - return processRegion(op.getRegion(), forLayout, history, ctx); -} - -/** - * @brief Copy the layout for each branch and recursively map the branches. - */ -WalkResult handle(scf::IfOp op, Layout& layout, RoutingContext& ctx) { - /// Recursively handle each branch region. - Layout ifLayout(layout); - SmallVector ifHistory; - const auto ifRes = - processRegion(op.getThenRegion(), ifLayout, ifHistory, ctx); - if (ifRes.failed()) { - return ifRes; - } - - Layout elseLayout(layout); - SmallVector elseHistory; - const auto elseRes = - processRegion(op.getElseRegion(), elseLayout, elseHistory, ctx); - if (elseRes.failed()) { - return elseRes; - } - - /// Forward out-of-if values. - const auto results = op->getResults().take_front(ctx.arch->nqubits()); - for (const auto [in, out] : llvm::zip(layout.getHardwareQubits(), results)) { - layout.remapQubitValue(in, out); - } - - return WalkResult::advance(); -} - -/** - * @brief Indicates the end of a region defined by a scf op. - * - * Restores layout by uncomputation. - */ -WalkResult handle(scf::YieldOp op, Layout& layout, - ArrayRef history, RoutingContext& ctx) { - /// Uncompute SWAPs. - insertSWAPs(op.getLoc(), llvm::to_vector(llvm::reverse(history)), layout, - ctx.rewriter); - /// Count SWAPs. - *(ctx.stats.numSwaps) += history.size(); - return WalkResult::advance(); -} - -/** - * @brief Add hardware qubit with respective program & hardware index to - * layout. - * - * Thanks to the placement pass, we can apply the identity layout here. - */ -WalkResult handle(QubitOp op, Layout& layout) { - const std::size_t index = op.getIndex(); - layout.add(index, index, op.getQubit()); - return WalkResult::advance(); -} - -/** - * @brief Ensures the executability of two-qubit gates on the given target - * architecture by inserting SWAPs. - */ -WalkResult handle(UnitaryInterface op, Layout& layout, - SmallVector& history, RoutingContext& ctx) { - const std::vector inQubits = op.getAllInQubits(); - const std::vector outQubits = op.getAllOutQubits(); - const std::size_t nacts = inQubits.size(); - - // Global-phase or zero-qubit unitary: Nothing to do. - if (nacts == 0) { - return WalkResult::advance(); - } - - if (isa(op)) { - for (const auto [in, out] : llvm::zip(inQubits, outQubits)) { - layout.remapQubitValue(in, out); - } - return WalkResult::advance(); - } - - /// Expect two-qubit gate decomposition. - if (nacts > 2) { - return op->emitOpError() << "acts on more than two qubits"; - } - - /// Single-qubit: Forward mapping. - if (nacts == 1) { - layout.remapQubitValue(inQubits[0], outQubits[0]); - return WalkResult::advance(); - } - - if (!isExecutable(op, layout, *ctx.arch)) { - findAndInsertSWAPs(op, layout, history, ctx); - } - - const auto [execIn0, execIn1] = getIns(op); - const auto [execOut0, execOut1] = getOuts(op); - - LLVM_DEBUG({ - llvm::dbgs() << llvm::format("handleUnitary: gate= p%d:h%d, p%d:h%d\n", - layout.lookupProgramIndex(execIn0), - layout.lookupHardwareIndex(execIn0), - layout.lookupProgramIndex(execIn1), - layout.lookupHardwareIndex(execIn1)); - }); - - if (isa(op)) { - layout.swap(execIn0, execIn1); - history.push_back({layout.lookupHardwareIndex(execIn0), - layout.lookupHardwareIndex(execIn1)}); - } - - layout.remapQubitValue(execIn0, execOut0); - layout.remapQubitValue(execIn1, execOut1); - - return WalkResult::advance(); -} - -/** - * @brief Traverse the given region pre-order and insert SWAPs for any - * non-executable gate. - */ -LogicalResult processRegion(Region& region, Layout& layout, - SmallVector& history, - RoutingContext& ctx) { - for (Operation& curr : region.getOps()) { - const OpBuilder::InsertionGuard guard(ctx.rewriter); - ctx.rewriter.setInsertionPoint(&curr); - - const auto res = - TypeSwitch(&curr) - /// mqtopt Dialect - .Case([&](UnitaryInterface op) { - return handle(op, layout, history, ctx); - }) - .Case([&](QubitOp op) { return handle(op, layout); }) - .Case([&](ResetOp op) { - remap(op, layout); - return WalkResult::advance(); - }) - .Case([&](MeasureOp op) { - remap(op, layout); - return WalkResult::advance(); - }) - /// built-in Dialect - .Case([&]([[maybe_unused]] ModuleOp op) { - return WalkResult::advance(); - }) - /// func Dialect - .Case([&]([[maybe_unused]] func::ReturnOp op) { - return WalkResult::advance(); - }) - /// scf Dialect - .Case( - [&](scf::ForOp op) { return handle(op, layout, ctx); }) - .Case( - [&](scf::IfOp op) { return handle(op, layout, ctx); }) - .Case([&](scf::YieldOp op) { - return handle(op, layout, history, ctx); - }) - /// Skip the rest. - .Default([](auto) { return WalkResult::skip(); }); - - if (res.wasInterrupted()) { - return failure(); - } - } - - return success(); -} - -/** - * @brief Naively route the given module for the targeted architecture. - * Processes each entry_point function separately. - */ -LogicalResult route(ModuleOp module, std::unique_ptr arch, - Statistics& stats) { - RoutingContext ctx{.arch = std::move(arch), - .stats = stats, - .rewriter = PatternRewriter(module->getContext())}; - for (auto func : module.getOps()) { - LLVM_DEBUG(llvm::dbgs() << "handleFunc: " << func.getSymName() << '\n'); - - if (!isEntryPoint(func)) { - LLVM_DEBUG(llvm::dbgs() << "\tskip non entry\n"); - return success(); // Ignore non entry_point functions for now. - } - - Layout layout(ctx.arch->nqubits()); - SmallVector history; - const auto res = processRegion(func.getBody(), layout, history, ctx); - if (res.failed()) { - return res; - } - } - return success(); -} - /** * @brief Simple pre-order traversal of the IR that routes any non-executable * gates by inserting SWAPs along the shortest path. @@ -377,26 +114,99 @@ struct NaiveRoutingPassSC final using NaiveRoutingPassSCBase::NaiveRoutingPassSCBase; void runOnOperation() override { - if (preflight().failed()) { + if (failed(preflight())) { signalPassFailure(); return; } - auto arch = getArchitecture(archName); - if (!arch) { - emitError(UnknownLoc::get(&getContext())) - << "unsupported architecture '" << archName << "'"; + if (failed(route())) { signalPassFailure(); return; - } - - Statistics stats{.numSwaps = &numSwaps}; - if (route(getOperation(), std::move(arch), stats).failed()) { - signalPassFailure(); }; } private: + LogicalResult route() { + ModuleOp module(getOperation()); + PatternRewriter rewriter(module->getContext()); + /// AStarHeuristicRouter router(HeuristicWeights(alpha, lambda, + /// nlookahead)); + std::unique_ptr arch(getArchitecture(archName)); + + if (!arch) { + Location location = UnknownLoc::get(&getContext()); + emitError(location) << "unsupported architecture '" << archName << "'"; + return failure(); + } + + for (auto func : module.getOps()) { + LLVM_DEBUG(llvm::dbgs() << "handleFunc: " << func.getSymName() << '\n'); + + if (!isEntryPoint(func)) { + LLVM_DEBUG(llvm::dbgs() << "\tskip non entry\n"); + return success(); // Ignore non entry_point functions for now. + } + + /// Iteratively process each unit in the function. + std::queue units; + units.emplace( + SequentialUnit::fromEntryPointFunction(func, arch->nqubits())); + for (; !units.empty(); units.pop()) { + SequentialUnit& unit = units.front(); + + SmallVector history; + for (Operation& curr : unit) { + rewriter.setInsertionPoint(&curr); + + /// Forward layout. + TypeSwitch(&curr) + .Case([&](UnitaryInterface op) { + if (isTwoQubitGate(op)) { + if (!isExecutable(op, unit.layout(), *arch)) { + const auto ins = getIns(op); + const auto gate = std::make_pair( + unit.layout().lookupProgramIndex(ins.first), + unit.layout().lookupProgramIndex(ins.second)); + const auto swaps = + NaiveRouter::route(gate, unit.layout(), *arch); + if (!swaps.empty()) { + history.append(swaps); + insertSWAPs(op->getLoc(), swaps, unit.layout(), rewriter); + numSwaps += swaps.size(); + } + } + } + + if (isa(op)) { + const auto ins = getIns(op); + unit.layout().swap(ins.first, ins.second); + history.push_back( + {unit.layout().lookupHardwareIndex(ins.first), + unit.layout().lookupHardwareIndex(ins.second)}); + } + remap(op, unit.layout()); + }) + .Case([&](ResetOp op) { remap(op, unit.layout()); }) + .Case([&](MeasureOp op) { remap(op, unit.layout()); }) + .Case([&](scf::YieldOp op) { + if (unit.restore()) { + rewriter.setInsertionPointAfter(op->getPrevNode()); + insertSWAPs(op.getLoc(), + llvm::to_vector(llvm::reverse(history)), + unit.layout(), rewriter); + } + }); + } + + for (auto next : unit.next()) { + units.emplace(next); + } + } + } + + return success(); + } + LogicalResult preflight() { if (archName.empty()) { return emitError(UnknownLoc::get(&getContext()), From 79b76f8d497435d3c82774250b1905a2cd678db8 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 21 Nov 2025 12:19:14 +0100 Subject: [PATCH 28/56] Fix linting --- .../Transforms/Transpilation/LayeredUnit.h | 10 +- .../MQTOpt/Transforms/Transpilation/Router.h | 4 +- .../Transforms/Transpilation/SequentialUnit.h | 2 + .../MQTOpt/Transforms/Transpilation/Unit.h | 1 + .../Transforms/Transpilation/Common.cpp | 8 +- .../Transforms/Transpilation/LayeredUnit.cpp | 205 +++++++++--------- .../Transpilation/SequentialUnit.cpp | 10 +- .../Transpilation/sc/AStarRoutingPass.cpp | 34 +-- .../Transpilation/sc/NaiveRoutingPass.cpp | 16 +- .../Transforms/Transpilation/basics.mlir | 8 +- 10 files changed, 155 insertions(+), 143 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.h index 1bf5b90fc5..f9d4df2413 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.h @@ -25,7 +25,6 @@ namespace mqt::ir::opt { struct Wire { Wire(const WireIterator& it, QubitIndex index) : it(it), index(index) {} - WireIterator it; QubitIndex index; }; @@ -114,6 +113,7 @@ class SlidingWindow { std::size_t nlookahead; }; +/// @brief A LayeredUnit traverses a program layer-by-layer. class LayeredUnit : public Unit { public: static LayeredUnit fromEntryPointFunction(mlir::func::FuncOp func, @@ -125,10 +125,7 @@ class LayeredUnit : public Unit { return {std::move(layout), &func.getBody()}; } - LayeredUnit(Layout layout, Region* region, bool restore = false) - : Unit(std::move(layout), region, restore) { - init(layout_, region); - } + LayeredUnit(Layout layout, Region* region, bool restore = false); [[nodiscard]] SmallVector next(); [[nodiscard]] SlidingWindow slidingWindow(std::size_t nlookahead) const; @@ -138,9 +135,6 @@ class LayeredUnit : public Unit { #endif private: - /// @brief Compute (schedule) the layers of the unit. - void init(const Layout& layout, Region* region); - SmallVector gateLayers; SmallVector opLayers; }; diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h index 1d1d247e72..f9dceecbfb 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h @@ -177,9 +177,7 @@ class AStarHeuristicRouter { } private: - /** - * @brief Expand frontier with all neighbouring SWAPs in the current front. - */ + /// @brief Expand frontier with all neighbouring SWAPs in the current front. void expand(MinQueue& frontier, const Node& parent, ArrayRef window, const Architecture& arch) const { llvm::SmallDenseSet expansionSet{}; diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.h index bfe68f36b8..1dceb8420f 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.h @@ -19,6 +19,8 @@ #include namespace mqt::ir::opt { + +/// @brief A SequentialUnit traverses a program sequentially. class SequentialUnit : public Unit { public: static SequentialUnit fromEntryPointFunction(mlir::func::FuncOp func, diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h index 3af9d23f75..d335961db0 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h @@ -16,6 +16,7 @@ namespace mqt::ir::opt { +/// @brief An Unit divides a quantum-classical program into routable sections. class Unit { public: Unit(Layout layout, mlir::Region* region, bool restore = false) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp index c947d43b08..8ca8e83221 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp @@ -29,12 +29,12 @@ namespace { /** * @brief A function attribute that specifies an (QIR) entry point function. */ -constexpr llvm::StringLiteral ENTRY_POINT_ATTR{"entry_point"}; +constexpr mlir::StringLiteral ENTRY_POINT_ATTR{"entry_point"}; /** * @brief Attribute to forward function-level attributes to LLVM IR. */ -constexpr llvm::StringLiteral PASSTHROUGH_ATTR{"passthrough"}; +constexpr mlir::StringLiteral PASSTHROUGH_ATTR{"passthrough"}; } // namespace bool isEntryPoint(mlir::func::FuncOp op) { @@ -45,8 +45,8 @@ bool isEntryPoint(mlir::func::FuncOp op) { } return llvm::any_of(passthroughAttr, [](const mlir::Attribute attr) { - return isa(attr) && - cast(attr) == ENTRY_POINT_ATTR; + return mlir::isa(attr) && + mlir::cast(attr) == ENTRY_POINT_ATTR; }); } diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp index cc27659ce2..2f891b036c 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp @@ -10,30 +10,32 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.h" +#include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h" +#include #include +#include #include #include #include -#include #include #include +#include +#include +#include #include +#include +#include +#include namespace mqt::ir::opt { namespace { /// @brief Map to handle multi-qubit gates when traversing the def-use chain. class SynchronizationMap { - /// @brief Maps operations to to-be-released iterators. - DenseMap> onHold; - - /// @brief Maps operations to ref counts. An op can be released whenever the - /// count reaches zero. - DenseMap refCount; - public: /// @returns true iff. the operation is contained in the map. bool contains(Operation* op) const { return onHold.contains(op); } @@ -59,12 +61,18 @@ class SynchronizationMap { return std::nullopt; } + +private: + /// @brief Maps operations to to-be-released iterators. + DenseMap> onHold; + /// @brief Maps operations to ref counts. + DenseMap refCount; }; SmallVector skipTwoQubitBlock(ArrayRef wires, OpLayer& opLayer) { assert(wires.size() == 2 && "expected two wires"); - WireIterator end; + const WireIterator end; auto [it0, index0] = wires[0]; auto [it1, index1] = wires[1]; while (it0 != end && it1 != end) { @@ -78,7 +86,7 @@ SmallVector skipTwoQubitBlock(ArrayRef wires, OpLayer& opLayer) { break; } - UnitaryInterface u0 = cast(op0); + const UnitaryInterface u0 = cast(op0); /// Advance for single qubit gate on wire 0. if (!isTwoQubitGate(u0)) { @@ -87,7 +95,7 @@ SmallVector skipTwoQubitBlock(ArrayRef wires, OpLayer& opLayer) { continue; } - UnitaryInterface u1 = cast(op1); + const UnitaryInterface u1 = cast(op1); /// Advance for single qubit gate on wire 1. if (!isTwoQubitGate(u1)) { @@ -112,96 +120,17 @@ SmallVector skipTwoQubitBlock(ArrayRef wires, OpLayer& opLayer) { } } // namespace -SmallVector LayeredUnit::next() { - if (divider_ == nullptr) { - return {}; - } - - SmallVector units; - TypeSwitch(divider_) - .Case([&](scf::ForOp op) { - /// Copy layout. - Layout forLayout(layout_); - - /// Forward out-of-loop and in-loop values. - const auto nqubits = layout_.getNumQubits(); - const auto initArgs = op.getInitArgs().take_front(nqubits); - const auto results = op.getResults().take_front(nqubits); - const auto iterArgs = op.getRegionIterArgs().take_front(nqubits); - for (const auto [arg, res, iter] : - llvm::zip(initArgs, results, iterArgs)) { - layout_.remapQubitValue(arg, res); - forLayout.remapQubitValue(arg, iter); - } - - units.emplace_back(std::move(layout_), region_, restore_); - units.emplace_back(std::move(forLayout), &op.getRegion(), true); - }) - .Case([&](scf::IfOp op) { - units.emplace_back(layout_, &op.getThenRegion(), true); - units.emplace_back(layout_, &op.getElseRegion(), true); - - /// Forward results. - const auto results = - op->getResults().take_front(layout_.getNumQubits()); - for (const auto [in, out] : - llvm::zip(layout_.getHardwareQubits(), results)) { - layout_.remapQubitValue(in, out); - } - - units.emplace_back(layout_, region_, restore_); - }) - .Default( - [](auto) { throw std::runtime_error("invalid 'next' operation"); }); - - return units; -} - -SlidingWindow LayeredUnit::slidingWindow(std::size_t nlookahead) const { - return SlidingWindow(gateLayers, opLayers, nlookahead); -} - -#ifndef NDEBUG -LLVM_DUMP_METHOD void LayeredUnit::dump(llvm::raw_ostream& os) const { - os << "schedule: gate layers=\n"; - for (const auto [i, layer] : llvm::enumerate(gateLayers)) { - os << '\t' << i << ": "; - os << "gates= "; - if (!layer.empty()) { - for (const auto [prog0, prog1] : layer) { - os << "(" << prog0 << "," << prog1 << "), "; - } - } else { - os << "(), "; - } - os << '\n'; - } - - os << "schedule: op layers=\n"; - for (const auto [i, layer] : llvm::enumerate(opLayers)) { - os << '\t' << i << ": "; - os << "#ops= " << layer.ops.size(); - if (!layer.ops.empty()) { - os << " anchor= " << layer.anchor->getLoc(); - } - os << '\n'; - } - if (divider_ != nullptr) { - os << "schedule: followUp= " << divider_->getLoc() << '\n'; - } -} -#endif - -void LayeredUnit::init(const Layout& layout, Region* region) { +LayeredUnit::LayeredUnit(Layout layout, Region* region, bool restore) + : Unit(std::move(layout), region, restore) { SynchronizationMap sync; SmallVector curr; SmallVector next; - curr.reserve(layout.getNumQubits()); - next.reserve(layout.getNumQubits()); + curr.reserve(layout_.getNumQubits()); + next.reserve(layout_.getNumQubits()); - for (const auto q : layout.getHardwareQubits()) { - curr.emplace_back(WireIterator(q, region), layout.lookupProgramIndex(q)); + for (const auto q : layout_.getHardwareQubits()) { + curr.emplace_back(WireIterator(q, region_), layout_.lookupProgramIndex(q)); } while (true) { @@ -264,7 +193,7 @@ void LayeredUnit::init(const Layout& layout, Region* region) { }) .Case([&](scf::YieldOp yield) { if (!sync.contains(yield)) { - sync.add(yield, Wire(++it, index), layout.getNumQubits()); + sync.add(yield, Wire(++it, index), layout_.getNumQubits()); return true; } @@ -277,7 +206,7 @@ void LayeredUnit::init(const Layout& layout, Region* region) { }) .Case([&](RegionBranchOpInterface op) { if (!sync.contains(op)) { - sync.add(op, Wire(++it, index), layout.getNumQubits()); + sync.add(op, Wire(++it, index), layout_.getNumQubits()); return true; } @@ -331,4 +260,84 @@ void LayeredUnit::init(const Layout& layout, Region* region) { } }; } + +SmallVector LayeredUnit::next() { + if (divider_ == nullptr) { + return {}; + } + + SmallVector units; + TypeSwitch(divider_) + .Case([&](scf::ForOp op) { + /// Copy layout. + Layout forLayout(layout_); + + /// Forward out-of-loop and in-loop values. + const auto nqubits = layout_.getNumQubits(); + const auto initArgs = op.getInitArgs().take_front(nqubits); + const auto results = op.getResults().take_front(nqubits); + const auto iterArgs = op.getRegionIterArgs().take_front(nqubits); + for (const auto [arg, res, iter] : + llvm::zip(initArgs, results, iterArgs)) { + layout_.remapQubitValue(arg, res); + forLayout.remapQubitValue(arg, iter); + } + + units.emplace_back(std::move(layout_), region_, restore_); + units.emplace_back(std::move(forLayout), &op.getRegion(), true); + }) + .Case([&](scf::IfOp op) { + units.emplace_back(layout_, &op.getThenRegion(), true); + units.emplace_back(layout_, &op.getElseRegion(), true); + + /// Forward results. + const auto results = + op->getResults().take_front(layout_.getNumQubits()); + for (const auto [in, out] : + llvm::zip(layout_.getHardwareQubits(), results)) { + layout_.remapQubitValue(in, out); + } + + units.emplace_back(layout_, region_, restore_); + }) + .Default( + [](auto) { throw std::runtime_error("invalid 'next' operation"); }); + + return units; +} + +SlidingWindow LayeredUnit::slidingWindow(std::size_t nlookahead) const { + return SlidingWindow(gateLayers, opLayers, nlookahead); +} + +#ifndef NDEBUG +LLVM_DUMP_METHOD void LayeredUnit::dump(llvm::raw_ostream& os) const { + os << "schedule: gate layers=\n"; + for (const auto [i, layer] : llvm::enumerate(gateLayers)) { + os << '\t' << i << ": "; + os << "gates= "; + if (!layer.empty()) { + for (const auto [prog0, prog1] : layer) { + os << "(" << prog0 << "," << prog1 << "), "; + } + } else { + os << "(), "; + } + os << '\n'; + } + + os << "schedule: op layers=\n"; + for (const auto [i, layer] : llvm::enumerate(opLayers)) { + os << '\t' << i << ": "; + os << "#ops= " << layer.ops.size(); + if (!layer.ops.empty()) { + os << " anchor= " << layer.anchor->getLoc(); + } + os << '\n'; + } + if (divider_ != nullptr) { + os << "schedule: followUp= " << divider_->getLoc() << '\n'; + } +} +#endif } // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp index ebca9f0af9..e2b3a78f04 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp @@ -20,12 +20,20 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h" +#include +#include #include +#include #include +#include +#include #include #include +#include +#include namespace mqt::ir::opt { @@ -36,7 +44,7 @@ SequentialUnit::SequentialUnit(Layout layout, mlir::Region* region, mlir::Region::OpIterator it = start_; for (; it != end_; ++it) { mlir::Operation* op = &*it; - if (isa(op)) { + if (mlir::isa(op)) { divider_ = op; break; } diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index 4463014015..b229777f3e 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -13,21 +13,18 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h" #include -#include #include #include #include #include -#include #include #include #include -#include #include -#include #include #include #include @@ -39,7 +36,6 @@ #include #include #include -#include #include #include #include @@ -59,13 +55,13 @@ using namespace mlir; /** * @brief Insert SWAP ops at the rewriter's insertion point. * - * @param location The location of the inserted SWAP ops. + * @param loc The location of the inserted SWAP ops. * @param swaps The hardware indices of the SWAPs. * @param layout The current layout. * @param rewriter The pattern rewriter. */ -void insertSWAPs(Location location, ArrayRef swaps, - Layout& layout, PatternRewriter& rewriter) { +void insertSWAPs(Location loc, ArrayRef swaps, Layout& layout, + PatternRewriter& rewriter) { for (const auto [hw0, hw1] : swaps) { const Value in0 = layout.lookupHardwareValue(hw0); const Value in1 = layout.lookupHardwareValue(hw1); @@ -78,7 +74,7 @@ void insertSWAPs(Location location, ArrayRef swaps, prog0, hw1, prog0, hw0, prog1, hw1); }); - auto swap = createSwap(location, in0, in1, rewriter); + auto swap = createSwap(loc, in0, in1, rewriter); const auto [out0, out1] = getOuts(swap); rewriter.setInsertionPointAfter(swap); @@ -122,12 +118,13 @@ struct AStarRoutingPassSC final LogicalResult route() { ModuleOp module(getOperation()); PatternRewriter rewriter(module->getContext()); - AStarHeuristicRouter router(HeuristicWeights(alpha, lambda, nlookahead)); + const AStarHeuristicRouter router( + HeuristicWeights(alpha, lambda, nlookahead)); std::unique_ptr arch(getArchitecture(archName)); if (!arch) { - Location location = UnknownLoc::get(&getContext()); - emitError(location) << "unsupported architecture '" << archName << "'"; + const Location loc = UnknownLoc::get(&getContext()); + emitError(loc) << "unsupported architecture '" << archName << "'"; return failure(); } @@ -145,17 +142,19 @@ struct AStarRoutingPassSC final for (; !units.empty(); units.pop()) { LayeredUnit& unit = units.front(); + unit.dump(); + SmallVector history; for (const auto window : unit.slidingWindow(nlookahead)) { Operation* anchor = window.opLayer->anchor; - ArrayRef layers = window.gateLayers; - ArrayRef ops = window.opLayer->ops; + const ArrayRef layers = window.gateLayers; + const ArrayRef ops = window.opLayer->ops; /// Find and insert SWAPs. rewriter.setInsertionPoint(anchor); const auto swaps = router.route(layers, unit.layout(), *arch); if (!swaps) { - Location loc = UnknownLoc::get(&getContext()); + const Location loc = UnknownLoc::get(&getContext()); return emitError(loc, "A* failed to find a valid SWAP sequence"); } @@ -203,7 +202,8 @@ struct AStarRoutingPassSC final } } - for (auto next : unit.next()) { + for (const auto& next : unit.next()) { + llvm::dbgs() << "next!\n"; units.emplace(next); } } @@ -214,7 +214,7 @@ struct AStarRoutingPassSC final LogicalResult preflight() { if (archName.empty()) { - Location loc = UnknownLoc::get(&getContext()); + const Location loc = UnknownLoc::get(&getContext()); return emitError(loc, "required option 'arch' not provided"); } diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp index 661511fc1a..63f211fa53 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp @@ -17,7 +17,6 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.h" #include -#include #include #include #include @@ -41,6 +40,7 @@ #include #include #include +#include #include #define DEBUG_TYPE "route-naive-sc" @@ -56,13 +56,13 @@ using namespace mlir; /** * @brief Insert SWAP ops at the rewriter's insertion point. * - * @param location The location of the inserted SWAP ops. + * @param loc The location of the inserted SWAP ops. * @param swaps The hardware indices of the SWAPs. * @param layout The current layout. * @param rewriter The pattern rewriter. */ -void insertSWAPs(Location location, ArrayRef swaps, - Layout& layout, PatternRewriter& rewriter) { +void insertSWAPs(Location loc, ArrayRef swaps, Layout& layout, + PatternRewriter& rewriter) { for (const auto [hw0, hw1] : swaps) { const Value in0 = layout.lookupHardwareValue(hw0); const Value in1 = layout.lookupHardwareValue(hw1); @@ -75,7 +75,7 @@ void insertSWAPs(Location location, ArrayRef swaps, prog0, hw1, prog0, hw0, prog1, hw1); }); - auto swap = createSwap(location, in0, in1, rewriter); + auto swap = createSwap(loc, in0, in1, rewriter); const auto [out0, out1] = getOuts(swap); rewriter.setInsertionPointAfter(swap); @@ -134,8 +134,8 @@ struct NaiveRoutingPassSC final std::unique_ptr arch(getArchitecture(archName)); if (!arch) { - Location location = UnknownLoc::get(&getContext()); - emitError(location) << "unsupported architecture '" << archName << "'"; + const Location loc = UnknownLoc::get(&getContext()); + emitError(loc) << "unsupported architecture '" << archName << "'"; return failure(); } @@ -198,7 +198,7 @@ struct NaiveRoutingPassSC final }); } - for (auto next : unit.next()) { + for (const auto& next : unit.next()) { units.emplace(next); } } diff --git a/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/basics.mlir b/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/basics.mlir index 2b8980130b..44cac2fe68 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/basics.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/basics.mlir @@ -8,10 +8,10 @@ // Instead of applying checks, the routing verifier pass ensures the validity of this program. -// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(canonicalize, placement-sc{strategy=identity arch=MQTTest}, route-naive-sc{arch=MQTTest},verify-routing-sc{arch=MQTTest})" -verify-diagnostics | FileCheck %s -// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(canonicalize, placement-sc{strategy=identity arch=MQTTest}, route-astar-sc{arch=MQTTest},verify-routing-sc{arch=MQTTest})" -verify-diagnostics | FileCheck %s -// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(canonicalize, placement-sc{strategy=identity arch=IBMFalcon}, route-naive-sc{arch=IBMFalcon},verify-routing-sc{arch=IBMFalcon})" -verify-diagnostics | FileCheck %s -// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(canonicalize, placement-sc{strategy=identity arch=IBMFalcon}, route-astar-sc{arch=IBMFalcon},verify-routing-sc{arch=IBMFalcon})" -verify-diagnostics | FileCheck %s +// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(placement-sc{strategy=identity arch=MQTTest}, route-naive-sc{arch=MQTTest},verify-routing-sc{arch=MQTTest})" -verify-diagnostics | FileCheck %s +// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(placement-sc{strategy=identity arch=MQTTest}, route-astar-sc{arch=MQTTest},verify-routing-sc{arch=MQTTest})" -verify-diagnostics | FileCheck %s +// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(placement-sc{strategy=identity arch=IBMFalcon}, route-naive-sc{arch=IBMFalcon},verify-routing-sc{arch=IBMFalcon})" -verify-diagnostics | FileCheck %s +// RUN: quantum-opt %s -split-input-file --pass-pipeline="builtin.module(placement-sc{strategy=identity arch=IBMFalcon}, route-astar-sc{arch=IBMFalcon},verify-routing-sc{arch=IBMFalcon})" -verify-diagnostics | FileCheck %s module { // CHECK-LABEL: func.func @entrySABRE From 346fe58e02a1af0f680130f245c8da2343a6a3ff Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 21 Nov 2025 12:22:37 +0100 Subject: [PATCH 29/56] Add LLVM_DEBUG --- .../MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index b229777f3e..67f64e736b 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -142,7 +142,7 @@ struct AStarRoutingPassSC final for (; !units.empty(); units.pop()) { LayeredUnit& unit = units.front(); - unit.dump(); + LLVM_DEBUG(unit.dump()); SmallVector history; for (const auto window : unit.slidingWindow(nlookahead)) { From a23d79c19619b0d7c81389e21a0050263b2d2674 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 21 Nov 2025 12:32:03 +0100 Subject: [PATCH 30/56] Fix more linting issues --- mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp | 2 -- .../lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp | 1 + .../Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp index 8ca8e83221..4b4d20f575 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp @@ -14,8 +14,6 @@ #include #include -#include -#include #include #include #include diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp index 2f891b036c..4383e7e9df 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp @@ -13,6 +13,7 @@ #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/WireIterator.h" #include diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp index e2b3a78f04..433200ee98 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include From 10412dab6fb7651b9c2e2ffcc61adbb232c9768c Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 21 Nov 2025 12:49:48 +0100 Subject: [PATCH 31/56] Remove grid layouts --- .../Transforms/Transpilation/Architecture.cpp | 217 ------------------ 1 file changed, 217 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Architecture.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Architecture.cpp index df895696d7..74d3dcaa4f 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Architecture.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Architecture.cpp @@ -148,223 +148,6 @@ std::unique_ptr getArchitecture(const llvm::StringRef name) { return std::make_unique("IBM-Falcon", 127, COUPLING); } - if (name == "MQTGrid4") { - static const Architecture::CouplingSet COUPLING{ - {0, 4}, {4, 0}, {0, 1}, {1, 0}, {1, 5}, {5, 1}, {1, 2}, - {2, 1}, {2, 6}, {6, 2}, {2, 3}, {3, 2}, {3, 7}, {7, 3}, - {4, 8}, {8, 4}, {4, 5}, {5, 4}, {5, 9}, {9, 5}, {5, 6}, - {6, 5}, {6, 10}, {10, 6}, {6, 7}, {7, 6}, {7, 11}, {11, 7}, - {8, 12}, {12, 8}, {8, 9}, {9, 8}, {9, 13}, {13, 9}, {9, 10}, - {10, 9}, {10, 14}, {14, 10}, {10, 11}, {11, 10}, {11, 15}, {15, 11}, - {12, 13}, {13, 12}, {13, 14}, {14, 13}, {14, 15}, {15, 14}}; - - return std::make_unique("MQTGrid4", 16, COUPLING); - } - - if (name == "MQTGrid8") { - static const Architecture::CouplingSet COUPLING{ - {0, 8}, {8, 0}, {0, 1}, {1, 0}, {1, 9}, {9, 1}, {1, 2}, - {2, 1}, {2, 10}, {10, 2}, {2, 3}, {3, 2}, {3, 11}, {11, 3}, - {3, 4}, {4, 3}, {4, 12}, {12, 4}, {4, 5}, {5, 4}, {5, 13}, - {13, 5}, {5, 6}, {6, 5}, {6, 14}, {14, 6}, {6, 7}, {7, 6}, - {7, 15}, {15, 7}, {8, 16}, {16, 8}, {8, 9}, {9, 8}, {9, 17}, - {17, 9}, {9, 10}, {10, 9}, {10, 18}, {18, 10}, {10, 11}, {11, 10}, - {11, 19}, {19, 11}, {11, 12}, {12, 11}, {12, 20}, {20, 12}, {12, 13}, - {13, 12}, {13, 21}, {21, 13}, {13, 14}, {14, 13}, {14, 22}, {22, 14}, - {14, 15}, {15, 14}, {15, 23}, {23, 15}, {16, 24}, {24, 16}, {16, 17}, - {17, 16}, {17, 25}, {25, 17}, {17, 18}, {18, 17}, {18, 26}, {26, 18}, - {18, 19}, {19, 18}, {19, 27}, {27, 19}, {19, 20}, {20, 19}, {20, 28}, - {28, 20}, {20, 21}, {21, 20}, {21, 29}, {29, 21}, {21, 22}, {22, 21}, - {22, 30}, {30, 22}, {22, 23}, {23, 22}, {23, 31}, {31, 23}, {24, 32}, - {32, 24}, {24, 25}, {25, 24}, {25, 33}, {33, 25}, {25, 26}, {26, 25}, - {26, 34}, {34, 26}, {26, 27}, {27, 26}, {27, 35}, {35, 27}, {27, 28}, - {28, 27}, {28, 36}, {36, 28}, {28, 29}, {29, 28}, {29, 37}, {37, 29}, - {29, 30}, {30, 29}, {30, 38}, {38, 30}, {30, 31}, {31, 30}, {31, 39}, - {39, 31}, {32, 40}, {40, 32}, {32, 33}, {33, 32}, {33, 41}, {41, 33}, - {33, 34}, {34, 33}, {34, 42}, {42, 34}, {34, 35}, {35, 34}, {35, 43}, - {43, 35}, {35, 36}, {36, 35}, {36, 44}, {44, 36}, {36, 37}, {37, 36}, - {37, 45}, {45, 37}, {37, 38}, {38, 37}, {38, 46}, {46, 38}, {38, 39}, - {39, 38}, {39, 47}, {47, 39}, {40, 48}, {48, 40}, {40, 41}, {41, 40}, - {41, 49}, {49, 41}, {41, 42}, {42, 41}, {42, 50}, {50, 42}, {42, 43}, - {43, 42}, {43, 51}, {51, 43}, {43, 44}, {44, 43}, {44, 52}, {52, 44}, - {44, 45}, {45, 44}, {45, 53}, {53, 45}, {45, 46}, {46, 45}, {46, 54}, - {54, 46}, {46, 47}, {47, 46}, {47, 55}, {55, 47}, {48, 56}, {56, 48}, - {48, 49}, {49, 48}, {49, 57}, {57, 49}, {49, 50}, {50, 49}, {50, 58}, - {58, 50}, {50, 51}, {51, 50}, {51, 59}, {59, 51}, {51, 52}, {52, 51}, - {52, 60}, {60, 52}, {52, 53}, {53, 52}, {53, 61}, {61, 53}, {53, 54}, - {54, 53}, {54, 62}, {62, 54}, {54, 55}, {55, 54}, {55, 63}, {63, 55}, - {56, 57}, {57, 56}, {57, 58}, {58, 57}, {58, 59}, {59, 58}, {59, 60}, - {60, 59}, {60, 61}, {61, 60}, {61, 62}, {62, 61}, {62, 63}, {63, 62}}; - - return std::make_unique("MQTGrid8", 64, COUPLING); - } - - if (name == "MQTGrid16") { - static const Architecture::CouplingSet COUPLING{ - {0, 16}, {16, 0}, {0, 1}, {1, 0}, {1, 17}, {17, 1}, - {1, 2}, {2, 1}, {2, 18}, {18, 2}, {2, 3}, {3, 2}, - {3, 19}, {19, 3}, {3, 4}, {4, 3}, {4, 20}, {20, 4}, - {4, 5}, {5, 4}, {5, 21}, {21, 5}, {5, 6}, {6, 5}, - {6, 22}, {22, 6}, {6, 7}, {7, 6}, {7, 23}, {23, 7}, - {7, 8}, {8, 7}, {8, 24}, {24, 8}, {8, 9}, {9, 8}, - {9, 25}, {25, 9}, {9, 10}, {10, 9}, {10, 26}, {26, 10}, - {10, 11}, {11, 10}, {11, 27}, {27, 11}, {11, 12}, {12, 11}, - {12, 28}, {28, 12}, {12, 13}, {13, 12}, {13, 29}, {29, 13}, - {13, 14}, {14, 13}, {14, 30}, {30, 14}, {14, 15}, {15, 14}, - {15, 31}, {31, 15}, {16, 32}, {32, 16}, {16, 17}, {17, 16}, - {17, 33}, {33, 17}, {17, 18}, {18, 17}, {18, 34}, {34, 18}, - {18, 19}, {19, 18}, {19, 35}, {35, 19}, {19, 20}, {20, 19}, - {20, 36}, {36, 20}, {20, 21}, {21, 20}, {21, 37}, {37, 21}, - {21, 22}, {22, 21}, {22, 38}, {38, 22}, {22, 23}, {23, 22}, - {23, 39}, {39, 23}, {23, 24}, {24, 23}, {24, 40}, {40, 24}, - {24, 25}, {25, 24}, {25, 41}, {41, 25}, {25, 26}, {26, 25}, - {26, 42}, {42, 26}, {26, 27}, {27, 26}, {27, 43}, {43, 27}, - {27, 28}, {28, 27}, {28, 44}, {44, 28}, {28, 29}, {29, 28}, - {29, 45}, {45, 29}, {29, 30}, {30, 29}, {30, 46}, {46, 30}, - {30, 31}, {31, 30}, {31, 47}, {47, 31}, {32, 48}, {48, 32}, - {32, 33}, {33, 32}, {33, 49}, {49, 33}, {33, 34}, {34, 33}, - {34, 50}, {50, 34}, {34, 35}, {35, 34}, {35, 51}, {51, 35}, - {35, 36}, {36, 35}, {36, 52}, {52, 36}, {36, 37}, {37, 36}, - {37, 53}, {53, 37}, {37, 38}, {38, 37}, {38, 54}, {54, 38}, - {38, 39}, {39, 38}, {39, 55}, {55, 39}, {39, 40}, {40, 39}, - {40, 56}, {56, 40}, {40, 41}, {41, 40}, {41, 57}, {57, 41}, - {41, 42}, {42, 41}, {42, 58}, {58, 42}, {42, 43}, {43, 42}, - {43, 59}, {59, 43}, {43, 44}, {44, 43}, {44, 60}, {60, 44}, - {44, 45}, {45, 44}, {45, 61}, {61, 45}, {45, 46}, {46, 45}, - {46, 62}, {62, 46}, {46, 47}, {47, 46}, {47, 63}, {63, 47}, - {48, 64}, {64, 48}, {48, 49}, {49, 48}, {49, 65}, {65, 49}, - {49, 50}, {50, 49}, {50, 66}, {66, 50}, {50, 51}, {51, 50}, - {51, 67}, {67, 51}, {51, 52}, {52, 51}, {52, 68}, {68, 52}, - {52, 53}, {53, 52}, {53, 69}, {69, 53}, {53, 54}, {54, 53}, - {54, 70}, {70, 54}, {54, 55}, {55, 54}, {55, 71}, {71, 55}, - {55, 56}, {56, 55}, {56, 72}, {72, 56}, {56, 57}, {57, 56}, - {57, 73}, {73, 57}, {57, 58}, {58, 57}, {58, 74}, {74, 58}, - {58, 59}, {59, 58}, {59, 75}, {75, 59}, {59, 60}, {60, 59}, - {60, 76}, {76, 60}, {60, 61}, {61, 60}, {61, 77}, {77, 61}, - {61, 62}, {62, 61}, {62, 78}, {78, 62}, {62, 63}, {63, 62}, - {63, 79}, {79, 63}, {64, 80}, {80, 64}, {64, 65}, {65, 64}, - {65, 81}, {81, 65}, {65, 66}, {66, 65}, {66, 82}, {82, 66}, - {66, 67}, {67, 66}, {67, 83}, {83, 67}, {67, 68}, {68, 67}, - {68, 84}, {84, 68}, {68, 69}, {69, 68}, {69, 85}, {85, 69}, - {69, 70}, {70, 69}, {70, 86}, {86, 70}, {70, 71}, {71, 70}, - {71, 87}, {87, 71}, {71, 72}, {72, 71}, {72, 88}, {88, 72}, - {72, 73}, {73, 72}, {73, 89}, {89, 73}, {73, 74}, {74, 73}, - {74, 90}, {90, 74}, {74, 75}, {75, 74}, {75, 91}, {91, 75}, - {75, 76}, {76, 75}, {76, 92}, {92, 76}, {76, 77}, {77, 76}, - {77, 93}, {93, 77}, {77, 78}, {78, 77}, {78, 94}, {94, 78}, - {78, 79}, {79, 78}, {79, 95}, {95, 79}, {80, 96}, {96, 80}, - {80, 81}, {81, 80}, {81, 97}, {97, 81}, {81, 82}, {82, 81}, - {82, 98}, {98, 82}, {82, 83}, {83, 82}, {83, 99}, {99, 83}, - {83, 84}, {84, 83}, {84, 100}, {100, 84}, {84, 85}, {85, 84}, - {85, 101}, {101, 85}, {85, 86}, {86, 85}, {86, 102}, {102, 86}, - {86, 87}, {87, 86}, {87, 103}, {103, 87}, {87, 88}, {88, 87}, - {88, 104}, {104, 88}, {88, 89}, {89, 88}, {89, 105}, {105, 89}, - {89, 90}, {90, 89}, {90, 106}, {106, 90}, {90, 91}, {91, 90}, - {91, 107}, {107, 91}, {91, 92}, {92, 91}, {92, 108}, {108, 92}, - {92, 93}, {93, 92}, {93, 109}, {109, 93}, {93, 94}, {94, 93}, - {94, 110}, {110, 94}, {94, 95}, {95, 94}, {95, 111}, {111, 95}, - {96, 112}, {112, 96}, {96, 97}, {97, 96}, {97, 113}, {113, 97}, - {97, 98}, {98, 97}, {98, 114}, {114, 98}, {98, 99}, {99, 98}, - {99, 115}, {115, 99}, {99, 100}, {100, 99}, {100, 116}, {116, 100}, - {100, 101}, {101, 100}, {101, 117}, {117, 101}, {101, 102}, {102, 101}, - {102, 118}, {118, 102}, {102, 103}, {103, 102}, {103, 119}, {119, 103}, - {103, 104}, {104, 103}, {104, 120}, {120, 104}, {104, 105}, {105, 104}, - {105, 121}, {121, 105}, {105, 106}, {106, 105}, {106, 122}, {122, 106}, - {106, 107}, {107, 106}, {107, 123}, {123, 107}, {107, 108}, {108, 107}, - {108, 124}, {124, 108}, {108, 109}, {109, 108}, {109, 125}, {125, 109}, - {109, 110}, {110, 109}, {110, 126}, {126, 110}, {110, 111}, {111, 110}, - {111, 127}, {127, 111}, {112, 128}, {128, 112}, {112, 113}, {113, 112}, - {113, 129}, {129, 113}, {113, 114}, {114, 113}, {114, 130}, {130, 114}, - {114, 115}, {115, 114}, {115, 131}, {131, 115}, {115, 116}, {116, 115}, - {116, 132}, {132, 116}, {116, 117}, {117, 116}, {117, 133}, {133, 117}, - {117, 118}, {118, 117}, {118, 134}, {134, 118}, {118, 119}, {119, 118}, - {119, 135}, {135, 119}, {119, 120}, {120, 119}, {120, 136}, {136, 120}, - {120, 121}, {121, 120}, {121, 137}, {137, 121}, {121, 122}, {122, 121}, - {122, 138}, {138, 122}, {122, 123}, {123, 122}, {123, 139}, {139, 123}, - {123, 124}, {124, 123}, {124, 140}, {140, 124}, {124, 125}, {125, 124}, - {125, 141}, {141, 125}, {125, 126}, {126, 125}, {126, 142}, {142, 126}, - {126, 127}, {127, 126}, {127, 143}, {143, 127}, {128, 144}, {144, 128}, - {128, 129}, {129, 128}, {129, 145}, {145, 129}, {129, 130}, {130, 129}, - {130, 146}, {146, 130}, {130, 131}, {131, 130}, {131, 147}, {147, 131}, - {131, 132}, {132, 131}, {132, 148}, {148, 132}, {132, 133}, {133, 132}, - {133, 149}, {149, 133}, {133, 134}, {134, 133}, {134, 150}, {150, 134}, - {134, 135}, {135, 134}, {135, 151}, {151, 135}, {135, 136}, {136, 135}, - {136, 152}, {152, 136}, {136, 137}, {137, 136}, {137, 153}, {153, 137}, - {137, 138}, {138, 137}, {138, 154}, {154, 138}, {138, 139}, {139, 138}, - {139, 155}, {155, 139}, {139, 140}, {140, 139}, {140, 156}, {156, 140}, - {140, 141}, {141, 140}, {141, 157}, {157, 141}, {141, 142}, {142, 141}, - {142, 158}, {158, 142}, {142, 143}, {143, 142}, {143, 159}, {159, 143}, - {144, 160}, {160, 144}, {144, 145}, {145, 144}, {145, 161}, {161, 145}, - {145, 146}, {146, 145}, {146, 162}, {162, 146}, {146, 147}, {147, 146}, - {147, 163}, {163, 147}, {147, 148}, {148, 147}, {148, 164}, {164, 148}, - {148, 149}, {149, 148}, {149, 165}, {165, 149}, {149, 150}, {150, 149}, - {150, 166}, {166, 150}, {150, 151}, {151, 150}, {151, 167}, {167, 151}, - {151, 152}, {152, 151}, {152, 168}, {168, 152}, {152, 153}, {153, 152}, - {153, 169}, {169, 153}, {153, 154}, {154, 153}, {154, 170}, {170, 154}, - {154, 155}, {155, 154}, {155, 171}, {171, 155}, {155, 156}, {156, 155}, - {156, 172}, {172, 156}, {156, 157}, {157, 156}, {157, 173}, {173, 157}, - {157, 158}, {158, 157}, {158, 174}, {174, 158}, {158, 159}, {159, 158}, - {159, 175}, {175, 159}, {160, 176}, {176, 160}, {160, 161}, {161, 160}, - {161, 177}, {177, 161}, {161, 162}, {162, 161}, {162, 178}, {178, 162}, - {162, 163}, {163, 162}, {163, 179}, {179, 163}, {163, 164}, {164, 163}, - {164, 180}, {180, 164}, {164, 165}, {165, 164}, {165, 181}, {181, 165}, - {165, 166}, {166, 165}, {166, 182}, {182, 166}, {166, 167}, {167, 166}, - {167, 183}, {183, 167}, {167, 168}, {168, 167}, {168, 184}, {184, 168}, - {168, 169}, {169, 168}, {169, 185}, {185, 169}, {169, 170}, {170, 169}, - {170, 186}, {186, 170}, {170, 171}, {171, 170}, {171, 187}, {187, 171}, - {171, 172}, {172, 171}, {172, 188}, {188, 172}, {172, 173}, {173, 172}, - {173, 189}, {189, 173}, {173, 174}, {174, 173}, {174, 190}, {190, 174}, - {174, 175}, {175, 174}, {175, 191}, {191, 175}, {176, 192}, {192, 176}, - {176, 177}, {177, 176}, {177, 193}, {193, 177}, {177, 178}, {178, 177}, - {178, 194}, {194, 178}, {178, 179}, {179, 178}, {179, 195}, {195, 179}, - {179, 180}, {180, 179}, {180, 196}, {196, 180}, {180, 181}, {181, 180}, - {181, 197}, {197, 181}, {181, 182}, {182, 181}, {182, 198}, {198, 182}, - {182, 183}, {183, 182}, {183, 199}, {199, 183}, {183, 184}, {184, 183}, - {184, 200}, {200, 184}, {184, 185}, {185, 184}, {185, 201}, {201, 185}, - {185, 186}, {186, 185}, {186, 202}, {202, 186}, {186, 187}, {187, 186}, - {187, 203}, {203, 187}, {187, 188}, {188, 187}, {188, 204}, {204, 188}, - {188, 189}, {189, 188}, {189, 205}, {205, 189}, {189, 190}, {190, 189}, - {190, 206}, {206, 190}, {190, 191}, {191, 190}, {191, 207}, {207, 191}, - {192, 208}, {208, 192}, {192, 193}, {193, 192}, {193, 209}, {209, 193}, - {193, 194}, {194, 193}, {194, 210}, {210, 194}, {194, 195}, {195, 194}, - {195, 211}, {211, 195}, {195, 196}, {196, 195}, {196, 212}, {212, 196}, - {196, 197}, {197, 196}, {197, 213}, {213, 197}, {197, 198}, {198, 197}, - {198, 214}, {214, 198}, {198, 199}, {199, 198}, {199, 215}, {215, 199}, - {199, 200}, {200, 199}, {200, 216}, {216, 200}, {200, 201}, {201, 200}, - {201, 217}, {217, 201}, {201, 202}, {202, 201}, {202, 218}, {218, 202}, - {202, 203}, {203, 202}, {203, 219}, {219, 203}, {203, 204}, {204, 203}, - {204, 220}, {220, 204}, {204, 205}, {205, 204}, {205, 221}, {221, 205}, - {205, 206}, {206, 205}, {206, 222}, {222, 206}, {206, 207}, {207, 206}, - {207, 223}, {223, 207}, {208, 224}, {224, 208}, {208, 209}, {209, 208}, - {209, 225}, {225, 209}, {209, 210}, {210, 209}, {210, 226}, {226, 210}, - {210, 211}, {211, 210}, {211, 227}, {227, 211}, {211, 212}, {212, 211}, - {212, 228}, {228, 212}, {212, 213}, {213, 212}, {213, 229}, {229, 213}, - {213, 214}, {214, 213}, {214, 230}, {230, 214}, {214, 215}, {215, 214}, - {215, 231}, {231, 215}, {215, 216}, {216, 215}, {216, 232}, {232, 216}, - {216, 217}, {217, 216}, {217, 233}, {233, 217}, {217, 218}, {218, 217}, - {218, 234}, {234, 218}, {218, 219}, {219, 218}, {219, 235}, {235, 219}, - {219, 220}, {220, 219}, {220, 236}, {236, 220}, {220, 221}, {221, 220}, - {221, 237}, {237, 221}, {221, 222}, {222, 221}, {222, 238}, {238, 222}, - {222, 223}, {223, 222}, {223, 239}, {239, 223}, {224, 240}, {240, 224}, - {224, 225}, {225, 224}, {225, 241}, {241, 225}, {225, 226}, {226, 225}, - {226, 242}, {242, 226}, {226, 227}, {227, 226}, {227, 243}, {243, 227}, - {227, 228}, {228, 227}, {228, 244}, {244, 228}, {228, 229}, {229, 228}, - {229, 245}, {245, 229}, {229, 230}, {230, 229}, {230, 246}, {246, 230}, - {230, 231}, {231, 230}, {231, 247}, {247, 231}, {231, 232}, {232, 231}, - {232, 248}, {248, 232}, {232, 233}, {233, 232}, {233, 249}, {249, 233}, - {233, 234}, {234, 233}, {234, 250}, {250, 234}, {234, 235}, {235, 234}, - {235, 251}, {251, 235}, {235, 236}, {236, 235}, {236, 252}, {252, 236}, - {236, 237}, {237, 236}, {237, 253}, {253, 237}, {237, 238}, {238, 237}, - {238, 254}, {254, 238}, {238, 239}, {239, 238}, {239, 255}, {255, 239}, - {240, 241}, {241, 240}, {241, 242}, {242, 241}, {242, 243}, {243, 242}, - {243, 244}, {244, 243}, {244, 245}, {245, 244}, {245, 246}, {246, 245}, - {246, 247}, {247, 246}, {247, 248}, {248, 247}, {248, 249}, {249, 248}, - {249, 250}, {250, 249}, {250, 251}, {251, 250}, {251, 252}, {252, 251}, - {252, 253}, {253, 252}, {253, 254}, {254, 253}, {254, 255}, {255, 254}}; - - return std::make_unique("MQTGrid16", 256, COUPLING); - } - return nullptr; } }; // namespace mqt::ir::opt From 6438f073db0098031166b7aa897a3a97ead4dc3c Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 27 Nov 2025 14:08:45 +0100 Subject: [PATCH 32/56] Fix heuristic bug --- .../Dialect/MQTOpt/Transforms/Transpilation/Router.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h index f9dceecbfb..916f187220 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h @@ -134,8 +134,7 @@ class AStarHeuristicRouter { for (const auto [i, layer] : llvm::enumerate(window)) { for (const auto [prog0, prog1] : layer) { const auto [hw0, hw1] = layout.getHardwareIndices(prog0, prog1); - const std::size_t dist = arch.distanceBetween(hw0, hw1); - const std::size_t nswaps = dist < 2 ? 0 : dist - 2; + const std::size_t nswaps = arch.distanceBetween(hw0, hw1) - 1; nn += weights.lambdas[i] * static_cast(nswaps); } } @@ -181,6 +180,13 @@ class AStarHeuristicRouter { void expand(MinQueue& frontier, const Node& parent, ArrayRef window, const Architecture& arch) const { llvm::SmallDenseSet expansionSet{}; + + /// Currently: Don't revert last SWAP. + /// TODO: Idea? Don't revert "front" (independent) SWAPs? + if (!parent.sequence.empty()) { + expansionSet.insert(parent.sequence.back()); + } + for (const QubitIndexPair gate : window.front()) { for (const auto prog : {gate.first, gate.second}) { const auto hw0 = parent.layout.getHardwareIndex(prog); From 48ff8f4eb2294dd00d8886c5f4184c99bd8938d8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 06:18:20 +0000 Subject: [PATCH 33/56] =?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 --- .../MQTOpt/Transforms/Transpilation/LayeredUnit.h | 3 ++- .../Dialect/MQTOpt/Transforms/Transpilation/Router.h | 11 +++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.h index 05ca28a306..8f022fb2ae 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.h @@ -62,7 +62,8 @@ class SlidingWindow { WindowView w; const auto sz = window->opLayers->size(); const auto len = std::min(1 + window->nlookahead, sz - pos); - w.gateLayers = mlir::ArrayRef(*window->gateLayers).slice(pos, len); + w.gateLayers = + mlir::ArrayRef(*window->gateLayers).slice(pos, len); w.opLayer = &(*window->opLayers)[pos]; if (pos + 1 < window->gateLayers->size()) { w.nextAnchor = (*window->opLayers)[pos + 1].anchor; diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h index 67b3885b5a..0e76210c72 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h @@ -76,8 +76,9 @@ class AStarHeuristicRouter { * @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, QubitIndexPair swap, mlir::ArrayRef window, - const Architecture& arch, const HeuristicWeights& weights) + Node(const Node& parent, QubitIndexPair swap, + mlir::ArrayRef window, const Architecture& arch, + const HeuristicWeights& weights) : sequence(parent.sequence), layout(parent.layout), f(0) { /// Apply node-specific swap to given layout. layout.swap(layout.getProgramIndex(swap.first), @@ -128,7 +129,8 @@ class AStarHeuristicRouter { * its hardware qubits. Intuitively, this is the number of SWAPs that a * naive router would insert to route the layers. */ - [[nodiscard]] float h(mlir::ArrayRef window, const Architecture& arch, + [[nodiscard]] float h(mlir::ArrayRef window, + const Architecture& arch, const HeuristicWeights& weights) const { float nn{0}; for (const auto [i, layer] : llvm::enumerate(window)) { @@ -178,7 +180,8 @@ class AStarHeuristicRouter { private: /// @brief Expand frontier with all neighbouring SWAPs in the current front. void expand(MinQueue& frontier, const Node& parent, - mlir::ArrayRef window, const Architecture& arch) const { + mlir::ArrayRef window, + const Architecture& arch) const { llvm::SmallDenseSet expansionSet{}; /// Currently: Don't revert last SWAP. From 33448a109b5d28c9c2c9557f724e3d62b4d3e4bb Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 1 Dec 2025 07:40:24 +0100 Subject: [PATCH 34/56] Reduce code duplication --- .../MQTOpt/Transforms/Transpilation/Layout.h | 45 +++++++++++++++++++ .../Transforms/Transpilation/LayeredUnit.cpp | 35 ++++----------- .../Transpilation/SequentialUnit.cpp | 27 ++--------- 3 files changed, 58 insertions(+), 49 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h index cc67c58bd0..f82e4798b4 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h @@ -11,6 +11,7 @@ #pragma once #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" +#include "mlir/Dialect/SCF/IR/SCF.h" #include #include @@ -304,6 +305,50 @@ inline void remap(MeasureOp op, Layout& layout) { layout.remapQubitValue(op.getInQubit(), op.getOutQubit()); } +/** + * @brief Remap input qubits to in-loop-body values (iteration args). + * + * @param op The 'scf.for' op. + * @param layout The current layout. + */ +inline void remapToLoopBody(mlir::scf::ForOp op, Layout& layout) { + const auto nqubits = layout.getNumQubits(); + const auto args = op.getInitArgs().take_front(nqubits); + const auto iterArgs = op.getRegionIterArgs().take_front(nqubits); + for (const auto [arg, iter] : llvm::zip(args, iterArgs)) { + layout.remapQubitValue(arg, iter); + } +} + +/** + * @brief Remap input qubits to out-of-loop values (results). + * + * @param op The 'scf.for' op. + * @param layout The current layout. + */ +inline void remapToLoopResults(mlir::scf::ForOp op, Layout& layout) { + const auto nqubits = layout.getNumQubits(); + const auto args = op.getInitArgs().take_front(nqubits); + const auto results = op.getResults().take_front(nqubits); + for (const auto [arg, iter] : llvm::zip(args, results)) { + layout.remapQubitValue(arg, iter); + } +} + +/** + * @brief Remap current qubit values to if results. + * + * @param op The 'scf.if' op. + * @param layout The current layout. + */ +inline void remapIfResults(mlir::scf::IfOp op, Layout& layout) { + const auto nqubits = layout.getNumQubits(); + const auto results = op->getResults().take_front(nqubits); + for (const auto [in, out] : llvm::zip(layout.getHardwareQubits(), results)) { + layout.remapQubitValue(in, out); + } +} + } // namespace mqt::ir::opt namespace llvm { diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp index 98bcaaab78..fcd6a4ed3b 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -87,12 +88,12 @@ mlir::SmallVector skipTwoQubitBlock(mlir::ArrayRef wires, auto [it1, index1] = wires[1]; while (it0 != std::default_sentinel && it1 != std::default_sentinel) { mlir::Operation* op0 = *it0; - if (!isa(op0) || isa(op0)) { + if (!mlir::isa(op0) || mlir::isa(op0)) { break; } mlir::Operation* op1 = *it1; - if (!isa(op1) || isa(op1)) { + if (!mlir::isa(op1) || mlir::isa(op1)) { break; } @@ -182,7 +183,8 @@ LayeredUnit::LayeredUnit(Layout layout, mlir::Region* region, bool restore) sync.visit(op, Wire(++it, index))) { opLayer.addOp(op); - if (!isa(op)) { // Is ready two-qubit unitary? + // Is ready two-qubit unitary? + if (!mlir::isa(op)) { gateLayer.emplace_back((*iterators)[0].index, (*iterators)[1].index); next.append(skipTwoQubitBlock(*iterators, opLayer)); @@ -277,35 +279,16 @@ mlir::SmallVector LayeredUnit::next() { mlir::SmallVector units; mlir::TypeSwitch(divider_) .Case([&](mlir::scf::ForOp op) { - /// Copy layout. - Layout forLayout(layout_); - - /// Forward out-of-loop and in-loop values. - const auto nqubits = layout_.getNumQubits(); - const auto initArgs = op.getInitArgs().take_front(nqubits); - const auto results = op.getResults().take_front(nqubits); - const auto iterArgs = op.getRegionIterArgs().take_front(nqubits); - for (const auto [arg, res, iter] : - llvm::zip(initArgs, results, iterArgs)) { - layout_.remapQubitValue(arg, res); - forLayout.remapQubitValue(arg, iter); - } - + Layout forLayout(layout_); // Copy layout. + remapToLoopBody(op, forLayout); + remapToLoopResults(op, layout_); units.emplace_back(std::move(layout_), region_, restore_); units.emplace_back(std::move(forLayout), &op.getRegion(), true); }) .Case([&](mlir::scf::IfOp op) { units.emplace_back(layout_, &op.getThenRegion(), true); units.emplace_back(layout_, &op.getElseRegion(), true); - - /// Forward results. - const auto results = - op->getResults().take_front(layout_.getNumQubits()); - for (const auto [in, out] : - llvm::zip(layout_.getHardwareQubits(), results)) { - layout_.remapQubitValue(in, out); - } - + remapIfResults(op, layout_); units.emplace_back(layout_, region_, restore_); }) .Default( diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp index 433200ee98..9abac1ae81 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp @@ -59,20 +59,9 @@ SequentialUnit::SequentialUnit(Layout layout, mlir::Region* region, mlir::SmallVector units; mlir::TypeSwitch(divider_) .Case([&](mlir::scf::ForOp op) { - /// Copy layout. - Layout forLayout(layout_); - - /// Forward out-of-loop and in-loop values. - const auto nqubits = layout_.getNumQubits(); - const auto initArgs = op.getInitArgs().take_front(nqubits); - const auto results = op.getResults().take_front(nqubits); - const auto iterArgs = op.getRegionIterArgs().take_front(nqubits); - for (const auto [arg, res, iter] : - llvm::zip(initArgs, results, iterArgs)) { - layout_.remapQubitValue(arg, res); - forLayout.remapQubitValue(arg, iter); - } - + Layout forLayout(layout_); // Copy layout. + remapToLoopBody(op, forLayout); + remapToLoopResults(op, layout_); units.emplace_back(std::move(layout_), region_, std::next(end_), restore_); units.emplace_back(std::move(forLayout), &op.getRegion(), true); @@ -80,15 +69,7 @@ SequentialUnit::SequentialUnit(Layout layout, mlir::Region* region, .Case([&](mlir::scf::IfOp op) { units.emplace_back(layout_, &op.getThenRegion(), true); units.emplace_back(layout_, &op.getElseRegion(), true); - - /// Forward results. - const auto results = - op->getResults().take_front(layout_.getNumQubits()); - for (const auto [in, out] : - llvm::zip(layout_.getHardwareQubits(), results)) { - layout_.remapQubitValue(in, out); - } - + remapIfResults(op, layout_); units.emplace_back(std::move(layout_), region_, std::next(end_), restore_); }) From 3404e7ad003410b6513f6f1695f6257796c8393a Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 1 Dec 2025 08:22:06 +0100 Subject: [PATCH 35/56] Remove stack from verification pass --- .../Transpilation/SequentialUnit.cpp | 1 - .../Transpilation/sc/NaiveRoutingPass.cpp | 4 +- .../sc/RoutingVerificationPass.cpp | 329 ++++++------------ .../Transpilation/routing-verification.mlir | 15 - 4 files changed, 116 insertions(+), 233 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp index 9abac1ae81..88ed839d4f 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp @@ -24,7 +24,6 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h" #include -#include #include #include #include diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp index 63f211fa53..9ec4236ed5 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp @@ -122,15 +122,13 @@ struct NaiveRoutingPassSC final if (failed(route())) { signalPassFailure(); return; - }; + } } private: LogicalResult route() { ModuleOp module(getOperation()); PatternRewriter rewriter(module->getContext()); - /// AStarHeuristicRouter router(HeuristicWeights(alpha, lambda, - /// nlookahead)); std::unique_ptr arch(getArchitecture(archName)); if (!arch) { diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp index d7d405b9a0..13556155ba 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp @@ -13,12 +13,14 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Stack.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.h" #include #include #include #include +#include +#include #include #include #include @@ -31,7 +33,6 @@ #include #include #include -#include #define DEBUG_TYPE "routing-verification-sc" @@ -44,178 +45,18 @@ namespace { using namespace mlir; /** - * @brief The necessary datastructures for verification. - */ -struct VerificationContext { - explicit VerificationContext(std::unique_ptr arch) - : arch(std::move(arch)) {} - - std::unique_ptr arch; - LayoutStack stack{}; -}; - -/** - * @brief Push new state onto the stack. Skip non entry-point functions. - */ -WalkResult handleFunc(func::FuncOp op, VerificationContext& ctx) { - if (!isEntryPoint(op)) { - return WalkResult::skip(); - } - - /// Function body state. - ctx.stack.emplace(ctx.arch->nqubits()); - - return WalkResult::advance(); -} - -/** - * @brief Defines the end of a region: Pop the top of the stack. - */ -WalkResult handleReturn(VerificationContext& ctx) { - ctx.stack.pop(); - return WalkResult::advance(); -} - -/** - * @brief Prepares state for nested regions: Pushes a copy of the state on - * the stack. Forwards all out-of-loop and in-loop SSA values for their - * respective map in the stack. - */ -WalkResult handleFor(scf::ForOp op, VerificationContext& ctx) { - /// Loop body state. - ctx.stack.duplicateTop(); - - /// Forward out-of-loop and in-loop values. - const auto initArgs = op.getInitArgs().take_front(ctx.arch->nqubits()); - const auto results = op.getResults().take_front(ctx.arch->nqubits()); - const auto iterArgs = op.getRegionIterArgs().take_front(ctx.arch->nqubits()); - for (const auto [arg, res, iter] : llvm::zip(initArgs, results, iterArgs)) { - ctx.stack.getItemAtDepth(FOR_PARENT_DEPTH).remapQubitValue(arg, res); - ctx.stack.top().remapQubitValue(arg, iter); - } - - return WalkResult::advance(); -} - -/** - * @brief Prepares state for nested regions: Pushes two copies of the state on - * the stack. Forwards the results in the parent state. - */ -WalkResult handleIf(scf::IfOp op, VerificationContext& ctx) { - /// Prepare stack. - ctx.stack.duplicateTop(); /// Else - ctx.stack.duplicateTop(); /// Then. - - /// Forward results for all hardware qubits. - const auto results = op->getResults().take_front(ctx.arch->nqubits()); - Layout& stateBeforeIf = ctx.stack.getItemAtDepth(IF_PARENT_DEPTH); - for (const auto [hardwareIdx, res] : llvm::enumerate(results)) { - const Value q = stateBeforeIf.lookupHardwareValue(hardwareIdx); - stateBeforeIf.remapQubitValue(q, res); - } - - return WalkResult::advance(); -} - -/** - * @brief Defines the end of a nested region: Pop the top of the stack. - */ -WalkResult handleYield(scf::YieldOp op, VerificationContext& ctx) { - if (isa(op->getParentOp()) || isa(op->getParentOp())) { - assert(ctx.stack.size() >= 2 && "expected at least two elements on stack."); - - if (!llvm::equal(ctx.stack.top().getCurrentLayout(), - ctx.stack.getItemAtDepth(1).getCurrentLayout())) { - return op.emitOpError() << "layouts must match after restoration"; - } - - ctx.stack.pop(); - } - return WalkResult::advance(); -} - -/** - * @brief Add hardware qubit with respective program & hardware index to layout. - */ -WalkResult handleQubit(QubitOp op, VerificationContext& ctx) { - const std::size_t index = op.getIndex(); - ctx.stack.top().add(index, index, op.getQubit()); - return WalkResult::advance(); -} - -/** - * @brief Verifies if the unitary acts on either zero, one, or two qubits: - * - Advances for zero qubit unitaries (Nothing to do) - * - Forwards SSA values for one qubit. - * - Verifies executability for two-qubit gates for the given architecture and - * forwards SSA values. - */ -WalkResult handleUnitary(UnitaryInterface op, VerificationContext& ctx) { - const std::vector inQubits = op.getAllInQubits(); - const std::vector outQubits = op.getAllOutQubits(); - const std::size_t nacts = inQubits.size(); - - if (nacts == 0) { - return WalkResult::advance(); - } - - if (isa(op)) { - for (const auto [in, out] : llvm::zip(inQubits, outQubits)) { - ctx.stack.top().remapQubitValue(in, out); - } - return WalkResult::advance(); - } - - if (nacts > 2) { - return op->emitOpError() << "acts on more than two qubits"; - } - - const Value in0 = inQubits[0]; - const Value out0 = outQubits[0]; - - Layout& state = ctx.stack.top(); - - if (nacts == 1) { - state.remapQubitValue(in0, out0); - return WalkResult::advance(); - } - - const Value in1 = inQubits[1]; - const Value out1 = outQubits[1]; - - const auto idx0 = state.lookupHardwareIndex(in0); - const auto idx1 = state.lookupHardwareIndex(in1); - - if (!ctx.arch->areAdjacent(idx0, idx1)) { - return op->emitOpError() << "(" << idx0 << "," << idx1 << ")" - << " is not executable on target architecture '" - << ctx.arch->name() << "'"; - } - - if (isa(op)) { - state.swap(in0, in1); - } - - state.remapQubitValue(in0, out0); - state.remapQubitValue(in1, out1); - - return WalkResult::advance(); -} - -/** - * @brief Update layout. - */ -WalkResult handleReset(ResetOp op, VerificationContext& ctx) { - ctx.stack.top().remapQubitValue(op.getInQubit(), op.getOutQubit()); - return WalkResult::advance(); -} - -/** - * @brief Update layout. + * @brief Given a layout, validate if the two-qubit unitary op is executable on + * the targeted architecture. + * + * @param op The two-qubit unitary. + * @param layout The current layout. + * @param arch The targeted architecture. */ -WalkResult handleMeasure(MeasureOp op, VerificationContext& ctx) { - ctx.stack.top().remapQubitValue(op.getInQubit(), op.getOutQubit()); - return WalkResult::advance(); +[[nodiscard]] bool isExecutable(UnitaryInterface op, const Layout& layout, + const Architecture& arch) { + const auto ins = getIns(op); + return arch.areAdjacent(layout.lookupHardwareIndex(ins.first), + layout.lookupHardwareIndex(ins.second)); } /** @@ -228,59 +69,119 @@ struct RoutingVerificationPassSC final RoutingVerificationPassSC>::RoutingVerificationSCPassBase; void runOnOperation() override { - if (preflight().failed()) { + if (failed(preflight())) { signalPassFailure(); return; } - auto arch = getArchitecture(archName); - if (!arch) { - emitError(UnknownLoc::get(&getContext())) - << "unsupported architecture '" << archName << "'"; + if (failed(verify())) { signalPassFailure(); return; } + } - VerificationContext ctx(std::move(arch)); - const auto res = - getOperation()->walk([&](Operation* op) { - return TypeSwitch(op) - /// built-in Dialect - .Case( - [&](ModuleOp /* op */) { return WalkResult::advance(); }) - /// func Dialect - .Case( - [&](func::FuncOp op) { return handleFunc(op, ctx); }) - .Case( - [&](func::ReturnOp /* op */) { return handleReturn(ctx); }) - /// scf Dialect - .Case( - [&](scf::ForOp op) { return handleFor(op, ctx); }) - .Case([&](scf::IfOp op) { return handleIf(op, ctx); }) - .Case( - [&](scf::YieldOp op) { return handleYield(op, ctx); }) - /// mqtopt Dialect - .Case([&](QubitOp op) { return handleQubit(op, ctx); }) - .Case([&](auto op) { - return WalkResult( - op->emitOpError("not allowed for transpiled program")); - }) - .Case([&](ResetOp op) { return handleReset(op, ctx); }) - .Case( - [&](MeasureOp op) { return handleMeasure(op, ctx); }) - .Case([&](UnitaryInterface unitary) { - return handleUnitary(unitary, ctx); - }) - /// Skip the rest. - .Default([](auto) { return WalkResult::skip(); }); - }); +private: + LogicalResult verify() { + ModuleOp module(getOperation()); + PatternRewriter rewriter(module->getContext()); + std::unique_ptr arch(getArchitecture(archName)); - if (res.wasInterrupted()) { - signalPassFailure(); + if (!arch) { + const Location loc = UnknownLoc::get(&getContext()); + emitError(loc) << "unsupported architecture '" << archName << "'"; + return failure(); } + + for (auto func : module.getOps()) { + LLVM_DEBUG(llvm::dbgs() << "handleFunc: " << func.getSymName() << '\n'); + + if (!isEntryPoint(func)) { + LLVM_DEBUG(llvm::dbgs() << "\tskip non entry\n"); + return success(); // Ignore non entry_point functions for now. + } + + /// Iteratively process each unit in the function. + std::queue units; + units.emplace( + SequentialUnit::fromEntryPointFunction(func, arch->nqubits())); + for (; !units.empty(); units.pop()) { + SequentialUnit& unit = units.front(); + + Layout unmodified(unit.layout()); + SmallVector history; + for (Operation& curr : unit) { + rewriter.setInsertionPoint(&curr); + + const auto res = + TypeSwitch(&curr) + .Case([&](UnitaryInterface op) + -> LogicalResult { + if (isTwoQubitGate(op)) { + /// Verify that the two-qubit gate is executable. + if (!isExecutable(op, unit.layout(), *arch)) { + const auto ins = getIns(op); + const auto hw0 = + unit.layout().lookupHardwareIndex(ins.first); + const auto hw1 = + unit.layout().lookupHardwareIndex(ins.second); + + return op->emitOpError() + << "(" << hw0 << "," << hw1 << ")" + << " is not executable on target architecture '" + << arch->name() << "'"; + } + } + + if (isa(op)) { + const auto ins = getIns(op); + unit.layout().swap(ins.first, ins.second); + history.push_back( + {unit.layout().lookupHardwareIndex(ins.first), + unit.layout().lookupHardwareIndex(ins.second)}); + } + + remap(op, unit.layout()); + return success(); + }) + .Case([&](ResetOp op) { + remap(op, unit.layout()); + return success(); + }) + .Case([&](MeasureOp op) { + remap(op, unit.layout()); + return success(); + }) + .Case([&](scf::YieldOp op) -> LogicalResult { + if (!unit.restore()) { + return success(); + } + + /// Verify that the layouts match at the end. + const auto mappingBefore = unmodified.getCurrentLayout(); + const auto mappingNow = unit.layout().getCurrentLayout(); + if (llvm::equal(mappingBefore, mappingNow)) { + return success(); + } + + return op.emitOpError() + << "layouts must match after restoration"; + }) + .Default([](auto) { return success(); }); + + if (failed(res)) { + return res; + } + } + + for (const auto& next : unit.next()) { + units.emplace(next); + } + } + } + + return success(); } -private: LogicalResult preflight() { if (archName.empty()) { return emitError(UnknownLoc::get(&getContext()), diff --git a/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/routing-verification.mlir b/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/routing-verification.mlir index 2f37073eab..aa0a89a5f2 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/routing-verification.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/Transpilation/routing-verification.mlir @@ -8,21 +8,6 @@ // RUN: quantum-opt %s -split-input-file --verify-routing-sc="arch=MQTTest" -verify-diagnostics -module { - func.func @tooManyQubits() attributes {passthrough = ["entry_point"]} { - %q0_0 = mqtopt.qubit 0 - %q1_0 = mqtopt.qubit 1 - %q2_0 = mqtopt.qubit 2 - - // expected-error@+1 {{'mqtopt.x' op acts on more than two qubits}} - %q0_1, %q1_1, %q2_1 = mqtopt.x() %q0_0 ctrl %q1_0, %q2_0 : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit - - return - } -} - -// ----- - module { func.func @gateNotExecutable() attributes {passthrough = ["entry_point"]} { %q0_0 = mqtopt.qubit 0 From c3176045323e7d995954810b235be9ef1548adf3 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 1 Dec 2025 08:26:57 +0100 Subject: [PATCH 36/56] Add std::queue header --- .../Transforms/Transpilation/sc/RoutingVerificationPass.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp index 13556155ba..a965b79b5e 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #define DEBUG_TYPE "routing-verification-sc" From fcfbff62e7f6424912ea025b228eb6d28cbb962c Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 1 Dec 2025 08:41:18 +0100 Subject: [PATCH 37/56] Remove header --- .../Transforms/Transpilation/sc/RoutingVerificationPass.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp index a965b79b5e..4cb9ed8ff9 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp @@ -16,7 +16,6 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.h" #include -#include #include #include #include From dcc49fb67d3596b99ccfa420096d4705abe86ceb Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 1 Dec 2025 09:01:39 +0100 Subject: [PATCH 38/56] Add bunny suggestions --- .../Dialect/MQTOpt/Transforms/Transpilation/Common.h | 1 + .../MQTOpt/Transforms/Transpilation/SequentialUnit.h | 2 ++ .../MQTOpt/Transforms/Transpilation/SequentialUnit.cpp | 10 ---------- .../Transforms/Transpilation/sc/AStarRoutingPass.cpp | 4 ++-- .../Transforms/Transpilation/sc/NaiveRoutingPass.cpp | 2 +- .../Transpilation/sc/RoutingVerificationPass.cpp | 2 +- 6 files changed, 7 insertions(+), 14 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h index f773d5570c..57258de754 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.h index 1dceb8420f..88bba711fa 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.h @@ -10,11 +10,13 @@ #pragma once +#include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h" #include #include +#include #include #include diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp index 88ed839d4f..d83c336d28 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp @@ -8,16 +8,6 @@ * Licensed under the MIT License */ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index 760c905787..9af1ecf9e0 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -107,7 +107,7 @@ struct AStarRoutingPassSC final if (failed(route())) { signalPassFailure(); return; - }; + } } private: @@ -133,7 +133,7 @@ struct AStarRoutingPassSC final if (!isEntryPoint(func)) { LLVM_DEBUG(llvm::dbgs() << "\tskip non entry\n"); - return success(); // Ignore non entry_point functions for now. + continue; } /// Iteratively process each unit in the function. diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp index 9ec4236ed5..e301b864f1 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp @@ -142,7 +142,7 @@ struct NaiveRoutingPassSC final if (!isEntryPoint(func)) { LLVM_DEBUG(llvm::dbgs() << "\tskip non entry\n"); - return success(); // Ignore non entry_point functions for now. + continue; } /// Iteratively process each unit in the function. diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp index 4cb9ed8ff9..e7638a8f7e 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp @@ -97,7 +97,7 @@ struct RoutingVerificationPassSC final if (!isEntryPoint(func)) { LLVM_DEBUG(llvm::dbgs() << "\tskip non entry\n"); - return success(); // Ignore non entry_point functions for now. + continue; } /// Iteratively process each unit in the function. From 6aa592743222f3e5fb66e2da134f93aa6a494592 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 1 Dec 2025 09:19:17 +0100 Subject: [PATCH 39/56] Add QubitIndex.h --- .../Transforms/Transpilation/Architecture.h | 4 +- .../MQTOpt/Transforms/Transpilation/Common.h | 39 +++++++++----- .../MQTOpt/Transforms/Transpilation/Layout.h | 5 +- .../Transforms/Transpilation/QubitIndex.h | 20 +++++++ .../Transforms/Transpilation/Common.cpp | 36 +++++++++++++ .../Transpilation/sc/AStarRoutingPass.cpp | 37 ------------- .../Transpilation/sc/NaiveRoutingPass.cpp | 52 ------------------- .../Transpilation/sc/PlacementPass.cpp | 10 ++++ .../sc/RoutingVerificationPass.cpp | 15 ------ 9 files changed, 97 insertions(+), 121 deletions(-) create mode 100644 mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/QubitIndex.h diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h index 597ce55fb8..412062aefb 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h @@ -10,14 +10,16 @@ #pragma once -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/QubitIndex.h" #include #include +#include #include #include #include #include +#include #include #include diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h index 57258de754..ac2afea6a3 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h @@ -11,6 +11,9 @@ #pragma once #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/QubitIndex.h" #include #include @@ -21,20 +24,6 @@ #include namespace mqt::ir::opt { -/** - * @brief 'For' pushes once onto the stack, hence the parent is at depth one. - */ -constexpr std::size_t FOR_PARENT_DEPTH = 1UL; - -/** - * @brief 'If' pushes twice onto the stack, hence the parent is at depth two. - */ -constexpr std::size_t IF_PARENT_DEPTH = 2UL; - -/** - * @brief Type alias for qubit indices. - */ -using QubitIndex = uint32_t; /** * @brief A pair of SSA Values. @@ -112,4 +101,26 @@ void replaceAllUsesInRegionAndChildrenExcept(mlir::Value oldValue, mlir::Region* region, mlir::Operation* exceptOp, mlir::PatternRewriter& rewriter); + +/** + * @brief Given a layout, validate if the two-qubit unitary op is executable on + * the targeted architecture. + * + * @param op The two-qubit unitary. + * @param layout The current layout. + * @param arch The targeted architecture. + */ +[[nodiscard]] bool isExecutable(UnitaryInterface op, const Layout& layout, + const Architecture& arch); + +/** + * @brief Insert SWAP ops at the rewriter's insertion point. + * + * @param loc The location of the inserted SWAP ops. + * @param swaps The hardware indices of the SWAPs. + * @param layout The current layout. + * @param rewriter The pattern rewriter. + */ +void insertSWAPs(mlir::Location loc, mlir::ArrayRef swaps, + Layout& layout, mlir::PatternRewriter& rewriter); } // namespace mqt::ir::opt diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h index f82e4798b4..b887ff6c45 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h @@ -10,11 +10,12 @@ #pragma once -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" -#include "mlir/Dialect/SCF/IR/SCF.h" +#include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/QubitIndex.h" #include #include +#include #include #include #include diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/QubitIndex.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/QubitIndex.h new file mode 100644 index 0000000000..89b4d2f6ad --- /dev/null +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/QubitIndex.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include + +namespace mqt::ir::opt { +/** + * @brief Type alias for qubit indices. + */ +using QubitIndex = uint32_t; +} // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp index 4b4d20f575..ea0ccc2859 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp @@ -138,4 +138,40 @@ void replaceAllUsesInRegionAndChildrenExcept(mlir::Value oldValue, return false; }); } + +[[nodiscard]] bool isExecutable(UnitaryInterface op, const Layout& layout, + const Architecture& arch) { + const auto ins = getIns(op); + return arch.areAdjacent(layout.lookupHardwareIndex(ins.first), + layout.lookupHardwareIndex(ins.second)); +} + +void insertSWAPs(mlir::Location loc, mlir::ArrayRef swaps, + Layout& layout, mlir::PatternRewriter& rewriter) { + for (const auto [hw0, hw1] : swaps) { + const mlir::Value in0 = layout.lookupHardwareValue(hw0); + const mlir::Value in1 = layout.lookupHardwareValue(hw1); + [[maybe_unused]] const auto [prog0, prog1] = + layout.getProgramIndices(hw0, hw1); + + // LLVM_DEBUG({ + // llvm::dbgs() << llvm::format( + // "route: swap= p%d:h%d, p%d:h%d <- p%d:h%d, p%d:h%d\n", prog1, hw0, + // prog0, hw1, prog0, hw0, prog1, hw1); + // }); + + auto swap = createSwap(loc, in0, in1, rewriter); + const auto [out0, out1] = getOuts(swap); + + rewriter.setInsertionPointAfter(swap); + replaceAllUsesInRegionAndChildrenExcept(in0, out1, swap->getParentRegion(), + swap, rewriter); + replaceAllUsesInRegionAndChildrenExcept(in1, out0, swap->getParentRegion(), + swap, rewriter); + + layout.swap(in0, in1); + layout.remapQubitValue(in0, out0); + layout.remapQubitValue(in1, out1); + } +} } // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index 9af1ecf9e0..0468d797f8 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -52,43 +52,6 @@ namespace mqt::ir::opt { namespace { using namespace mlir; -/** - * @brief Insert SWAP ops at the rewriter's insertion point. - * - * @param loc The location of the inserted SWAP ops. - * @param swaps The hardware indices of the SWAPs. - * @param layout The current layout. - * @param rewriter The pattern rewriter. - */ -void insertSWAPs(Location loc, ArrayRef swaps, Layout& layout, - PatternRewriter& rewriter) { - for (const auto [hw0, hw1] : swaps) { - const Value in0 = layout.lookupHardwareValue(hw0); - const Value in1 = layout.lookupHardwareValue(hw1); - [[maybe_unused]] const auto [prog0, prog1] = - layout.getProgramIndices(hw0, hw1); - - LLVM_DEBUG({ - llvm::dbgs() << llvm::format( - "route: swap= p%d:h%d, p%d:h%d <- p%d:h%d, p%d:h%d\n", prog1, hw0, - prog0, hw1, prog0, hw0, prog1, hw1); - }); - - auto swap = createSwap(loc, in0, in1, rewriter); - const auto [out0, out1] = getOuts(swap); - - rewriter.setInsertionPointAfter(swap); - replaceAllUsesInRegionAndChildrenExcept(in0, out1, swap->getParentRegion(), - swap, rewriter); - replaceAllUsesInRegionAndChildrenExcept(in1, out0, swap->getParentRegion(), - swap, rewriter); - - layout.swap(in0, in1); - layout.remapQubitValue(in0, out0); - layout.remapQubitValue(in1, out1); - } -} - /** * @brief Routes the program by dividing the circuit into layers of parallel * two-qubit gates and iteratively searches and inserts SWAPs for each layer diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp index e301b864f1..0f7e08af27 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp @@ -53,58 +53,6 @@ namespace mqt::ir::opt { namespace { using namespace mlir; -/** - * @brief Insert SWAP ops at the rewriter's insertion point. - * - * @param loc The location of the inserted SWAP ops. - * @param swaps The hardware indices of the SWAPs. - * @param layout The current layout. - * @param rewriter The pattern rewriter. - */ -void insertSWAPs(Location loc, ArrayRef swaps, Layout& layout, - PatternRewriter& rewriter) { - for (const auto [hw0, hw1] : swaps) { - const Value in0 = layout.lookupHardwareValue(hw0); - const Value in1 = layout.lookupHardwareValue(hw1); - [[maybe_unused]] const auto [prog0, prog1] = - layout.getProgramIndices(hw0, hw1); - - LLVM_DEBUG({ - llvm::dbgs() << llvm::format( - "route: swap= p%d:h%d, p%d:h%d <- p%d:h%d, p%d:h%d\n", prog1, hw0, - prog0, hw1, prog0, hw0, prog1, hw1); - }); - - auto swap = createSwap(loc, in0, in1, rewriter); - const auto [out0, out1] = getOuts(swap); - - rewriter.setInsertionPointAfter(swap); - replaceAllUsesInRegionAndChildrenExcept(in0, out1, swap->getParentRegion(), - swap, rewriter); - replaceAllUsesInRegionAndChildrenExcept(in1, out0, swap->getParentRegion(), - swap, rewriter); - - layout.swap(in0, in1); - layout.remapQubitValue(in0, out0); - layout.remapQubitValue(in1, out1); - } -} - -/** - * @brief Given a layout, validate if the two-qubit unitary op is executable on - * the targeted architecture. - * - * @param op The two-qubit unitary. - * @param layout The current layout. - * @param arch The targeted architecture. - */ -[[nodiscard]] bool isExecutable(UnitaryInterface op, const Layout& layout, - const Architecture& arch) { - const auto ins = getIns(op); - return arch.areAdjacent(layout.lookupHardwareIndex(ins.first), - layout.lookupHardwareIndex(ins.second)); -} - /** * @brief Simple pre-order traversal of the IR that routes any non-executable * gates by inserting SWAPs along the shortest path. diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp index d259029904..c0234c227d 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp @@ -51,6 +51,16 @@ namespace mqt::ir::opt { namespace { using namespace mlir; +/** + * @brief 'For' pushes once onto the stack, hence the parent is at depth one. + */ +constexpr std::size_t FOR_PARENT_DEPTH = 1UL; + +/** + * @brief 'If' pushes twice onto the stack, hence the parent is at depth two. + */ +constexpr std::size_t IF_PARENT_DEPTH = 2UL; + /** * @brief A queue of hardware indices. */ diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp index e7638a8f7e..5748834eda 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp @@ -44,21 +44,6 @@ namespace mqt::ir::opt { namespace { using namespace mlir; -/** - * @brief Given a layout, validate if the two-qubit unitary op is executable on - * the targeted architecture. - * - * @param op The two-qubit unitary. - * @param layout The current layout. - * @param arch The targeted architecture. - */ -[[nodiscard]] bool isExecutable(UnitaryInterface op, const Layout& layout, - const Architecture& arch) { - const auto ins = getIns(op); - return arch.areAdjacent(layout.lookupHardwareIndex(ins.first), - layout.lookupHardwareIndex(ins.second)); -} - /** * @brief This pass verifies that all two-qubit gates are executable on the * target architecture. From 8993868de9edd6e0174635375412378204054ff6 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 1 Dec 2025 09:40:43 +0100 Subject: [PATCH 40/56] Fix linting --- .../Dialect/MQTOpt/Transforms/Transpilation/Common.cpp | 8 ++------ .../MQTOpt/Transforms/Transpilation/LayeredUnit.cpp | 1 + .../Transforms/Transpilation/sc/AStarRoutingPass.cpp | 7 +++++++ .../Transforms/Transpilation/sc/NaiveRoutingPass.cpp | 7 +++++++ .../MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp | 1 + 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp index ea0ccc2859..1530ef8264 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp @@ -11,6 +11,8 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" #include #include @@ -154,12 +156,6 @@ void insertSWAPs(mlir::Location loc, mlir::ArrayRef swaps, [[maybe_unused]] const auto [prog0, prog1] = layout.getProgramIndices(hw0, hw1); - // LLVM_DEBUG({ - // llvm::dbgs() << llvm::format( - // "route: swap= p%d:h%d, p%d:h%d <- p%d:h%d, p%d:h%d\n", prog1, hw0, - // prog0, hw1, prog0, hw0, prog1, hw1); - // }); - auto swap = createSwap(loc, in0, in1, rewriter); const auto [out0, out1] = getOuts(swap); diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp index fcd6a4ed3b..ba780b05a1 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp @@ -14,6 +14,7 @@ #include "mlir/Dialect/MQTOpt/IR/WireIterator.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/QubitIndex.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h" #include diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index 0468d797f8..2f12e445ae 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -125,6 +125,13 @@ struct AStarRoutingPassSC final history.append(*swaps); insertSWAPs(anchor->getLoc(), *swaps, unit.layout(), rewriter); numSwaps += swaps->size(); + + LLVM_DEBUG({ + for (const auto [hw0, hw1] : *swaps) { + llvm::dbgs() + << llvm::format("route: swap= hw(%d, %d)\n", hw0, hw1); + } + }); } /// Process all operations contained in the layer. diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp index 0f7e08af27..a8127ac8f4 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp @@ -119,6 +119,13 @@ struct NaiveRoutingPassSC final history.append(swaps); insertSWAPs(op->getLoc(), swaps, unit.layout(), rewriter); numSwaps += swaps.size(); + + LLVM_DEBUG({ + for (const auto [hw0, hw1] : swaps) { + llvm::dbgs() << llvm::format( + "route: swap= hw(%d, %d)\n", hw0, hw1); + } + }); } } } diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp index c0234c227d..9cd3aab700 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp @@ -13,6 +13,7 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/QubitIndex.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Stack.h" #include From 8d3419e1182d37cb3f40e5ee066b03c3d4726a0b Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 2 Dec 2025 07:43:36 +0100 Subject: [PATCH 41/56] Remove SlidingWindow Iterator --- .../Transforms/Transpilation/LayeredUnit.h | 103 ++++-------------- .../MQTOpt/Transforms/Transpilation/Router.h | 19 ++-- .../Transforms/Transpilation/SequentialUnit.h | 11 +- .../Transforms/Transpilation/LayeredUnit.cpp | 89 ++++++++------- .../Transpilation/SequentialUnit.cpp | 14 ++- .../Transpilation/sc/AStarRoutingPass.cpp | 28 +++-- 6 files changed, 104 insertions(+), 160 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.h index 8f022fb2ae..804eb9a76b 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.h @@ -15,18 +15,17 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h" #include -#include #include #include #include namespace mqt::ir::opt { -using GateLayer = mlir::SmallVector; - -struct OpLayer { - /// @brief All ops contained inside this layer. +struct Layer { + /// @brief All (zero, one, two-qubit) ops contained inside this layer. mlir::SmallVector ops; + /// @brief The program index pairs of all two-qubit ops. + mlir::SmallVector twoQubitProgs; /// @brief The first op in ops in textual IR order. mlir::Operation* anchor{}; @@ -37,99 +36,35 @@ struct OpLayer { anchor = op; } } - - [[nodiscard]] bool empty() const { return ops.empty(); } -}; - -struct WindowView { - mlir::ArrayRef gateLayers; - const OpLayer* opLayer{}; - mlir::Operation* nextAnchor{}; -}; - -class SlidingWindow { -public: - struct Iterator { - using value_type = WindowView; - using difference_type = std::ptrdiff_t; - using iterator_category = std::forward_iterator_tag; - - Iterator() = default; - Iterator(const SlidingWindow* window, std::size_t pos) - : window(window), pos(pos) {} - - WindowView operator*() const { - WindowView w; - const auto sz = window->opLayers->size(); - const auto len = std::min(1 + window->nlookahead, sz - pos); - w.gateLayers = - mlir::ArrayRef(*window->gateLayers).slice(pos, len); - w.opLayer = &(*window->opLayers)[pos]; - if (pos + 1 < window->gateLayers->size()) { - w.nextAnchor = (*window->opLayers)[pos + 1].anchor; - } - return w; - } - - Iterator& operator++() { - ++pos; - return *this; - } - - Iterator operator++(int) { - auto tmp = *this; - ++*this; - return tmp; - } - - bool operator==(const Iterator& other) const { - return pos == other.pos && window == other.window; - } - - private: - const SlidingWindow* window; - std::size_t pos; - }; - - explicit SlidingWindow(const mlir::SmallVector& gateLayers, - const mlir::SmallVector& opLayers, - const std::size_t nlookahead) - : gateLayers(&gateLayers), opLayers(&opLayers), nlookahead(nlookahead) {} - - [[nodiscard]] Iterator begin() const { return {this, 0}; } - [[nodiscard]] Iterator end() const { return {this, opLayers->size()}; } - - static_assert(std::forward_iterator); - -private: - const mlir::SmallVector* gateLayers; - const mlir::SmallVector* opLayers; - std::size_t nlookahead; + /// @returns true iff. there are no ops in this layer. + [[nodiscard]] bool hasZeroOps() const { return ops.empty(); } + /// @returns true iff. there are no two-qubit ops in this layer. + [[nodiscard]] bool hasZero2QOps() const { return twoQubitProgs.empty(); } }; /// @brief A LayeredUnit traverses a program layer-by-layer. class LayeredUnit : public Unit { public: - static LayeredUnit fromEntryPointFunction(mlir::func::FuncOp func, - const std::size_t nqubits) { - Layout layout(nqubits); - for_each(func.getOps(), [&](QubitOp op) { - layout.add(op.getIndex(), op.getIndex(), op.getQubit()); - }); - return {std::move(layout), &func.getBody()}; - } + using Layers = mlir::SmallVector; + + [[nodiscard]] static LayeredUnit + fromEntryPointFunction(mlir::func::FuncOp func, std::size_t nqubits); LayeredUnit(Layout layout, mlir::Region* region, bool restore = false); [[nodiscard]] mlir::SmallVector next(); - [[nodiscard]] SlidingWindow slidingWindow(std::size_t nlookahead) const; + [[nodiscard]] Layers::const_iterator begin() const { return layers_.begin(); } + [[nodiscard]] Layers::const_iterator end() const { return layers_.end(); } + [[nodiscard]] const Layer& operator[](std::size_t i) const { + return layers_[i]; + } + [[nodiscard]] std::size_t size() const { return layers_.size(); } #ifndef NDEBUG LLVM_DUMP_METHOD void dump(llvm::raw_ostream& os = llvm::dbgs()) const; #endif private: - mlir::SmallVector gateLayers; - mlir::SmallVector opLayers; + Layers layers_; }; } // namespace mqt::ir::opt diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h index 0e76210c72..4b7c67cf0d 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h @@ -12,7 +12,6 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" #include @@ -77,8 +76,8 @@ class AStarHeuristicRouter { * swap to the layout of the parent node and evaluate the cost. */ Node(const Node& parent, QubitIndexPair swap, - mlir::ArrayRef window, const Architecture& arch, - const HeuristicWeights& weights) + mlir::SmallVector> window, + const Architecture& arch, const HeuristicWeights& weights) : sequence(parent.sequence), layout(parent.layout), f(0) { /// Apply node-specific swap to given layout. layout.swap(layout.getProgramIndex(swap.first), @@ -95,7 +94,7 @@ class AStarHeuristicRouter { * @brief Return true if the current sequence of SWAPs makes all gates * executable. */ - [[nodiscard]] bool isGoal(const GateLayer& layer, + [[nodiscard]] bool isGoal(mlir::ArrayRef layer, const Architecture& arch) const { return std::ranges::all_of(layer, [&](const QubitIndexPair gate) { return arch.areAdjacent(layout.getHardwareIndex(gate.first), @@ -129,9 +128,9 @@ class AStarHeuristicRouter { * its hardware qubits. Intuitively, this is the number of SWAPs that a * naive router would insert to route the layers. */ - [[nodiscard]] float h(mlir::ArrayRef window, - const Architecture& arch, - const HeuristicWeights& weights) const { + [[nodiscard]] float + h(mlir::SmallVector> window, + const Architecture& arch, const HeuristicWeights& weights) const { float nn{0}; for (const auto [i, layer] : llvm::enumerate(window)) { for (const auto [prog0, prog1] : layer) { @@ -148,8 +147,8 @@ class AStarHeuristicRouter { public: [[nodiscard]] std::optional> - route(mlir::ArrayRef window, const ThinLayout& layout, - const Architecture& arch) const { + route(mlir::SmallVector> window, + const ThinLayout& layout, const Architecture& arch) const { Node root(layout); /// Early exit. No SWAPs required: @@ -180,7 +179,7 @@ class AStarHeuristicRouter { private: /// @brief Expand frontier with all neighbouring SWAPs in the current front. void expand(MinQueue& frontier, const Node& parent, - mlir::ArrayRef window, + mlir::SmallVector> window, const Architecture& arch) const { llvm::SmallDenseSet expansionSet{}; diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.h index 88bba711fa..07accbc648 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.h @@ -10,7 +10,6 @@ #pragma once -#include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h" @@ -25,14 +24,8 @@ namespace mqt::ir::opt { /// @brief A SequentialUnit traverses a program sequentially. class SequentialUnit : public Unit { public: - static SequentialUnit fromEntryPointFunction(mlir::func::FuncOp func, - const std::size_t nqubits) { - Layout layout(nqubits); - for_each(func.getOps(), [&](QubitOp op) { - layout.add(op.getIndex(), op.getIndex(), op.getQubit()); - }); - return {std::move(layout), &func.getBody()}; - } + [[nodiscard]] static SequentialUnit + fromEntryPointFunction(mlir::func::FuncOp func, std::size_t nqubits); SequentialUnit(Layout layout, mlir::Region* region, mlir::Region::OpIterator start, bool restore = false); diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp index ba780b05a1..4c1b01f45f 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp @@ -38,7 +38,7 @@ namespace mqt::ir::opt { namespace { -/// @brief A wire links a WireIterator and a program index. +/// @brief A wire links a WireIterator to a program index. struct Wire { Wire(const WireIterator& it, QubitIndex index) : it(it), index(index) {} WireIterator it; @@ -82,7 +82,7 @@ class SynchronizationMap { }; mlir::SmallVector skipTwoQubitBlock(mlir::ArrayRef wires, - OpLayer& opLayer) { + Layer& opLayer) { assert(wires.size() == 2 && "expected two wires"); auto [it0, index0] = wires[0]; @@ -132,6 +132,15 @@ mlir::SmallVector skipTwoQubitBlock(mlir::ArrayRef wires, } } // namespace +LayeredUnit LayeredUnit::fromEntryPointFunction(mlir::func::FuncOp func, + const std::size_t nqubits) { + Layout layout(nqubits); + for_each(func.getOps(), [&](QubitOp op) { + layout.add(op.getIndex(), op.getIndex(), op.getQubit()); + }); + return {std::move(layout), &func.getBody()}; +} + LayeredUnit::LayeredUnit(Layout layout, mlir::Region* region, bool restore) : Unit(std::move(layout), region, restore) { SynchronizationMap sync; @@ -153,8 +162,7 @@ LayeredUnit::LayeredUnit(Layout layout, mlir::Region* region, bool restore) /// of the respective two-qubit gates, and prepare iterators for next /// iteration. - GateLayer gateLayer; - OpLayer opLayer; + Layer layer; bool haltOnWire{}; @@ -169,7 +177,7 @@ LayeredUnit::LayeredUnit(Layout layout, mlir::Region* region, bool restore) /// Skip over one-qubit gates. Note: Might be a BarrierOp. if (nins == 1) { - opLayer.addOp(op); + layer.addOp(op); ++it; return false; } @@ -182,13 +190,13 @@ LayeredUnit::LayeredUnit(Layout layout, mlir::Region* region, bool restore) if (const auto iterators = sync.visit(op, Wire(++it, index))) { - opLayer.addOp(op); + layer.addOp(op); // Is ready two-qubit unitary? if (!mlir::isa(op)) { - gateLayer.emplace_back((*iterators)[0].index, - (*iterators)[1].index); - next.append(skipTwoQubitBlock(*iterators, opLayer)); + layer.twoQubitProgs.emplace_back((*iterators)[0].index, + (*iterators)[1].index); + next.append(skipTwoQubitBlock(*iterators, layer)); } else { next.append(*iterators); } @@ -197,7 +205,7 @@ LayeredUnit::LayeredUnit(Layout layout, mlir::Region* region, bool restore) return true; }) .Case([&](auto op) { - opLayer.addOp(op); + layer.addOp(op); ++it; return false; }) @@ -209,7 +217,7 @@ LayeredUnit::LayeredUnit(Layout layout, mlir::Region* region, bool restore) if (const auto iterators = sync.visit(yield, Wire(++it, index))) { - opLayer.addOp(yield); + layer.addOp(yield); } return true; @@ -242,24 +250,24 @@ LayeredUnit::LayeredUnit(Layout layout, mlir::Region* region, bool restore) } } - /// If there is no gates to route, merge the last layer with this one and - /// keep the anchor the same. Otherwise, add the layer. + if (!layer.hasZeroOps()) { + if (layer.hasZero2QOps()) { - if (gateLayer.empty()) { - if (!opLayer.empty()) { - if (opLayers.empty()) { - gateLayers.emplace_back(); - opLayers.emplace_back(); - } - opLayers.back().ops.append(opLayer.ops); - if (opLayers.back().anchor == nullptr) { - opLayers.back().anchor = opLayer.anchor; + /// If there is no gates to route, merge the last layer + /// with this one and keep the anchor the same. + + if (layers_.empty()) { + layers_.emplace_back(layer); + } else { + layers_.back().ops.append(layer.ops); + if (layers_.back().anchor == nullptr) { + layers_.back().anchor = layer.anchor; + } } + } else { + /// Otherwise, add the layer. + layers_.emplace_back(layer); } - - } else if (!opLayer.empty()) { - gateLayers.emplace_back(gateLayer); - opLayers.emplace_back(opLayer); } /// Prepare next iteration or stop. @@ -298,18 +306,19 @@ mlir::SmallVector LayeredUnit::next() { return units; } -SlidingWindow LayeredUnit::slidingWindow(std::size_t nlookahead) const { - return SlidingWindow(gateLayers, opLayers, nlookahead); -} - #ifndef NDEBUG LLVM_DUMP_METHOD void LayeredUnit::dump(llvm::raw_ostream& os) const { - os << "schedule: gate layers=\n"; - for (const auto [i, layer] : llvm::enumerate(gateLayers)) { + os << "schedule: layers=\n"; + for (const auto [i, layer] : llvm::enumerate(layers_)) { os << '\t' << i << ": "; - os << "gates= "; - if (!layer.empty()) { - for (const auto [prog0, prog1] : layer) { + os << "#ops= " << layer.ops.size(); + if (!layer.ops.empty()) { + os << " anchor= " << layer.anchor->getLoc(); + } + os << '\n'; + os << "schedule: gates= "; + if (!layer.hasZero2QOps()) { + for (const auto [prog0, prog1] : layer.twoQubitProgs) { os << "(" << prog0 << "," << prog1 << "), "; } } else { @@ -317,16 +326,6 @@ LLVM_DUMP_METHOD void LayeredUnit::dump(llvm::raw_ostream& os) const { } os << '\n'; } - - os << "schedule: op layers=\n"; - for (const auto [i, layer] : llvm::enumerate(opLayers)) { - os << '\t' << i << ": "; - os << "#ops= " << layer.ops.size(); - if (!layer.ops.empty()) { - os << " anchor= " << layer.anchor->getLoc(); - } - os << '\n'; - } if (divider_ != nullptr) { os << "schedule: followUp= " << divider_->getLoc() << '\n'; } diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp index d83c336d28..14437a5fbd 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp @@ -10,11 +10,13 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.h" +#include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h" #include #include +#include #include #include #include @@ -25,6 +27,16 @@ namespace mqt::ir::opt { +SequentialUnit +SequentialUnit::fromEntryPointFunction(mlir::func::FuncOp func, + const std::size_t nqubits) { + Layout layout(nqubits); + for_each(func.getOps(), [&](QubitOp op) { + layout.add(op.getIndex(), op.getIndex(), op.getQubit()); + }); + return {std::move(layout), &func.getBody()}; +} + SequentialUnit::SequentialUnit(Layout layout, mlir::Region* region, mlir::Region::OpIterator start, bool restore) : Unit(std::move(layout), region, restore), start_(start), @@ -40,7 +52,7 @@ SequentialUnit::SequentialUnit(Layout layout, mlir::Region* region, end_ = it; } -[[nodiscard]] mlir::SmallVector SequentialUnit::next() { +mlir::SmallVector SequentialUnit::next() { if (divider_ == nullptr) { return {}; } diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index 2f12e445ae..2270535b91 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -104,18 +104,23 @@ struct AStarRoutingPassSC final units.emplace(LayeredUnit::fromEntryPointFunction(func, arch->nqubits())); for (; !units.empty(); units.pop()) { LayeredUnit& unit = units.front(); - LLVM_DEBUG(unit.dump()); SmallVector history; - for (const auto window : unit.slidingWindow(nlookahead)) { - Operation* anchor = window.opLayer->anchor; - const ArrayRef layers = window.gateLayers; - const ArrayRef ops = window.opLayer->ops; + for (const auto& [i, layer] : llvm::enumerate(unit)) { + + /// Compute sliding window. + const auto len = std::min(1 + nlookahead, unit.size() - i); + SmallVector> window; + window.reserve(len); + llvm::transform(ArrayRef(unit.begin(), unit.end()).slice(i, len), + std::back_inserter(window), [&](const Layer& l) { + return ArrayRef(l.twoQubitProgs); + }); /// Find and insert SWAPs. - rewriter.setInsertionPoint(anchor); - const auto swaps = router.route(layers, unit.layout(), *arch); + rewriter.setInsertionPoint(layer.anchor); + const auto swaps = router.route(window, unit.layout(), *arch); if (!swaps) { const Location loc = UnknownLoc::get(&getContext()); return emitError(loc, "A* failed to find a valid SWAP sequence"); @@ -123,7 +128,8 @@ struct AStarRoutingPassSC final if (!swaps->empty()) { history.append(*swaps); - insertSWAPs(anchor->getLoc(), *swaps, unit.layout(), rewriter); + insertSWAPs(layer.anchor->getLoc(), *swaps, unit.layout(), + rewriter); numSwaps += swaps->size(); LLVM_DEBUG({ @@ -135,12 +141,12 @@ struct AStarRoutingPassSC final } /// Process all operations contained in the layer. - for (Operation* curr : ops) { + for (Operation* curr : layer.ops) { rewriter.setInsertionPoint(curr); /// Re-order to fix any SSA Dominance issues. - if (window.nextAnchor != nullptr) { - rewriter.moveOpBefore(curr, window.nextAnchor); + if (i + 1 < unit.size()) { + rewriter.moveOpBefore(curr, unit[i + 1].anchor); } /// Forward layout. From 13f29002bc7d0c9f94e685d8bbaf3d45497b52c6 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 2 Dec 2025 07:53:40 +0100 Subject: [PATCH 42/56] Fix linting --- .../lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp | 1 + .../Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp | 2 ++ .../MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp | 2 ++ 3 files changed, 5 insertions(+) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp index 4c1b01f45f..5f847ac0a5 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp index 14437a5fbd..c6258133c9 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp @@ -14,7 +14,9 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h" +#include #include +#include #include #include #include diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index 2270535b91..fd40da6c46 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -16,7 +16,9 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h" +#include #include +#include #include #include #include From fabda2351b0c3c77626c19cdb9e3dec112bb23dc Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 2 Dec 2025 08:39:58 +0100 Subject: [PATCH 43/56] Apply bunny suggestions --- .../mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h | 2 ++ mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp | 1 + .../Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp | 4 +--- .../MQTOpt/Transforms/Transpilation/SequentialUnit.cpp | 4 +--- .../MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp | 2 +- .../Transforms/Transpilation/sc/RoutingVerificationPass.cpp | 3 --- 6 files changed, 6 insertions(+), 10 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h index 4b7c67cf0d..ddd2d8ccaf 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h @@ -15,6 +15,7 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" #include +#include #include #include #include @@ -32,6 +33,7 @@ class NaiveRouter { const auto hw0 = layout.getHardwareIndex(gate.first); const auto hw1 = layout.getHardwareIndex(gate.second); const auto path = arch.shortestPathBetween(hw0, hw1); + assert(path.size() >= 2 && "NaiveRouter::route: invalid shortest path"); for (std::size_t i = 0; i < path.size() - 2; ++i) { swaps.emplace_back(path[i], path[i + 1]); } diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp index 1530ef8264..222c8686ff 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp @@ -143,6 +143,7 @@ void replaceAllUsesInRegionAndChildrenExcept(mlir::Value oldValue, [[nodiscard]] bool isExecutable(UnitaryInterface op, const Layout& layout, const Architecture& arch) { + assert(isTwoQubitGate(op)); const auto ins = getIns(op); return arch.areAdjacent(layout.lookupHardwareIndex(ins.first), layout.lookupHardwareIndex(ins.second)); diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp index 5f847ac0a5..b0ebdd6f45 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp @@ -33,7 +33,6 @@ #include #include #include -#include #include namespace mqt::ir::opt { @@ -301,8 +300,7 @@ mlir::SmallVector LayeredUnit::next() { remapIfResults(op, layout_); units.emplace_back(layout_, region_, restore_); }) - .Default( - [](auto) { throw std::runtime_error("invalid 'next' operation"); }); + .Default([](auto) { llvm_unreachable("invalid 'next' operation"); }); return units; } diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp index c6258133c9..2d4d63af4a 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include namespace mqt::ir::opt { @@ -76,8 +75,7 @@ mlir::SmallVector SequentialUnit::next() { units.emplace_back(std::move(layout_), region_, std::next(end_), restore_); }) - .Default( - [](auto) { throw std::runtime_error("invalid 'next' operation"); }); + .Default([](auto) { llvm_unreachable("invalid 'next' operation"); }); return units; } diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index fd40da6c46..a3e160f9d3 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -168,7 +168,7 @@ struct AStarRoutingPassSC final [&](MeasureOp op) { remap(op, unit.layout()); }) .Case([&](scf::YieldOp op) { if (unit.restore()) { - rewriter.setInsertionPointAfter(op->getPrevNode()); + rewriter.setInsertionPoint(op); insertSWAPs(op.getLoc(), llvm::to_vector(llvm::reverse(history)), unit.layout(), rewriter); diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp index 5748834eda..7c2aa14932 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp @@ -68,7 +68,6 @@ struct RoutingVerificationPassSC final private: LogicalResult verify() { ModuleOp module(getOperation()); - PatternRewriter rewriter(module->getContext()); std::unique_ptr arch(getArchitecture(archName)); if (!arch) { @@ -95,8 +94,6 @@ struct RoutingVerificationPassSC final Layout unmodified(unit.layout()); SmallVector history; for (Operation& curr : unit) { - rewriter.setInsertionPoint(&curr); - const auto res = TypeSwitch(&curr) .Case([&](UnitaryInterface op) From 0746263a87b62c29cdd7ce31422635f2613befba Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 3 Dec 2025 06:44:57 +0100 Subject: [PATCH 44/56] Apply bunny suggestions --- .../Transforms/Transpilation/LayeredUnit.cpp | 28 +++++++------------ .../Transpilation/SequentialUnit.cpp | 1 + .../sc/RoutingVerificationPass.cpp | 4 +-- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp index b0ebdd6f45..79573ae597 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp @@ -251,22 +251,14 @@ LayeredUnit::LayeredUnit(Layout layout, mlir::Region* region, bool restore) } if (!layer.hasZeroOps()) { - if (layer.hasZero2QOps()) { - - /// If there is no gates to route, merge the last layer - /// with this one and keep the anchor the same. - - if (layers_.empty()) { - layers_.emplace_back(layer); - } else { - layers_.back().ops.append(layer.ops); - if (layers_.back().anchor == nullptr) { - layers_.back().anchor = layer.anchor; - } - } - } else { - /// Otherwise, add the layer. + if (!layer.hasZero2QOps() || layers_.empty()) { layers_.emplace_back(layer); + } else { + /// If there is no gates to route, merge into the previous layer. + layers_.back().ops.append(layer.ops); + if (layers_.back().anchor == nullptr) { + layers_.back().anchor = layer.anchor; + } } } @@ -309,13 +301,13 @@ mlir::SmallVector LayeredUnit::next() { LLVM_DUMP_METHOD void LayeredUnit::dump(llvm::raw_ostream& os) const { os << "schedule: layers=\n"; for (const auto [i, layer] : llvm::enumerate(layers_)) { - os << '\t' << i << ": "; - os << "#ops= " << layer.ops.size(); + os << '\t' << '[' << i << "]:\n"; + os << "\t #ops= " << layer.ops.size(); if (!layer.ops.empty()) { os << " anchor= " << layer.anchor->getLoc(); } os << '\n'; - os << "schedule: gates= "; + os << "\t gates= "; if (!layer.hasZero2QOps()) { for (const auto [prog0, prog1] : layer.twoQubitProgs) { os << "(" << prog0 << "," << prog1 << "), "; diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp index 2d4d63af4a..eff06abd4e 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp index 7c2aa14932..e81bab13b9 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp @@ -93,9 +93,9 @@ struct RoutingVerificationPassSC final Layout unmodified(unit.layout()); SmallVector history; - for (Operation& curr : unit) { + for (const Operation& curr : unit) { const auto res = - TypeSwitch(&curr) + TypeSwitch(&curr) .Case([&](UnitaryInterface op) -> LogicalResult { if (isTwoQubitGate(op)) { From 5ca6ec008b3451855411b14745ccb7e72b0f23a2 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 3 Dec 2025 07:10:36 +0100 Subject: [PATCH 45/56] Remove history --- .../Transforms/Transpilation/sc/RoutingVerificationPass.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp index e81bab13b9..7296a4bd7a 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp @@ -92,7 +92,6 @@ struct RoutingVerificationPassSC final SequentialUnit& unit = units.front(); Layout unmodified(unit.layout()); - SmallVector history; for (const Operation& curr : unit) { const auto res = TypeSwitch(&curr) @@ -117,9 +116,6 @@ struct RoutingVerificationPassSC final if (isa(op)) { const auto ins = getIns(op); unit.layout().swap(ins.first, ins.second); - history.push_back( - {unit.layout().lookupHardwareIndex(ins.first), - unit.layout().lookupHardwareIndex(ins.second)}); } remap(op, unit.layout()); From f60eecbdc614e7a6b4b12d7492ab6ab04ae56733 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 4 Dec 2025 06:38:43 +0100 Subject: [PATCH 46/56] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 173a0c3d6b..20e3e8bc90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Changed +- ♻️ Group dynamic circuit operations into scheduling units for MLIR routing ([#1301]) ([**@MatthiasReumann**]) - 👷 Use `munich-quantum-software/setup-mlir` to set up MLIR ([#1294]) ([**@denialhaag**]) - ♻️ Preserve tuple structure and improve site type clarity of the MQT NA Default QDMI Device ([#1299]) ([**@marcelwa**]) - ♻️ Move DD package evaluation module to standalone script ([#1327]) ([**@burgholzer**]) @@ -266,6 +267,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool [#1336]: https://github.com/munich-quantum-toolkit/core/pull/1336 [#1327]: https://github.com/munich-quantum-toolkit/core/pull/1327 [#1310]: https://github.com/munich-quantum-toolkit/core/pull/1310 +[#1301]: https://github.com/munich-quantum-toolkit/core/pull/1301 [#1300]: https://github.com/munich-quantum-toolkit/core/pull/1300 [#1299]: https://github.com/munich-quantum-toolkit/core/pull/1299 [#1294]: https://github.com/munich-quantum-toolkit/core/pull/1294 From 0fcf836d6d43df00c4f799a9550bf8dabd38b2f7 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 4 Dec 2025 06:39:12 +0100 Subject: [PATCH 47/56] Fix "A Unit" --- .../include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h index d335961db0..dbcb35ecbc 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h @@ -16,7 +16,7 @@ namespace mqt::ir::opt { -/// @brief An Unit divides a quantum-classical program into routable sections. +/// @brief A Unit divides a quantum-classical program into routable sections. class Unit { public: Unit(Layout layout, mlir::Region* region, bool restore = false) From 06e1d7e0be330ee192ca685d1ab6e442a72a8d45 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 4 Dec 2025 06:57:17 +0100 Subject: [PATCH 48/56] Make remap functions class methods --- .../Transforms/Transpilation/Architecture.h | 7 +- .../MQTOpt/Transforms/Transpilation/Common.h | 1 - .../MQTOpt/Transforms/Transpilation/Layout.h | 149 +++++++++--------- .../Transforms/Transpilation/QubitIndex.h | 20 --- .../Transforms/Transpilation/LayeredUnit.cpp | 8 +- .../Transpilation/SequentialUnit.cpp | 6 +- .../Transpilation/sc/AStarRoutingPass.cpp | 7 +- .../Transpilation/sc/NaiveRoutingPass.cpp | 6 +- .../Transpilation/sc/PlacementPass.cpp | 1 - .../sc/RoutingVerificationPass.cpp | 6 +- 10 files changed, 92 insertions(+), 119 deletions(-) delete mode 100644 mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/QubitIndex.h diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h index 412062aefb..cc0d77fb81 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h @@ -10,8 +10,6 @@ #pragma once -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/QubitIndex.h" - #include #include #include @@ -25,6 +23,11 @@ namespace mqt::ir::opt { +/** + * @brief Type alias for qubit indices. + */ +using QubitIndex = uint32_t; + /** * @brief A quantum accelerator's architecture. * @details Computes all-shortest paths at construction. diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h index ac2afea6a3..2f5b7ad855 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h @@ -13,7 +13,6 @@ #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/QubitIndex.h" #include #include diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h index b887ff6c45..eb8400ed66 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h @@ -11,7 +11,7 @@ #pragma once #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/QubitIndex.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" #include #include @@ -256,6 +256,76 @@ class [[nodiscard]] Layout : public ThinLayout { return qubits_; } + /** + * @brief Remap all input to output qubits for the given unitary op. + * + * @param op The unitary op. + */ + void remap(UnitaryInterface op) { + for (const auto& [in, out] : + llvm::zip_equal(op.getAllInQubits(), op.getAllOutQubits())) { + remapQubitValue(in, out); + } + } + + /** + * @brief Remap input to output qubit for the given reset op. + * + * @param op The reset op. + * @param layout The current layout. + */ + void remap(ResetOp op) { remapQubitValue(op.getInQubit(), op.getOutQubit()); } + + /** + * @brief Remap input to output qubit for the given measure op. + * + * @param op The measure op. + */ + void remap(MeasureOp op) { + remapQubitValue(op.getInQubit(), op.getOutQubit()); + } + + /** + * @brief Remap input qubits to in-loop-body values (iteration args). + * + * @param op The 'scf.for' op. + */ + void remapToLoopBody(mlir::scf::ForOp op) { + const auto nqubits = getNumQubits(); + const auto args = op.getInitArgs().take_front(nqubits); + const auto iterArgs = op.getRegionIterArgs().take_front(nqubits); + for (const auto [arg, iter] : llvm::zip(args, iterArgs)) { + remapQubitValue(arg, iter); + } + } + + /** + * @brief Remap input qubits to out-of-loop values (results). + * + * @param op The 'scf.for' op. + */ + void remapToLoopResults(mlir::scf::ForOp op) { + const auto nqubits = getNumQubits(); + const auto args = op.getInitArgs().take_front(nqubits); + const auto results = op.getResults().take_front(nqubits); + for (const auto [arg, iter] : llvm::zip(args, results)) { + remapQubitValue(arg, iter); + } + } + + /** + * @brief Remap current qubit values to if results. + * + * @param op The 'scf.if' op. + */ + void remapIfResults(mlir::scf::IfOp op) { + const auto nqubits = getNumQubits(); + const auto results = op->getResults().take_front(nqubits); + for (const auto [in, out] : llvm::zip(getHardwareQubits(), results)) { + remapQubitValue(in, out); + } + } + private: struct QubitInfo { QubitIndex prog; @@ -273,83 +343,6 @@ class [[nodiscard]] Layout : public ThinLayout { mlir::SmallVector qubits_; }; -/** - * @brief Remap all input to output qubits for the given unitary op. - * - * @param op The unitary op. - * @param layout The current layout. - */ -inline void remap(UnitaryInterface op, Layout& layout) { - for (const auto& [in, out] : - llvm::zip_equal(op.getAllInQubits(), op.getAllOutQubits())) { - layout.remapQubitValue(in, out); - } -} - -/** - * @brief Remap input to output qubit for the given reset op. - * - * @param op The reset op. - * @param layout The current layout. - */ -inline void remap(ResetOp op, Layout& layout) { - layout.remapQubitValue(op.getInQubit(), op.getOutQubit()); -} - -/** - * @brief Remap input to output qubit for the given measure op. - * - * @param op The measure op. - * @param layout The current layout. - */ -inline void remap(MeasureOp op, Layout& layout) { - layout.remapQubitValue(op.getInQubit(), op.getOutQubit()); -} - -/** - * @brief Remap input qubits to in-loop-body values (iteration args). - * - * @param op The 'scf.for' op. - * @param layout The current layout. - */ -inline void remapToLoopBody(mlir::scf::ForOp op, Layout& layout) { - const auto nqubits = layout.getNumQubits(); - const auto args = op.getInitArgs().take_front(nqubits); - const auto iterArgs = op.getRegionIterArgs().take_front(nqubits); - for (const auto [arg, iter] : llvm::zip(args, iterArgs)) { - layout.remapQubitValue(arg, iter); - } -} - -/** - * @brief Remap input qubits to out-of-loop values (results). - * - * @param op The 'scf.for' op. - * @param layout The current layout. - */ -inline void remapToLoopResults(mlir::scf::ForOp op, Layout& layout) { - const auto nqubits = layout.getNumQubits(); - const auto args = op.getInitArgs().take_front(nqubits); - const auto results = op.getResults().take_front(nqubits); - for (const auto [arg, iter] : llvm::zip(args, results)) { - layout.remapQubitValue(arg, iter); - } -} - -/** - * @brief Remap current qubit values to if results. - * - * @param op The 'scf.if' op. - * @param layout The current layout. - */ -inline void remapIfResults(mlir::scf::IfOp op, Layout& layout) { - const auto nqubits = layout.getNumQubits(); - const auto results = op->getResults().take_front(nqubits); - for (const auto [in, out] : llvm::zip(layout.getHardwareQubits(), results)) { - layout.remapQubitValue(in, out); - } -} - } // namespace mqt::ir::opt namespace llvm { diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/QubitIndex.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/QubitIndex.h deleted file mode 100644 index 89b4d2f6ad..0000000000 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/QubitIndex.h +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM - * Copyright (c) 2025 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#pragma once - -#include - -namespace mqt::ir::opt { -/** - * @brief Type alias for qubit indices. - */ -using QubitIndex = uint32_t; -} // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp index 79573ae597..518fbf4c26 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp @@ -12,9 +12,9 @@ #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include "mlir/Dialect/MQTOpt/IR/WireIterator.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/QubitIndex.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h" #include @@ -281,15 +281,15 @@ mlir::SmallVector LayeredUnit::next() { mlir::TypeSwitch(divider_) .Case([&](mlir::scf::ForOp op) { Layout forLayout(layout_); // Copy layout. - remapToLoopBody(op, forLayout); - remapToLoopResults(op, layout_); + forLayout.remapToLoopBody(op); + layout_.remapToLoopResults(op); units.emplace_back(std::move(layout_), region_, restore_); units.emplace_back(std::move(forLayout), &op.getRegion(), true); }) .Case([&](mlir::scf::IfOp op) { units.emplace_back(layout_, &op.getThenRegion(), true); units.emplace_back(layout_, &op.getElseRegion(), true); - remapIfResults(op, layout_); + layout_.remapIfResults(op); units.emplace_back(layout_, region_, restore_); }) .Default([](auto) { llvm_unreachable("invalid 'next' operation"); }); diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp index eff06abd4e..3169cdf7e6 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/SequentialUnit.cpp @@ -63,8 +63,8 @@ mlir::SmallVector SequentialUnit::next() { mlir::TypeSwitch(divider_) .Case([&](mlir::scf::ForOp op) { Layout forLayout(layout_); // Copy layout. - remapToLoopBody(op, forLayout); - remapToLoopResults(op, layout_); + forLayout.remapToLoopBody(op); + layout_.remapToLoopResults(op); units.emplace_back(std::move(layout_), region_, std::next(end_), restore_); units.emplace_back(std::move(forLayout), &op.getRegion(), true); @@ -72,7 +72,7 @@ mlir::SmallVector SequentialUnit::next() { .Case([&](mlir::scf::IfOp op) { units.emplace_back(layout_, &op.getThenRegion(), true); units.emplace_back(layout_, &op.getElseRegion(), true); - remapIfResults(op, layout_); + layout_.remapIfResults(op); units.emplace_back(std::move(layout_), region_, std::next(end_), restore_); }) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index a3e160f9d3..358c0789b1 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -161,11 +161,10 @@ struct AStarRoutingPassSC final {unit.layout().lookupHardwareIndex(ins.first), unit.layout().lookupHardwareIndex(ins.second)}); } - remap(op, unit.layout()); + unit.layout().remap(op); }) - .Case([&](ResetOp op) { remap(op, unit.layout()); }) - .Case( - [&](MeasureOp op) { remap(op, unit.layout()); }) + .Case([&](ResetOp op) { unit.layout().remap(op); }) + .Case([&](MeasureOp op) { unit.layout().remap(op); }) .Case([&](scf::YieldOp op) { if (unit.restore()) { rewriter.setInsertionPoint(op); diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp index a8127ac8f4..02f39098d7 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp @@ -137,10 +137,10 @@ struct NaiveRoutingPassSC final {unit.layout().lookupHardwareIndex(ins.first), unit.layout().lookupHardwareIndex(ins.second)}); } - remap(op, unit.layout()); + unit.layout().remap(op); }) - .Case([&](ResetOp op) { remap(op, unit.layout()); }) - .Case([&](MeasureOp op) { remap(op, unit.layout()); }) + .Case([&](ResetOp op) { unit.layout().remap(op); }) + .Case([&](MeasureOp op) { unit.layout().remap(op); }) .Case([&](scf::YieldOp op) { if (unit.restore()) { rewriter.setInsertionPointAfter(op->getPrevNode()); diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp index 9cd3aab700..c0234c227d 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp @@ -13,7 +13,6 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/QubitIndex.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Stack.h" #include diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp index 7296a4bd7a..12817dc3bc 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp @@ -118,15 +118,15 @@ struct RoutingVerificationPassSC final unit.layout().swap(ins.first, ins.second); } - remap(op, unit.layout()); + unit.layout().remap(op); return success(); }) .Case([&](ResetOp op) { - remap(op, unit.layout()); + unit.layout().remap(op); return success(); }) .Case([&](MeasureOp op) { - remap(op, unit.layout()); + unit.layout().remap(op); return success(); }) .Case([&](scf::YieldOp op) -> LogicalResult { From e4676c1df02ef6c4426bb9a5759b67bd5b49692d Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 4 Dec 2025 07:11:19 +0100 Subject: [PATCH 49/56] Use templated range for insertSWAPs function --- .../MQTOpt/Transforms/Transpilation/Common.h | 30 +++++++++++++++++-- .../Transforms/Transpilation/Common.cpp | 23 -------------- .../Transpilation/sc/AStarRoutingPass.cpp | 3 +- .../Transpilation/sc/NaiveRoutingPass.cpp | 3 +- 4 files changed, 29 insertions(+), 30 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h index 2f5b7ad855..ce022ef077 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h @@ -14,12 +14,14 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" +#include #include #include #include #include #include #include +#include #include namespace mqt::ir::opt { @@ -116,10 +118,32 @@ void replaceAllUsesInRegionAndChildrenExcept(mlir::Value oldValue, * @brief Insert SWAP ops at the rewriter's insertion point. * * @param loc The location of the inserted SWAP ops. - * @param swaps The hardware indices of the SWAPs. + * @param swaps A range of hardware indices for the SWAPs. * @param layout The current layout. * @param rewriter The pattern rewriter. */ -void insertSWAPs(mlir::Location loc, mlir::ArrayRef swaps, - Layout& layout, mlir::PatternRewriter& rewriter); +template + requires std::same_as, QubitIndexPair> +void insertSWAPs(mlir::Location loc, Range&& swaps, Layout& layout, + mlir::PatternRewriter& rewriter) { + for (const auto [hw0, hw1] : std::forward(swaps)) { + const mlir::Value in0 = layout.lookupHardwareValue(hw0); + const mlir::Value in1 = layout.lookupHardwareValue(hw1); + [[maybe_unused]] const auto [prog0, prog1] = + layout.getProgramIndices(hw0, hw1); + + auto swap = createSwap(loc, in0, in1, rewriter); + const auto [out0, out1] = getOuts(swap); + + rewriter.setInsertionPointAfter(swap); + replaceAllUsesInRegionAndChildrenExcept(in0, out1, swap->getParentRegion(), + swap, rewriter); + replaceAllUsesInRegionAndChildrenExcept(in1, out0, swap->getParentRegion(), + swap, rewriter); + + layout.swap(in0, in1); + layout.remapQubitValue(in0, out0); + layout.remapQubitValue(in1, out1); + } +} } // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp index 222c8686ff..c4d9e76fbc 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp @@ -148,27 +148,4 @@ void replaceAllUsesInRegionAndChildrenExcept(mlir::Value oldValue, return arch.areAdjacent(layout.lookupHardwareIndex(ins.first), layout.lookupHardwareIndex(ins.second)); } - -void insertSWAPs(mlir::Location loc, mlir::ArrayRef swaps, - Layout& layout, mlir::PatternRewriter& rewriter) { - for (const auto [hw0, hw1] : swaps) { - const mlir::Value in0 = layout.lookupHardwareValue(hw0); - const mlir::Value in1 = layout.lookupHardwareValue(hw1); - [[maybe_unused]] const auto [prog0, prog1] = - layout.getProgramIndices(hw0, hw1); - - auto swap = createSwap(loc, in0, in1, rewriter); - const auto [out0, out1] = getOuts(swap); - - rewriter.setInsertionPointAfter(swap); - replaceAllUsesInRegionAndChildrenExcept(in0, out1, swap->getParentRegion(), - swap, rewriter); - replaceAllUsesInRegionAndChildrenExcept(in1, out0, swap->getParentRegion(), - swap, rewriter); - - layout.swap(in0, in1); - layout.remapQubitValue(in0, out0); - layout.remapQubitValue(in1, out1); - } -} } // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index 358c0789b1..50b2262a5a 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -168,8 +168,7 @@ struct AStarRoutingPassSC final .Case([&](scf::YieldOp op) { if (unit.restore()) { rewriter.setInsertionPoint(op); - insertSWAPs(op.getLoc(), - llvm::to_vector(llvm::reverse(history)), + insertSWAPs(op.getLoc(), llvm::reverse(history), unit.layout(), rewriter); } }) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp index 02f39098d7..79b911c119 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp @@ -144,8 +144,7 @@ struct NaiveRoutingPassSC final .Case([&](scf::YieldOp op) { if (unit.restore()) { rewriter.setInsertionPointAfter(op->getPrevNode()); - insertSWAPs(op.getLoc(), - llvm::to_vector(llvm::reverse(history)), + insertSWAPs(op.getLoc(), llvm::reverse(history), unit.layout(), rewriter); } }); From 2fb08ddb6c54cefaad91c252eb985ab14f65f49b Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 4 Dec 2025 07:48:10 +0100 Subject: [PATCH 50/56] Add swap check to unitary remap method --- .../MQTOpt/Transforms/Transpilation/Common.h | 20 +++++++++---------- .../MQTOpt/Transforms/Transpilation/Layout.h | 7 +++++++ .../Transpilation/sc/AStarRoutingPass.cpp | 12 +++++------ .../Transpilation/sc/NaiveRoutingPass.cpp | 12 +++++------ .../sc/RoutingVerificationPass.cpp | 5 ----- 5 files changed, 28 insertions(+), 28 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h index ce022ef077..421ea1b28a 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h @@ -129,21 +129,19 @@ void insertSWAPs(mlir::Location loc, Range&& swaps, Layout& layout, for (const auto [hw0, hw1] : std::forward(swaps)) { const mlir::Value in0 = layout.lookupHardwareValue(hw0); const mlir::Value in1 = layout.lookupHardwareValue(hw1); - [[maybe_unused]] const auto [prog0, prog1] = - layout.getProgramIndices(hw0, hw1); auto swap = createSwap(loc, in0, in1, rewriter); - const auto [out0, out1] = getOuts(swap); rewriter.setInsertionPointAfter(swap); - replaceAllUsesInRegionAndChildrenExcept(in0, out1, swap->getParentRegion(), - swap, rewriter); - replaceAllUsesInRegionAndChildrenExcept(in1, out0, swap->getParentRegion(), - swap, rewriter); - - layout.swap(in0, in1); - layout.remapQubitValue(in0, out0); - layout.remapQubitValue(in1, out1); + + mlir::Region* region = swap->getParentRegion(); + mlir::Value out0 = swap.getOutQubits()[0]; + mlir::Value out1 = swap.getOutQubits()[1]; + + replaceAllUsesInRegionAndChildrenExcept(in0, out1, region, swap, rewriter); + replaceAllUsesInRegionAndChildrenExcept(in1, out0, region, swap, rewriter); + + layout.remap(swap); } } } // namespace mqt::ir::opt diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h index eb8400ed66..2961e82c2b 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -259,9 +260,15 @@ class [[nodiscard]] Layout : public ThinLayout { /** * @brief Remap all input to output qubits for the given unitary op. * + * If the unitary op is a SWAP, exchange the respective program qubits. + * * @param op The unitary op. */ void remap(UnitaryInterface op) { + if (mlir::isa(op)) { + swap(op.getInQubits()[0], op.getInQubits()[1]); + } + for (const auto& [in, out] : llvm::zip_equal(op.getAllInQubits(), op.getAllOutQubits())) { remapQubitValue(in, out); diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index 50b2262a5a..841a360b3c 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -154,12 +155,11 @@ struct AStarRoutingPassSC final /// Forward layout. TypeSwitch(curr) .Case([&](UnitaryInterface op) { - if (isa(op)) { - const auto ins = getIns(op); - unit.layout().swap(ins.first, ins.second); - history.push_back( - {unit.layout().lookupHardwareIndex(ins.first), - unit.layout().lookupHardwareIndex(ins.second)}); + if (auto swap = dyn_cast(op.getOperation())) { + const auto in0 = swap.getInQubits()[0]; + const auto in1 = swap.getInQubits()[1]; + history.push_back({unit.layout().lookupHardwareIndex(in0), + unit.layout().lookupHardwareIndex(in1)}); } unit.layout().remap(op); }) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp index 79b911c119..d3957c4971 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -130,12 +131,11 @@ struct NaiveRoutingPassSC final } } - if (isa(op)) { - const auto ins = getIns(op); - unit.layout().swap(ins.first, ins.second); - history.push_back( - {unit.layout().lookupHardwareIndex(ins.first), - unit.layout().lookupHardwareIndex(ins.second)}); + if (auto swap = dyn_cast(op.getOperation())) { + const auto in0 = swap.getInQubits()[0]; + const auto in1 = swap.getInQubits()[1]; + history.push_back({unit.layout().lookupHardwareIndex(in0), + unit.layout().lookupHardwareIndex(in1)}); } unit.layout().remap(op); }) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp index 12817dc3bc..b60fe1cfef 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp @@ -113,11 +113,6 @@ struct RoutingVerificationPassSC final } } - if (isa(op)) { - const auto ins = getIns(op); - unit.layout().swap(ins.first, ins.second); - } - unit.layout().remap(op); return success(); }) From b4e658d846e0d6a8edcfc10ec28405c4526b8cea Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 4 Dec 2025 07:53:56 +0100 Subject: [PATCH 51/56] Remove casting header --- .../MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp | 1 - .../MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index 841a360b3c..909df45b50 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp index d3957c4971..d40ae89897 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include From 2122f50a42aa85715902c1600c823fcc3103b141 Mon Sep 17 00:00:00 2001 From: matthias Date: Thu, 4 Dec 2025 12:53:18 +0100 Subject: [PATCH 52/56] Apply suggestions from code review Co-authored-by: Lukas Burgholzer Signed-off-by: matthias --- CHANGELOG.md | 2 +- .../MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp | 5 ++--- .../MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp | 5 ++--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20e3e8bc90..e7f018b1ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Changed -- ♻️ Group dynamic circuit operations into scheduling units for MLIR routing ([#1301]) ([**@MatthiasReumann**]) +- ♻️ Group circuit operations into scheduling units for MLIR routing ([#1301]) ([**@MatthiasReumann**]) - 👷 Use `munich-quantum-software/setup-mlir` to set up MLIR ([#1294]) ([**@denialhaag**]) - ♻️ Preserve tuple structure and improve site type clarity of the MQT NA Default QDMI Device ([#1299]) ([**@marcelwa**]) - ♻️ Move DD package evaluation module to standalone script ([#1327]) ([**@burgholzer**]) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index 909df45b50..5226bf878d 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -156,9 +156,8 @@ struct AStarRoutingPassSC final .Case([&](UnitaryInterface op) { if (auto swap = dyn_cast(op.getOperation())) { const auto in0 = swap.getInQubits()[0]; - const auto in1 = swap.getInQubits()[1]; - history.push_back({unit.layout().lookupHardwareIndex(in0), - unit.layout().lookupHardwareIndex(in1)}); +history.emplace_back(unit.layout().lookupHardwareIndex(in0), + unit.layout().lookupHardwareIndex(in1)); } unit.layout().remap(op); }) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp index d40ae89897..ae0a19300d 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp @@ -132,9 +132,8 @@ struct NaiveRoutingPassSC final if (auto swap = dyn_cast(op.getOperation())) { const auto in0 = swap.getInQubits()[0]; - const auto in1 = swap.getInQubits()[1]; - history.push_back({unit.layout().lookupHardwareIndex(in0), - unit.layout().lookupHardwareIndex(in1)}); +history.emplace_back(unit.layout().lookupHardwareIndex(in0), +unit.layout().lookupHardwareIndex(in1)); } unit.layout().remap(op); }) From dca2e0a59159ce462e3d43ba29a6415a3f32d99b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 4 Dec 2025 11:53:46 +0000 Subject: [PATCH 53/56] =?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 --- .../MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp | 5 +++-- .../MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index 5226bf878d..8a1a713173 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -156,8 +156,9 @@ struct AStarRoutingPassSC final .Case([&](UnitaryInterface op) { if (auto swap = dyn_cast(op.getOperation())) { const auto in0 = swap.getInQubits()[0]; -history.emplace_back(unit.layout().lookupHardwareIndex(in0), - unit.layout().lookupHardwareIndex(in1)); + history.emplace_back( + unit.layout().lookupHardwareIndex(in0), + unit.layout().lookupHardwareIndex(in1)); } unit.layout().remap(op); }) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp index ae0a19300d..3dc1f666d8 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp @@ -132,8 +132,8 @@ struct NaiveRoutingPassSC final if (auto swap = dyn_cast(op.getOperation())) { const auto in0 = swap.getInQubits()[0]; -history.emplace_back(unit.layout().lookupHardwareIndex(in0), -unit.layout().lookupHardwareIndex(in1)); + history.emplace_back(unit.layout().lookupHardwareIndex(in0), + unit.layout().lookupHardwareIndex(in1)); } unit.layout().remap(op); }) From 1264ab355162c47a8ab55e5a3a3a31eb6dd85878 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 4 Dec 2025 16:43:43 +0100 Subject: [PATCH 54/56] Add 'shortestSWAPsBetween' to architecture class --- .../Transforms/Transpilation/Architecture.h | 8 ++++ .../MQTOpt/Transforms/Transpilation/Router.h | 9 +--- .../Transforms/Transpilation/Architecture.cpp | 48 ++++++++++++++----- .../Transpilation/sc/AStarRoutingPass.cpp | 1 + .../Transpilation/sc/NaiveRoutingPass.cpp | 1 + 5 files changed, 49 insertions(+), 18 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h index cc0d77fb81..7190e946d2 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h @@ -20,6 +20,7 @@ #include #include #include +#include namespace mqt::ir::opt { @@ -71,6 +72,13 @@ class Architecture { [[nodiscard]] llvm::SmallVector shortestPathBetween(QubitIndex u, QubitIndex v) const; + /** + * @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(QubitIndex u, QubitIndex v) const; + /** * @brief Return the length of the shortest path between @p u and @p v. */ diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h index ddd2d8ccaf..5a618ed092 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Router.h @@ -26,18 +26,13 @@ namespace mqt::ir::opt { class NaiveRouter { public: - [[nodiscard]] static mlir::SmallVector + [[nodiscard]] static mlir::SmallVector route(QubitIndexPair gate, const ThinLayout& layout, const Architecture& arch) { mlir::SmallVector swaps; const auto hw0 = layout.getHardwareIndex(gate.first); const auto hw1 = layout.getHardwareIndex(gate.second); - const auto path = arch.shortestPathBetween(hw0, hw1); - assert(path.size() >= 2 && "NaiveRouter::route: invalid shortest path"); - for (std::size_t i = 0; i < path.size() - 2; ++i) { - swaps.emplace_back(path[i], path[i + 1]); - } - return swaps; + return arch.shortestSWAPsBetween(hw0, hw1); } }; diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Architecture.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Architecture.cpp index 74d3dcaa4f..3311e2e10c 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Architecture.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Architecture.cpp @@ -10,8 +10,6 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" - #include #include #include @@ -19,28 +17,56 @@ #include #include #include +#include namespace mqt::ir::opt { -[[nodiscard]] llvm::SmallVector +llvm::SmallVector Architecture::shortestPathBetween(QubitIndex u, QubitIndex v) const { - llvm::SmallVector path; + 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)); } - path.push_back(v); - while (u != v) { - v = prev_[u][v]; - path.push_back(v); + llvm::SmallVector path; + QubitIndex curr = v; + path.push_back(curr); + while (curr != u) { + curr = prev_[u][curr]; + path.push_back(curr); } return path; } -[[nodiscard]] std::size_t Architecture::distanceBetween(QubitIndex u, - QubitIndex v) const { +llvm::SmallVector> +Architecture::shortestSWAPsBetween(QubitIndex u, QubitIndex 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; + QubitIndex curr = v; + QubitIndex last = v; + + while (curr != u) { + curr = prev_[u][curr]; + swaps.emplace_back(last, curr); + last = curr; + } + + return swaps; +} + +std::size_t Architecture::distanceBetween(QubitIndex u, QubitIndex 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)); @@ -80,7 +106,7 @@ void Architecture::collectNeighbours() { } } -[[nodiscard]] llvm::SmallVector +llvm::SmallVector Architecture::neighboursOf(QubitIndex u) const { return neighbours_[u]; } diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp index 8a1a713173..3d3770d41e 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/AStarRoutingPass.cpp @@ -156,6 +156,7 @@ struct AStarRoutingPassSC final .Case([&](UnitaryInterface op) { if (auto swap = dyn_cast(op.getOperation())) { const auto in0 = swap.getInQubits()[0]; + const auto in1 = swap.getInQubits()[1]; history.emplace_back( unit.layout().lookupHardwareIndex(in0), unit.layout().lookupHardwareIndex(in1)); diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp index 3dc1f666d8..f39915a40c 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp @@ -132,6 +132,7 @@ struct NaiveRoutingPassSC final if (auto swap = dyn_cast(op.getOperation())) { const auto in0 = swap.getInQubits()[0]; + const auto in1 = swap.getInQubits()[1]; history.emplace_back(unit.layout().lookupHardwareIndex(in0), unit.layout().lookupHardwareIndex(in1)); } From 869fee685899749a18b1b5dd248d15273d52e782 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 4 Dec 2025 17:15:27 +0100 Subject: [PATCH 55/56] Remove QubitIndex and add isExecutable to Architecture --- .../Transforms/Transpilation/Architecture.h | 32 +++++++------ .../MQTOpt/Transforms/Transpilation/Common.h | 15 +----- .../MQTOpt/Transforms/Transpilation/Layout.h | 48 +++++++++---------- .../Transforms/Transpilation/Architecture.cpp | 30 ++++++++---- .../Transforms/Transpilation/Common.cpp | 10 ---- .../Transforms/Transpilation/LayeredUnit.cpp | 6 +-- .../Transpilation/sc/NaiveRoutingPass.cpp | 2 +- .../Transpilation/sc/PlacementPass.cpp | 15 +++--- .../sc/RoutingVerificationPass.cpp | 2 +- 9 files changed, 77 insertions(+), 83 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h index 7190e946d2..e55777951e 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h @@ -10,6 +10,9 @@ #pragma once +#include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" + #include #include #include @@ -24,19 +27,14 @@ namespace mqt::ir::opt { -/** - * @brief Type alias for qubit indices. - */ -using QubitIndex = uint32_t; - /** * @brief A quantum accelerator's architecture. * @details Computes all-shortest paths at construction. */ class Architecture { public: - using CouplingSet = mlir::DenseSet>; - using NeighbourVector = mlir::SmallVector>; + using CouplingSet = mlir::DenseSet>; + using NeighbourVector = mlir::SmallVector>; explicit Architecture(std::string name, std::size_t nqubits, CouplingSet couplingSet) @@ -61,7 +59,7 @@ class Architecture { /** * @brief Return true if @p u and @p v are adjacent. */ - [[nodiscard]] bool areAdjacent(QubitIndex u, QubitIndex v) const { + [[nodiscard]] bool areAdjacent(uint32_t u, uint32_t v) const { return couplingSet_.contains({u, v}); } @@ -70,25 +68,31 @@ class Architecture { * @returns The path from the destination (v) to source (u) qubit. */ [[nodiscard]] llvm::SmallVector - shortestPathBetween(QubitIndex u, QubitIndex v) const; + shortestPathBetween(uint32_t u, uint32_t v) const; /** * @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(QubitIndex u, QubitIndex v) const; + [[nodiscard]] llvm::SmallVector> + shortestSWAPsBetween(uint32_t u, uint32_t v) const; /** * @brief Return the length of the shortest path between @p u and @p v. */ - [[nodiscard]] std::size_t distanceBetween(QubitIndex u, QubitIndex v) const; + [[nodiscard]] std::size_t distanceBetween(uint32_t u, uint32_t v) const; /** * @brief Collect all neighbours of @p u. */ - [[nodiscard]] llvm::SmallVector - neighboursOf(QubitIndex u) const; + [[nodiscard]] llvm::SmallVector neighboursOf(uint32_t u) const; + + /** + * @brief Validate if a two-qubit op is executable on the architecture for a + * given layout. + */ + [[nodiscard]] bool isExecutable(UnitaryInterface op, + const Layout& layout) const; private: using Matrix = llvm::SmallVector>; diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h index 421ea1b28a..959873f1e2 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h @@ -11,11 +11,11 @@ #pragma once #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" #include #include +#include #include #include #include @@ -34,7 +34,7 @@ using ValuePair = std::pair; /** * @brief Represents a pair of qubit indices. */ -using QubitIndexPair = std::pair; +using QubitIndexPair = std::pair; /** * @brief Return true if the function contains "entry_point" in the passthrough @@ -103,17 +103,6 @@ void replaceAllUsesInRegionAndChildrenExcept(mlir::Value oldValue, mlir::Operation* exceptOp, mlir::PatternRewriter& rewriter); -/** - * @brief Given a layout, validate if the two-qubit unitary op is executable on - * the targeted architecture. - * - * @param op The two-qubit unitary. - * @param layout The current layout. - * @param arch The targeted architecture. - */ -[[nodiscard]] bool isExecutable(UnitaryInterface op, const Layout& layout, - const Architecture& arch); - /** * @brief Insert SWAP ops at the rewriter's insertion point. * diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h index 2961e82c2b..ae77d656e8 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h @@ -11,8 +11,8 @@ #pragma once #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" +#include #include #include #include @@ -41,7 +41,7 @@ class [[nodiscard]] ThinLayout { * @param prog The program index. * @param hw The hardware index. */ - void add(QubitIndex prog, QubitIndex hw) { + void add(uint32_t prog, uint32_t hw) { assert(prog < programToHardware_.size() && "add: program index out of bounds"); assert(hw < hardwareToProgram_.size() && @@ -55,7 +55,7 @@ class [[nodiscard]] ThinLayout { * @param hw The hardware index. * @return The program index of the respective hardware index. */ - [[nodiscard]] QubitIndex getProgramIndex(const QubitIndex hw) const { + [[nodiscard]] uint32_t getProgramIndex(const uint32_t hw) const { assert(hw < hardwareToProgram_.size() && "getProgramIndex: hardware index out of bounds"); return hardwareToProgram_[hw]; @@ -66,7 +66,7 @@ class [[nodiscard]] ThinLayout { * @param prog The program index. * @return The hardware index of the respective program index. */ - [[nodiscard]] QubitIndex getHardwareIndex(const QubitIndex prog) const { + [[nodiscard]] uint32_t getHardwareIndex(const uint32_t prog) const { assert(prog < programToHardware_.size() && "getHardwareIndex: program index out of bounds"); return programToHardware_[prog]; @@ -79,9 +79,9 @@ class [[nodiscard]] ThinLayout { */ 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))...}; } /** @@ -91,17 +91,17 @@ class [[nodiscard]] ThinLayout { */ 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 hardware indices of two program indices. */ - void swap(const QubitIndex prog0, const QubitIndex prog1) { - const QubitIndex hw0 = programToHardware_[prog0]; - const QubitIndex hw1 = programToHardware_[prog1]; + 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]); @@ -118,12 +118,12 @@ class [[nodiscard]] ThinLayout { /** * @brief Maps a program qubit index to its hardware index. */ - mlir::SmallVector programToHardware_; + mlir::SmallVector programToHardware_; /** * @brief Maps a hardware qubit index to its program index. */ - mlir::SmallVector hardwareToProgram_; + mlir::SmallVector hardwareToProgram_; private: friend struct llvm::DenseMapInfo; @@ -146,7 +146,7 @@ class [[nodiscard]] Layout : public ThinLayout { * @param hw The hardware index. * @param q The SSA value associated with the indices. */ - void add(QubitIndex prog, QubitIndex hw, mlir::Value q) { + void add(uint32_t prog, uint32_t hw, mlir::Value q) { ThinLayout::add(prog, hw); qubits_[hw] = q; valueToMapping_.try_emplace(q, prog, hw); @@ -157,7 +157,7 @@ class [[nodiscard]] Layout : public ThinLayout { * @param q The SSA Value representing the qubit. * @return The hardware index where this qubit currently resides. */ - [[nodiscard]] QubitIndex lookupHardwareIndex(const mlir::Value q) const { + [[nodiscard]] uint32_t lookupHardwareIndex(const mlir::Value q) const { const auto it = valueToMapping_.find(q); assert(it != valueToMapping_.end() && "lookupHardwareIndex: unknown value"); return it->second.hw; @@ -169,7 +169,7 @@ class [[nodiscard]] Layout : public ThinLayout { * @return The SSA value currently representing the qubit at the hardware * location. */ - [[nodiscard]] mlir::Value lookupHardwareValue(const QubitIndex hw) const { + [[nodiscard]] mlir::Value lookupHardwareValue(const uint32_t hw) const { assert(hw < qubits_.size() && "lookupHardwareValue: hardware index out of bounds"); return qubits_[hw]; @@ -180,7 +180,7 @@ class [[nodiscard]] Layout : public ThinLayout { * @param q The SSA Value representing the qubit. * @return The program index where this qubit currently resides. */ - [[nodiscard]] QubitIndex lookupProgramIndex(const mlir::Value q) const { + [[nodiscard]] uint32_t lookupProgramIndex(const mlir::Value q) const { const auto it = valueToMapping_.find(q); assert(it != valueToMapping_.end() && "lookupProgramIndex: unknown value"); return it->second.prog; @@ -192,7 +192,7 @@ class [[nodiscard]] Layout : public ThinLayout { * @return The SSA value currently representing the qubit at the program * location. */ - [[nodiscard]] mlir::Value lookupProgramValue(const QubitIndex prog) const { + [[nodiscard]] mlir::Value lookupProgramValue(const uint32_t prog) const { assert(prog < this->programToHardware_.size() && "lookupProgramValue: program index out of bounds"); return qubits_[this->programToHardware_[prog]]; @@ -235,8 +235,8 @@ class [[nodiscard]] Layout : public ThinLayout { assert(it0 != valueToMapping_.end() && it1 != valueToMapping_.end() && "swap: unknown values"); - const QubitIndex prog0 = it0->second.prog; - const QubitIndex prog1 = it1->second.prog; + const uint32_t prog0 = it0->second.prog; + const uint32_t prog1 = it1->second.prog; std::swap(it0->second.prog, it1->second.prog); @@ -246,7 +246,7 @@ class [[nodiscard]] Layout : public ThinLayout { /** * @brief Return the current layout. */ - mlir::ArrayRef getCurrentLayout() const { + mlir::ArrayRef getCurrentLayout() const { return this->programToHardware_; } @@ -335,8 +335,8 @@ class [[nodiscard]] Layout : public ThinLayout { private: struct QubitInfo { - QubitIndex prog; - QubitIndex hw; + uint32_t prog; + uint32_t hw; }; /** @@ -355,7 +355,7 @@ class [[nodiscard]] Layout : public ThinLayout { namespace llvm { template <> struct DenseMapInfo { using Layout = mqt::ir::opt::ThinLayout; - using VectorInfo = DenseMapInfo>; + using VectorInfo = DenseMapInfo>; static Layout getEmptyKey() { Layout layout(0); diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Architecture.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Architecture.cpp index 3311e2e10c..6dbc9a1a3e 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Architecture.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Architecture.cpp @@ -10,6 +10,9 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" +#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" + #include #include #include @@ -21,7 +24,7 @@ namespace mqt::ir::opt { llvm::SmallVector -Architecture::shortestPathBetween(QubitIndex u, QubitIndex v) const { +Architecture::shortestPathBetween(uint32_t u, uint32_t v) const { if (u == v) { return {}; } @@ -32,7 +35,7 @@ Architecture::shortestPathBetween(QubitIndex u, QubitIndex v) const { } llvm::SmallVector path; - QubitIndex curr = v; + uint32_t curr = v; path.push_back(curr); while (curr != u) { curr = prev_[u][curr]; @@ -42,8 +45,8 @@ Architecture::shortestPathBetween(QubitIndex u, QubitIndex v) const { return path; } -llvm::SmallVector> -Architecture::shortestSWAPsBetween(QubitIndex u, QubitIndex v) const { +llvm::SmallVector> +Architecture::shortestSWAPsBetween(uint32_t u, uint32_t v) const { if (u == v) { return {}; } @@ -53,9 +56,9 @@ Architecture::shortestSWAPsBetween(QubitIndex u, QubitIndex v) const { " and " + std::to_string(v)); } - llvm::SmallVector> swaps; - QubitIndex curr = v; - QubitIndex last = v; + llvm::SmallVector> swaps; + uint32_t curr = v; + uint32_t last = v; while (curr != u) { curr = prev_[u][curr]; @@ -66,7 +69,7 @@ Architecture::shortestSWAPsBetween(QubitIndex u, QubitIndex v) const { return swaps; } -std::size_t Architecture::distanceBetween(QubitIndex u, QubitIndex v) const { +std::size_t Architecture::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)); @@ -106,11 +109,18 @@ void Architecture::collectNeighbours() { } } -llvm::SmallVector -Architecture::neighboursOf(QubitIndex u) const { +llvm::SmallVector Architecture::neighboursOf(uint32_t u) const { return neighbours_[u]; } +bool Architecture::isExecutable(UnitaryInterface op, + const Layout& layout) const { + assert(isTwoQubitGate(op)); + const auto ins = getIns(op); + return areAdjacent(layout.lookupHardwareIndex(ins.first), + layout.lookupHardwareIndex(ins.second)); +} + std::unique_ptr getArchitecture(const llvm::StringRef name) { if (name == "MQTTest") { static const Architecture::CouplingSet COUPLING{ diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp index c4d9e76fbc..4b4d20f575 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Common.cpp @@ -11,8 +11,6 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" #include #include @@ -140,12 +138,4 @@ void replaceAllUsesInRegionAndChildrenExcept(mlir::Value oldValue, return false; }); } - -[[nodiscard]] bool isExecutable(UnitaryInterface op, const Layout& layout, - const Architecture& arch) { - assert(isTwoQubitGate(op)); - const auto ins = getIns(op); - return arch.areAdjacent(layout.lookupHardwareIndex(ins.first), - layout.lookupHardwareIndex(ins.second)); -} } // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp index 518fbf4c26..c976fec6bb 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/LayeredUnit.cpp @@ -12,13 +12,13 @@ #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include "mlir/Dialect/MQTOpt/IR/WireIterator.h" -#include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Unit.h" #include #include +#include #include #include #include @@ -40,9 +40,9 @@ namespace { /// @brief A wire links a WireIterator to a program index. struct Wire { - Wire(const WireIterator& it, QubitIndex index) : it(it), index(index) {} + Wire(const WireIterator& it, uint32_t index) : it(it), index(index) {} WireIterator it; - QubitIndex index; + uint32_t index; }; /// @brief Map to handle multi-qubit gates when traversing the def-use chain. diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp index f39915a40c..32d4cb5d99 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/NaiveRoutingPass.cpp @@ -108,7 +108,7 @@ struct NaiveRoutingPassSC final TypeSwitch(&curr) .Case([&](UnitaryInterface op) { if (isTwoQubitGate(op)) { - if (!isExecutable(op, unit.layout(), *arch)) { + if (!arch->isExecutable(op, unit.layout())) { const auto ins = getIns(op); const auto gate = std::make_pair( unit.layout().lookupProgramIndex(ins.first), diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp index c0234c227d..c82a55a56f 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/PlacementPass.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -64,7 +65,7 @@ constexpr std::size_t IF_PARENT_DEPTH = 2UL; /** * @brief A queue of hardware indices. */ -using HardwareIndexPool = std::deque; +using HardwareIndexPool = std::deque; /** * @brief A base class for all initial placement strategies. @@ -77,7 +78,7 @@ class InitialPlacer { InitialPlacer(InitialPlacer&&) noexcept = default; InitialPlacer& operator=(InitialPlacer&&) noexcept = default; virtual ~InitialPlacer() = default; - [[nodiscard]] virtual SmallVector operator()() = 0; + [[nodiscard]] virtual SmallVector operator()() = 0; }; /** @@ -87,8 +88,8 @@ class IdentityPlacer final : public InitialPlacer { public: explicit IdentityPlacer(const std::size_t nqubits) : nqubits_(nqubits) {} - [[nodiscard]] SmallVector operator()() override { - SmallVector mapping(nqubits_); + [[nodiscard]] SmallVector operator()() override { + SmallVector mapping(nqubits_); std::iota(mapping.begin(), mapping.end(), 0); return mapping; } @@ -105,8 +106,8 @@ class RandomPlacer final : public InitialPlacer { explicit RandomPlacer(const std::size_t nqubits, const std::mt19937_64& rng) : nqubits_(nqubits), rng_(rng) {} - [[nodiscard]] SmallVector operator()() override { - SmallVector mapping(nqubits_); + [[nodiscard]] SmallVector operator()() override { + SmallVector mapping(nqubits_); std::iota(mapping.begin(), mapping.end(), 0); std::ranges::shuffle(mapping, rng_); return mapping; @@ -148,7 +149,7 @@ WalkResult handleFunc(func::FuncOp op, PlacementContext& ctx, /// Create static / hardware qubits for entry_point functions. SmallVector qubits(ctx.arch->nqubits()); - for (QubitIndex i = 0; i < ctx.arch->nqubits(); ++i) { + for (uint32_t i = 0; i < ctx.arch->nqubits(); ++i) { auto qubitOp = rewriter.create(rewriter.getInsertionPoint()->getLoc(), i); rewriter.setInsertionPointAfter(qubitOp); diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp index b60fe1cfef..5f237a9307 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/sc/RoutingVerificationPass.cpp @@ -99,7 +99,7 @@ struct RoutingVerificationPassSC final -> LogicalResult { if (isTwoQubitGate(op)) { /// Verify that the two-qubit gate is executable. - if (!isExecutable(op, unit.layout(), *arch)) { + if (!arch->isExecutable(op, unit.layout())) { const auto ins = getIns(op); const auto hw0 = unit.layout().lookupHardwareIndex(ins.first); From b050bf5323d07cb89d9f91e85d1376e3db0d1997 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 4 Dec 2025 17:25:42 +0100 Subject: [PATCH 56/56] Fix linting --- .../Transforms/Transpilation/Architecture.h | 7 ------ .../Transforms/Transpilation/Architecture.cpp | 23 ++----------------- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h index e55777951e..9e0a0d11f2 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h @@ -63,13 +63,6 @@ class Architecture { return couplingSet_.contains({u, v}); } - /** - * @brief Collect the shortest path between @p u and @p v. - * @returns The path from the destination (v) to source (u) qubit. - */ - [[nodiscard]] llvm::SmallVector - shortestPathBetween(uint32_t u, uint32_t v) const; - /** * @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. diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Architecture.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Architecture.cpp index 6dbc9a1a3e..c8da124504 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Architecture.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Transpilation/Architecture.cpp @@ -10,9 +10,11 @@ #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Architecture.h" +#include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Common.h" #include "mlir/Dialect/MQTOpt/Transforms/Transpilation/Layout.h" +#include #include #include #include @@ -23,27 +25,6 @@ #include namespace mqt::ir::opt { -llvm::SmallVector -Architecture::shortestPathBetween(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 path; - uint32_t curr = v; - path.push_back(curr); - while (curr != u) { - curr = prev_[u][curr]; - path.push_back(curr); - } - - return path; -} llvm::SmallVector> Architecture::shortestSWAPsBetween(uint32_t u, uint32_t v) const {