Skip to content

Commit a087c6c

Browse files
šŸ› Fix SSA Dominance Issues for Mapping Pass for Circuits With Barriers (#1581)
1 parent 8d7cc71 commit a087c6c

3 files changed

Lines changed: 120 additions & 41 deletions

File tree

ā€ŽCHANGELOG.mdā€Ž

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel
1313

1414
- ✨ Add Sampler and Estimator Primitives to the QDMI-Qiskit Interface ([#1507]) ([**@marcelwa**])
1515
- ✨ Add conversions between Jeff and QCO ([#1479], [#1548], [#1565]) ([**@denialhaag**])
16-
- ✨ Add a `place-and-route` pass for mapping circuits to architectures with restricted topologies ([#1537], [#1547], [#1568]) ([**@MatthiasReumann**])
16+
- ✨ Add a `place-and-route` pass for mapping circuits to architectures with restricted topologies ([#1537], [#1547], [#1568], [#1581]) ([**@MatthiasReumann**])
1717
- ✨ Add initial infrastructure for new QC and QCO MLIR dialects
1818
([#1264], [#1330], [#1402], [#1428], [#1430], [#1436], [#1443], [#1446], [#1464], [#1465], [#1470], [#1471], [#1472], [#1474], [#1475], [#1506], [#1510], [#1513], [#1521], [#1542], [#1548], [#1550], [#1554], [#1570], [#1572], [#1573])
1919
([**@burgholzer**], [**@denialhaag**], [**@taminob**], [**@DRovara**], [**@li-mingbao**], [**@Ectras**], [**@MatthiasReumann**], [**@simon1hofmann**])
@@ -333,6 +333,7 @@ _šŸ“š Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool
333333

334334
<!-- PR links -->
335335

336+
[#1581]: https://github.com/munich-quantum-toolkit/core/pull/1581
336337
[#1573]: https://github.com/munich-quantum-toolkit/core/pull/1573
337338
[#1572]: https://github.com/munich-quantum-toolkit/core/pull/1572
338339
[#1571]: https://github.com/munich-quantum-toolkit/core/pull/1571

ā€Žmlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cppā€Ž

Lines changed: 107 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,65 @@ struct MappingPass : impl::MappingPassBase<MappingPass> {
321321

322322
using MinQueue = std::priority_queue<Node, std::vector<Node>, std::greater<>>;
323323

324+
struct [[nodiscard]] TrialResult {
325+
explicit TrialResult(Layout layout) : layout(std::move(layout)) {}
326+
327+
/// @brief The computed initial layout.
328+
Layout layout;
329+
/// @brief A vector of SWAPs for each layer.
330+
SmallVector<SmallVector<IndexGate>> swaps;
331+
/// @brief The number of inserted SWAPs.
332+
std::size_t nswaps{};
333+
};
334+
335+
struct SynchronizationMap {
336+
/**
337+
* @returns true if the operation is contained in the map.
338+
*/
339+
bool contains(Operation* op) const { return onHold.contains(op); }
340+
341+
/**
342+
* @brief Add op with respective iterator and ref count to the map.
343+
*/
344+
void add(Operation* op, WireIterator* it, const std::size_t cnt) {
345+
onHold.try_emplace(op, SmallVector<WireIterator*>{it});
346+
// Decrease the cnt by one because the op was visited when adding.
347+
refCount.try_emplace(op, cnt - 1);
348+
}
349+
350+
/**
351+
* @brief Decrement ref count of op and potentially release its iterators.
352+
*/
353+
std::optional<ArrayRef<WireIterator*>> visit(Operation* op,
354+
WireIterator* it) {
355+
assert(refCount.contains(op) && "expected sync map to contain op");
356+
357+
// Add iterator for later release.
358+
onHold[op].push_back(it);
359+
360+
// Release iterators whenever the ref count reaches zero.
361+
if (--refCount[op] == 0) {
362+
return onHold[op];
363+
}
364+
365+
return std::nullopt;
366+
}
367+
368+
/**
369+
* @brief Clear the contents of the map.
370+
*/
371+
void clear() {
372+
onHold.clear();
373+
refCount.clear();
374+
}
375+
376+
private:
377+
/// @brief Maps operations to to-be-released iterators.
378+
DenseMap<Operation*, SmallVector<WireIterator*, 2>> onHold;
379+
/// @brief Maps operations to ref counts.
380+
DenseMap<Operation*, std::size_t> refCount;
381+
};
382+
324383
public:
325384
using MappingPassBase::MappingPassBase;
326385

@@ -382,17 +441,6 @@ struct MappingPass : impl::MappingPassBase<MappingPass> {
382441
}
383442

384443
private:
385-
struct [[nodiscard]] TrialResult {
386-
explicit TrialResult(Layout layout) : layout(std::move(layout)) {}
387-
388-
/// @brief The computed initial layout.
389-
Layout layout;
390-
/// @brief A vector of SWAPs for each layer.
391-
SmallVector<SmallVector<IndexGate>> swaps;
392-
/// @brief The number of inserted SWAPs.
393-
std::size_t nswaps{};
394-
};
395-
396444
/**
397445
* @brief Find the best trial result in terms of the number of SWAPs.
398446
* @returns the best trial result or nullptr if no result is valid.
@@ -615,28 +663,33 @@ struct MappingPass : impl::MappingPassBase<MappingPass> {
615663
while (shouldContinue(it)) {
616664
const auto res =
617665
TypeSwitch<Operation*, WalkResult>(it.operation())
618-
.Case<UnitaryOpInterface>([&](UnitaryOpInterface op) {
619-
assert(op.getNumQubits() > 0 && op.getNumQubits() <= 2);
666+
.Case<BarrierOp>([&](auto) {
667+
std::ranges::advance(it, step);
668+
return WalkResult::advance();
669+
})
670+
.template Case<UnitaryOpInterface>(
671+
[&](UnitaryOpInterface op) {
672+
assert(op.getNumQubits() > 0 && op.getNumQubits() <= 2);
620673

621-
if (op.getNumQubits() == 1) {
622-
std::ranges::advance(it, step);
623-
return WalkResult::advance();
624-
}
674+
if (op.getNumQubits() == 1) {
675+
std::ranges::advance(it, step);
676+
return WalkResult::advance();
677+
}
625678

626-
if (visited.contains(op)) {
627-
const auto otherIndex = visited[op];
628-
layer.insert(std::make_pair(index, otherIndex));
679+
if (visited.contains(op)) {
680+
const auto otherIndex = visited[op];
681+
layer.insert(std::make_pair(index, otherIndex));
629682

630-
std::ranges::advance(wires[index], step);
631-
std::ranges::advance(wires[otherIndex], step);
683+
std::ranges::advance(wires[index], step);
684+
std::ranges::advance(wires[otherIndex], step);
632685

633-
visited.erase(op);
634-
} else {
635-
visited.try_emplace(op, index);
636-
}
686+
visited.erase(op);
687+
} else {
688+
visited.try_emplace(op, index);
689+
}
637690

638-
return WalkResult::interrupt();
639-
})
691+
return WalkResult::interrupt();
692+
})
640693
.template Case<AllocOp, StaticOp, ResetOp, MeasureOp,
641694
DeallocOp>([&](auto) {
642695
std::ranges::advance(it, step);
@@ -709,25 +762,30 @@ struct MappingPass : impl::MappingPassBase<MappingPass> {
709762
// Helper function that advances the iterator to the input qubit (the
710763
// operation producing it) of a deallocation or two-qubit op.
711764
const auto advFront = [](WireIterator& it) {
765+
auto next = std::next(it);
712766
while (true) {
713-
const auto next = std::next(it);
714767
if (isa<DeallocOp>(next.operation())) {
715768
break;
716769
}
717770

771+
if (isa<BarrierOp>(next.operation())) {
772+
break;
773+
}
774+
718775
auto op = dyn_cast<UnitaryOpInterface>(next.operation());
719776
if (op && op.getNumQubits() > 1) {
720777
break;
721778
}
722779

723780
std::ranges::advance(it, 1);
781+
std::ranges::advance(next, 1);
724782
}
725783
};
726784

727785
auto wires = toWires(place(dynQubits, result.layout, funcBody, rewriter));
728786

729-
DenseMap<Operation*, WireIterator*> seen;
730-
for (const auto [i, swaps] : enumerate(result.swaps)) {
787+
SynchronizationMap ready;
788+
for (const auto& swaps : result.swaps) {
731789
// Advance all wires to the next front of one-qubit outputs
732790
// (the SSA values).
733791
for_each(wires, advFront);
@@ -760,21 +818,30 @@ struct MappingPass : impl::MappingPassBase<MappingPass> {
760818
std::ranges::advance(wires[hw1], 1);
761819
}
762820

763-
// Jump over "ready" two-qubit gates.
821+
// Jump over "ready" gates.
764822
for (auto& it : wires) {
765823
auto op = dyn_cast<UnitaryOpInterface>(std::next(it).operation());
766-
if (op && op.getNumQubits() > 1) {
767-
if (seen.contains(op)) {
768-
std::ranges::advance(it, 1);
769-
std::ranges::advance(*seen[op], 1);
770-
continue;
771-
}
824+
if (!op) {
825+
continue;
826+
}
827+
828+
if (op.getNumQubits() < 2) {
829+
continue;
830+
}
772831

773-
seen.try_emplace(op, &it);
832+
if (!ready.contains(op)) {
833+
ready.add(op, &it, op.getNumQubits());
834+
continue;
835+
}
836+
837+
if (auto opt = ready.visit(op, &it)) {
838+
for (WireIterator* wire : *opt) {
839+
std::ranges::advance(*wire, 1);
840+
}
774841
}
775842
}
776843

777-
seen.clear(); // Prepare for next iteration.
844+
ready.clear(); // Prepare for next iteration.
778845
}
779846
}
780847
};

ā€Žmlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cppā€Ž

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ class MappingPassTest : public testing::Test,
6363

6464
bool executable = true;
6565
std::ignore = moduleOp->walk([&](qc::UnitaryOpInterface op) {
66+
if (isa<qc::BarrierOp>(op)) {
67+
return WalkResult::advance();
68+
}
6669
if (op.getNumQubits() > 1) {
6770
assert(op.getNumQubits() == 2 &&
6871
"Expected only 2-qubit gates after decomposition");
@@ -178,6 +181,14 @@ TEST_P(MappingPassTest, Sabre) {
178181

179182
builder.cx(q3, q0);
180183

184+
builder.barrier({q0, q1, q2, q3, q4, q5});
185+
builder.measure(q0);
186+
builder.measure(q1);
187+
builder.measure(q2);
188+
builder.measure(q3);
189+
builder.measure(q4);
190+
builder.measure(q5);
191+
181192
builder.dealloc(q0);
182193
builder.dealloc(q1);
183194
builder.dealloc(q2);

0 commit comments

Comments
Ā (0)