[Mlir-commits] [mlir] [MLIR] Fix infinite loop in tryFold (PR #189232)
Mehdi Amini
llvmlistbot at llvm.org
Sun Mar 29 05:40:07 PDT 2026
https://github.com/joker-eph created https://github.com/llvm/llvm-project/pull/189232
In a graph region (e.g. the module body), or in unreachable blocks in SSACFG region, an op may use its own result as an operand, creating a circular SSA dependency. When such an op was passed to OpBuilder::tryFold, the do-while loop could spin forever:
1. foldCommutative moved the constant operand to the RHS (in-place fold, foldResults still empty).
2. The next fold call returned the op's own result, signalling an in-place fold, but foldSingleResultHook re-ran trait folds, found nothing to swap, and returned success with empty foldResults.
3. Step 2 repeated without end.
Fix: before each iteration of the loop, snapshot the operand list. If a fold returns empty results but the operands are unchanged, we have reached a fixpoint. Stop the loop and return failure from tryFold so that dialect conversion falls through to a conversion pattern instead of looping.
Fixes #159675
Assisted-by: Claude Code
>From 102ef2798704853d8c6fa3e039d2b870a9c18d1c Mon Sep 17 00:00:00 2001
From: Mehdi Amini <joker.eph at gmail.com>
Date: Sat, 28 Mar 2026 11:28:13 -0700
Subject: [PATCH] [MLIR] Fix infinite loop in tryFold for circular SSA uses in
graph regions
In a graph region (e.g. the module body), an op may use its own result as
an operand, creating a circular SSA dependency. When such an op was passed
to OpBuilder::tryFold, the do-while loop could spin forever:
1. foldCommutative moved the constant operand to the RHS (in-place fold,
foldResults still empty).
2. The next fold call returned the op's own result, signalling an in-place
fold, but foldSingleResultHook re-ran trait folds, found nothing to swap,
and returned success with empty foldResults.
3. Step 2 repeated without end.
Fix: before each iteration of the loop, snapshot the operand list. If a fold
returns empty results but the operands are unchanged, we have reached a
fixpoint. Stop the loop and return failure from tryFold so that dialect
conversion falls through to a conversion pattern instead of looping.
Add a regression test in Conversion/ArithToLLVM/ that previously hung.
Fixes #159675
Assisted-by: Claude Code
---
mlir/lib/IR/Builders.cpp | 28 +++++++++++++++++--
.../graph-region-circular-ssa.mlir | 28 +++++++++++++++++++
2 files changed, 53 insertions(+), 3 deletions(-)
create mode 100644 mlir/test/Conversion/ArithToLLVM/graph-region-circular-ssa.mlir
diff --git a/mlir/lib/IR/Builders.cpp b/mlir/lib/IR/Builders.cpp
index cf64954751d5e..f68abf056cc21 100644
--- a/mlir/lib/IR/Builders.cpp
+++ b/mlir/lib/IR/Builders.cpp
@@ -497,15 +497,37 @@ OpBuilder::tryFold(Operation *op, SmallVectorImpl<Value> &results,
return cleanupFailure();
int count = 0;
+ bool fixpoint = false;
do {
LDBG() << "Folded in place #" << count
<< " times: " << OpWithFlags(op, OpPrintingFlags().skipRegions());
count++;
- } while (foldResults.empty() && succeeded(op->fold(foldResults)));
+ } while (foldResults.empty() && [&]() {
+ // Snapshot the op's observable state before the next fold attempt to detect
+ // fixpoints. An in-place fold may modify operands, discardable attributes,
+ // or properties (inherent attributes) without producing fold results. If
+ // none of these change, no further progress is possible and we stop to
+ // avoid an infinite loop.
+ // Note: getRawDictionaryAttrs() and operand Values are interned pointers,
+ // so equality is O(1). hashProperties() covers the properties blob.
+ SmallVector<Value> operandsBefore(op->getOperands());
+ DictionaryAttr attrsBefore = op->getRawDictionaryAttrs();
+ llvm::hash_code propHashBefore = op->hashProperties();
+ if (!succeeded(op->fold(foldResults)))
+ return false;
+ if (foldResults.empty() && llvm::equal(op->getOperands(), operandsBefore) &&
+ op->getRawDictionaryAttrs() == attrsBefore &&
+ op->hashProperties() == propHashBefore) {
+ fixpoint = true;
+ return false;
+ }
+ return true;
+ }());
- // An in-place fold does not require generation of any constants.
+ // An in-place fold does not require generation of any constants. Return
+ // failure if no actual progress was made (fixpoint reached).
if (foldResults.empty())
- return success();
+ return fixpoint ? cleanupFailure() : success();
// A temporary builder used for creating constants during folding.
OpBuilder cstBuilder(context);
diff --git a/mlir/test/Conversion/ArithToLLVM/graph-region-circular-ssa.mlir b/mlir/test/Conversion/ArithToLLVM/graph-region-circular-ssa.mlir
new file mode 100644
index 0000000000000..49568a07535f2
--- /dev/null
+++ b/mlir/test/Conversion/ArithToLLVM/graph-region-circular-ssa.mlir
@@ -0,0 +1,28 @@
+// RUN: mlir-opt --convert-arith-to-llvm %s | FileCheck %s
+
+// Regression test for https://github.com/llvm/llvm-project/issues/159675
+//
+// In a graph region (e.g. the module body), SSA values may be used before
+// they are defined. The arith.addi below uses %1 (a constant zero) as its
+// LHS operand, but %1 is defined *after* %0 in the block, creating a
+// circular SSA dependency from %0's perspective.
+//
+// During dialect conversion, OpBuilder::tryFold attempted to fold arith.addi:
+// 1. foldCommutative swapped operands so the constant moved to RHS.
+// 2. On the next iteration, addi.fold() returned %0 (the op's own result)
+// because the RHS is now a zero constant, signalling an in-place fold.
+// 3. foldSingleResultHook called trait folds again; foldCommutative found
+// nothing to swap and returned success with empty fold-results.
+// 4. The do-while loop condition was still true, causing an infinite loop.
+//
+// The fix detects this fixpoint: if a fold returns empty results but leaves
+// the op state (operands, attributes, and properties) unchanged, the loop
+// stops and tryFold returns failure so that the normal conversion pattern is
+// applied instead.
+
+// CHECK: module {
+// CHECK: llvm.add
+"builtin.module"() ({
+ %0 = "arith.addi"(%1, %0) <{overflowFlags = #arith.overflow<none>}> : (index, index) -> index
+ %1 = "arith.constant"() <{value = 0 : index}> : () -> index
+}) : () -> ()
More information about the Mlir-commits
mailing list