Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
📝 WalkthroughSummary by CodeRabbit
WalkthroughIntroduces a new MLIR passes library for quantum circuit mapping, featuring an Architecture class that models quantum accelerator connectivity, a HeuristicMappingPass implementing A* routing for qubit layout optimization, and comprehensive build system integration with unit tests for validation. Changes
Sequence DiagramsequenceDiagram
participant Client as QC Program
participant Pass as HeuristicMappingPass
participant Arch as Architecture
participant Search as A* Search
participant Layout as Layout Manager
participant IR as MLIR IR
Client->>Pass: runOnOperation()
Pass->>Pass: collectDynamicQubits()
Pass->>Pass: computeBidirectionalLayers()
Pass->>Pass: collectLayers()
loop For each repeat iteration
Pass->>Search: search(currentLayout, layers)
Search->>Arch: areAdjacent(u, v)
Arch-->>Search: adjacency result
Search->>Arch: distanceBetween(u, v)
Arch-->>Search: distance via Floyd-Warshall
Search->>Layout: evaluate swap candidates
Layout-->>Search: goal check result
Search-->>Pass: optimal swap sequence
Pass->>Layout: apply swaps
end
Pass->>Pass: place() - map to static qubits
Pass->>Pass: routeHot() - insert SWAPs in IR
Pass->>IR: emit SWAPOp instructions
IR-->>Client: mapped QCO program
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@mlir/lib/Compiler/CMakeLists.txt`:
- Line 23: The target currently links QcoPasses as a PUBLIC dependency, exposing
it transitively; change the linkage to PRIVATE so QcoPasses is not exported in
the public interface. Locate the target_link_libraries line that references
QcoPasses in the mlir/lib/Compiler/CMakeLists.txt and replace PUBLIC with
PRIVATE (keeping the same target name and ordering), since public headers under
mlir/include/mlir/Compiler/* do not expose QcoPasses symbols and only
implementation uses passes like QCToQCO, QCOToQC, and QCToQIR.
In `@mlir/lib/Passes/Mapping/Architecture.cpp`:
- Around line 44-47: The loop in Architecture.cpp uses u and v from couplingSet_
as direct indices into dist_, prev_ and neighbours_ without bounds checks;
validate that u and v are within the valid ranges before indexing (e.g., ensure
u < dist_.size() and v < dist_[u].size() and v < prev_[u].size() and u/v <
neighbours_.size() as appropriate), and if an index is out-of-range either skip
the pair (continue) or emit a clear error/diagnostic; apply the same validation
to the other occurrences where couplingSet_ pairs are indexed (the references
around the lines corresponding to the second occurrence at 70-71) and use the
unique symbols couplingSet_, dist_, prev_, neighbours_ to locate and fix all
spots.
In `@mlir/lib/Passes/Mapping/HeuristicMapping.cpp`:
- Line 331: Remove the unconditional call to layout.dump() from the normal pass
path and only print when debugging is enabled: locate the call to layout.dump()
in HeuristicMapping (the pass implementation in HeuristicMapping.cpp) and wrap
it with the pass/module debug check (e.g., a conditional using LLVM_DEBUG,
DEBUG_TYPE, or a isDebugEnabled-style flag used in this codebase) so that
layout.dump() executes only under debug logging; ensure you reference the same
debug macro or helper used elsewhere in this file to keep behavior consistent.
- Around line 313-321: The code assumes the number of dynamic qubits (dyn from
collectDynamicQubits) fits the hardware; if dyn.size() > arch.nqubits()
subsequent Layout lookups (Layout::identity, etc.) will index out of range; add
a guard after computing dyn (and before Layout::identity and the SABRE loop)
that checks if dyn.size() > arch.nqubits() and handle it deterministically
(e.g., return a failed LogicalResult/Optional, emit an error/message via the
existing logging/diagnostic mechanism, or early-clamp/resize mappings) so later
uses of layout and any indexing into arch.nqubits()-sized structures are safe;
reference symbols: collectDynamicQubits, dyn, arch.nqubits(), Layout::identity,
and the repeats loop.
- Around line 303-309: The code currently hardcodes the architecture when
constructing Architecture arch(...) which ignores the user-provided -arch
option; update the constructor to use the pass option (e.g. this->archName)
instead of the literal "RigettiNovera" and avoid hardcoding the qubit
count/edges: replace the call Architecture arch("RigettiNovera", 9, {...}) with
a construction that uses the selected architecture name and its associated
topology (for example Architecture arch(this->archName, /*numQubits*/
this->nqubits or query a helper that returns the node count and edge list for
archName) or call a factory like Architecture::fromName(this->archName)),
ensuring the code references the pass field (this->archName) so the -arch option
is honored; keep Parameters weights(this->alpha, this->lambda, this->nlookahead)
as-is.
- Around line 609-620: The SWAPOp is being created with a stale insertion point
because rewriter.create<SWAPOp>(unknown, ...) is called without first moving the
insertion point; before calling rewriter.create in the loop that iterates over
swaps (the block using wires, swaps, in0, in1 and then calling
rewriter.replaceAllUsesExcept), set the rewriter insertion point to the current
wire/front location (e.g., use the appropriate rewriter.setInsertionPoint or
setInsertionPointAfter API on the operation representing the wire front) so the
SWAPOp is inserted at the correct place relative to in0/in1, then create the
SWAPOp and perform replaceAllUsesExcept as shown.
- Line 478: The variable "step" is declared with the unsigned type std::size_t
but needs to hold negative values for Direction::Backward; change its type to a
signed pointer-difference type (e.g., std::ptrdiff_t) so it matches
WireIterator::difference_type and avoids underflow when set to -1; update the
declaration near the constexpr std::size_t step = d == Direction::Forward ? 1 :
-1; (used with std::ranges::advance and wire iteration) to use std::ptrdiff_t
(or WireIterator::difference_type) instead.
In `@mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp`:
- Line 135: Remove the unconditional IR dumps from the test by deleting or
gating the module->dump() calls so they do not print on every successful run;
locate the invocations of module->dump() in test_heuristic_mapping.cpp (the
calls at and near the two occurrences noted) and either remove them or wrap them
behind a debug/verbose guard (e.g., LLVM_DEBUG/if (verbose)) so CI output is not
noisy while preserving the ability to enable dumps for debugging.
- Around line 66-73: The current code uses mappings[op.getQubit(...)] which can
insert default entries and only checks op.getNumQubits() > 1; change this to
defensively lookup qubit indices (e.g., use mappings.find or contains) for
op.getQubit(0) and op.getQubit(1), verify both exist before using them, and
explicitly require op.getNumQubits() == 2 (reject ops with 0, 1, or >2 qubits) —
if a lookup fails or the arity is not 2, set executable = false and return
WalkResult::interrupt() (retain checks using arch.areAdjacent(i0, i1) only after
successful lookups).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: dccba142-3321-49bd-bcfa-8639de6bb40c
📒 Files selected for processing (14)
mlir/include/mlir/CMakeLists.txtmlir/include/mlir/Passes/CMakeLists.txtmlir/include/mlir/Passes/Mapping/Architecture.hmlir/include/mlir/Passes/Passes.hmlir/include/mlir/Passes/Passes.tdmlir/lib/CMakeLists.txtmlir/lib/Compiler/CMakeLists.txtmlir/lib/Passes/CMakeLists.txtmlir/lib/Passes/Mapping/Architecture.cppmlir/lib/Passes/Mapping/HeuristicMapping.cppmlir/unittests/CMakeLists.txtmlir/unittests/Passes/CMakeLists.txtmlir/unittests/Passes/Mapping/CMakeLists.txtmlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp
| QCToQCO | ||
| QCOToQC | ||
| QCToQIR | ||
| QcoPasses |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
🧩 Analysis chain
🏁 Script executed:
# First, check the actual CMakeLists.txt file to see current state
cat -n mlir/lib/Compiler/CMakeLists.txt | head -40Repository: munich-quantum-toolkit/core
Length of output: 1195
🏁 Script executed:
# Check for QcoPasses references in public headers
fd -e h -e hpp . mlir/include/mlir/Compiler -type f -exec grep -l "QcoPasses\|qco::create\|HeuristicMapping" {} \;Repository: munich-quantum-toolkit/core
Length of output: 241
🏁 Script executed:
# Check what's actually in the public headers
find mlir/include/mlir/Compiler -type f \( -name "*.h" -o -name "*.hpp" \) -exec basename {} \;Repository: munich-quantum-toolkit/core
Length of output: 89
🏁 Script executed:
# Search for QcoPasses usage in CompilerPipeline implementation
rg -n "QcoPasses|qco::.*Pass|HeuristicMapping|Passes\.h" mlir/lib/Compiler/CompilerPipeline.cppRepository: munich-quantum-toolkit/core
Length of output: 109
🏁 Script executed:
# Check if CompilerPipeline.h (public header) references QcoPasses
cat mlir/include/mlir/Compiler/CompilerPipeline.hRepository: munich-quantum-toolkit/core
Length of output: 4359
🏁 Script executed:
# Check the actual CompilerPipeline.cpp file to see QcoPasses usage
cat mlir/lib/Compiler/CompilerPipeline.cpp | head -100Repository: munich-quantum-toolkit/core
Length of output: 3215
🏁 Script executed:
# Search more broadly for any QCO-related pass references in the implementation
rg -n "qco::|QcoPasses|HeuristicMapping|Dialects/QCO" mlir/lib/Compiler/CompilerPipeline.cppRepository: munich-quantum-toolkit/core
Length of output: 53
🏁 Script executed:
# Check what headers are included in CompilerPipeline.cpp
rg -n "^#include" mlir/lib/Compiler/CompilerPipeline.cppRepository: munich-quantum-toolkit/core
Length of output: 557
🏁 Script executed:
# Check the full CompilerPipeline.cpp implementation to see if QcoPasses is referenced
wc -l mlir/lib/Compiler/CompilerPipeline.cppRepository: munich-quantum-toolkit/core
Length of output: 113
🏁 Script executed:
# Look at the entire CompilerPipeline.cpp implementation to find all pass additions
sed -n '50,250p' mlir/lib/Compiler/CompilerPipeline.cppRepository: munich-quantum-toolkit/core
Length of output: 5918
🏁 Script executed:
# Search for any reference to QcoPasses or qco-specific passes in the file
grep -n "addPass\|createHeuristicMapping\|QcoPasses" mlir/lib/Compiler/CompilerPipeline.cppRepository: munich-quantum-toolkit/core
Length of output: 268
🏁 Script executed:
# Check what QcoPasses library actually provides (CMakeLists for QcoPasses)
find mlir -name CMakeLists.txt -exec grep -l "QcoPasses" {} \;Repository: munich-quantum-toolkit/core
Length of output: 219
🏁 Script executed:
# Check the QcoPasses CMakeLists to see what it provides and depends on
cat mlir/lib/Passes/CMakeLists.txtRepository: munich-quantum-toolkit/core
Length of output: 1070
🏁 Script executed:
# Check if there's a QcoPasses header being exposed
find mlir/include -name "*QcoPasses*" -o -name "*Passes.h" | grep -i qcoRepository: munich-quantum-toolkit/core
Length of output: 53
🏁 Script executed:
# Check mlir/include/mlir/Passes/CMakeLists.txt
cat mlir/include/mlir/Passes/CMakeLists.txtRepository: munich-quantum-toolkit/core
Length of output: 478
🏁 Script executed:
# Double-check: confirm CompilerPipeline.cpp has no include directives for QcoPasses headers
rg "mlir/Passes|QcoPasses" mlir/lib/Compiler/CompilerPipeline.cppRepository: munich-quantum-toolkit/core
Length of output: 53
🏁 Script executed:
# Check if any file in mlir/include/mlir/Compiler/ references QcoPasses
rg "QcoPasses|mlir/Passes/Passes.h" mlir/include/mlir/Compiler/Repository: munich-quantum-toolkit/core
Length of output: 53
🏁 Script executed:
# Verify what's actually in the Passes include directory
ls -la mlir/include/mlir/Passes/Repository: munich-quantum-toolkit/core
Length of output: 420
🏁 Script executed:
# Final verification: Check what createQCToQCO, createQCOToQC, createQCToQIR actually come from
rg -l "createQCToQCO|createQCOToQC|createQCToQIR" mlir/include/mlir/Conversion/Repository: munich-quantum-toolkit/core
Length of output: 53
🏁 Script executed:
# Confirm that the pass creation functions are from conversion headers, not QcoPasses
rg "createQCToQCO|createQCOToQC|createQCToQIR" mlir/include/mlir/Conversion/ -A2Repository: munich-quantum-toolkit/core
Length of output: 53
🏁 Script executed:
# Check what headers are in mlir/include/mlir/Passes/
cat mlir/include/mlir/Passes/Passes.hRepository: munich-quantum-toolkit/core
Length of output: 912
🏁 Script executed:
# Verify the Conversion headers don't depend on QcoPasses
rg "QcoPasses|mlir/Passes/Passes.h" mlir/include/mlir/Conversion/QCToQCO/ mlir/include/mlir/Conversion/QCOToQC/ mlir/include/mlir/Conversion/QCToQIR/Repository: munich-quantum-toolkit/core
Length of output: 53
Consider making QcoPasses a PRIVATE dependency.
Line 23 currently exports QcoPasses transitively via PUBLIC. The public headers (mlir/include/mlir/Compiler/*) do not expose any QcoPasses symbols, and the implementation only uses conversion passes (QCToQCO, QCOToQC, QCToQIR) and standard MLIR transformation passes. Moving QcoPasses to PRIVATE keeps the public link surface minimal.
♻️ Possible CMake adjustment
add_mlir_library(
MQTCompilerPipeline
CompilerPipeline.cpp
ADDITIONAL_HEADER_DIRS
${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Compiler
LINK_LIBS
PUBLIC
MLIRPass
MLIRTransforms
MLIRTransformUtils
QCToQCO
QCOToQC
QCToQIR
- QcoPasses
MQT::MLIRSupport
+ PRIVATE
+ QcoPasses
DISABLE_INSTALL)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@mlir/lib/Compiler/CMakeLists.txt` at line 23, The target currently links
QcoPasses as a PUBLIC dependency, exposing it transitively; change the linkage
to PRIVATE so QcoPasses is not exported in the public interface. Locate the
target_link_libraries line that references QcoPasses in the
mlir/lib/Compiler/CMakeLists.txt and replace PUBLIC with PRIVATE (keeping the
same target name and ordering), since public headers under
mlir/include/mlir/Compiler/* do not expose QcoPasses symbols and only
implementation uses passes like QCToQCO, QCOToQC, and QCToQIR.
| for (const auto& [u, v] : couplingSet_) { | ||
| dist_[u][v] = 1; | ||
| prev_[u][v] = u; | ||
| } |
There was a problem hiding this comment.
Validate coupling-map indices before indexing matrices/vectors.
u/v from couplingSet_ are used as direct indices without range checks. Invalid input can trigger OOB access in dist_, prev_, and neighbours_.
🛡️ Proposed fix
void Architecture::floydWarshallWithPathReconstruction() {
for (const auto& [u, v] : couplingSet_) {
+ if (u >= nqubits_ || v >= nqubits_) {
+ report_fatal_error("coupling edge out of range: (" + Twine(u) + ", " +
+ Twine(v) + ") for architecture size " +
+ Twine(nqubits_));
+ }
dist_[u][v] = 1;
prev_[u][v] = u;
}Also applies to: 70-71
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@mlir/lib/Passes/Mapping/Architecture.cpp` around lines 44 - 47, The loop in
Architecture.cpp uses u and v from couplingSet_ as direct indices into dist_,
prev_ and neighbours_ without bounds checks; validate that u and v are within
the valid ranges before indexing (e.g., ensure u < dist_.size() and v <
dist_[u].size() and v < prev_[u].size() and u/v < neighbours_.size() as
appropriate), and if an index is out-of-range either skip the pair (continue) or
emit a clear error/diagnostic; apply the same validation to the other
occurrences where couplingSet_ pairs are indexed (the references around the
lines corresponding to the second occurrence at 70-71) and use the unique
symbols couplingSet_, dist_, prev_, neighbours_ to locate and fix all spots.
| Parameters weights(this->alpha, this->lambda, this->nlookahead); | ||
| // TODO: Hardcoded architecture. | ||
| Architecture arch("RigettiNovera", 9, | ||
| {{0, 3}, {3, 0}, {0, 1}, {1, 0}, {1, 4}, {4, 1}, | ||
| {1, 2}, {2, 1}, {2, 5}, {5, 2}, {3, 6}, {6, 3}, | ||
| {3, 4}, {4, 3}, {4, 7}, {7, 4}, {4, 5}, {5, 4}, | ||
| {5, 8}, {8, 5}, {6, 7}, {7, 6}, {7, 8}, {8, 7}}); |
There was a problem hiding this comment.
The -arch pass option is currently ignored.
Line 305 hardcodes RigettiNovera regardless of archName, so user-supplied architecture selection has no effect.
🔧 Proposed fix
- // TODO: Hardcoded architecture.
+ if (!archName.empty() && archName != "RigettiNovera") {
+ getOperation()->emitError()
+ << "unsupported architecture '" << archName
+ << "' (currently only 'RigettiNovera' is implemented)";
+ signalPassFailure();
+ return;
+ }
Architecture arch("RigettiNovera", 9,
{{0, 3}, {3, 0}, {0, 1}, {1, 0}, {1, 4}, {4, 1},
{1, 2}, {2, 1}, {2, 5}, {5, 2}, {3, 6}, {6, 3},
{3, 4}, {4, 3}, {4, 7}, {7, 4}, {4, 5}, {5, 4},
{5, 8}, {8, 5}, {6, 7}, {7, 6}, {7, 8}, {8, 7}});🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@mlir/lib/Passes/Mapping/HeuristicMapping.cpp` around lines 303 - 309, The
code currently hardcodes the architecture when constructing Architecture
arch(...) which ignores the user-provided -arch option; update the constructor
to use the pass option (e.g. this->archName) instead of the literal
"RigettiNovera" and avoid hardcoding the qubit count/edges: replace the call
Architecture arch("RigettiNovera", 9, {...}) with a construction that uses the
selected architecture name and its associated topology (for example Architecture
arch(this->archName, /*numQubits*/ this->nqubits or query a helper that returns
the node count and edge list for archName) or call a factory like
Architecture::fromName(this->archName)), ensuring the code references the pass
field (this->archName) so the -arch option is honored; keep Parameters
weights(this->alpha, this->lambda, this->nlookahead) as-is.
| const auto dyn = collectDynamicQubits(func.getFunctionBody()); | ||
| const auto [ltr, rtl] = computeBidirectionalLayers(dyn); | ||
|
|
||
| // Use the SABRE Approach to improve the initial layout choice (here: | ||
| // identity): Traverse the layers from left-to-right-to-left and | ||
| // cold-route along the way. Repeat this procedure "repeats" times. | ||
|
|
||
| Layout layout = Layout::identity(arch.nqubits()); | ||
| for (std::size_t r = 0; r < this->repeats; ++r) { |
There was a problem hiding this comment.
Guard against circuits that exceed hardware capacity.
If dyn.size() > arch.nqubits(), later layout lookups index out of range and can hit UB in release builds.
🛡️ Proposed fix
const auto dyn = collectDynamicQubits(func.getFunctionBody());
+ if (dyn.size() > arch.nqubits()) {
+ func.emitError() << "heuristic mapping requires at most "
+ << arch.nqubits() << " qubits, got " << dyn.size();
+ signalPassFailure();
+ return;
+ }
const auto [ltr, rtl] = computeBidirectionalLayers(dyn);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@mlir/lib/Passes/Mapping/HeuristicMapping.cpp` around lines 313 - 321, The
code assumes the number of dynamic qubits (dyn from collectDynamicQubits) fits
the hardware; if dyn.size() > arch.nqubits() subsequent Layout lookups
(Layout::identity, etc.) will index out of range; add a guard after computing
dyn (and before Layout::identity and the SABRE loop) that checks if dyn.size() >
arch.nqubits() and handle it deterministically (e.g., return a failed
LogicalResult/Optional, emit an error/message via the existing
logging/diagnostic mechanism, or early-clamp/resize mappings) so later uses of
layout and any indexing into arch.nqubits()-sized structures are safe; reference
symbols: collectDynamicQubits, dyn, arch.nqubits(), Layout::identity, and the
repeats loop.
| return; | ||
| } | ||
| } | ||
| layout.dump(); |
There was a problem hiding this comment.
Remove unconditional debug output from the normal pass path.
Line 331 prints layout data for every function execution; this should be debug-gated.
🧹 Proposed fix
- layout.dump();
+ LLVM_DEBUG(layout.dump());📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| layout.dump(); | |
| LLVM_DEBUG(layout.dump()); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@mlir/lib/Passes/Mapping/HeuristicMapping.cpp` at line 331, Remove the
unconditional call to layout.dump() from the normal pass path and only print
when debugging is enabled: locate the call to layout.dump() in HeuristicMapping
(the pass implementation in HeuristicMapping.cpp) and wrap it with the
pass/module debug check (e.g., a conditional using LLVM_DEBUG, DEBUG_TYPE, or a
isDebugEnabled-style flag used in this codebase) so that layout.dump() executes
only under debug logging; ensure you reference the same debug macro or helper
used elsewhere in this file to keep behavior consistent.
| */ | ||
| template <Direction d> | ||
| static SmallVector<Layer> collectLayers(MutableArrayRef<WireIterator> wires) { | ||
| constexpr std::size_t step = d == Direction::Forward ? 1 : -1; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Verify the unsigned-backstep declaration is present
rg -nP 'constexpr\s+std::size_t\s+step\s*=\s*d\s*==\s*Direction::Forward\s*\?\s*1\s*:\s*-1' mlir/lib/Passes/Mapping/HeuristicMapping.cpp
# Also check the file structure around this line
echo "---"
echo "Context around line 478:"
sed -n '470,490p' mlir/lib/Passes/Mapping/HeuristicMapping.cppRepository: munich-quantum-toolkit/core
Length of output: 974
🏁 Script executed:
# Check how the 'step' variable is used
rg -A 10 -B 5 'constexpr.*step.*Direction::Forward' mlir/lib/Passes/Mapping/HeuristicMapping.cppRepository: munich-quantum-toolkit/core
Length of output: 622
🏁 Script executed:
# Search for LLVM equivalents or patterns in MLIR codebase
echo "Searching for LLVM equivalents to std::size_t/std::ptrdiff_t..."
rg -t cpp 'llvm::ssize_t|llvm::ptrdiff_t|llvm::ptrdiff_t' mlir/lib/Passes/Mapping/HeuristicMapping.cpp | head -20
echo "---"
echo "Checking LLVM types used in this file:"
rg -t cpp 'llvm::' mlir/lib/Passes/Mapping/HeuristicMapping.cpp | head -20Repository: munich-quantum-toolkit/core
Length of output: 359
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find how 'step' is used in the function after line 478
rg -A 50 'constexpr.*step.*Direction::Forward' mlir/lib/Passes/Mapping/HeuristicMapping.cpp | grep -E 'step|advance|std::ranges'Repository: munich-quantum-toolkit/core
Length of output: 478
🏁 Script executed:
# Get more context - the full loop that uses step
sed -n '478,530p' mlir/lib/Passes/Mapping/HeuristicMapping.cppRepository: munich-quantum-toolkit/core
Length of output: 2098
🏁 Script executed:
# Check if std::ranges::advance is used and how it handles the step
rg -B 5 -A 15 'std::ranges::advance' mlir/lib/Passes/Mapping/HeuristicMapping.cppRepository: munich-quantum-toolkit/core
Length of output: 2842
🏁 Script executed:
# Check the MLIR includes and how this file uses LLVM types
head -50 mlir/lib/Passes/Mapping/HeuristicMapping.cppRepository: munich-quantum-toolkit/core
Length of output: 1414
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check LLVM/MLIR convention for signed difference type
rg -t cpp 'std::ptrdiff_t|llvm::ssize_t|ptrdiff_t' mlir/ --max-count=5
echo "---"
# Check how other MLIR code handles ranges::advance with negative steps
rg -B 5 -A 2 'std::ranges::advance' mlir/ | head -40Repository: munich-quantum-toolkit/core
Length of output: 3214
🏁 Script executed:
# Check if there's a typed Direction enum
rg -B 5 -A 10 'enum.*Direction|class Direction' mlir/Repository: munich-quantum-toolkit/core
Length of output: 1309
Change std::size_t to std::ptrdiff_t to handle backward traversal correctly.
The step variable must support negative values when traversing backward (Direction::Backward passes -1). Storing -1 in std::size_t causes unsigned underflow and wraps to SIZE_MAX, which is then passed to std::ranges::advance(). Since WireIterator::difference_type is std::ptrdiff_t, the step must use the same signed type.
Proposed fix
- constexpr std::size_t step = d == Direction::Forward ? 1 : -1;
+ constexpr std::ptrdiff_t step = d == Direction::Forward ? 1 : -1;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| constexpr std::size_t step = d == Direction::Forward ? 1 : -1; | |
| constexpr std::ptrdiff_t step = d == Direction::Forward ? 1 : -1; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@mlir/lib/Passes/Mapping/HeuristicMapping.cpp` at line 478, The variable
"step" is declared with the unsigned type std::size_t but needs to hold negative
values for Direction::Backward; change its type to a signed pointer-difference
type (e.g., std::ptrdiff_t) so it matches WireIterator::difference_type and
avoids underflow when set to -1; update the declaration near the constexpr
std::size_t step = d == Direction::Forward ? 1 : -1; (used with
std::ranges::advance and wire iteration) to use std::ptrdiff_t (or
WireIterator::difference_type) instead.
| const auto unknown = rewriter.getUnknownLoc(); | ||
| for (const auto [hw0, hw1] : *swaps) { | ||
| const auto in0 = wires[hw0].qubit(); | ||
| const auto in1 = wires[hw1].qubit(); | ||
|
|
||
| auto op = rewriter.create<SWAPOp>(unknown, in0, in1); | ||
| const auto out0 = op.getQubit0Out(); | ||
| const auto out1 = op.getQubit1Out(); | ||
|
|
||
| rewriter.replaceAllUsesExcept(in0, out1, op); | ||
| rewriter.replaceAllUsesExcept(in1, out0, op); | ||
|
|
There was a problem hiding this comment.
SWAP ops are created with a stale insertion point.
rewriter.create<SWAPOp> on Line 614 has no local setInsertionPoint, so insertion can occur far from the wire front (leftover insertion point from previous rewrites), which risks invalid ordering/dominance after replaceAllUsesExcept.
🔧 Proposed fix
const auto unknown = rewriter.getUnknownLoc();
for (const auto [hw0, hw1] : *swaps) {
const auto in0 = wires[hw0].qubit();
const auto in1 = wires[hw1].qubit();
+ assert(in0.getNumUses() == 1 && in1.getNumUses() == 1 &&
+ "expected linear qubit wires");
+ auto* use0 = *in0.getUsers().begin();
+ auto* use1 = *in1.getUsers().begin();
+ auto* insertBefore = use0->isBeforeInBlock(use1) ? use0 : use1;
+ rewriter.setInsertionPoint(insertBefore);
auto op = rewriter.create<SWAPOp>(unknown, in0, in1);
const auto out0 = op.getQubit0Out();
const auto out1 = op.getQubit1Out();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const auto unknown = rewriter.getUnknownLoc(); | |
| for (const auto [hw0, hw1] : *swaps) { | |
| const auto in0 = wires[hw0].qubit(); | |
| const auto in1 = wires[hw1].qubit(); | |
| auto op = rewriter.create<SWAPOp>(unknown, in0, in1); | |
| const auto out0 = op.getQubit0Out(); | |
| const auto out1 = op.getQubit1Out(); | |
| rewriter.replaceAllUsesExcept(in0, out1, op); | |
| rewriter.replaceAllUsesExcept(in1, out0, op); | |
| const auto unknown = rewriter.getUnknownLoc(); | |
| for (const auto [hw0, hw1] : *swaps) { | |
| const auto in0 = wires[hw0].qubit(); | |
| const auto in1 = wires[hw1].qubit(); | |
| assert(in0.getNumUses() == 1 && in1.getNumUses() == 1 && | |
| "expected linear qubit wires"); | |
| auto* use0 = *in0.getUsers().begin(); | |
| auto* use1 = *in1.getUsers().begin(); | |
| auto* insertBefore = use0->isBeforeInBlock(use1) ? use0 : use1; | |
| rewriter.setInsertionPoint(insertBefore); | |
| auto op = rewriter.create<SWAPOp>(unknown, in0, in1); | |
| const auto out0 = op.getQubit0Out(); | |
| const auto out1 = op.getQubit1Out(); | |
| rewriter.replaceAllUsesExcept(in0, out1, op); | |
| rewriter.replaceAllUsesExcept(in1, out0, op); | |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@mlir/lib/Passes/Mapping/HeuristicMapping.cpp` around lines 609 - 620, The
SWAPOp is being created with a stale insertion point because
rewriter.create<SWAPOp>(unknown, ...) is called without first moving the
insertion point; before calling rewriter.create in the loop that iterates over
swaps (the block using wires, swaps, in0, in1 and then calling
rewriter.replaceAllUsesExcept), set the rewriter insertion point to the current
wire/front location (e.g., use the appropriate rewriter.setInsertionPoint or
setInsertionPointAfter API on the operation representing the wire front) so the
SWAPOp is inserted at the correct place relative to in0/in1, then create the
SWAPOp and perform replaceAllUsesExcept as shown.
| if (op.getNumQubits() > 1) { | ||
| const auto i0 = mappings[op.getQubit(0)]; | ||
| const auto i1 = mappings[op.getQubit(1)]; | ||
| if (!arch.areAdjacent(i0, i1)) { | ||
| executable = false; | ||
| return WalkResult::interrupt(); | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Read the test file around the flagged lines to understand context
cat -n mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp | sed -n '40,85p'Repository: munich-quantum-toolkit/core
Length of output: 1978
🌐 Web query:
llvm::DenseMap operator[] behavior when key is missing insert default value
💡 Result:
llvm::DenseMap::operator[] inserts when the key is missing.
- If
Keyis not present,operator[]callslookupOrInsertIntoBucket(Key)and returns a reference to the newly created mapped value. The insertion constructs the value withValueT(...); foroperator[]there are no constructor args, so this becomesValueT()(value-/default-construction). This meansValueTmust be default-constructible. [1][2] - If
Keyis present, it returns a reference to the existing value (no insertion). [2]
If you want “get with default” without inserting, use lookup() (returns ValueT() if missing) or lookup_or(key, default) (returns your provided default). [3]
Sources:
[1] LLVM DenseMap.h source (definition of operator[] and lookupOrInsertIntoBucket) (llvm.org)
[2] LLVM DenseMapBase doxygen (shows operator[] definitions and where they live) (llvm.org)
[3] LLVM DenseMapBase doxygen / DenseMap.h (lookup / lookup_or behavior) (llvm.org)
Citations:
- 1: https://llvm.org/doxygen/DenseMap_8h_source.html
- 2: https://llvm.org/doxygen/classllvm_1_1DenseMapBase.html
- 3: https://llvm.org/doxygen/DenseMap_8h_source.html
Replace operator[] with defensive lookups and explicitly check for 2-qubit gates.
Lines 67–68 use mappings[op.getQubit(...)], which inserts a default value (0) for missing keys instead of failing. This silently masks incomplete mappings. Additionally, the condition op.getNumQubits() > 1 checks only the first two qubits of multi-qubit operations; ops with 3+ qubits should be explicitly rejected.
Proposed fix
std::ignore = module->walk([&](qc::UnitaryOpInterface op) {
- if (op.getNumQubits() > 1) {
- const auto i0 = mappings[op.getQubit(0)];
- const auto i1 = mappings[op.getQubit(1)];
- if (!arch.areAdjacent(i0, i1)) {
+ if (op.getNumQubits() == 2) {
+ const auto it0 = mappings.find(op.getQubit(0));
+ const auto it1 = mappings.find(op.getQubit(1));
+ if (it0 == mappings.end() || it1 == mappings.end() ||
+ !arch.areAdjacent(it0->second, it1->second)) {
+ executable = false;
+ return WalkResult::interrupt();
+ }
+ } else if (op.getNumQubits() > 2) {
executable = false;
return WalkResult::interrupt();
- }
}
return WalkResult::advance();
});🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp` around lines 66 -
73, The current code uses mappings[op.getQubit(...)] which can insert default
entries and only checks op.getNumQubits() > 1; change this to defensively lookup
qubit indices (e.g., use mappings.find or contains) for op.getQubit(0) and
op.getQubit(1), verify both exist before using them, and explicitly require
op.getNumQubits() == 2 (reject ops with 0, 1, or >2 qubits) — if a lookup fails
or the arity is not 2, set executable = false and return WalkResult::interrupt()
(retain checks using arch.areAdjacent(i0, i1) only after successful lookups).
| runHeuristicMapping(module); | ||
| EXPECT_TRUE(isExecutable(module, arch)); | ||
|
|
||
| module->dump(); |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Remove unconditional IR dumps from the test path.
Line 135 and Line 185 print the full module on every successful run, which adds avoidable CI noise.
🧹 Proposed cleanup
- module->dump();Also applies to: 185-185
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@mlir/unittests/Passes/Mapping/test_heuristic_mapping.cpp` at line 135, Remove
the unconditional IR dumps from the test by deleting or gating the
module->dump() calls so they do not print on every successful run; locate the
invocations of module->dump() in test_heuristic_mapping.cpp (the calls at and
near the two occurrences noted) and either remove them or wrap them behind a
debug/verbose guard (e.g., LLVM_DEBUG/if (verbose)) so CI output is not noisy
while preserving the ability to enable dumps for debugging.
Description
This pull request reimplements the heuristic mapping pass of the old mqtopt dialect. Similarly to #1510 the mapping pass ignores structured control flow (for now). I've copied the CMake setup of #1206.
A key difference between the implementations is that here I avoid building the "fat" Layout object (which keeps track of the SSA values) and use the
WireIterators and the "lightweight" Layout instead. Moreover, just because I wanted to highlight how cool theWireIteratoris, this pull request adds LightSABRE-like back and forth traversal to find a better initial layout.Lastly, I think it should be possible to use the ideas implemented here for a unitized IR also.
Checklist: