[llvm] [LoopSplitUtils] Do not clone loops which are unsafe to clone (PR #214258)
Sean Clarke via llvm-commits
llvm-commits at lists.llvm.org
Wed Aug 5 08:40:23 PDT 2026
https://github.com/xarkenz updated https://github.com/llvm/llvm-project/pull/214258
>From 58be730db32afdf687b6aa45fd3a4d89a84f2f17 Mon Sep 17 00:00:00 2001
From: Sean Clarke <sclarke at tenstorrent.com>
Date: Wed, 5 Aug 2026 10:28:07 -0500
Subject: [PATCH 1/2] Add baseline tests
---
.../test/Transforms/LoopSplit/unsafe-clone.ll | 135 ++++++++++++++++++
1 file changed, 135 insertions(+)
create mode 100644 llvm/test/Transforms/LoopSplit/unsafe-clone.ll
diff --git a/llvm/test/Transforms/LoopSplit/unsafe-clone.ll b/llvm/test/Transforms/LoopSplit/unsafe-clone.ll
new file mode 100644
index 0000000000000..ea751b4181d13
--- /dev/null
+++ b/llvm/test/Transforms/LoopSplit/unsafe-clone.ll
@@ -0,0 +1,135 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes=loop-split-utils -loop-split-points=4 -S < %s | FileCheck %s
+
+declare void @use(i32)
+declare void @prevent_clone(i32) noduplicate
+
+define void @cannot_clone_noduplicate_call(i32 %n) {
+; CHECK-LABEL: define void @cannot_clone_noduplicate_call(
+; CHECK-SAME: i32 [[N:%.*]]) {
+; CHECK-NEXT: [[LS_GUARD0:.*:]]
+; CHECK-NEXT: [[SMAX:%.*]] = call i32 @llvm.smax.i32(i32 [[N]], i32 1)
+; CHECK-NEXT: [[TMP0:%.*]] = add nsw i32 [[SMAX]], -1
+; CHECK-NEXT: [[SMIN:%.*]] = call i32 @llvm.smin.i32(i32 [[TMP0]], i32 3)
+; CHECK-NEXT: [[ITR_CHK:%.*]] = icmp sle i32 0, [[SMIN]]
+; CHECK-NEXT: br i1 [[ITR_CHK]], label %[[ENTRY:.*]], label %[[LS_GUARD1:.*]]
+; CHECK: [[ENTRY]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: call void @prevent_clone(i32 [[IV]])
+; CHECK-NEXT: [[IV_NEXT]] = add i32 [[IV]], 1
+; CHECK-NEXT: [[ITR_CHK1:%.*]] = icmp sle i32 [[IV_NEXT]], [[SMIN]]
+; CHECK-NEXT: br i1 [[ITR_CHK1]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: br label %[[LS_GUARD1]]
+; CHECK: [[LS_GUARD1]]:
+; CHECK-NEXT: [[ITR_CHK2:%.*]] = icmp sle i32 4, [[TMP0]]
+; CHECK-NEXT: br i1 [[ITR_CHK2]], label %[[ENTRY_LS1:.*]], label %[[LS_FINAL_EXIT:.*]]
+; CHECK: [[ENTRY_LS1]]:
+; CHECK-NEXT: br label %[[LOOP_LS1:.*]]
+; CHECK: [[LOOP_LS1]]:
+; CHECK-NEXT: [[IV_LS1:%.*]] = phi i32 [ 4, %[[ENTRY_LS1]] ], [ [[IV_NEXT_LS1:%.*]], %[[LOOP_LS1]] ]
+; CHECK-NEXT: call void @prevent_clone(i32 [[IV_LS1]])
+; CHECK-NEXT: [[IV_NEXT_LS1]] = add i32 [[IV_LS1]], 1
+; CHECK-NEXT: [[ITR_CHK3:%.*]] = icmp sle i32 [[IV_NEXT_LS1]], [[TMP0]]
+; CHECK-NEXT: br i1 [[ITR_CHK3]], label %[[LOOP_LS1]], label %[[LS_EXIT1:.*]]
+; CHECK: [[LS_EXIT1]]:
+; CHECK-NEXT: br label %[[LS_FINAL_EXIT]]
+; CHECK: [[LS_FINAL_EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i32 [ 0, %entry ], [ %iv.next, %loop ]
+ call void @prevent_clone(i32 %iv)
+ %iv.next = add i32 %iv, 1
+ %cmp = icmp slt i32 %iv.next, %n
+ br i1 %cmp, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+define void @cannot_clone_indirectbr(i32 %n, i1 %which) {
+; CHECK-LABEL: define void @cannot_clone_indirectbr(
+; CHECK-SAME: i32 [[N:%.*]], i1 [[WHICH:%.*]]) {
+; CHECK-NEXT: [[LS_GUARD0:.*:]]
+; CHECK-NEXT: [[BR_ADDR:%.*]] = select i1 [[WHICH]], ptr blockaddress(@cannot_clone_indirectbr, %[[BLOCK_A:.*]]), ptr blockaddress(@cannot_clone_indirectbr, %[[BLOCK_B:.*]])
+; CHECK-NEXT: [[SMAX:%.*]] = call i32 @llvm.smax.i32(i32 [[N]], i32 1)
+; CHECK-NEXT: [[TMP0:%.*]] = add nsw i32 [[SMAX]], -1
+; CHECK-NEXT: [[SMIN:%.*]] = call i32 @llvm.smin.i32(i32 [[TMP0]], i32 3)
+; CHECK-NEXT: [[ITR_CHK:%.*]] = icmp sle i32 0, [[SMIN]]
+; CHECK-NEXT: br i1 [[ITR_CHK]], label %[[ENTRY:.*]], label %[[LS_GUARD1:.*]]
+; CHECK: [[ENTRY]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LATCH:.*]] ]
+; CHECK-NEXT: indirectbr ptr [[BR_ADDR]], [label %[[BLOCK_A]], label %[[BLOCK_B]]]
+; CHECK: [[BLOCK_A]]:
+; CHECK-NEXT: [[VAL_A:%.*]] = add i32 [[IV]], 100
+; CHECK-NEXT: br label %[[LATCH]]
+; CHECK: [[BLOCK_B]]:
+; CHECK-NEXT: [[VAL_B:%.*]] = add i32 [[IV]], 200
+; CHECK-NEXT: br label %[[LATCH]]
+; CHECK: [[LATCH]]:
+; CHECK-NEXT: [[VAL:%.*]] = phi i32 [ [[VAL_A]], %[[BLOCK_A]] ], [ [[VAL_B]], %[[BLOCK_B]] ]
+; CHECK-NEXT: call void @use(i32 [[VAL]])
+; CHECK-NEXT: [[IV_NEXT]] = add i32 [[IV]], 1
+; CHECK-NEXT: [[ITR_CHK1:%.*]] = icmp sle i32 [[IV_NEXT]], [[SMIN]]
+; CHECK-NEXT: br i1 [[ITR_CHK1]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: br label %[[LS_GUARD1]]
+; CHECK: [[LS_GUARD1]]:
+; CHECK-NEXT: [[ITR_CHK2:%.*]] = icmp sle i32 4, [[TMP0]]
+; CHECK-NEXT: br i1 [[ITR_CHK2]], label %[[ENTRY_LS1:.*]], label %[[LS_FINAL_EXIT:.*]]
+; CHECK: [[ENTRY_LS1]]:
+; CHECK-NEXT: br label %[[LOOP_LS1:.*]]
+; CHECK: [[LOOP_LS1]]:
+; CHECK-NEXT: [[IV_LS1:%.*]] = phi i32 [ 4, %[[ENTRY_LS1]] ], [ [[IV_NEXT_LS1:%.*]], %[[LATCH_LS1:.*]] ]
+; CHECK-NEXT: indirectbr ptr [[BR_ADDR]], [label %[[BLOCK_A_LS1:.*]], label %[[BLOCK_B_LS1:.*]]]
+; CHECK: [[BLOCK_B_LS1]]:
+; CHECK-NEXT: [[VAL_B_LS1:%.*]] = add i32 [[IV_LS1]], 200
+; CHECK-NEXT: br label %[[LATCH_LS1]]
+; CHECK: [[BLOCK_A_LS1]]:
+; CHECK-NEXT: [[VAL_A_LS1:%.*]] = add i32 [[IV_LS1]], 100
+; CHECK-NEXT: br label %[[LATCH_LS1]]
+; CHECK: [[LATCH_LS1]]:
+; CHECK-NEXT: [[VAL_LS1:%.*]] = phi i32 [ [[VAL_A_LS1]], %[[BLOCK_A_LS1]] ], [ [[VAL_B_LS1]], %[[BLOCK_B_LS1]] ]
+; CHECK-NEXT: call void @use(i32 [[VAL_LS1]])
+; CHECK-NEXT: [[IV_NEXT_LS1]] = add i32 [[IV_LS1]], 1
+; CHECK-NEXT: [[ITR_CHK3:%.*]] = icmp sle i32 [[IV_NEXT_LS1]], [[TMP0]]
+; CHECK-NEXT: br i1 [[ITR_CHK3]], label %[[LOOP_LS1]], label %[[LS_EXIT1:.*]]
+; CHECK: [[LS_EXIT1]]:
+; CHECK-NEXT: br label %[[LS_FINAL_EXIT]]
+; CHECK: [[LS_FINAL_EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ %br.addr = select i1 %which, ptr blockaddress(@cannot_clone_indirectbr, %block.a), ptr blockaddress(@cannot_clone_indirectbr, %block.b)
+ br label %loop
+
+loop:
+ %iv = phi i32 [ 0, %entry ], [ %iv.next, %latch ]
+ indirectbr ptr %br.addr, [label %block.a, label %block.b]
+
+block.a:
+ %val.a = add i32 %iv, 100
+ br label %latch
+
+block.b:
+ %val.b = add i32 %iv, 200
+ br label %latch
+
+latch:
+ %val = phi i32 [ %val.a, %block.a ], [ %val.b, %block.b ]
+ call void @use(i32 %val)
+ %iv.next = add i32 %iv, 1
+ %cmp = icmp slt i32 %iv.next, %n
+ br i1 %cmp, label %loop, label %exit
+
+exit:
+ ret void
+}
>From 64b5d85b35232b0fb43dfe3dc145b8d970f45bca Mon Sep 17 00:00:00 2001
From: Sean Clarke <sclarke at tenstorrent.com>
Date: Wed, 5 Aug 2026 10:30:51 -0500
Subject: [PATCH 2/2] Ensure loop is safe to clone
---
llvm/lib/Transforms/Utils/LoopSplitUtils.cpp | 6 ++
.../test/Transforms/LoopSplit/unsafe-clone.ll | 73 +++----------------
2 files changed, 17 insertions(+), 62 deletions(-)
diff --git a/llvm/lib/Transforms/Utils/LoopSplitUtils.cpp b/llvm/lib/Transforms/Utils/LoopSplitUtils.cpp
index db07e3f571f66..7e96367400904 100644
--- a/llvm/lib/Transforms/Utils/LoopSplitUtils.cpp
+++ b/llvm/lib/Transforms/Utils/LoopSplitUtils.cpp
@@ -223,6 +223,12 @@ bool LoopSplitUtils::isLegal() {
return false;
}
+ // Splitting a loop clones it, so cloning must be safe.
+ if (!L->isSafeToClone()) {
+ LLVM_DEBUG(dbgs() << DEBUG_TYPE ": loop not safe to clone\n");
+ return false;
+ }
+
// A computable backedge-taken count fixes the iteration space we rebuild.
const SCEV *BTC = SE->getBackedgeTakenCount(L);
if (isa<SCEVCouldNotCompute>(BTC)) {
diff --git a/llvm/test/Transforms/LoopSplit/unsafe-clone.ll b/llvm/test/Transforms/LoopSplit/unsafe-clone.ll
index ea751b4181d13..4743a63924a07 100644
--- a/llvm/test/Transforms/LoopSplit/unsafe-clone.ll
+++ b/llvm/test/Transforms/LoopSplit/unsafe-clone.ll
@@ -7,36 +7,15 @@ declare void @prevent_clone(i32) noduplicate
define void @cannot_clone_noduplicate_call(i32 %n) {
; CHECK-LABEL: define void @cannot_clone_noduplicate_call(
; CHECK-SAME: i32 [[N:%.*]]) {
-; CHECK-NEXT: [[LS_GUARD0:.*:]]
-; CHECK-NEXT: [[SMAX:%.*]] = call i32 @llvm.smax.i32(i32 [[N]], i32 1)
-; CHECK-NEXT: [[TMP0:%.*]] = add nsw i32 [[SMAX]], -1
-; CHECK-NEXT: [[SMIN:%.*]] = call i32 @llvm.smin.i32(i32 [[TMP0]], i32 3)
-; CHECK-NEXT: [[ITR_CHK:%.*]] = icmp sle i32 0, [[SMIN]]
-; CHECK-NEXT: br i1 [[ITR_CHK]], label %[[ENTRY:.*]], label %[[LS_GUARD1:.*]]
-; CHECK: [[ENTRY]]:
-; CHECK-NEXT: br label %[[LOOP:.*]]
-; CHECK: [[LOOP]]:
-; CHECK-NEXT: [[IV:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
-; CHECK-NEXT: call void @prevent_clone(i32 [[IV]])
-; CHECK-NEXT: [[IV_NEXT]] = add i32 [[IV]], 1
-; CHECK-NEXT: [[ITR_CHK1:%.*]] = icmp sle i32 [[IV_NEXT]], [[SMIN]]
-; CHECK-NEXT: br i1 [[ITR_CHK1]], label %[[LOOP]], label %[[EXIT:.*]]
-; CHECK: [[EXIT]]:
-; CHECK-NEXT: br label %[[LS_GUARD1]]
-; CHECK: [[LS_GUARD1]]:
-; CHECK-NEXT: [[ITR_CHK2:%.*]] = icmp sle i32 4, [[TMP0]]
-; CHECK-NEXT: br i1 [[ITR_CHK2]], label %[[ENTRY_LS1:.*]], label %[[LS_FINAL_EXIT:.*]]
-; CHECK: [[ENTRY_LS1]]:
+; CHECK-NEXT: [[ENTRY_LS1:.*]]:
; CHECK-NEXT: br label %[[LOOP_LS1:.*]]
; CHECK: [[LOOP_LS1]]:
-; CHECK-NEXT: [[IV_LS1:%.*]] = phi i32 [ 4, %[[ENTRY_LS1]] ], [ [[IV_NEXT_LS1:%.*]], %[[LOOP_LS1]] ]
+; CHECK-NEXT: [[IV_LS1:%.*]] = phi i32 [ 0, %[[ENTRY_LS1]] ], [ [[IV_NEXT_LS1:%.*]], %[[LOOP_LS1]] ]
; CHECK-NEXT: call void @prevent_clone(i32 [[IV_LS1]])
; CHECK-NEXT: [[IV_NEXT_LS1]] = add i32 [[IV_LS1]], 1
-; CHECK-NEXT: [[ITR_CHK3:%.*]] = icmp sle i32 [[IV_NEXT_LS1]], [[TMP0]]
+; CHECK-NEXT: [[ITR_CHK3:%.*]] = icmp slt i32 [[IV_NEXT_LS1]], [[N]]
; CHECK-NEXT: br i1 [[ITR_CHK3]], label %[[LOOP_LS1]], label %[[LS_EXIT1:.*]]
; CHECK: [[LS_EXIT1]]:
-; CHECK-NEXT: br label %[[LS_FINAL_EXIT]]
-; CHECK: [[LS_FINAL_EXIT]]:
; CHECK-NEXT: ret void
;
entry:
@@ -56,54 +35,24 @@ exit:
define void @cannot_clone_indirectbr(i32 %n, i1 %which) {
; CHECK-LABEL: define void @cannot_clone_indirectbr(
; CHECK-SAME: i32 [[N:%.*]], i1 [[WHICH:%.*]]) {
-; CHECK-NEXT: [[LS_GUARD0:.*:]]
+; CHECK-NEXT: [[ENTRY:.*]]:
; CHECK-NEXT: [[BR_ADDR:%.*]] = select i1 [[WHICH]], ptr blockaddress(@cannot_clone_indirectbr, %[[BLOCK_A:.*]]), ptr blockaddress(@cannot_clone_indirectbr, %[[BLOCK_B:.*]])
-; CHECK-NEXT: [[SMAX:%.*]] = call i32 @llvm.smax.i32(i32 [[N]], i32 1)
-; CHECK-NEXT: [[TMP0:%.*]] = add nsw i32 [[SMAX]], -1
-; CHECK-NEXT: [[SMIN:%.*]] = call i32 @llvm.smin.i32(i32 [[TMP0]], i32 3)
-; CHECK-NEXT: [[ITR_CHK:%.*]] = icmp sle i32 0, [[SMIN]]
-; CHECK-NEXT: br i1 [[ITR_CHK]], label %[[ENTRY:.*]], label %[[LS_GUARD1:.*]]
-; CHECK: [[ENTRY]]:
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
-; CHECK-NEXT: [[IV:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LATCH:.*]] ]
+; CHECK-NEXT: [[IV:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP_LS1:.*]] ]
; CHECK-NEXT: indirectbr ptr [[BR_ADDR]], [label %[[BLOCK_A]], label %[[BLOCK_B]]]
; CHECK: [[BLOCK_A]]:
; CHECK-NEXT: [[VAL_A:%.*]] = add i32 [[IV]], 100
-; CHECK-NEXT: br label %[[LATCH]]
+; CHECK-NEXT: br label %[[LOOP_LS1]]
; CHECK: [[BLOCK_B]]:
; CHECK-NEXT: [[VAL_B:%.*]] = add i32 [[IV]], 200
-; CHECK-NEXT: br label %[[LATCH]]
-; CHECK: [[LATCH]]:
-; CHECK-NEXT: [[VAL:%.*]] = phi i32 [ [[VAL_A]], %[[BLOCK_A]] ], [ [[VAL_B]], %[[BLOCK_B]] ]
-; CHECK-NEXT: call void @use(i32 [[VAL]])
-; CHECK-NEXT: [[IV_NEXT]] = add i32 [[IV]], 1
-; CHECK-NEXT: [[ITR_CHK1:%.*]] = icmp sle i32 [[IV_NEXT]], [[SMIN]]
-; CHECK-NEXT: br i1 [[ITR_CHK1]], label %[[LOOP]], label %[[EXIT:.*]]
-; CHECK: [[EXIT]]:
-; CHECK-NEXT: br label %[[LS_GUARD1]]
-; CHECK: [[LS_GUARD1]]:
-; CHECK-NEXT: [[ITR_CHK2:%.*]] = icmp sle i32 4, [[TMP0]]
-; CHECK-NEXT: br i1 [[ITR_CHK2]], label %[[ENTRY_LS1:.*]], label %[[LS_FINAL_EXIT:.*]]
-; CHECK: [[ENTRY_LS1]]:
-; CHECK-NEXT: br label %[[LOOP_LS1:.*]]
+; CHECK-NEXT: br label %[[LOOP_LS1]]
; CHECK: [[LOOP_LS1]]:
-; CHECK-NEXT: [[IV_LS1:%.*]] = phi i32 [ 4, %[[ENTRY_LS1]] ], [ [[IV_NEXT_LS1:%.*]], %[[LATCH_LS1:.*]] ]
-; CHECK-NEXT: indirectbr ptr [[BR_ADDR]], [label %[[BLOCK_A_LS1:.*]], label %[[BLOCK_B_LS1:.*]]]
-; CHECK: [[BLOCK_B_LS1]]:
-; CHECK-NEXT: [[VAL_B_LS1:%.*]] = add i32 [[IV_LS1]], 200
-; CHECK-NEXT: br label %[[LATCH_LS1]]
-; CHECK: [[BLOCK_A_LS1]]:
-; CHECK-NEXT: [[VAL_A_LS1:%.*]] = add i32 [[IV_LS1]], 100
-; CHECK-NEXT: br label %[[LATCH_LS1]]
-; CHECK: [[LATCH_LS1]]:
-; CHECK-NEXT: [[VAL_LS1:%.*]] = phi i32 [ [[VAL_A_LS1]], %[[BLOCK_A_LS1]] ], [ [[VAL_B_LS1]], %[[BLOCK_B_LS1]] ]
+; CHECK-NEXT: [[VAL_LS1:%.*]] = phi i32 [ [[VAL_A]], %[[BLOCK_A]] ], [ [[VAL_B]], %[[BLOCK_B]] ]
; CHECK-NEXT: call void @use(i32 [[VAL_LS1]])
-; CHECK-NEXT: [[IV_NEXT_LS1]] = add i32 [[IV_LS1]], 1
-; CHECK-NEXT: [[ITR_CHK3:%.*]] = icmp sle i32 [[IV_NEXT_LS1]], [[TMP0]]
-; CHECK-NEXT: br i1 [[ITR_CHK3]], label %[[LOOP_LS1]], label %[[LS_EXIT1:.*]]
-; CHECK: [[LS_EXIT1]]:
-; CHECK-NEXT: br label %[[LS_FINAL_EXIT]]
+; CHECK-NEXT: [[IV_NEXT]] = add i32 [[IV]], 1
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[IV_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[CMP]], label %[[LOOP]], label %[[LS_FINAL_EXIT:.*]]
; CHECK: [[LS_FINAL_EXIT]]:
; CHECK-NEXT: ret void
;
More information about the llvm-commits
mailing list