[Mlir-commits] [mlir] [MLIR][SCCP] Fix in-place folds leaking into IR during simulation (PR #213933)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Tue Aug 4 07:11:01 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir
Author: Berke Ates (Berke-Ates)
<details>
<summary>Changes</summary>
SparseConstantPropagation restored the operation only when `fold` did not return any fold results. But a folder can mutate the op in place and still return out-of-place results or fail, e.g. `vector.extract` folds constant dynamic positions into static ones before attempting further folds.
The constants fed to `fold` are speculative lattice values, so the mutation bakes a possibly-wrong constant into the IR. SCCP would permanently replace a loop-carried dynamic index with its first lattice value for example.
Fix: Restore the original operands and attributes after every fold call, regardless of its outcome.
---
Full diff: https://github.com/llvm/llvm-project/pull/213933.diff
2 Files Affected:
- (modified) mlir/lib/Analysis/DataFlow/ConstantPropagationAnalysis.cpp (+13-11)
- (modified) mlir/test/Transforms/sccp.mlir (+49)
``````````diff
diff --git a/mlir/lib/Analysis/DataFlow/ConstantPropagationAnalysis.cpp b/mlir/lib/Analysis/DataFlow/ConstantPropagationAnalysis.cpp
index dbf68ac575dbe..e6e7491b041ee 100644
--- a/mlir/lib/Analysis/DataFlow/ConstantPropagationAnalysis.cpp
+++ b/mlir/lib/Analysis/DataFlow/ConstantPropagationAnalysis.cpp
@@ -72,21 +72,23 @@ LogicalResult SparseConstantPropagation::visitOperation(
SmallVector<Value, 8> originalOperands(op->getOperands());
DictionaryAttr originalAttrs = op->getAttrDictionary();
- // Simulate the result of folding this operation to a constant. If folding
- // fails or was an in-place fold, mark the results as overdefined.
+ // Simulate the result of folding this operation to a constant.
SmallVector<OpFoldResult, 8> foldResults;
foldResults.reserve(op->getNumResults());
- if (failed(op->fold(constantOperands, foldResults))) {
- setAllToEntryStates(results);
- return success();
- }
+ LogicalResult folded = op->fold(constantOperands, foldResults);
- // If the folding was in-place, mark the results as overdefined and reset
- // the operation. We don't allow in-place folds as the desire here is for
- // simulated execution, and not general folding.
- if (foldResults.empty()) {
+ // `fold` can mutate the operation in place and still return an out-of-place
+ // result, so the mutation must be reverted regardless of the outcome.
+ // Only write the operands back if the fold changed them, as `setOperands`
+ // relinks use-lists even for identical values.
+ if (!llvm::equal(op->getOperands(), originalOperands))
op->setOperands(originalOperands);
- op->setAttrs(originalAttrs);
+ op->setAttrs(originalAttrs);
+
+ // If folding failed or was in-place, mark the results as overdefined. We
+ // don't allow in-place folds here: the goal is simulated execution, not
+ // general folding.
+ if (failed(folded) || foldResults.empty()) {
setAllToEntryStates(results);
return success();
}
diff --git a/mlir/test/Transforms/sccp.mlir b/mlir/test/Transforms/sccp.mlir
index 251a74dc20647..9f2217524287e 100644
--- a/mlir/test/Transforms/sccp.mlir
+++ b/mlir/test/Transforms/sccp.mlir
@@ -335,3 +335,52 @@ func.func @fold_to_non_operand_value(%x: i64, %cond: i1) -> i64 {
%cast2 = builtin.unrealized_conversion_cast %cast1 : index to i64
return %cast2 : i64
}
+
+// -----
+
+// Regression test: SCCP must revert in-place folds. The `vector.extract`
+// folder rewrites a constant dynamic position into a static one in place, but
+// the constants SCCP feeds into `fold` are speculative: on the first visit of
+// ^bb1 %iv is Constant 0, before the back edge widens it to overdefined.
+// Without the revert, the dynamic extract would permanently read element 0.
+
+// CHECK-LABEL: func @no_inplace_extract_fold_of_speculative_constant
+func.func @no_inplace_extract_fold_of_speculative_constant(%a: f32, %b: f32) -> f32 {
+ %c0_i32 = arith.constant 0 : i32
+ %c1_i32 = arith.constant 1 : i32
+ %c2_i32 = arith.constant 2 : i32
+ %v = vector.from_elements %a, %b : vector<2xf32>
+ cf.br ^bb1(%c0_i32 : i32)
+^bb1(%iv: i32):
+ %idx = arith.index_cast %iv : i32 to index
+ // CHECK: vector.extract %{{.*}}[%{{.*}}] : f32 from vector<2xf32>
+ %e = vector.extract %v[%idx] : f32 from vector<2xf32>
+ %next = arith.addi %iv, %c1_i32 : i32
+ %cond = arith.cmpi ne, %next, %c2_i32 : i32
+ cf.cond_br %cond, ^bb1(%next : i32), ^bb2
+^bb2:
+ return %e : f32
+}
+
+// -----
+
+// The same in-place position rewrite happens in the `vector.insert` folder.
+
+// CHECK-LABEL: func @no_inplace_insert_fold_of_speculative_constant
+func.func @no_inplace_insert_fold_of_speculative_constant() -> vector<2xf32> {
+ %c0_i32 = arith.constant 0 : i32
+ %c1_i32 = arith.constant 1 : i32
+ %c2_i32 = arith.constant 2 : i32
+ %f = arith.constant 3.000000e+00 : f32
+ %init = arith.constant dense<[1.000000e+00, 2.000000e+00]> : vector<2xf32>
+ cf.br ^bb1(%c0_i32, %init : i32, vector<2xf32>)
+^bb1(%iv: i32, %acc: vector<2xf32>):
+ %idx = arith.index_cast %iv : i32 to index
+ // CHECK: vector.insert %{{.*}}, %{{.*}}[%{{.*}}] : f32 into vector<2xf32>
+ %ins = vector.insert %f, %acc[%idx] : f32 into vector<2xf32>
+ %next = arith.addi %iv, %c1_i32 : i32
+ %cond = arith.cmpi ne, %next, %c2_i32 : i32
+ cf.cond_br %cond, ^bb1(%next, %ins : i32, vector<2xf32>), ^bb2
+^bb2:
+ return %ins : vector<2xf32>
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/213933
More information about the Mlir-commits
mailing list