From 069cbd9a84616a3dd11e2ab06514c2036d418b49 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Mon, 31 Aug 2026 17:30:18 +0200 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=90=9B=20Harden=20MLIR=20constant=20f?= =?UTF-8?q?olding=20(#2255)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assisted-by: GPT-5.6 Sol via Codex --- .../mlir/Dialect/MQT/Utils/ConstantFolding.h | 8 +- .../lib/Dialect/MQT/Utils/ConstantFolding.cpp | 130 +++++++++++++----- .../Dialect/MQT/Utils/CMakeLists.txt | 5 +- .../MQT/Utils/test_constant_folding.cpp | 25 +++- 4 files changed, 129 insertions(+), 39 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQT/Utils/ConstantFolding.h b/mlir/include/mlir/Dialect/MQT/Utils/ConstantFolding.h index 52187ad578..4b79c9a7d8 100644 --- a/mlir/include/mlir/Dialect/MQT/Utils/ConstantFolding.h +++ b/mlir/include/mlir/Dialect/MQT/Utils/ConstantFolding.h @@ -21,11 +21,11 @@ namespace mlir::mqt { /// Convert a floating-point or integer attribute to a double. [[nodiscard]] std::optional attributeToDouble(Attribute attr); -/// Convert a direct arithmetic constant to a double. +/// Convert a direct constant-like value to a double. [[nodiscard]] std::optional valueToDouble(Value value); /** - * Recursively constant-fold a pure SSA expression DAG to an attribute. + * Iteratively constant-fold a pure SSA expression DAG to an attribute. * * The cache memoizes successful and failed evaluations so shared operands are * resolved once. @@ -37,10 +37,10 @@ namespace mlir::mqt { valueToConstantAttr(Value value, DenseMap>& cache); -/// Recursively constant-fold a pure SSA expression DAG to an attribute. +/// Iteratively constant-fold a pure SSA expression DAG to an attribute. [[nodiscard]] std::optional valueToConstantAttr(Value value); -/// Recursively constant-fold a pure SSA expression DAG to a double. +/// Iteratively constant-fold a pure SSA expression DAG to a double. [[nodiscard]] std::optional valueToConstantDouble(Value value); } // namespace mlir::mqt diff --git a/mlir/lib/Dialect/MQT/Utils/ConstantFolding.cpp b/mlir/lib/Dialect/MQT/Utils/ConstantFolding.cpp index b9a446ec3c..b67496dee1 100644 --- a/mlir/lib/Dialect/MQT/Utils/ConstantFolding.cpp +++ b/mlir/lib/Dialect/MQT/Utils/ConstantFolding.cpp @@ -10,8 +10,8 @@ #include "mlir/Dialect/MQT/Utils/ConstantFolding.h" +#include #include -#include #include #include #include @@ -41,11 +41,11 @@ std::optional attributeToDouble(Attribute attr) { } std::optional valueToDouble(Value value) { - auto constantOp = value.getDefiningOp(); - if (!constantOp) { + Attribute attr; + if (!matchPattern(value, m_Constant(&attr))) { return std::nullopt; } - return attributeToDouble(constantOp.getValue()); + return attributeToDouble(attr); } std::optional @@ -55,39 +55,105 @@ valueToConstantAttr(Value value, return it->second; } - Attribute attr; - if (matchPattern(value, m_Constant(&attr))) { - return cache[value] = attr; - } + struct Frame { + Value value; + Operation* operation; + unsigned nextOperand = 0; + }; - Operation* operation = value.getDefiningOp(); - if (operation == nullptr || operation->getNumRegions() != 0 || - !isPure(operation)) { - return cache[value] = std::nullopt; - } + SmallVector worklist; + llvm::SmallDenseSet active; + const auto schedule = [&](Value candidate) { + if (cache.contains(candidate)) { + return; + } + Attribute attr; + if (matchPattern(candidate, m_Constant(&attr))) { + cache[candidate] = attr; + return; + } + Operation* operation = candidate.getDefiningOp(); + if (operation == nullptr || operation->getNumRegions() != 0 || + !isPure(operation)) { + cache[candidate] = std::nullopt; + return; + } + active.insert(candidate); + worklist.push_back({candidate, operation}); + }; - SmallVector operands; - operands.reserve(operation->getNumOperands()); - for (Value operand : operation->getOperands()) { - const auto folded = valueToConstantAttr(operand, cache); - if (!folded) { - return cache[value] = std::nullopt; + schedule(value); + while (!worklist.empty()) { + auto& frame = worklist.back(); + bool scheduledOperand = false; + while (frame.nextOperand < frame.operation->getNumOperands()) { + Value operand = frame.operation->getOperand(frame.nextOperand++); + if (cache.contains(operand)) { + continue; + } + if (active.contains(operand)) { + cache[operand] = std::nullopt; + continue; + } + schedule(operand); + scheduledOperand = true; + break; + } + if (scheduledOperand) { + continue; } - operands.push_back(*folded); - } - SmallVector results; - if (failed(operation->fold(operands, results)) || results.size() != 1) { - return cache[value] = std::nullopt; - } - std::optional folded; - if (auto resultAttr = dyn_cast_if_present(results.front())) { - folded = resultAttr; - } else if (auto resultValue = dyn_cast_if_present(results.front())) { - /* Identity-style folds can return an existing SSA value. */ - folded = valueToConstantAttr(resultValue, cache); + SmallVector operands; + operands.reserve(frame.operation->getNumOperands()); + bool failedOperand = false; + for (Value operand : frame.operation->getOperands()) { + const auto it = cache.find(operand); + if (it == cache.end() || !it->second) { + failedOperand = true; + break; + } + operands.push_back(*it->second); + } + if (failedOperand) { + active.erase(frame.value); + cache[frame.value] = std::nullopt; + worklist.pop_back(); + continue; + } + + SmallVector results; + if (failed(frame.operation->fold(operands, results)) || + results.size() != 1) { + active.erase(frame.value); + cache[frame.value] = std::nullopt; + worklist.pop_back(); + continue; + } + if (auto resultAttr = dyn_cast_if_present(results.front())) { + active.erase(frame.value); + cache[frame.value] = resultAttr; + worklist.pop_back(); + continue; + } + + auto resultValue = dyn_cast_if_present(results.front()); + if (!resultValue || resultValue == frame.value || + active.contains(resultValue)) { + active.erase(frame.value); + cache[frame.value] = std::nullopt; + worklist.pop_back(); + continue; + } + if (!cache.contains(resultValue)) { + schedule(resultValue); + continue; + } + active.erase(frame.value); + cache[frame.value] = cache.lookup(resultValue); + worklist.pop_back(); } - return cache[value] = folded; + + return cache.lookup(value); } std::optional valueToConstantAttr(Value value) { diff --git a/mlir/unittests/Dialect/MQT/Utils/CMakeLists.txt b/mlir/unittests/Dialect/MQT/Utils/CMakeLists.txt index 3096dc4013..592bf561a2 100644 --- a/mlir/unittests/Dialect/MQT/Utils/CMakeLists.txt +++ b/mlir/unittests/Dialect/MQT/Utils/CMakeLists.txt @@ -8,8 +8,9 @@ set(mqt_utils_target mqt-core-mlir-unittests-mqt-utils) add_executable(${mqt_utils_target} test_constant_folding.cpp test_gate_powering.cpp) -target_link_libraries(${mqt_utils_target} PRIVATE GTest::gtest_main MLIRArithDialect - MLIRFuncDialect MLIRIR MLIRMQTUtils) +target_link_libraries( + ${mqt_utils_target} PRIVATE GTest::gtest_main MLIRArithDialect MLIRFuncDialect MLIRIndexDialect + MLIRIR MLIRMQTUtils) mqt_mlir_configure_unittest_target(${mqt_utils_target}) gtest_discover_tests(${mqt_utils_target} PROPERTIES LABELS mqt-mlir-unittests DISCOVERY_TIMEOUT 60) diff --git a/mlir/unittests/Dialect/MQT/Utils/test_constant_folding.cpp b/mlir/unittests/Dialect/MQT/Utils/test_constant_folding.cpp index 3ccde2b38b..cb1f0554a4 100644 --- a/mlir/unittests/Dialect/MQT/Utils/test_constant_folding.cpp +++ b/mlir/unittests/Dialect/MQT/Utils/test_constant_folding.cpp @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include #include @@ -39,7 +41,8 @@ class ConstantFoldingTest : public ::testing::Test { std::unique_ptr builder; void SetUp() override { - context.loadDialect(); + context.loadDialect(); auto loc = FileLineColLoc::get(&context, "", 1, 1); module = ModuleOp::create(loc); @@ -66,6 +69,13 @@ TEST_F(ConstantFoldingTest, valueToDoubleCastFromInteger) { EXPECT_DOUBLE_EQ(*stdValue, 42.0); } +TEST_F(ConstantFoldingTest, valueToDoubleConstantLike) { + auto op = index::ConstantOp::create(*builder, 42); + const auto stdValue = mlir::mqt::valueToDouble(op.getResult()); + ASSERT_TRUE(stdValue.has_value()); + EXPECT_DOUBLE_EQ(*stdValue, 42.0); +} + TEST_F(ConstantFoldingTest, valueToDoubleCastFromNegativeInteger) { auto op = arith::ConstantOp::create(*builder, builder->getSI32IntegerAttr(-123)); @@ -251,3 +261,16 @@ TEST_F(ConstantFoldingTest, valueToConstantDoubleSharedOperandsFailure) { EXPECT_FALSE(it->second.has_value()); } } + +TEST_F(ConstantFoldingTest, valueToConstantAttrHandlesDeepExpressions) { + constexpr int depth = 10000; + Value value = arith::ConstantIntOp::create(*builder, 1, 64); + Value zero = arith::ConstantIntOp::create(*builder, 0, 64); + for (int i = 0; i < depth; ++i) { + value = arith::AddIOp::create(*builder, value, zero); + } + + const auto folded = mlir::mqt::valueToConstantAttr(value); + ASSERT_TRUE(folded); + EXPECT_EQ(cast(*folded).getInt(), 1); +} From 1d7f50055a83f46a31424a689bbb077c8aa331cd Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Tue, 1 Sep 2026 13:40:08 +0200 Subject: [PATCH 2/2] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Drop=20speculative=20c?= =?UTF-8?q?onstant-folding=20worklist?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restore the memoized recursive fold implementation and remove the synthetic deep-expression regression. Keep only the constant-like operation support with its focused test. Assisted-by: GPT-5.6 via Codex --- .../mlir/Dialect/MQT/Utils/ConstantFolding.h | 6 +- .../lib/Dialect/MQT/Utils/ConstantFolding.cpp | 123 ++++-------------- .../MQT/Utils/test_constant_folding.cpp | 13 -- 3 files changed, 31 insertions(+), 111 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQT/Utils/ConstantFolding.h b/mlir/include/mlir/Dialect/MQT/Utils/ConstantFolding.h index 4b79c9a7d8..f917893375 100644 --- a/mlir/include/mlir/Dialect/MQT/Utils/ConstantFolding.h +++ b/mlir/include/mlir/Dialect/MQT/Utils/ConstantFolding.h @@ -25,7 +25,7 @@ namespace mlir::mqt { [[nodiscard]] std::optional valueToDouble(Value value); /** - * Iteratively constant-fold a pure SSA expression DAG to an attribute. + * Recursively constant-fold a pure SSA expression DAG to an attribute. * * The cache memoizes successful and failed evaluations so shared operands are * resolved once. @@ -37,10 +37,10 @@ namespace mlir::mqt { valueToConstantAttr(Value value, DenseMap>& cache); -/// Iteratively constant-fold a pure SSA expression DAG to an attribute. +/// Recursively constant-fold a pure SSA expression DAG to an attribute. [[nodiscard]] std::optional valueToConstantAttr(Value value); -/// Iteratively constant-fold a pure SSA expression DAG to a double. +/// Recursively constant-fold a pure SSA expression DAG to a double. [[nodiscard]] std::optional valueToConstantDouble(Value value); } // namespace mlir::mqt diff --git a/mlir/lib/Dialect/MQT/Utils/ConstantFolding.cpp b/mlir/lib/Dialect/MQT/Utils/ConstantFolding.cpp index b67496dee1..13e746202a 100644 --- a/mlir/lib/Dialect/MQT/Utils/ConstantFolding.cpp +++ b/mlir/lib/Dialect/MQT/Utils/ConstantFolding.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/MQT/Utils/ConstantFolding.h" -#include #include #include #include @@ -55,105 +54,39 @@ valueToConstantAttr(Value value, return it->second; } - struct Frame { - Value value; - Operation* operation; - unsigned nextOperand = 0; - }; - - SmallVector worklist; - llvm::SmallDenseSet active; - const auto schedule = [&](Value candidate) { - if (cache.contains(candidate)) { - return; - } - Attribute attr; - if (matchPattern(candidate, m_Constant(&attr))) { - cache[candidate] = attr; - return; - } - Operation* operation = candidate.getDefiningOp(); - if (operation == nullptr || operation->getNumRegions() != 0 || - !isPure(operation)) { - cache[candidate] = std::nullopt; - return; - } - active.insert(candidate); - worklist.push_back({candidate, operation}); - }; - - schedule(value); - while (!worklist.empty()) { - auto& frame = worklist.back(); - bool scheduledOperand = false; - while (frame.nextOperand < frame.operation->getNumOperands()) { - Value operand = frame.operation->getOperand(frame.nextOperand++); - if (cache.contains(operand)) { - continue; - } - if (active.contains(operand)) { - cache[operand] = std::nullopt; - continue; - } - schedule(operand); - scheduledOperand = true; - break; - } - if (scheduledOperand) { - continue; - } - - SmallVector operands; - operands.reserve(frame.operation->getNumOperands()); - bool failedOperand = false; - for (Value operand : frame.operation->getOperands()) { - const auto it = cache.find(operand); - if (it == cache.end() || !it->second) { - failedOperand = true; - break; - } - operands.push_back(*it->second); - } - if (failedOperand) { - active.erase(frame.value); - cache[frame.value] = std::nullopt; - worklist.pop_back(); - continue; - } + Attribute attr; + if (matchPattern(value, m_Constant(&attr))) { + return cache[value] = attr; + } - SmallVector results; - if (failed(frame.operation->fold(operands, results)) || - results.size() != 1) { - active.erase(frame.value); - cache[frame.value] = std::nullopt; - worklist.pop_back(); - continue; - } - if (auto resultAttr = dyn_cast_if_present(results.front())) { - active.erase(frame.value); - cache[frame.value] = resultAttr; - worklist.pop_back(); - continue; - } + Operation* operation = value.getDefiningOp(); + if (operation == nullptr || operation->getNumRegions() != 0 || + !isPure(operation)) { + return cache[value] = std::nullopt; + } - auto resultValue = dyn_cast_if_present(results.front()); - if (!resultValue || resultValue == frame.value || - active.contains(resultValue)) { - active.erase(frame.value); - cache[frame.value] = std::nullopt; - worklist.pop_back(); - continue; - } - if (!cache.contains(resultValue)) { - schedule(resultValue); - continue; + SmallVector operands; + operands.reserve(operation->getNumOperands()); + for (Value operand : operation->getOperands()) { + const auto folded = valueToConstantAttr(operand, cache); + if (!folded) { + return cache[value] = std::nullopt; } - active.erase(frame.value); - cache[frame.value] = cache.lookup(resultValue); - worklist.pop_back(); + operands.push_back(*folded); } - return cache.lookup(value); + SmallVector results; + if (failed(operation->fold(operands, results)) || results.size() != 1) { + return cache[value] = std::nullopt; + } + std::optional folded; + if (auto resultAttr = dyn_cast_if_present(results.front())) { + folded = resultAttr; + } else if (auto resultValue = dyn_cast_if_present(results.front())) { + /* Identity-style folds can return an existing SSA value. */ + folded = valueToConstantAttr(resultValue, cache); + } + return cache[value] = folded; } std::optional valueToConstantAttr(Value value) { diff --git a/mlir/unittests/Dialect/MQT/Utils/test_constant_folding.cpp b/mlir/unittests/Dialect/MQT/Utils/test_constant_folding.cpp index cb1f0554a4..527551ddc9 100644 --- a/mlir/unittests/Dialect/MQT/Utils/test_constant_folding.cpp +++ b/mlir/unittests/Dialect/MQT/Utils/test_constant_folding.cpp @@ -261,16 +261,3 @@ TEST_F(ConstantFoldingTest, valueToConstantDoubleSharedOperandsFailure) { EXPECT_FALSE(it->second.has_value()); } } - -TEST_F(ConstantFoldingTest, valueToConstantAttrHandlesDeepExpressions) { - constexpr int depth = 10000; - Value value = arith::ConstantIntOp::create(*builder, 1, 64); - Value zero = arith::ConstantIntOp::create(*builder, 0, 64); - for (int i = 0; i < depth; ++i) { - value = arith::AddIOp::create(*builder, value, zero); - } - - const auto folded = mlir::mqt::valueToConstantAttr(value); - ASSERT_TRUE(folded); - EXPECT_EQ(cast(*folded).getInt(), 1); -}