[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