diff --git a/CHANGELOG.md b/CHANGELOG.md index 6312fb8d9b..8c3b5c34c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel - ✨ 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 @@ -403,6 +403,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool [#1737]: https://github.com/munich-quantum-toolkit/core/pull/1737 +[#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 [#1718]: https://github.com/munich-quantum-toolkit/core/pull/1718 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..75acfdbe1c --- /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 tensor chain. + **/ +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::qtensor diff --git a/mlir/lib/Dialect/QCO/Utils/WireIterator.cpp b/mlir/lib/Dialect/QCO/Utils/WireIterator.cpp index 50edf48229..45522cd8bf 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_)) { @@ -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/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..6b94307613 --- /dev/null +++ b/mlir/lib/Dialect/QTensor/Utils/CMakeLists.txt @@ -0,0 +1,44 @@ +# 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}) diff --git a/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp b/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp new file mode 100644 index 0000000000..9853c7f4bb --- /dev/null +++ b/mlir/lib/Dialect/QTensor/Utils/TensorIterator.cpp @@ -0,0 +1,114 @@ +/* + * 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" + +#include +#include +#include +#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([&](ExtractOp op) { tensor_ = op.getOutTensor(); }) + .Case([&](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([&](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); + tensor_ = cast>(operand->get()); + return; + } + + llvm::reportFatalInternalError( + "expected scf.for result for tied init lookup"); + }) + .Default([&](Operation* op) { + llvm::reportFatalInternalError("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 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..3650fb2f6f --- /dev/null +++ b/mlir/unittests/Dialect/QTensor/Utils/CMakeLists.txt @@ -0,0 +1,16 @@ +# 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) 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..57b5da86b6 --- /dev/null +++ b/mlir/unittests/Dialect/QTensor/Utils/test_tensoriterator.cpp @@ -0,0 +1,162 @@ +/* + * 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" +#include "mlir/Dialect/QTensor/Utils/TensorIterator.h" + +#include +#include +#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) { + QCOProgramBuilder builder(context.get()); + builder.initialize(); + + 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); + 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); + 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)); + + 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(), 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; + ASSERT_EQ(it, std::default_sentinel); + + ++it; + ASSERT_EQ(it, std::default_sentinel); + + --it; + 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); + + --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); +}