From 10a4a2a1558bf8d507871d1d15be3a57e67b4a39 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 22 May 2026 09:30:03 +0200 Subject: [PATCH 01/10] Add TensorIterator (cherry picked from commit dd935d7607fa542c8944a38065f4d3cfee3945d6) --- .../Dialect/QTensor/Utils/TensorIterator.h | 86 +++++++++++++++ mlir/lib/Dialect/QCO/Utils/WireIterator.cpp | 4 +- mlir/lib/Dialect/QTensor/CMakeLists.txt | 1 + mlir/lib/Dialect/QTensor/Utils/CMakeLists.txt | 42 +++++++ .../Dialect/QTensor/Utils/TensorIterator.cpp | 104 ++++++++++++++++++ 5 files changed, 235 insertions(+), 2 deletions(-) create mode 100644 mlir/include/mlir/Dialect/QTensor/Utils/TensorIterator.h create mode 100644 mlir/lib/Dialect/QTensor/Utils/CMakeLists.txt create mode 100644 mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp diff --git a/mlir/include/mlir/Dialect/QTensor/Utils/TensorIterator.h b/mlir/include/mlir/Dialect/QTensor/Utils/TensorIterator.h new file mode 100644 index 0000000000..c318bf0ea7 --- /dev/null +++ b/mlir/include/mlir/Dialect/QTensor/Utils/TensorIterator.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include +#include +#include +#include + +#include + +namespace mlir::qtensor { + +/** + * @brief A bidirectional_iterator traversing the def-use chain of a tensor. + **/ +class [[nodiscard]] TensorIterator { +public: + using iterator_category = std::bidirectional_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = Operation*; + + TensorIterator() : op_(nullptr), tensor_(nullptr), isSentinel_(false) {} + explicit TensorIterator(TypedValue tensor) + : op_(tensor.getDefiningOp()), tensor_(tensor), isSentinel_(false) {} + + /// @returns the operation the iterator points to. + [[nodiscard]] Operation* operation() const { return op_; } + + /// @returns the operation the iterator points to. + [[nodiscard]] Operation* operator*() const { return operation(); } + + /// @returns the tensor the iterator points to. + [[nodiscard]] TypedValue tensor() const; + + TensorIterator& operator++() { + forward(); + return *this; + } + + TensorIterator operator++(int) { + auto tmp = *this; + operator++(); + return tmp; + } + + TensorIterator& operator--() { + backward(); + return *this; + } + + TensorIterator operator--(int) { + auto tmp = *this; + operator--(); + return tmp; + } + + bool operator==(const TensorIterator& other) const { + return other.tensor_ == tensor_ && other.op_ == op_ && + other.isSentinel_ == isSentinel_; + } + + bool operator==([[maybe_unused]] std::default_sentinel_t s) const { + return isSentinel_; + } + +private: + /// @brief Move to the next operation on the tensor def-use chain. + void forward(); + + /// @brief Move to the previous operation on the tensor def-use chain. + void backward(); + + Operation* op_; + TypedValue tensor_; + bool isSentinel_; +}; +} // namespace mlir::qco diff --git a/mlir/lib/Dialect/QCO/Utils/WireIterator.cpp b/mlir/lib/Dialect/QCO/Utils/WireIterator.cpp index 50edf48229..c951a15b12 100644 --- a/mlir/lib/Dialect/QCO/Utils/WireIterator.cpp +++ b/mlir/lib/Dialect/QCO/Utils/WireIterator.cpp @@ -39,8 +39,8 @@ void WireIterator::forward() { } // Find the user-operation of the qubit SSA value. - assert(qubit_.getNumUses() == 1 && "expected linear typing"); - op_ = *(qubit_.getUsers().begin()); + assert(qubit_.hasOneUse() && "expected linear typing"); + op_ = *(qubit_.user_begin()); // A sink/insert defines the end of the qubit wire (dynamic and static). if (isa(op_)) { diff --git a/mlir/lib/Dialect/QTensor/CMakeLists.txt b/mlir/lib/Dialect/QTensor/CMakeLists.txt index 3b0a561d0f..7b705e9b19 100644 --- a/mlir/lib/Dialect/QTensor/CMakeLists.txt +++ b/mlir/lib/Dialect/QTensor/CMakeLists.txt @@ -8,3 +8,4 @@ add_subdirectory(IR) add_subdirectory(Transforms) +add_subdirectory(Utils) diff --git a/mlir/lib/Dialect/QTensor/Utils/CMakeLists.txt b/mlir/lib/Dialect/QTensor/Utils/CMakeLists.txt new file mode 100644 index 0000000000..687cfadb6d --- /dev/null +++ b/mlir/lib/Dialect/QTensor/Utils/CMakeLists.txt @@ -0,0 +1,42 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +file(GLOB_RECURSE UTILS_CPP "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") + +add_mlir_dialect_library( + MLIRQTensorUtils + ${UTILS_CPP} + ADDITIONAL_HEADER_DIRS + ${PROJECT_SOURCE_DIR}/mlir/include/mlir/Dialect/QTensor + DEPENDS + MLIRQTensorOpsIncGen + LINK_LIBS + PUBLIC + MLIRQTensorDialect) + +mqt_mlir_target_use_project_options(MLIRQTensorUtils) + +# collect header files +file(GLOB_RECURSE UTILS_HEADERS_SOURCE "${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QTensor/Utils/*.h") +file(GLOB_RECURSE UTILS_HEADERS_BUILD "${MQT_MLIR_BUILD_INCLUDE_DIR}/mlir/Dialect/QTensor/Utils/*.inc") + +# add public headers using file sets +target_sources( + MLIRQTensorUtils + PUBLIC FILE_SET + HEADERS + BASE_DIRS + ${MQT_MLIR_SOURCE_INCLUDE_DIR} + FILES + ${UTILS_HEADERS_SOURCE} + FILE_SET + HEADERS + BASE_DIRS + ${MQT_MLIR_BUILD_INCLUDE_DIR} + FILES + ${UTILS_HEADERS_BUILD}) \ No newline at end of file diff --git a/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp b/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp new file mode 100644 index 0000000000..1aa9e1ea25 --- /dev/null +++ b/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp @@ -0,0 +1,104 @@ +#include "mlir/Dialect/QTensor/Utils/TensorIterator.h" + +#include "mlir/Dialect/QTensor/IR/QTensorOps.h" + +#include +#include +#include +#include + +#include +namespace mlir::qtensor { +TypedValue TensorIterator::tensor() const { + // A tensor deallocation doesn't have an OpResult. + if (isa(op_)) { + return nullptr; + } + return tensor_; +} + +void TensorIterator::forward() { + // If the iterator is a sentinel already, there is nothing to do. + if (isSentinel_) { + return; + } + + // Find the user-operation of the tensor SSA value. + assert(tensor_.hasOneUse() && "expected linear typing"); + op_ = *(tensor_.user_begin()); + + // A deallocation defines the end of the tensor's life-chain. + if (isa(op_)) { + isSentinel_ = true; + return; + } + + // Find the output from the input tensor SSA value. + if (!(isa(op_))) { + TypeSwitch(op_) + .Case( + [&](qtensor::ExtractOp op) { tensor_ = op.getOutTensor(); }) + .Case( + [&](qtensor::InsertOp op) { tensor_ = op.getResult(); }) + .Case([&](scf::ForOp op) { + tensor_ = cast>( + op.getTiedLoopResult(&*(tensor_.use_begin()))); + }) + .Default([&](Operation* op) { + report_fatal_error("unknown op in def-use chain: " + + op->getName().getStringRef()); + }); + } +} + +void TensorIterator::backward() { + // If the iterator is a sentinel, reactivate the iterator. + if (isSentinel_) { + isSentinel_ = false; + return; + } + + // For deallocations and scf::YieldOps, tensor_ is an OpOperand. + // Hence, only get the def-op. + if (isa(op_)) { + op_ = tensor_.getDefiningOp(); + return; + } + + // Allocations and FromElements define the start of the tensor's life-chain. + // Consequently, stop and early exit. + if (isa(op_)) { + return; + } + + // Find the input from the output tensor SSA value. + TypeSwitch(op_) + .Case( + [&](qtensor::ExtractOp op) { tensor_ = op.getTensor(); }) + .Case( + [&](qtensor::InsertOp op) { tensor_ = op.getDest(); }) + .Case([&](scf::ForOp op) { + if (auto res = dyn_cast(tensor_)) { + OpOperand* operand = op.getTiedLoopInit(res); + tensor_ = cast>(operand->get()); + return; + } + + llvm::report_fatal_error( + "expected scf.for result for tied init lookup"); + }) + .Default([&](Operation* op) { + report_fatal_error("unknown op in def-use chain: " + + op->getName().getStringRef()); + }); + + // Get the operation that produces the tensor value. + // If the current tensor SSA value is a BlockArgument (no defining op), the + // operation will be a nullptr. + op_ = tensor_.getDefiningOp(); +} + +static_assert(std::bidirectional_iterator); +static_assert(std::sentinel_for, + "std::default_sentinel_t must be a sentinel for TensorIterator."); +} // namespace mlir::qtensor \ No newline at end of file From fb549713eeb0683338c16fb7ab07702d4050d8bc Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 22 May 2026 09:30:11 +0200 Subject: [PATCH 02/10] Add qtensor-utils unit test (cherry picked from commit 872eb4d06fb73946a1c383dfb2a6d0b0cbabb758) --- .../Dialect/QTensor/Utils/TensorIterator.h | 2 +- .../Dialect/QTensor/Utils/TensorIterator.cpp | 2 +- mlir/unittests/Dialect/QTensor/CMakeLists.txt | 1 + .../Dialect/QTensor/Utils/CMakeLists.txt | 15 ++ .../QTensor/Utils/test_tensoriterator.cpp | 134 ++++++++++++++++++ 5 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 mlir/unittests/Dialect/QTensor/Utils/CMakeLists.txt create mode 100644 mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp diff --git a/mlir/include/mlir/Dialect/QTensor/Utils/TensorIterator.h b/mlir/include/mlir/Dialect/QTensor/Utils/TensorIterator.h index c318bf0ea7..f7af43a2e8 100644 --- a/mlir/include/mlir/Dialect/QTensor/Utils/TensorIterator.h +++ b/mlir/include/mlir/Dialect/QTensor/Utils/TensorIterator.h @@ -20,7 +20,7 @@ namespace mlir::qtensor { /** - * @brief A bidirectional_iterator traversing the def-use chain of a tensor. + * @brief A bidirectional_iterator traversing the tensor chain. **/ class [[nodiscard]] TensorIterator { public: diff --git a/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp b/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp index 1aa9e1ea25..b221f48752 100644 --- a/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp +++ b/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp @@ -60,7 +60,7 @@ void TensorIterator::backward() { // For deallocations and scf::YieldOps, tensor_ is an OpOperand. // Hence, only get the def-op. - if (isa(op_)) { + if (isa(op_)) { op_ = tensor_.getDefiningOp(); return; } diff --git a/mlir/unittests/Dialect/QTensor/CMakeLists.txt b/mlir/unittests/Dialect/QTensor/CMakeLists.txt index b181a84fed..e4045824ac 100644 --- a/mlir/unittests/Dialect/QTensor/CMakeLists.txt +++ b/mlir/unittests/Dialect/QTensor/CMakeLists.txt @@ -7,3 +7,4 @@ # Licensed under the MIT License add_subdirectory(IR) +add_subdirectory(Utils) diff --git a/mlir/unittests/Dialect/QTensor/Utils/CMakeLists.txt b/mlir/unittests/Dialect/QTensor/Utils/CMakeLists.txt new file mode 100644 index 0000000000..4da42a02ff --- /dev/null +++ b/mlir/unittests/Dialect/QTensor/Utils/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +set(qtensor_utils_target mqt-core-mlir-unittest-qtensor-utils) +add_executable(${qtensor_utils_target} test_tensoriterator.cpp) +target_link_libraries(${qtensor_utils_target} PRIVATE GTest::gtest_main MLIRQTensorDialect MLIRQTensorUtils + MLIRQCOProgramBuilder) +mqt_mlir_configure_unittest_target(${qtensor_utils_target}) + +gtest_discover_tests(${qtensor_utils_target} PROPERTIES LABELS mqt-mlir-unittests DISCOVERY_TIMEOUT 60) \ No newline at end of file diff --git a/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp b/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp new file mode 100644 index 0000000000..2c4a215f7c --- /dev/null +++ b/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp @@ -0,0 +1,134 @@ +#include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QTensor/IR/QTensorDialect.h" +#include "mlir/Dialect/QTensor/Utils/TensorIterator.h" +#include "mlir/Support/IRVerification.h" +#include "mlir/Support/Passes.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace mlir; +using namespace mlir::qtensor; +using namespace mlir::qco; + +namespace { + +class TensorIteratorTest : public ::testing::Test { +protected: + std::unique_ptr context; + + void SetUp() override { + DialectRegistry registry; + registry.insert(); + context = std::make_unique(); + context->appendDialectRegistry(registry); + context->loadAllAvailableDialects(); + } +}; +} // namespace + +TEST_F(TensorIteratorTest, Traversal) { + qco::QCOProgramBuilder builder(context.get()); + builder.initialize(); + + auto tensor0 = builder.qtensorAlloc(3); + auto [tensor1, q00] = builder.qtensorExtract(tensor0, 0); + auto q01 = builder.h(q00); + auto tensor2 = builder.qtensorInsert(q01, tensor1, 0); + auto [tensor3, q02] = builder.qtensorExtract(tensor2, 0); + auto [tensor4, q10] = builder.qtensorExtract(tensor3, 1); + auto [q03, q11] = builder.cx(q02, q10); + auto tensor5 = builder.qtensorInsert(q03, tensor4, 0); + auto tensor6 = builder.qtensorInsert(q11, tensor5, 1); + builder.qtensorDealloc(tensor6); + [[maybe_unused]] auto m = builder.finalize(); + + TensorIterator it(cast>(tensor0)); + + ASSERT_EQ(it.operation(), tensor0.getDefiningOp()); // qtensor.alloc + ASSERT_EQ(it.tensor(), tensor0); + + ++it; + ASSERT_EQ(it.operation(), tensor1.getDefiningOp()); // qtensor.extract + ASSERT_EQ(it.tensor(), tensor1); + + ++it; + ASSERT_EQ(it.operation(), tensor2.getDefiningOp()); // qtensor.insert + ASSERT_EQ(it.tensor(), tensor2); + + ++it; + ASSERT_EQ(it.operation(), tensor3.getDefiningOp()); // qtensor.extract + ASSERT_EQ(it.tensor(), tensor3); + + ++it; + ASSERT_EQ(it.operation(), tensor4.getDefiningOp()); // qtensor.extract + ASSERT_EQ(it.tensor(), tensor4); + + ++it; + ASSERT_EQ(it.operation(), tensor5.getDefiningOp()); // qtensor.insert + ASSERT_EQ(it.tensor(), tensor5); + + ++it; + ASSERT_EQ(it.operation(), tensor6.getDefiningOp()); // qtensor.insert + ASSERT_EQ(it.tensor(), tensor6); + + ++it; + ASSERT_EQ(it.operation(), *(tensor6.user_begin())); // qtensor.dealloc + ASSERT_EQ(it.tensor(), nullptr); + + ++it; + ASSERT_EQ(it, std::default_sentinel); + + ++it; + ASSERT_EQ(it, std::default_sentinel); + + --it; + ASSERT_EQ(it.operation(), *(tensor6.user_begin())); // qtensor.dealloc + ASSERT_EQ(it.tensor(), nullptr); + + --it; + ASSERT_EQ(it.operation(), tensor6.getDefiningOp()); // qtensor.insert + ASSERT_EQ(it.tensor(), tensor6); + + --it; + ASSERT_EQ(it.operation(), tensor5.getDefiningOp()); // qtensor.insert + ASSERT_EQ(it.tensor(), tensor5); + + --it; + ASSERT_EQ(it.operation(), tensor4.getDefiningOp()); // qtensor.extract + ASSERT_EQ(it.tensor(), tensor4); + + --it; + ASSERT_EQ(it.operation(), tensor3.getDefiningOp()); // qtensor.extract + ASSERT_EQ(it.tensor(), tensor3); + + --it; + ASSERT_EQ(it.operation(), tensor2.getDefiningOp()); // qtensor.extract + ASSERT_EQ(it.tensor(), tensor2); + + --it; + ASSERT_EQ(it.operation(), tensor1.getDefiningOp()); // qtensor.extract + ASSERT_EQ(it.tensor(), tensor1); + + --it; + ASSERT_EQ(it.operation(), tensor0.getDefiningOp()); // qtensor.alloc + ASSERT_EQ(it.tensor(), tensor0); + + --it; + ASSERT_EQ(it.operation(), tensor0.getDefiningOp()); // qtensor.alloc + ASSERT_EQ(it.tensor(), tensor0); +} \ No newline at end of file From f60a182a7d38eeb27187b85b6e294f193981e414 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 07:33:20 +0000 Subject: [PATCH 03/10] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mlir/Dialect/QTensor/Utils/TensorIterator.h | 2 +- mlir/lib/Dialect/QTensor/Utils/CMakeLists.txt | 8 +++++--- mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp | 12 +++++++++++- .../unittests/Dialect/QTensor/Utils/CMakeLists.txt | 7 ++++--- .../Dialect/QTensor/Utils/test_tensoriterator.cpp | 14 ++++++++++++-- 5 files changed, 33 insertions(+), 10 deletions(-) diff --git a/mlir/include/mlir/Dialect/QTensor/Utils/TensorIterator.h b/mlir/include/mlir/Dialect/QTensor/Utils/TensorIterator.h index f7af43a2e8..75acfdbe1c 100644 --- a/mlir/include/mlir/Dialect/QTensor/Utils/TensorIterator.h +++ b/mlir/include/mlir/Dialect/QTensor/Utils/TensorIterator.h @@ -83,4 +83,4 @@ class [[nodiscard]] TensorIterator { TypedValue tensor_; bool isSentinel_; }; -} // namespace mlir::qco +} // namespace mlir::qtensor diff --git a/mlir/lib/Dialect/QTensor/Utils/CMakeLists.txt b/mlir/lib/Dialect/QTensor/Utils/CMakeLists.txt index 687cfadb6d..6b94307613 100644 --- a/mlir/lib/Dialect/QTensor/Utils/CMakeLists.txt +++ b/mlir/lib/Dialect/QTensor/Utils/CMakeLists.txt @@ -22,8 +22,10 @@ add_mlir_dialect_library( mqt_mlir_target_use_project_options(MLIRQTensorUtils) # collect header files -file(GLOB_RECURSE UTILS_HEADERS_SOURCE "${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QTensor/Utils/*.h") -file(GLOB_RECURSE UTILS_HEADERS_BUILD "${MQT_MLIR_BUILD_INCLUDE_DIR}/mlir/Dialect/QTensor/Utils/*.inc") +file(GLOB_RECURSE UTILS_HEADERS_SOURCE + "${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QTensor/Utils/*.h") +file(GLOB_RECURSE UTILS_HEADERS_BUILD + "${MQT_MLIR_BUILD_INCLUDE_DIR}/mlir/Dialect/QTensor/Utils/*.inc") # add public headers using file sets target_sources( @@ -39,4 +41,4 @@ target_sources( BASE_DIRS ${MQT_MLIR_BUILD_INCLUDE_DIR} FILES - ${UTILS_HEADERS_BUILD}) \ No newline at end of file + ${UTILS_HEADERS_BUILD}) diff --git a/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp b/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp index b221f48752..6429bd6f96 100644 --- a/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp +++ b/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp @@ -1,3 +1,13 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + #include "mlir/Dialect/QTensor/Utils/TensorIterator.h" #include "mlir/Dialect/QTensor/IR/QTensorOps.h" @@ -101,4 +111,4 @@ void TensorIterator::backward() { static_assert(std::bidirectional_iterator); static_assert(std::sentinel_for, "std::default_sentinel_t must be a sentinel for TensorIterator."); -} // namespace mlir::qtensor \ No newline at end of file +} // namespace mlir::qtensor diff --git a/mlir/unittests/Dialect/QTensor/Utils/CMakeLists.txt b/mlir/unittests/Dialect/QTensor/Utils/CMakeLists.txt index 4da42a02ff..3650fb2f6f 100644 --- a/mlir/unittests/Dialect/QTensor/Utils/CMakeLists.txt +++ b/mlir/unittests/Dialect/QTensor/Utils/CMakeLists.txt @@ -8,8 +8,9 @@ set(qtensor_utils_target mqt-core-mlir-unittest-qtensor-utils) add_executable(${qtensor_utils_target} test_tensoriterator.cpp) -target_link_libraries(${qtensor_utils_target} PRIVATE GTest::gtest_main MLIRQTensorDialect MLIRQTensorUtils - MLIRQCOProgramBuilder) +target_link_libraries(${qtensor_utils_target} PRIVATE GTest::gtest_main MLIRQTensorDialect + MLIRQTensorUtils MLIRQCOProgramBuilder) mqt_mlir_configure_unittest_target(${qtensor_utils_target}) -gtest_discover_tests(${qtensor_utils_target} PROPERTIES LABELS mqt-mlir-unittests DISCOVERY_TIMEOUT 60) \ No newline at end of file +gtest_discover_tests(${qtensor_utils_target} PROPERTIES LABELS mqt-mlir-unittests DISCOVERY_TIMEOUT + 60) diff --git a/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp b/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp index 2c4a215f7c..dc56477b5f 100644 --- a/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp +++ b/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp @@ -1,3 +1,13 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + #include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" #include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/QTensor/IR/QTensorDialect.h" @@ -124,11 +134,11 @@ TEST_F(TensorIteratorTest, Traversal) { ASSERT_EQ(it.operation(), tensor1.getDefiningOp()); // qtensor.extract ASSERT_EQ(it.tensor(), tensor1); - --it; + --it; ASSERT_EQ(it.operation(), tensor0.getDefiningOp()); // qtensor.alloc ASSERT_EQ(it.tensor(), tensor0); --it; ASSERT_EQ(it.operation(), tensor0.getDefiningOp()); // qtensor.alloc ASSERT_EQ(it.tensor(), tensor0); -} \ No newline at end of file +} From 3df1be074929dff0129bf22349a9ccbca3f3e17e Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 22 May 2026 09:37:02 +0200 Subject: [PATCH 04/10] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 417b2da3c6..67683b1ad1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Added +- ✨ Add bi-directional iterator that traverses the def-use chain of a tensor value ([#1730]) ([**@MatthiasReumann**]) - ✨ Add a `hadamard-lifting` pass for lifting Hadamard gates above Pauli gates ([#1605]) ([**@lirem101**], [**@burgholzer**]) - ✨ Add a `merge-single-qubit-rotation-gates` pass for merging consecutive rotation gates using quaternions ([#1407], [#1674]) ([**@J4MMlE**], [**@denialhaag**], [**@MatthiasReumann**]) - ✨ Add conversions between `jeff` and QCO ([#1479], [#1548], [#1565], [#1637], [#1676], [#1706]) ([**@denialhaag**], [**@burgholzer**]) @@ -399,6 +400,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool +[#1730]: https://github.com/munich-quantum-toolkit/core/pull/1730 [#1720]: https://github.com/munich-quantum-toolkit/core/pull/1720 [#1719]: https://github.com/munich-quantum-toolkit/core/pull/1719 [#1717]: https://github.com/munich-quantum-toolkit/core/pull/1717 From d9153d452f2e7132953bbac925ee66a41b279e2c Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 22 May 2026 09:39:49 +0200 Subject: [PATCH 05/10] Fix linting --- mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp | 4 ++++ .../Dialect/QTensor/Utils/test_tensoriterator.cpp | 9 +++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp b/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp index 6429bd6f96..937907820e 100644 --- a/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp +++ b/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp @@ -13,11 +13,15 @@ #include "mlir/Dialect/QTensor/IR/QTensorOps.h" #include +#include +#include #include #include #include +#include #include + namespace mlir::qtensor { TypedValue TensorIterator::tensor() const { // A tensor deallocation doesn't have an OpResult. diff --git a/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp b/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp index dc56477b5f..0281b66d42 100644 --- a/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp +++ b/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp @@ -12,22 +12,19 @@ #include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/QTensor/IR/QTensorDialect.h" #include "mlir/Dialect/QTensor/Utils/TensorIterator.h" -#include "mlir/Support/IRVerification.h" -#include "mlir/Support/Passes.h" #include +#include #include #include #include #include #include #include -#include #include -#include -#include +#include -#include +#include #include using namespace mlir; From 365356acd397ba07492bb99a5ed61f6a50b2106c Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 22 May 2026 09:48:38 +0200 Subject: [PATCH 06/10] Add scf.for to unit test --- mlir/lib/Dialect/QCO/Utils/WireIterator.cpp | 8 ++--- .../Dialect/QTensor/Utils/TensorIterator.cpp | 8 ++--- .../QTensor/Utils/test_tensoriterator.cpp | 31 +++++++++++++++---- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Utils/WireIterator.cpp b/mlir/lib/Dialect/QCO/Utils/WireIterator.cpp index c951a15b12..45522cd8bf 100644 --- a/mlir/lib/Dialect/QCO/Utils/WireIterator.cpp +++ b/mlir/lib/Dialect/QCO/Utils/WireIterator.cpp @@ -57,8 +57,8 @@ void WireIterator::forward() { .Case([&](MeasureOp op) { qubit_ = op.getQubitOut(); }) .Case([&](ResetOp op) { qubit_ = op.getQubitOut(); }) .Default([&](Operation* op) { - report_fatal_error("unknown op in def-use chain: " + - op->getName().getStringRef()); + llvm::reportFatalInternalError("unknown op in def-use chain: " + + op->getName().getStringRef()); }); } } @@ -90,8 +90,8 @@ void WireIterator::backward() { .Case([&](MeasureOp op) { qubit_ = op.getQubitIn(); }) .Case([&](ResetOp op) { qubit_ = op.getQubitIn(); }) .Default([&](Operation* op) { - report_fatal_error("unknown op in def-use chain: " + - op->getName().getStringRef()); + llvm::reportFatalInternalError("unknown op in def-use chain: " + + op->getName().getStringRef()); }); // Get the operation that produces the qubit value. diff --git a/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp b/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp index 937907820e..4f8f731366 100644 --- a/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp +++ b/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp @@ -13,7 +13,7 @@ #include "mlir/Dialect/QTensor/IR/QTensorOps.h" #include -#include +#include #include #include #include @@ -98,12 +98,12 @@ void TensorIterator::backward() { return; } - llvm::report_fatal_error( + llvm::reportFatalInternalError( "expected scf.for result for tied init lookup"); }) .Default([&](Operation* op) { - report_fatal_error("unknown op in def-use chain: " + - op->getName().getStringRef()); + llvm::reportFatalInternalError("unknown op in def-use chain: " + + op->getName().getStringRef()); }); // Get the operation that produces the tensor value. diff --git a/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp b/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp index 0281b66d42..1c11aba560 100644 --- a/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp +++ b/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp @@ -14,15 +14,16 @@ #include "mlir/Dialect/QTensor/Utils/TensorIterator.h" #include -#include #include #include +#include #include #include #include #include #include #include +#include #include #include @@ -40,7 +41,7 @@ class TensorIteratorTest : public ::testing::Test { void SetUp() override { DialectRegistry registry; registry.insert(); + scf::SCFDialect, QTensorDialect>(); context = std::make_unique(); context->appendDialectRegistry(registry); context->loadAllAvailableDialects(); @@ -52,7 +53,8 @@ TEST_F(TensorIteratorTest, Traversal) { qco::QCOProgramBuilder builder(context.get()); builder.initialize(); - auto tensor0 = builder.qtensorAlloc(3); + constexpr int64_t n = 3; + auto tensor0 = builder.qtensorAlloc(n); auto [tensor1, q00] = builder.qtensorExtract(tensor0, 0); auto q01 = builder.h(q00); auto tensor2 = builder.qtensorInsert(q01, tensor1, 0); @@ -61,7 +63,16 @@ TEST_F(TensorIteratorTest, Traversal) { auto [q03, q11] = builder.cx(q02, q10); auto tensor5 = builder.qtensorInsert(q03, tensor4, 0); auto tensor6 = builder.qtensorInsert(q11, tensor5, 1); - builder.qtensorDealloc(tensor6); + auto tensor7 = builder.scfFor( + 1, n, 1, {tensor6}, [&builder](Value iv, ValueRange iterArgs) { + Value loopTensor = iterArgs[0]; + Value q; + std::tie(loopTensor, q) = builder.qtensorExtract(loopTensor, iv); + q = builder.h(q); + loopTensor = builder.qtensorInsert(q, loopTensor, 0); + return SmallVector{loopTensor}; + })[0]; + builder.qtensorDealloc(tensor7); [[maybe_unused]] auto m = builder.finalize(); TensorIterator it(cast>(tensor0)); @@ -94,7 +105,11 @@ TEST_F(TensorIteratorTest, Traversal) { ASSERT_EQ(it.tensor(), tensor6); ++it; - ASSERT_EQ(it.operation(), *(tensor6.user_begin())); // qtensor.dealloc + ASSERT_EQ(it.operation(), tensor7.getDefiningOp()); // scf.for + ASSERT_EQ(it.tensor(), tensor7); + + ++it; + ASSERT_EQ(it.operation(), *(tensor7.user_begin())); // qtensor.dealloc ASSERT_EQ(it.tensor(), nullptr); ++it; @@ -104,9 +119,13 @@ TEST_F(TensorIteratorTest, Traversal) { ASSERT_EQ(it, std::default_sentinel); --it; - ASSERT_EQ(it.operation(), *(tensor6.user_begin())); // qtensor.dealloc + ASSERT_EQ(it.operation(), *(tensor7.user_begin())); // qtensor.dealloc ASSERT_EQ(it.tensor(), nullptr); + --it; + ASSERT_EQ(it.operation(), tensor7.getDefiningOp()); // scf.for + ASSERT_EQ(it.tensor(), tensor7); + --it; ASSERT_EQ(it.operation(), tensor6.getDefiningOp()); // qtensor.insert ASSERT_EQ(it.tensor(), tensor6); From 029e32d93e784632ed5b99c608a00d32f8a4ea3e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 07:49:09 +0000 Subject: [PATCH 07/10] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp b/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp index 4f8f731366..7b492679e4 100644 --- a/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp +++ b/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp @@ -13,11 +13,11 @@ #include "mlir/Dialect/QTensor/IR/QTensorOps.h" #include -#include #include #include #include #include +#include #include #include From edff31df9b4b221f5e42183345b9679d9acd3e28 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 22 May 2026 10:12:42 +0200 Subject: [PATCH 08/10] Add missing includes --- mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp b/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp index 1c11aba560..718d9c4989 100644 --- a/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp +++ b/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp @@ -25,8 +25,10 @@ #include #include +#include #include #include +#include using namespace mlir; using namespace mlir::qtensor; From 6e2049c2050259fa3bafc31d33e11536e5d35bcf Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 22 May 2026 16:51:16 +0200 Subject: [PATCH 09/10] :pencil2: adding this to the main MQT CC entry in the changelog this is foundational infrastructure after all Signed-off-by: burgholzer --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcacfd09fa..252ad20e5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,14 +12,13 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Added - 🚸 Add `debug`, `release`, `coverage`, and `lint` [CMake presets] ([#1660]) ([**@denialhaag**]) -- ✨ Add bi-directional iterator that traverses the def-use chain of a tensor value ([#1730]) ([**@MatthiasReumann**]) - ✨ Add a `quantum-loop-unroll` pass for unrolling for-loop operations containing quantum operations ([#1718]) ([**@MatthiasReumann**]) - ✨ Add a `hadamard-lifting` pass for lifting Hadamard gates above Pauli gates ([#1605]) ([**@lirem101**], [**@burgholzer**]) - ✨ Add a `merge-single-qubit-rotation-gates` pass for merging consecutive rotation gates using quaternions ([#1407], [#1674]) ([**@J4MMlE**], [**@denialhaag**], [**@MatthiasReumann**]) - ✨ Add conversions between `jeff` and QCO ([#1479], [#1548], [#1565], [#1637], [#1676], [#1706]) ([**@denialhaag**], [**@burgholzer**]) - ✨ 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**]) - ✨ Add initial infrastructure for new QC and QCO MLIR dialects - ([#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]) + ([#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]) ([**@burgholzer**], [**@denialhaag**], [**@taminob**], [**@DRovara**], [**@li-mingbao**], [**@Ectras**], [**@MatthiasReumann**], [**@simon1hofmann**]) ### Changed From f3358feacd9af4b6daa0257e7ac0e2997a140825 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 22 May 2026 17:13:58 +0200 Subject: [PATCH 10/10] :art: removing redundant namespace qualifiers Signed-off-by: burgholzer --- .../Dialect/QTensor/Utils/TensorIterator.cpp | 22 ++++++++----------- .../QTensor/Utils/test_tensoriterator.cpp | 2 +- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp b/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp index 7b492679e4..9853c7f4bb 100644 --- a/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp +++ b/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp @@ -25,7 +25,7 @@ namespace mlir::qtensor { TypedValue TensorIterator::tensor() const { // A tensor deallocation doesn't have an OpResult. - if (isa(op_)) { + if (isa(op_)) { return nullptr; } return tensor_; @@ -42,18 +42,16 @@ void TensorIterator::forward() { op_ = *(tensor_.user_begin()); // A deallocation defines the end of the tensor's life-chain. - if (isa(op_)) { + if (isa(op_)) { isSentinel_ = true; return; } // Find the output from the input tensor SSA value. - if (!(isa(op_))) { + if (!(isa(op_))) { TypeSwitch(op_) - .Case( - [&](qtensor::ExtractOp op) { tensor_ = op.getOutTensor(); }) - .Case( - [&](qtensor::InsertOp op) { tensor_ = op.getResult(); }) + .Case([&](ExtractOp op) { tensor_ = op.getOutTensor(); }) + .Case([&](InsertOp op) { tensor_ = op.getResult(); }) .Case([&](scf::ForOp op) { tensor_ = cast>( op.getTiedLoopResult(&*(tensor_.use_begin()))); @@ -74,23 +72,21 @@ void TensorIterator::backward() { // For deallocations and scf::YieldOps, tensor_ is an OpOperand. // Hence, only get the def-op. - if (isa(op_)) { + if (isa(op_)) { op_ = tensor_.getDefiningOp(); return; } // Allocations and FromElements define the start of the tensor's life-chain. // Consequently, stop and early exit. - if (isa(op_)) { + if (isa(op_)) { return; } // Find the input from the output tensor SSA value. TypeSwitch(op_) - .Case( - [&](qtensor::ExtractOp op) { tensor_ = op.getTensor(); }) - .Case( - [&](qtensor::InsertOp op) { tensor_ = op.getDest(); }) + .Case([&](ExtractOp op) { tensor_ = op.getTensor(); }) + .Case([&](InsertOp op) { tensor_ = op.getDest(); }) .Case([&](scf::ForOp op) { if (auto res = dyn_cast(tensor_)) { OpOperand* operand = op.getTiedLoopInit(res); diff --git a/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp b/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp index 718d9c4989..57b5da86b6 100644 --- a/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp +++ b/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp @@ -52,7 +52,7 @@ class TensorIteratorTest : public ::testing::Test { } // namespace TEST_F(TensorIteratorTest, Traversal) { - qco::QCOProgramBuilder builder(context.get()); + QCOProgramBuilder builder(context.get()); builder.initialize(); constexpr int64_t n = 3;