Skip to content

Commit af09841

Browse files
MatthiasReumannpre-commit-ci[bot]burgholzerdenialhaag
authored
✨ Add TensorIterator (#1730)
## Description This pull request adds the bi-directional `TensorIterator` class (the analog of the `WireIterator` for tensors). Cherry-picked from #1728 to ease the reviewing effort. ## Checklist <!--- This checklist serves as a reminder of a couple of things that ensure your pull request will be merged swiftly. --> - [x] The pull request only contains commits that are focused and relevant to this change. - [x] I have added appropriate tests that cover the new/changed functionality. - [x] I have updated the documentation to reflect these changes. - [x] I have added entries to the changelog for any noteworthy additions, changes, fixes, or removals. - [x] I have added migration instructions to the upgrade guide (if needed). - [x] The changes follow the project's style guidelines and introduce no new warnings. - [x] The changes are fully tested and pass the CI checks. - [x] I have reviewed my own code changes. **If PR contains AI-assisted content:** - [x] I have disclosed the use of AI tools in the PR description as per our [AI Usage Guidelines](https://github.com/munich-quantum-toolkit/core/blob/main/docs/ai_usage.md). - [x] AI-assisted commits include an `Assisted-by: [Model Name] via [Tool Name]` footer. - [x] I confirm that I have personally reviewed and understood all AI-generated content, and accept full responsibility for it. --------- Signed-off-by: burgholzer <burgholzer@me.com> Signed-off-by: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: burgholzer <burgholzer@me.com> Co-authored-by: Daniel Haag <121057143+denialhaag@users.noreply.github.com>
1 parent 75a1bfe commit af09841

9 files changed

Lines changed: 540 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel
1818
- ✨ Add conversions between `jeff` and QCO ([#1479], [#1548], [#1565], [#1637], [#1676], [#1706]) ([**@denialhaag**], [**@burgholzer**])
1919
- ✨ Add a `place-and-route` pass for mapping circuits to architectures with restricted topologies ([#1537], [#1547], [#1568], [#1581], [#1583], [#1588], [#1600], [#1664], [#1709], [#1716]) ([**@MatthiasReumann**], [**@burgholzer**])
2020
- ✨ Add initial infrastructure for new QC and QCO MLIR dialects
21-
([#1264], [#1330], [#1402], [#1428], [#1430], [#1436], [#1443], [#1446], [#1464], [#1465], [#1470], [#1471], [#1472], [#1474], [#1475], [#1506], [#1510], [#1513], [#1521], [#1542], [#1548], [#1550], [#1554], [#1567], [#1569], [#1570], [#1572], [#1573], [#1580], [#1602], [#1620], [#1623], [#1624], [#1626], [#1627], [#1635], [#1638], [#1673], [#1675], [#1700], [#1717])
21+
([#1264], [#1330], [#1402], [#1428], [#1430], [#1436], [#1443], [#1446], [#1464], [#1465], [#1470], [#1471], [#1472], [#1474], [#1475], [#1506], [#1510], [#1513], [#1521], [#1542], [#1548], [#1550], [#1554], [#1567], [#1569], [#1570], [#1572], [#1573], [#1580], [#1602], [#1620], [#1623], [#1624], [#1626], [#1627], [#1635], [#1638], [#1673], [#1675], [#1700], [#1717], [#1730])
2222
([**@burgholzer**], [**@denialhaag**], [**@taminob**], [**@DRovara**], [**@li-mingbao**], [**@Ectras**], [**@MatthiasReumann**], [**@simon1hofmann**])
2323

2424
### Changed
@@ -403,6 +403,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool
403403
<!-- PR links -->
404404

405405
[#1737]: https://github.com/munich-quantum-toolkit/core/pull/1737
406+
[#1730]: https://github.com/munich-quantum-toolkit/core/pull/1730
406407
[#1720]: https://github.com/munich-quantum-toolkit/core/pull/1720
407408
[#1719]: https://github.com/munich-quantum-toolkit/core/pull/1719
408409
[#1718]: https://github.com/munich-quantum-toolkit/core/pull/1718
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright (c) 2023 - 2026 Chair for Design Automation, TUM
3+
* Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH
4+
* All rights reserved.
5+
*
6+
* SPDX-License-Identifier: MIT
7+
*
8+
* Licensed under the MIT License
9+
*/
10+
11+
#pragma once
12+
13+
#include <mlir/IR/Builders.h>
14+
#include <mlir/IR/BuiltinTypes.h>
15+
#include <mlir/IR/Operation.h>
16+
#include <mlir/IR/Value.h>
17+
18+
#include <iterator>
19+
20+
namespace mlir::qtensor {
21+
22+
/**
23+
* @brief A bidirectional_iterator traversing the tensor chain.
24+
**/
25+
class [[nodiscard]] TensorIterator {
26+
public:
27+
using iterator_category = std::bidirectional_iterator_tag;
28+
using difference_type = std::ptrdiff_t;
29+
using value_type = Operation*;
30+
31+
TensorIterator() : op_(nullptr), tensor_(nullptr), isSentinel_(false) {}
32+
explicit TensorIterator(TypedValue<RankedTensorType> tensor)
33+
: op_(tensor.getDefiningOp()), tensor_(tensor), isSentinel_(false) {}
34+
35+
/// @returns the operation the iterator points to.
36+
[[nodiscard]] Operation* operation() const { return op_; }
37+
38+
/// @returns the operation the iterator points to.
39+
[[nodiscard]] Operation* operator*() const { return operation(); }
40+
41+
/// @returns the tensor the iterator points to.
42+
[[nodiscard]] TypedValue<RankedTensorType> tensor() const;
43+
44+
TensorIterator& operator++() {
45+
forward();
46+
return *this;
47+
}
48+
49+
TensorIterator operator++(int) {
50+
auto tmp = *this;
51+
operator++();
52+
return tmp;
53+
}
54+
55+
TensorIterator& operator--() {
56+
backward();
57+
return *this;
58+
}
59+
60+
TensorIterator operator--(int) {
61+
auto tmp = *this;
62+
operator--();
63+
return tmp;
64+
}
65+
66+
bool operator==(const TensorIterator& other) const {
67+
return other.tensor_ == tensor_ && other.op_ == op_ &&
68+
other.isSentinel_ == isSentinel_;
69+
}
70+
71+
bool operator==([[maybe_unused]] std::default_sentinel_t s) const {
72+
return isSentinel_;
73+
}
74+
75+
private:
76+
/// @brief Move to the next operation on the tensor def-use chain.
77+
void forward();
78+
79+
/// @brief Move to the previous operation on the tensor def-use chain.
80+
void backward();
81+
82+
Operation* op_;
83+
TypedValue<RankedTensorType> tensor_;
84+
bool isSentinel_;
85+
};
86+
} // namespace mlir::qtensor

mlir/lib/Dialect/QCO/Utils/WireIterator.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ void WireIterator::forward() {
3939
}
4040

4141
// Find the user-operation of the qubit SSA value.
42-
assert(qubit_.getNumUses() == 1 && "expected linear typing");
43-
op_ = *(qubit_.getUsers().begin());
42+
assert(qubit_.hasOneUse() && "expected linear typing");
43+
op_ = *(qubit_.user_begin());
4444

4545
// A sink/insert defines the end of the qubit wire (dynamic and static).
4646
if (isa<SinkOp, YieldOp, qtensor::InsertOp>(op_)) {
@@ -57,8 +57,8 @@ void WireIterator::forward() {
5757
.Case<MeasureOp>([&](MeasureOp op) { qubit_ = op.getQubitOut(); })
5858
.Case<ResetOp>([&](ResetOp op) { qubit_ = op.getQubitOut(); })
5959
.Default([&](Operation* op) {
60-
report_fatal_error("unknown op in def-use chain: " +
61-
op->getName().getStringRef());
60+
llvm::reportFatalInternalError("unknown op in def-use chain: " +
61+
op->getName().getStringRef());
6262
});
6363
}
6464
}
@@ -90,8 +90,8 @@ void WireIterator::backward() {
9090
.Case<MeasureOp>([&](MeasureOp op) { qubit_ = op.getQubitIn(); })
9191
.Case<ResetOp>([&](ResetOp op) { qubit_ = op.getQubitIn(); })
9292
.Default([&](Operation* op) {
93-
report_fatal_error("unknown op in def-use chain: " +
94-
op->getName().getStringRef());
93+
llvm::reportFatalInternalError("unknown op in def-use chain: " +
94+
op->getName().getStringRef());
9595
});
9696

9797
// Get the operation that produces the qubit value.

mlir/lib/Dialect/QTensor/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88

99
add_subdirectory(IR)
1010
add_subdirectory(Transforms)
11+
add_subdirectory(Utils)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM
2+
# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH
3+
# All rights reserved.
4+
#
5+
# SPDX-License-Identifier: MIT
6+
#
7+
# Licensed under the MIT License
8+
9+
file(GLOB_RECURSE UTILS_CPP "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
10+
11+
add_mlir_dialect_library(
12+
MLIRQTensorUtils
13+
${UTILS_CPP}
14+
ADDITIONAL_HEADER_DIRS
15+
${PROJECT_SOURCE_DIR}/mlir/include/mlir/Dialect/QTensor
16+
DEPENDS
17+
MLIRQTensorOpsIncGen
18+
LINK_LIBS
19+
PUBLIC
20+
MLIRQTensorDialect)
21+
22+
mqt_mlir_target_use_project_options(MLIRQTensorUtils)
23+
24+
# collect header files
25+
file(GLOB_RECURSE UTILS_HEADERS_SOURCE
26+
"${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QTensor/Utils/*.h")
27+
file(GLOB_RECURSE UTILS_HEADERS_BUILD
28+
"${MQT_MLIR_BUILD_INCLUDE_DIR}/mlir/Dialect/QTensor/Utils/*.inc")
29+
30+
# add public headers using file sets
31+
target_sources(
32+
MLIRQTensorUtils
33+
PUBLIC FILE_SET
34+
HEADERS
35+
BASE_DIRS
36+
${MQT_MLIR_SOURCE_INCLUDE_DIR}
37+
FILES
38+
${UTILS_HEADERS_SOURCE}
39+
FILE_SET
40+
HEADERS
41+
BASE_DIRS
42+
${MQT_MLIR_BUILD_INCLUDE_DIR}
43+
FILES
44+
${UTILS_HEADERS_BUILD})
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/*
2+
* Copyright (c) 2023 - 2026 Chair for Design Automation, TUM
3+
* Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH
4+
* All rights reserved.
5+
*
6+
* SPDX-License-Identifier: MIT
7+
*
8+
* Licensed under the MIT License
9+
*/
10+
11+
#include "mlir/Dialect/QTensor/Utils/TensorIterator.h"
12+
13+
#include "mlir/Dialect/QCO/IR/QCOOps.h"
14+
#include "mlir/Dialect/QTensor/IR/QTensorOps.h"
15+
16+
#include <llvm/ADT/STLExtras.h>
17+
#include <llvm/ADT/TypeSwitch.h>
18+
#include <llvm/Support/ErrorHandling.h>
19+
#include <mlir/Dialect/SCF/IR/SCF.h>
20+
#include <mlir/IR/Builders.h>
21+
#include <mlir/IR/Value.h>
22+
#include <mlir/Support/LLVM.h>
23+
24+
#include <cassert>
25+
#include <iterator>
26+
27+
namespace mlir::qtensor {
28+
TypedValue<RankedTensorType> TensorIterator::tensor() const {
29+
if (op_ == nullptr) {
30+
return tensor_;
31+
}
32+
33+
// The following operations don't have an OpResult.
34+
if (isa<DeallocOp, scf::YieldOp, qco::YieldOp>(op_)) {
35+
return nullptr;
36+
}
37+
38+
return tensor_;
39+
}
40+
41+
void TensorIterator::forward() {
42+
// If the iterator is a sentinel already, there is nothing to do.
43+
if (isSentinel_) {
44+
return;
45+
}
46+
47+
// Find the user-operation of the tensor SSA value.
48+
assert(tensor_.hasOneUse() && "expected linear typing");
49+
op_ = *(tensor_.user_begin());
50+
51+
// The following operations define the end of the tensor's life-chain.
52+
if (isa<DeallocOp, scf::YieldOp, qco::YieldOp>(op_)) {
53+
isSentinel_ = true;
54+
return;
55+
}
56+
57+
// Find the output from the input tensor SSA value.
58+
if (!(isa<AllocOp, FromElementsOp>(op_))) {
59+
TypeSwitch<Operation*>(op_)
60+
.Case<ExtractOp>([&](ExtractOp op) { tensor_ = op.getOutTensor(); })
61+
.Case<InsertOp>([&](InsertOp op) { tensor_ = op.getResult(); })
62+
.Case<scf::ForOp>([&](scf::ForOp op) {
63+
tensor_ = cast<TypedValue<RankedTensorType>>(
64+
op.getTiedLoopResult(&*(tensor_.use_begin())));
65+
})
66+
.Case<qco::IfOp>([&](qco::IfOp op) {
67+
auto it = llvm::find(op.getQubits(), tensor_);
68+
assert(it != op.getQubits().end());
69+
const auto idx = std::distance(op.getQubits().begin(), it);
70+
tensor_ = cast<TypedValue<RankedTensorType>>(op.getResults()[idx]);
71+
})
72+
.Default([&](Operation* op) {
73+
report_fatal_error("unknown op in def-use chain: " +
74+
op->getName().getStringRef());
75+
});
76+
}
77+
}
78+
79+
void TensorIterator::backward() {
80+
// If the iterator is a sentinel, reactivate the iterator.
81+
if (isSentinel_) {
82+
isSentinel_ = false;
83+
return;
84+
}
85+
86+
// If the op is a nullptr, the tensor value is a block argument and thus the
87+
// beginning of the tensor's life-chain.
88+
if (op_ == nullptr) {
89+
return;
90+
}
91+
92+
// For these operations, tensor_ is an OpOperand. Hence, only get the def-op.
93+
if (isa<DeallocOp, scf::YieldOp, qco::YieldOp>(op_)) {
94+
op_ = tensor_.getDefiningOp();
95+
return;
96+
}
97+
98+
// Allocations and FromElements define the start of the tensor's life-chain.
99+
// Consequently, stop and early exit.
100+
if (isa<AllocOp, FromElementsOp>(op_)) {
101+
return;
102+
}
103+
104+
// Find the input from the output tensor SSA value.
105+
TypeSwitch<Operation*>(op_)
106+
.Case<ExtractOp>([&](ExtractOp op) { tensor_ = op.getTensor(); })
107+
.Case<InsertOp>([&](InsertOp op) { tensor_ = op.getDest(); })
108+
.Case<scf::ForOp>([&](scf::ForOp op) {
109+
if (auto res = dyn_cast<OpResult>(tensor_)) {
110+
OpOperand* operand = op.getTiedLoopInit(res);
111+
tensor_ = cast<TypedValue<RankedTensorType>>(operand->get());
112+
return;
113+
}
114+
115+
llvm::reportFatalInternalError(
116+
"expected scf.for result for tied init lookup");
117+
})
118+
.Case<qco::IfOp>([&](qco::IfOp op) {
119+
if (auto res = dyn_cast<OpResult>(tensor_)) {
120+
auto it = llvm::find(op.getResults(), res);
121+
assert(it != op->result_end());
122+
const auto idx = std::distance(op.result_begin(), it);
123+
tensor_ = cast<TypedValue<RankedTensorType>>(op.getQubits()[idx]);
124+
return;
125+
}
126+
127+
llvm::reportFatalInternalError(
128+
"expected scf.for result for tied init lookup");
129+
})
130+
.Default([&](Operation* op) {
131+
llvm::reportFatalInternalError("unknown op in def-use chain: " +
132+
op->getName().getStringRef());
133+
});
134+
135+
// Get the operation that produces the tensor value.
136+
// If the current tensor SSA value is a BlockArgument (no defining op), the
137+
// operation will be a nullptr.
138+
op_ = tensor_.getDefiningOp();
139+
}
140+
141+
static_assert(std::bidirectional_iterator<TensorIterator>);
142+
static_assert(std::sentinel_for<std::default_sentinel_t, TensorIterator>,
143+
"std::default_sentinel_t must be a sentinel for TensorIterator.");
144+
} // namespace mlir::qtensor

mlir/unittests/Dialect/QTensor/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@
77
# Licensed under the MIT License
88

99
add_subdirectory(IR)
10+
add_subdirectory(Utils)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM
2+
# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH
3+
# All rights reserved.
4+
#
5+
# SPDX-License-Identifier: MIT
6+
#
7+
# Licensed under the MIT License
8+
9+
set(qtensor_utils_target mqt-core-mlir-unittest-qtensor-utils)
10+
add_executable(${qtensor_utils_target} test_tensoriterator.cpp)
11+
target_link_libraries(${qtensor_utils_target} PRIVATE GTest::gtest_main MLIRQTensorDialect
12+
MLIRQTensorUtils MLIRQCOProgramBuilder)
13+
mqt_mlir_configure_unittest_target(${qtensor_utils_target})
14+
15+
gtest_discover_tests(${qtensor_utils_target} PROPERTIES LABELS mqt-mlir-unittests DISCOVERY_TIMEOUT
16+
60)

0 commit comments

Comments
 (0)