[Mlir-commits] [mlir] [MLIR] Fix infinite loop in tryFold (PR #189232)
Mehdi Amini
llvmlistbot at llvm.org
Sun Mar 29 05:59:11 PDT 2026
https://github.com/joker-eph updated https://github.com/llvm/llvm-project/pull/189232
>From 5393ef7d6debf5d7b25ed9aed3e8a71c3b4bf343 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: cap the number of in-place fold iterations at 64. Legitimate chains are
very short in practice (foldCommutative swaps once, then the op folds to a
value or constant), so the cap is never reached for correct IR. For the
circular-SSA case the loop now terminates and tryFold returns failure, letting
dialect conversion fall 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 | 8 +++++-
.../graph-region-circular-ssa.mlir | 28 +++++++++++++++++++
2 files changed, 35 insertions(+), 1 deletion(-)
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..43b2f7d992a43 100644
--- a/mlir/lib/IR/Builders.cpp
+++ b/mlir/lib/IR/Builders.cpp
@@ -496,11 +496,17 @@ OpBuilder::tryFold(Operation *op, SmallVectorImpl<Value> &results,
if (failed(op->fold(foldResults)))
return cleanupFailure();
+ // Bound the number of in-place fold iterations. Legitimate chains are very
+ // short (e.g. foldCommutative swaps once, then the op folds to a value).
+ // Without a bound, circular SSA uses in graph regions can cause an infinite
+ // loop (e.g. addi(x, 0) where x is the op's own result).
+ constexpr int kMaxInPlaceFolds = 64;
int count = 0;
do {
LDBG() << "Folded in place #" << count
<< " times: " << OpWithFlags(op, OpPrintingFlags().skipRegions());
- count++;
+ if (++count >= kMaxInPlaceFolds)
+ return cleanupFailure();
} while (foldResults.empty() && succeeded(op->fold(foldResults)));
// An in-place fold does not require generation of any constants.
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