[llvm] da4894c - [LoopFusion] reject unsafe scalar flow dependences (#195895)
via llvm-commits
llvm-commits at lists.llvm.org
Wed May 27 20:01:27 PDT 2026
Author: Arda Serdar Pektezol
Date: 2026-05-28T08:31:22+05:30
New Revision: da4894c874edb5fc9b9b82a8a99ed471a49153bf
URL: https://github.com/llvm/llvm-project/commit/da4894c874edb5fc9b9b82a8a99ed471a49153bf
DIFF: https://github.com/llvm/llvm-project/commit/da4894c874edb5fc9b9b82a8a99ed471a49153bf.diff
LOG: [LoopFusion] reject unsafe scalar flow dependences (#195895)
`loop-fusion` treats any loop-invariant scalar non-anti dependence as
safe to fuse. In the linked issue, it incorrectly allows scalar flow
dependences where the first loop writes a loop-invariant location and
the second loop later reads that same location. Fusion interleaves the
producer and consumer and this changes the value observed by the second
loop.
Example C source would look like:
```C
for (int i = 0; i < N; i++) {
ptr[0] = i;
}
for (int j = 0; j < N; j++) {
out[j] = ptr[0];
}
=>
for (int i = 0; i < N; i++) {
ptr[0] = i;
out[i] = ptr[0];
}
```
This patch makes the DA scalar-dependence shortcut **_more
conservative_** by rejecting scalar non-anti and allowing input/output
dependences. This preserves the existing safe read and write cases while
preventing the miscompile above.
The patch also updates the `loop-fusion` debug message to reflect the
narrower accepted case, updates the existing regression to check the new
debug message, and adds a new regression from the linked issue.
Fixes #191238
Added:
llvm/test/Transforms/LoopFusion/pr191238.ll
Modified:
llvm/lib/Transforms/Scalar/LoopFuse.cpp
llvm/test/Transforms/LoopFusion/loop_invariant.ll
Removed:
################################################################################
diff --git a/llvm/lib/Transforms/Scalar/LoopFuse.cpp b/llvm/lib/Transforms/Scalar/LoopFuse.cpp
index 723dce9dec468..47bed80cb84ea 100644
--- a/llvm/lib/Transforms/Scalar/LoopFuse.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopFuse.cpp
@@ -1141,11 +1141,17 @@ struct LoopFuser {
assert(CurLoopLevel > Levels && "Fusion candidates are not separated");
- if (DepResult->isScalar(CurLoopLevel, true) && !DepResult->isAnti()) {
- LLVM_DEBUG(dbgs() << "Safe to fuse due to a loop-invariant non-anti "
- "dependency\n");
- NumDA++;
- return true;
+ if (DepResult->isScalar(CurLoopLevel, true)) {
+ if (DepResult->isInput() || DepResult->isOutput()) {
+ LLVM_DEBUG(dbgs() << "Safe to fuse due to a loop-invariant "
+ << (DepResult->isInput() ? "input" : "output")
+ << " dependency\n");
+ NumDA++;
+ return true;
+ }
+ LLVM_DEBUG(
+ dbgs() << "Not safe to fuse due to a scalar flow dependency\n");
+ return false;
}
unsigned CurDir = DepResult->getDirection(CurLoopLevel, true);
diff --git a/llvm/test/Transforms/LoopFusion/loop_invariant.ll b/llvm/test/Transforms/LoopFusion/loop_invariant.ll
index 7de4e7122464a..ea682ac363aad 100644
--- a/llvm/test/Transforms/LoopFusion/loop_invariant.ll
+++ b/llvm/test/Transforms/LoopFusion/loop_invariant.ll
@@ -4,7 +4,7 @@
define void @loop_invariant(i32 %N) {
; CHECK-DA: Performing Loop Fusion on function loop_invariant
-; CHECK-DA: Safe to fuse due to a loop-invariant non-anti dependency
+; CHECK-DA: Safe to fuse due to a loop-invariant output dependency
;
pre1:
%ptr = alloca i32, align 4
diff --git a/llvm/test/Transforms/LoopFusion/pr191238.ll b/llvm/test/Transforms/LoopFusion/pr191238.ll
new file mode 100644
index 0000000000000..9fded5775c73d
--- /dev/null
+++ b/llvm/test/Transforms/LoopFusion/pr191238.ll
@@ -0,0 +1,57 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -passes=loop-fusion < %s | FileCheck %s
+
+; The expected output keeps body1 and body2 as separate loops.
+; body2 must execute only after body1 finishes so every store to %out
+; uses the final value left in %ptr.
+
+define void @fill(i32 %N, ptr %out) {
+; CHECK-LABEL: define void @fill(
+; CHECK-SAME: i32 [[N:%.*]], ptr [[OUT:%.*]]) {
+; CHECK-NEXT: [[PRE1:.*]]:
+; CHECK-NEXT: [[PTR:%.*]] = alloca i32, align 4
+; CHECK-NEXT: br label %[[BODY1:.*]]
+; CHECK: [[BODY1]]:
+; CHECK-NEXT: [[I:%.*]] = phi i32 [ 0, %[[PRE1]] ], [ [[I_NEXT:%.*]], %[[BODY1]] ]
+; CHECK-NEXT: store i32 [[I]], ptr [[PTR]], align 4
+; CHECK-NEXT: [[I_NEXT]] = add i32 [[I]], 1
+; CHECK-NEXT: [[COND_NOT:%.*]] = icmp eq i32 [[I_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[COND_NOT]], label %[[BODY2_PREHEADER:.*]], label %[[BODY1]]
+; CHECK: [[BODY2_PREHEADER]]:
+; CHECK-NEXT: br label %[[BODY2:.*]]
+; CHECK: [[BODY2]]:
+; CHECK-NEXT: [[J:%.*]] = phi i32 [ [[J_NEXT:%.*]], %[[BODY2]] ], [ 0, %[[BODY2_PREHEADER]] ]
+; CHECK-NEXT: [[VAL:%.*]] = load i32, ptr [[PTR]], align 4
+; CHECK-NEXT: [[TMP0:%.*]] = sext i32 [[J]] to i64
+; CHECK-NEXT: [[OUT_PTR:%.*]] = getelementptr [4 x i8], ptr [[OUT]], i64 [[TMP0]]
+; CHECK-NEXT: store i32 [[VAL]], ptr [[OUT_PTR]], align 4
+; CHECK-NEXT: [[J_NEXT]] = add i32 [[J]], 1
+; CHECK-NEXT: [[COND2_NOT:%.*]] = icmp eq i32 [[J_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[COND2_NOT]], label %[[EXIT:.*]], label %[[BODY2]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+pre1:
+ %ptr = alloca i32, align 4
+ br label %body1
+
+body1: ; preds = %body1, %pre1
+ %i = phi i32 [ 0, %pre1 ], [ %i_next, %body1 ]
+ store i32 %i, ptr %ptr, align 4
+ %i_next = add i32 %i, 1
+ %cond.not = icmp eq i32 %i_next, %N
+ br i1 %cond.not, label %body2, label %body1
+
+body2: ; preds = %body2, %body1
+ %j = phi i32 [ %j_next, %body2 ], [ 0, %body1 ]
+ %val = load i32, ptr %ptr, align 4
+ %0 = sext i32 %j to i64
+ %out_ptr = getelementptr [4 x i8], ptr %out, i64 %0
+ store i32 %val, ptr %out_ptr, align 4
+ %j_next = add i32 %j, 1
+ %cond2.not = icmp eq i32 %j_next, %N
+ br i1 %cond2.not, label %exit, label %body2
+
+exit: ; preds = %body2
+ ret void
+}
More information about the llvm-commits
mailing list