Skip to content

✨Arena-Based A* Search For Mapping Pass#1574

Merged
burgholzer merged 19 commits intomainfrom
enh/unique-priority-queue
Mar 25, 2026
Merged

✨Arena-Based A* Search For Mapping Pass#1574
burgholzer merged 19 commits intomainfrom
enh/unique-priority-queue

Conversation

@MatthiasReumann
Copy link
Copy Markdown
Collaborator

@MatthiasReumann MatthiasReumann commented Mar 20, 2026

Description

This pull request updates the A* search implementation of the mapping pass in two ways:

  1. Use an Arena (of Nodes). Instead of storing the sequence of SWAPs for each node, a node now stores a pointer of its parent and reconstructs the sequence whenever a goal node is found. This reduces the memory footprint of a node significantly - although now all nodes need to be stored and kept in memory.
  2. If a layout is visited multiple times (via different sequences of SWAPs) child-nodes are only created whenever the previous depth is higher than the new one (and thus we've found a shorter sequence of SWAPs leading to the same layout).

Checklist

  • The pull request only contains commits that are focused and relevant to this change.
  • I have added appropriate tests that cover the new/changed functionality.
  • I have updated the documentation to reflect these changes.
  • I have added entries to the changelog for any noteworthy additions, changes, fixes, or removals.
  • I have added migration instructions to the upgrade guide (if needed).
  • The changes follow the project's style guidelines and introduce no new warnings.
  • The changes are fully tested and pass the CI checks.
  • I have reviewed my own code changes.

If PR contains AI-assisted content:

  • I have disclosed the use of AI tools in the PR description as per our AI Usage Guidelines.
  • AI-assisted commits include an Assisted-by: [Model Name] via [Tool Name] footer.
  • I confirm that I have personally reviewed and understood all AI-generated content, and accept full responsibility for it.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 20, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 20, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added a statistic to the Mapping pass that tracks the number of inserted SWAP operations during execution, providing detailed metrics on optimization performance.
  • Improvements

    • Updated the Mapping pass implementation with algorithmic enhancements and improved topological ordering behavior.
  • Tests

    • Adjusted test configuration parameters for the Mapping pass validation.

Walkthrough

Refactors the A* SWAP search: Node stores a single swap + parent + depth (no per-node sequences), uses a bump-pointer arena and pointer-priority-queue, makes Layout usable as DenseMap key (LayoutInfo), reconstructs swap lists via parents, updates trial commit bookkeeping, and increases unit-test trials.

Changes

Cohort / File(s) Summary
A* search & layout keying
mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp
Added LayoutInfo (empty/tombstone keys, hash/equality) so Layout is a DenseMap/DenseSet key; made Layout default-constructible.
A* node representation & frontier
mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp
Refactored Node to hold one swap, parent*, and depth (removed per-node swap sequences); compute g-cost from depth; switched to llvm::SpecificBumpPtrAllocator<Node> and a priority_queue<Node*>; record best depth per Layout to prune worse re-expansions; reconstruct swap path by walking parents.
Trial commit & block ordering
mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp
Made commitTrial non-static and increment this->numSwaps by applied swaps; after applying SWAP rewiring, topologically sort each block via sortTopologically(&b) (replacing prior SSA-dominance insertion-point logic).
Pass statistics
mlir/include/mlir/Dialect/QCO/Transforms/Passes.td
Added numSwaps statistic (num-inserted-swaps) to MappingPass for reporting inserted SWAP count.
Unit test tweak
mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp
Increased heuristic mapping pass trials from ntrials = 8 to ntrials = 16 in runHeuristicMapping.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • burgholzer

Poem

🐰 I hop through nodes with careful cheer,
Parents point back so the path is clear,
Depth counts the cost, swaps traced in line,
An arena of nodes, pointers neat and fine,
I nibble trials until the mapping's mine.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Arena-Based A* Search For Mapping Pass' accurately captures the main architectural change of replacing per-node SWAP sequences with an arena-based node allocation strategy.
Description check ✅ Passed The PR description clearly explains the two main changes (arena-based nodes and duplicate-layout pruning) with motivation, and includes AI disclosure. However, the checklist items are not marked as completed despite the claimed work.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch enh/unique-priority-queue

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 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/Dialect/QCO/Transforms/Mapping/Mapping.cpp`:
- Around line 588-598: Constructing Node(child) invokes expensive heuristic work
(h()) before we check whether its layout is already in discovered; move the
discovered check to avoid wasted computation. Compute the swapped layout from
curr and swap (the same value used by Node::layout) and call
discovered.insert(swappedLayout) first; if insertion fails, skip creating Node;
otherwise construct Node(curr, swap, layers, arch, params) and emplace it into
frontier as before. Use the same layout representation Node uses so the
deduplication logic remains identical.
- Line 18: The code uses DenseMapInfo<SmallVector<IndexType>> (see uses of
getEmptyKey(), getTombstoneKey(), getHashValue()) but LLVM has no DenseMapInfo
specialization for SmallVector, causing compile failure; fix by either adding a
custom DenseMapInfo<SmallVector<IndexType>> specialization (implement
getEmptyKey, getTombstoneKey, getHashValue and equality) tailored for
SmallVector<IndexType>, or refactor the map/key usage in Mapping.cpp to use a
supported key type (e.g., convert keys to ArrayRef<IndexType>,
std::vector<IndexType>, or a packed primitive like std::pair/hashable struct)
and update the code paths that call getEmptyKey/getTombstoneKey/getHashValue
accordingly to use the supported DenseMapInfo specialization.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3ab339b1-d359-42ac-8d81-1027f0f79838

📥 Commits

Reviewing files that changed from the base of the PR and between 8ca80f0 and 29ab7fe.

📒 Files selected for processing (1)
  • mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp (1)

560-596: ⚠️ Potential issue | 🟠 Major

Avoid “first arrival wins” deduplication for Layout states.

discovered is updated when a child is generated, not when the cheapest path to that state is known. Here h() depends only on layout, while g() depends on sequence.size(), so the same Layout can be reached later with a shorter prefix. Pruning that later node can increase SWAP count and, with the fixed budget, make a solvable search return failure().

♻️ One way to keep deduplication without losing better paths
-    MinQueue frontier{};
-    frontier.emplace(root);
-    DenseSet<Layout, LayoutInfo> discovered{root.layout};
+    MinQueue frontier{};
+    frontier.emplace(root);
+    DenseMap<Layout, std::size_t, LayoutInfo> bestDepth;
+    bestDepth.try_emplace(root.layout, 0);
     DenseSet<IndexGate> expansionSet;
@@
-      if (curr.isGoal(layers.front(), arch)) {
+      auto bestIt = bestDepth.find(curr.layout);
+      if (bestIt == bestDepth.end() ||
+          curr.sequence.size() != bestIt->second) {
+        continue;
+      }
+
+      if (curr.isGoal(layers.front(), arch)) {
         return curr.sequence;
       }
@@
-            // Multiple sequences of SWAPs may lead to the same layout.
-            // The if below ensures that we don't visit the same layout twice.
-            // TODO: In the future, should fidelities be ever considered, the
-            // sequence of SWAPs matters - so this will become more difficult.
-            if (!discovered.insert(child.layout).second) {
+            const auto depth = child.sequence.size();
+            auto [it, inserted] = bestDepth.try_emplace(child.layout, depth);
+            if (!inserted && it->second <= depth) {
               continue;
             }
+            it->second = depth;
 
             frontier.emplace(std::move(child));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp` around lines 560 - 596,
The current deduplication uses DenseSet<Layout> discovered and inserts
child.layout when generating a child, causing "first arrival wins" and
potentially discarding a later better path because h() depends only on layout
but g() (sequence.size()) varies. Replace discovered with a map from Layout to
best-known g (e.g., DenseMap<Layout, std::size_t> or DenseMap<Layout,int>), and
change the check in the child-generation block (inside the loop that constructs
Node child and where discovered.insert(child.layout) is called) to: if layout
not present OR child.sequence.size() < recorded_g then update the map with
child.sequence.size() and push frontier.emplace(std::move(child)); otherwise
skip pushing. Alternatively, defer marking a layout until node is popped and
accepted (update the map there) so only popped nodes set the best g for that
layout; apply this change to the discovered usage to avoid pruning better
sequences for Layout while keeping deduplication.
🤖 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/Dialect/QCO/Transforms/Mapping/Mapping.cpp`:
- Around line 533-535: The comment incorrectly describes the meaning of the b^3
budget; update the comment in Mapping.cpp where the "iteration budget is b^{3}"
is documented to state that b^3 bounds the number of node expansions (i.e.,
roughly exhausting all nodes up to depth 3, or exploring all paths of length up
to 3 in a b-ary search tree), rather than claiming it corresponds to exhausting
paths of length b^2; keep the note about the hard cap preventing impractical
runtimes and otherwise preserve the intent of the comment.

---

Outside diff comments:
In `@mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp`:
- Around line 560-596: The current deduplication uses DenseSet<Layout>
discovered and inserts child.layout when generating a child, causing "first
arrival wins" and potentially discarding a later better path because h() depends
only on layout but g() (sequence.size()) varies. Replace discovered with a map
from Layout to best-known g (e.g., DenseMap<Layout, std::size_t> or
DenseMap<Layout,int>), and change the check in the child-generation block
(inside the loop that constructs Node child and where
discovered.insert(child.layout) is called) to: if layout not present OR
child.sequence.size() < recorded_g then update the map with
child.sequence.size() and push frontier.emplace(std::move(child)); otherwise
skip pushing. Alternatively, defer marking a layout until node is popped and
accepted (update the map there) so only popped nodes set the best g for that
layout; apply this change to the discovered usage to avoid pruning better
sequences for Layout while keeping deduplication.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3bf73a1e-19ae-4164-bcdb-d29bc20ed256

📥 Commits

Reviewing files that changed from the base of the PR and between 29ab7fe and 67e2eff.

📒 Files selected for processing (1)
  • mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp (1)

586-596: 🧹 Nitpick | 🔵 Trivial

Consider checking discovered before constructing the full Node to avoid unnecessary heuristic computation.

The Node constructor invokes the expensive h() heuristic (iterating all layers/gates) before the layout is checked against discovered. Computing the swapped layout first and checking membership would skip this cost for already-seen layouts.

♻️ Proposed optimization
             const IndexGate swap = std::minmax(hw0, hw1);
             if (!expansionSet.insert(swap).second) {
               continue;
             }

-            Node child(curr, swap, layers, arch, params);
-
-            // Multiple sequences of SWAPs may lead to the same layout.
-            // The if below ensures that we don't visit the same layout twice.
-            // TODO: In the future, should fidelities be ever considered, the
-            // sequence of SWAPs matters - so this will become more difficult.
-            if (!discovered.insert(child.layout).second) {
+            // Check if layout was already discovered before constructing node.
+            Layout candidateLayout = curr.layout;
+            candidateLayout.swap(swap.first, swap.second);
+            if (!discovered.insert(candidateLayout).second) {
               continue;
             }

-            frontier.emplace(std::move(child));
+            // Multiple sequences of SWAPs may lead to the same layout.
+            // The insert above ensures that we don't visit the same layout twice.
+            // TODO: In the future, should fidelities be ever considered, the
+            // sequence of SWAPs matters - so this will become more difficult.
+            frontier.emplace(curr, swap, layers, arch, params);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp` around lines 586 - 596,
Avoid constructing Node (which runs the expensive h() heuristic) before checking
whether the resulting layout was already discovered: compute the layout that
would result from applying swap to curr (the layout-only computation), check
discovered.insert(newLayout).second and continue if false, and only then
construct Node child(curr, swap, layers, arch, params) and
frontier.emplace(std::move(child)); update references around Node, child.layout,
discovered, swap and frontier.emplace accordingly.
🤖 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/Dialect/QCO/Transforms/Mapping/Mapping.cpp`:
- Around line 239-242: The local 'using Info =
DenseMapInfo<decltype(a.programToHardware_)>;' in the static bool isEqual(const
Layout& a, const Layout& b) function redundantly re-declares/shadows the
class-level alias; remove that local using and simply reference the existing
Info alias when calling Info::isEqual(a.programToHardware_,
b.programToHardware_). Update the isEqual function in class Layout to use the
already-defined Info symbol (and ensure programToHardware_ is referenced with
the same names used there).

---

Duplicate comments:
In `@mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp`:
- Around line 586-596: Avoid constructing Node (which runs the expensive h()
heuristic) before checking whether the resulting layout was already discovered:
compute the layout that would result from applying swap to curr (the layout-only
computation), check discovered.insert(newLayout).second and continue if false,
and only then construct Node child(curr, swap, layers, arch, params) and
frontier.emplace(std::move(child)); update references around Node, child.layout,
discovered, swap and frontier.emplace accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: eff47478-c819-40aa-bb2d-475966051ad5

📥 Commits

Reviewing files that changed from the base of the PR and between 67e2eff and badfc92.

📒 Files selected for processing (1)
  • mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp

@MatthiasReumann MatthiasReumann self-assigned this Mar 20, 2026
@MatthiasReumann MatthiasReumann added c++ Anything related to C++ code MLIR Anything related to MLIR labels Mar 20, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 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/Dialect/QCO/Transforms/Mapping/Mapping.cpp`:
- Line 563: The code currently uses a seen-set (DenseSet<Layout, LayoutInfo>
discovered{root.layout}) and marks a layout as discovered immediately, which
drops later arrivals that may have shorter prefixes (lower g()). Change the
discovered structure to record the best-known prefix length/cost per Layout
(e.g., store an integer prefixLen or numeric cost in LayoutInfo) and update
insertion/lookup logic in the expansion/marking code paths (the places that call
g() and currently insert into discovered) to: compute the new prefix cost with
g(), compare it to the stored best cost for that Layout, and only skip expansion
or overwrite the stored entry if the new cost is not an improvement; otherwise
accept and update the stored best cost and allow the node. Ensure all places
that currently insert or check discovered (the blocks around where discovered is
created and where layouts are marked) use this improved comparison.
- Around line 217-241: Add Doxygen-style comments (/** ... */) above the new
helper methods in class LayoutInfo: getEmptyKey(), getTombstoneKey(),
getHashValue(const Layout& l), and isEqual(const Layout& a, const Layout& b).
For getEmptyKey() and getTombstoneKey() add a brief description and an `@return`
documenting that a sentinel Layout is returned; for getHashValue() add a brief
description and an `@param` for "l" and an `@return` describing the returned
unsigned hash; for isEqual() add a brief description plus `@param` entries for "a"
and "b" and an `@return` indicating it returns true when equal. Use the project's
Doxygen comment style and place the comments immediately above the corresponding
method declarations.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 030dcafe-0267-4509-ba87-edb0d9559780

📥 Commits

Reviewing files that changed from the base of the PR and between badfc92 and 5bbe704.

📒 Files selected for processing (1)
  • mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp

@MatthiasReumann MatthiasReumann marked this pull request as draft March 23, 2026 08:24
@MatthiasReumann
Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 24, 2026

✅ Actions performed

Full review triggered.

@MatthiasReumann MatthiasReumann marked this pull request as ready for review March 24, 2026 14:52
@MatthiasReumann MatthiasReumann changed the title ✨Discovered Layout Set for Mapping Pass ✨Arena-Based A* Search For Mapping Pass Mar 24, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp (2)

216-244: 🛠️ Refactor suggestion | 🟠 Major

Add method-level Doxygen to the new DenseMap hooks.

The class has a top-level block, but getEmptyKey(), getTombstoneKey(), getHashValue(), and isEqual() still break the surrounding per-method documentation pattern. Please add brief @param / @return docs for these helpers too.

As per coding guidelines, "MUST use Doxygen-style comments in C++ code".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp` around lines 216 - 244,
Add Doxygen-style method comments for the DenseMap hook helpers in class
LayoutInfo: document getEmptyKey() and getTombstoneKey() with brief `@return`
describing the special Layout returned, document getHashValue(const Layout& l)
with `@param` describing the Layout input and `@return` describing the computed
hash, and document isEqual(const Layout& a, const Layout& b) with `@param` entries
for a and b and `@return` describing the equality result; place these method-level
comments immediately above each corresponding method (getEmptyKey,
getTombstoneKey, getHashValue, isEqual) following the existing per-method
Doxygen style in the file.

577-579: ⚠️ Potential issue | 🟠 Major

Deduplicate layouts before they enter the frontier.

The bestDepth check only runs after pop, so the same layout is still enqueued and arena-allocated many times. A trivial backtrack already reproduces an old layout, and at Line 620 those stale pops also burn the b^3 budget, so the search can fail long before it performs b^3 real expansions.

♻️ Proposed fix
     llvm::SpecificBumpPtrAllocator<Node> arena;
     std::priority_queue<Node*, std::vector<Node*>, Node::ComparePointer>
         frontier;
     frontier.emplace(std::construct_at(arena.Allocate(), layout));

     DenseMap<Layout, std::size_t, LayoutInfo> bestDepth;
+    bestDepth.try_emplace(layout, 0);
     DenseSet<IndexGate> expansionSet;

     std::size_t i = 0;
     while (!frontier.empty() && i < budget) {
       Node* curr = frontier.top();
       frontier.pop();

-      // Multiple sequences of SWAPs can lead to the same layout and the same
-      // layout creates the same child-nodes. Thus, if we've seen a layout
-      // already at a lower depth don't reexpand the current node (and hence
-      // recreate the same child nodes).
-
-      const auto [it, inserted] =
-          bestDepth.try_emplace(curr->layout, curr->depth);
-      if (!inserted) {
-        const auto otherDepth = it->getSecond();
-        if (curr->depth >= otherDepth) {
-          ++i;
-          continue;
-        }
-
-        it->second = curr->depth;
+      if (curr->depth > bestDepth.lookup(curr->layout)) {
+        continue;
       }
 ...
             const IndexGate swap = std::minmax(hw0, hw1);
             if (!expansionSet.insert(swap).second) {
               continue;
             }

+            Layout childLayout = curr->layout;
+            childLayout.swap(swap.first, swap.second);
+            const auto childDepth = curr->depth + 1;
+            const auto [it, inserted] =
+                bestDepth.try_emplace(childLayout, childDepth);
+            if (!inserted && childDepth >= it->second) {
+              continue;
+            }
+            if (!inserted) {
+              it->second = childDepth;
+            }
+
             frontier.emplace(std::construct_at(arena.Allocate(), curr, swap,
                                                layers, arch, params));

Also applies to: 597-653

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp` around lines 577 - 579,
Duplicate layouts are being enqueued and arena-allocated repeatedly because the
duplicate check happens after pop; modify the enqueue path for the frontier
(where layouts are pushed into frontier/arena) to deduplicate before allocation
by computing a canonical key for the layout and consulting a visited set/hash;
only insert into arena and frontier if the key is not present, and add the key
to visited at that time, leaving the existing bestDepth check on pop unchanged;
reference the existing symbols: frontier, arena, bestDepth, pop, and the layout
object/key computation when adding the duplicate-filtering check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp`:
- Around line 216-244: Add Doxygen-style method comments for the DenseMap hook
helpers in class LayoutInfo: document getEmptyKey() and getTombstoneKey() with
brief `@return` describing the special Layout returned, document
getHashValue(const Layout& l) with `@param` describing the Layout input and
`@return` describing the computed hash, and document isEqual(const Layout& a,
const Layout& b) with `@param` entries for a and b and `@return` describing the
equality result; place these method-level comments immediately above each
corresponding method (getEmptyKey, getTombstoneKey, getHashValue, isEqual)
following the existing per-method Doxygen style in the file.
- Around line 577-579: Duplicate layouts are being enqueued and arena-allocated
repeatedly because the duplicate check happens after pop; modify the enqueue
path for the frontier (where layouts are pushed into frontier/arena) to
deduplicate before allocation by computing a canonical key for the layout and
consulting a visited set/hash; only insert into arena and frontier if the key is
not present, and add the key to visited at that time, leaving the existing
bestDepth check on pop unchanged; reference the existing symbols: frontier,
arena, bestDepth, pop, and the layout object/key computation when adding the
duplicate-filtering check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ad10f221-299b-41ef-8acc-1d30cb43662f

📥 Commits

Reviewing files that changed from the base of the PR and between a087c6c and 1775f5e.

📒 Files selected for processing (2)
  • mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp
  • mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp (1)

219-244: 🧹 Nitpick | 🔵 Trivial

Add Doxygen comments to the LayoutInfo static methods.

The methods getEmptyKey(), getTombstoneKey(), getHashValue(), and isEqual() lack Doxygen-style documentation. Please add brief @return / @param comments consistent with the project's documentation standards.

As per coding guidelines: "MUST use Doxygen-style comments in C++ code".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp` around lines 219 - 244,
Add Doxygen-style comments for the static methods in class LayoutInfo: annotate
getEmptyKey() and getTombstoneKey() with brief descriptions and `@return`
documenting that they return a Layout representing the empty/tombstone hash-map
sentinel; annotate getHashValue(const Layout& l) with a short summary and an
`@param` describing 'l' and an `@return` explaining it yields the computed hash of
the programToHardware_ map; annotate isEqual(const Layout& a, const Layout& b)
with a summary plus `@param` entries for 'a' and 'b' and an `@return` describing it
returns true when their programToHardware_ maps are equal. Ensure the comments
use the project's Doxygen style and are placed immediately above each static
method declaration in LayoutInfo.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp`:
- Around line 219-244: Add Doxygen-style comments for the static methods in
class LayoutInfo: annotate getEmptyKey() and getTombstoneKey() with brief
descriptions and `@return` documenting that they return a Layout representing the
empty/tombstone hash-map sentinel; annotate getHashValue(const Layout& l) with a
short summary and an `@param` describing 'l' and an `@return` explaining it yields
the computed hash of the programToHardware_ map; annotate isEqual(const Layout&
a, const Layout& b) with a summary plus `@param` entries for 'a' and 'b' and an
`@return` describing it returns true when their programToHardware_ maps are equal.
Ensure the comments use the project's Doxygen style and are placed immediately
above each static method declaration in LayoutInfo.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 952f6214-96bb-4f0c-bb29-ee36bc18e969

📥 Commits

Reviewing files that changed from the base of the PR and between 1775f5e and 589ab21.

📒 Files selected for processing (1)
  • mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp

Copy link
Copy Markdown
Member

@burgholzer burgholzer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some true nitpicks on some of the C++ details.
Otherwise, this looks great already!

@MatthiasReumann
Copy link
Copy Markdown
Collaborator Author

@burgholzer

fyi: as it seems there are still SSA dominance issues. I've added a sortTopological for now. Let's maybe re-benchmark and then evaluate if implementing the anchoring strategy makes sense (I unfortunately think it will).

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 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/Dialect/QCO/Transforms/Mapping/Mapping.cpp`:
- Around line 631-639: The inner loop in the block that builds the swap sequence
uses a variable named i that shadows an outer i declared earlier; rename the
inner loop counter to a distinct name (e.g., idx or j) to avoid shadowing and
improve readability: in the if (curr->isGoal(...)) branch where seq is sized
from curr->depth and you set std::size_t i = seq.size() - 1 and iterate for
(Node* n = curr; n->parent != nullptr; n = n->parent) { seq[i] = n->swap; --i;
}, change that inner i to a new identifier and update its uses (seq[...] and
--...) so Node*, curr, seq, depth and swap logic remain unchanged.
- Around line 632-637: The code initializes i as seq.size() - 1 which can
underflow when curr->depth == 0; change the initialization to avoid subtraction
on empty vector and guard the loop by computing i = seq.size(); if (i > 0) --i;
(or use a signed index like ssize_t start = static_cast<ssize_t>(seq.size()) - 1
and check start >= 0) before using seq[i] in the for loop that iterates Node* n
= curr; n->parent != nullptr; n = n->parent so that SmallVector<IndexGate> seq,
curr->depth, and the assignment seq[i] = n->swap are safe for depth==0.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 01a2771d-159d-445d-bd21-ec07373a04eb

📥 Commits

Reviewing files that changed from the base of the PR and between 589ab21 and 4b2af3e.

📒 Files selected for processing (2)
  • mlir/include/mlir/Dialect/QCO/Transforms/Passes.td
  • mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp

@burgholzer
Copy link
Copy Markdown
Member

@burgholzer

fyi: as it seems there are still SSA dominance issues. I've added a sortTopological for now. Let's maybe re-benchmark and then evaluate if implementing the anchoring strategy makes sense (I unfortunately think it will).

Interesting. Did this come up as part of this PR or in separate testing?
This is a bit unfortunate.

@MatthiasReumann
Copy link
Copy Markdown
Collaborator Author

@burgholzer
fyi: as it seems there are still SSA dominance issues. I've added a sortTopological for now. Let's maybe re-benchmark and then evaluate if implementing the anchoring strategy makes sense (I unfortunately think it will).

Interesting. Did this come up as part of this PR or in separate testing? This is a bit unfortunate.

The last pull request (#1581) fixed an issue how we handled BarrierOps (which led to SSA dominance issues). I thought this was the end of it until I tested a different grover's circuit.

I will open another pull request after this one and rethink our current SWAP insertion strategy. There must be some kind of simple way to insert a SWAP and reorder its dependencies such that no SSA dominance violation occurs. There must be.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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/Dialect/QCO/Transforms/Mapping/Mapping.cpp`:
- Around line 649-661: Before allocating and constructing child nodes into the
arena (where frontier.emplace(std::construct_at(arena.Allocate(), curr, swap,
layers, arch, params)) is called), check the recorded bestDepth for the
candidate layout (use the same key you use later to look up bestDepth) and skip
constructing the node if bestDepth <= currentDepth+1; this avoids unnecessary
arena construction and heuristic h() computation for layouts already reached at
equal or better depth while keeping expansionSet and swap insertion unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 32f5c084-28b6-41ec-b0bb-2c539231ad16

📥 Commits

Reviewing files that changed from the base of the PR and between 4b2af3e and febc127.

📒 Files selected for processing (1)
  • mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp

Copy link
Copy Markdown
Member

@burgholzer burgholzer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Let's get this in now.
However, I also believe there must be some way to avoid these dominance issues without the "Keule" that is topological sorting.

@burgholzer burgholzer added this to the MLIR Support milestone Mar 25, 2026
@burgholzer burgholzer merged commit b388077 into main Mar 25, 2026
29 checks passed
@burgholzer burgholzer deleted the enh/unique-priority-queue branch March 25, 2026 08:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++ Anything related to C++ code MLIR Anything related to MLIR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants