[llvm] [LICM] Allow hoisting may-throw calls when guaranteed to execute (PR #189388)

via llvm-commits llvm-commits at lists.llvm.org
Mon Mar 30 07:07:40 PDT 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Gabriel Baraldi (gbaraldi)

<details>
<summary>Changes</summary>

## Summary

- `canSinkOrHoistInst` previously rejected all may-throw calls for both sinking and hoisting via `CI->mayThrow()`. This is overly conservative for hoisting: if a call is guaranteed to execute on every loop iteration (verified by `isSafeToExecuteUnconditionally`), hoisting to the preheader is safe — if it throws, it would have thrown on the first iteration.
- Adds an `IsHoist` parameter to `canSinkOrHoistInst` so the `mayThrow()` check only blocks sinking.
- This enables hoisting of `readonly`/`readnone` calls that lack `nounwind` but are guaranteed to execute, which is common in languages like Julia where runtime calls (e.g. `sin`) may not be marked `nounwind`.

## Test plan
- Updated existing LICM tests (`call-hoisting.ll`, `read-only-calls.ll`, `preheader-safe.ll`, `hoist-metadata.ll`, `pr51333.ll`) to reflect new behavior and preserve coverage for hoisting past non-hoistable may-throw calls.
- Added new positive tests (readnone may-throw hoist, writeonly may-throw hoist) and negative tests (conditional execution, preceded by ICF) to `call-hoisting.ll`.
- Full LICM test suite passes (155 tests). Related test suites (LoopUnroll, LoopRotate, LoopIdiom, IndVarSimplify, LoopVectorize, MustExecute, PhaseOrdering, GVN) all pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---

Patch is 21.09 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/189388.diff


7 Files Affected:

- (modified) llvm/include/llvm/Transforms/Utils/LoopUtils.h (+5-1) 
- (modified) llvm/lib/Transforms/Scalar/LICM.cpp (+9-4) 
- (modified) llvm/test/Transforms/LICM/call-hoisting.ll (+115-3) 
- (modified) llvm/test/Transforms/LICM/hoist-metadata.ll (+1-1) 
- (modified) llvm/test/Transforms/LICM/pr51333.ll (+1-1) 
- (modified) llvm/test/Transforms/LICM/preheader-safe.ll (+95-43) 
- (modified) llvm/test/Transforms/LICM/read-only-calls.ll (+41-14) 


``````````diff
diff --git a/llvm/include/llvm/Transforms/Utils/LoopUtils.h b/llvm/include/llvm/Transforms/Utils/LoopUtils.h
index ccba9ee16885b..d2f5e36f7208e 100644
--- a/llvm/include/llvm/Transforms/Utils/LoopUtils.h
+++ b/llvm/include/llvm/Transforms/Utils/LoopUtils.h
@@ -441,12 +441,16 @@ LLVM_ABI void getLoopAnalysisUsage(AnalysisUsage &AU);
 /// to assess the legality of duplicating atomic loads.  Generally, this is
 /// true when moving out of loop and not true when moving into loops.
 /// If \p ORE is set use it to emit optimization remarks.
+/// \p IsHoist is true when hoisting, false when sinking. When hoisting, calls
+/// that may throw are allowed since the caller checks that the instruction is
+/// guaranteed to execute at least once via isSafeToExecuteUnconditionally.
 LLVM_ABI bool canSinkOrHoistInst(Instruction &I, AAResults *AA,
                                  DominatorTree *DT, Loop *CurLoop,
                                  MemorySSAUpdater &MSSAU,
                                  bool TargetExecutesOncePerLoop,
                                  SinkAndHoistLICMFlags &LICMFlags,
-                                 OptimizationRemarkEmitter *ORE = nullptr);
+                                 OptimizationRemarkEmitter *ORE = nullptr,
+                                 bool IsHoist = false);
 
 /// Returns the llvm.vector.reduce intrinsic that corresponds to the recurrence
 /// kind.
diff --git a/llvm/lib/Transforms/Scalar/LICM.cpp b/llvm/lib/Transforms/Scalar/LICM.cpp
index 859bc4cf83898..6101dd0fa2625 100644
--- a/llvm/lib/Transforms/Scalar/LICM.cpp
+++ b/llvm/lib/Transforms/Scalar/LICM.cpp
@@ -925,7 +925,8 @@ bool llvm::hoistRegion(DomTreeNode *N, AAResults *AA, LoopInfo *LI,
       // and we have accurately duplicated the control flow from the loop header
       // to that block.
       if (CurLoop->hasLoopInvariantOperands(&I) &&
-          canSinkOrHoistInst(I, AA, DT, CurLoop, MSSAU, true, Flags, ORE) &&
+          canSinkOrHoistInst(I, AA, DT, CurLoop, MSSAU, true, Flags, ORE,
+                             /*IsHoist=*/true) &&
           isSafeToExecuteUnconditionally(I, DT, TLI, CurLoop, SafetyInfo, ORE,
                                          Preheader->getTerminator(), AC,
                                          AllowSpeculation)) {
@@ -1165,7 +1166,7 @@ bool llvm::canSinkOrHoistInst(Instruction &I, AAResults *AA, DominatorTree *DT,
                               Loop *CurLoop, MemorySSAUpdater &MSSAU,
                               bool TargetExecutesOncePerLoop,
                               SinkAndHoistLICMFlags &Flags,
-                              OptimizationRemarkEmitter *ORE) {
+                              OptimizationRemarkEmitter *ORE, bool IsHoist) {
   // If we don't understand the instruction, bail early.
   if (!isHoistableAndSinkableInst(I))
     return false;
@@ -1208,8 +1209,12 @@ bool llvm::canSinkOrHoistInst(Instruction &I, AAResults *AA, DominatorTree *DT,
 
     return !Invalidated;
   } else if (CallInst *CI = dyn_cast<CallInst>(&I)) {
-    // Don't sink calls which can throw.
-    if (CI->mayThrow())
+    // Don't sink calls which can throw. When hoisting, may-throw calls are
+    // allowed because the caller verifies the instruction is guaranteed to
+    // execute via isSafeToExecuteUnconditionally. If it would throw, it would
+    // have thrown on the first loop iteration, so hoisting to the preheader is
+    // safe.
+    if (!IsHoist && CI->mayThrow())
       return false;
 
     // Convergent attribute has been used on operations that involve
diff --git a/llvm/test/Transforms/LICM/call-hoisting.ll b/llvm/test/Transforms/LICM/call-hoisting.ll
index bb28d1ca93233..1f5ef83d714a4 100644
--- a/llvm/test/Transforms/LICM/call-hoisting.ll
+++ b/llvm/test/Transforms/LICM/call-hoisting.ll
@@ -472,14 +472,14 @@ declare void @not_nounwind(i32 %v, ptr %p) writeonly argmemonly
 declare void @not_argmemonly(i32 %v, ptr %p) writeonly nounwind
 declare void @not_writeonly(i32 %v, ptr %p) argmemonly nounwind
 
-define void @neg_not_nounwind(ptr %loc) {
-; CHECK-LABEL: define void @neg_not_nounwind(
+define void @hoist_not_nounwind(ptr %loc) {
+; CHECK-LABEL: define void @hoist_not_nounwind(
 ; CHECK-SAME: ptr [[LOC:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    call void @not_nounwind(i32 0, ptr [[LOC]])
 ; CHECK-NEXT:    br label %[[LOOP:.*]]
 ; CHECK:       [[LOOP]]:
 ; CHECK-NEXT:    [[IV:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
-; CHECK-NEXT:    call void @not_nounwind(i32 0, ptr [[LOC]])
 ; CHECK-NEXT:    [[IV_NEXT]] = add i32 [[IV]], 1
 ; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[IV]], 200
 ; CHECK-NEXT:    br i1 [[CMP]], label %[[LOOP]], label %[[EXIT:.*]]
@@ -589,3 +589,115 @@ exit:
   ret void
 }
 
+declare i32 @readnone_maythrow(i32) memory(none)
+
+; A readnone call that may throw but is guaranteed to execute can be hoisted.
+define void @hoist_readnone_maythrow(i32 %n, ptr noalias %sink) {
+; CHECK-LABEL: define void @hoist_readnone_maythrow(
+; CHECK-SAME: i32 [[N:%.*]], ptr noalias [[SINK:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    [[RET:%.*]] = call i32 @readnone_maythrow(i32 [[N]])
+; CHECK-NEXT:    br label %[[LOOP:.*]]
+; CHECK:       [[LOOP]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT:    store volatile i32 [[RET]], ptr [[SINK]], align 4
+; CHECK-NEXT:    [[IV_NEXT]] = add i32 [[IV]], 1
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[IV]], 200
+; CHECK-NEXT:    br i1 [[CMP]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret void
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi i32 [0, %entry], [%iv.next, %loop]
+  %ret = call i32 @readnone_maythrow(i32 %n)
+  store volatile i32 %ret, ptr %sink
+  %iv.next = add i32 %iv, 1
+  %cmp = icmp slt i32 %iv, 200
+  br i1 %cmp, label %loop, label %exit
+
+exit:
+  ret void
+}
+
+declare i32 @readonly_maythrow(ptr %p) memory(argmem: read)
+
+; A readonly call that may throw cannot be hoisted if not guaranteed to execute
+; (here it's conditionally executed).
+define void @no_hoist_readonly_maythrow_conditional(ptr noalias %loc, ptr noalias %sink, i1 %cond) {
+; CHECK-LABEL: define void @no_hoist_readonly_maythrow_conditional(
+; CHECK-SAME: ptr noalias [[LOC:%.*]], ptr noalias [[SINK:%.*]], i1 [[COND:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    br label %[[LOOP:.*]]
+; CHECK:       [[LOOP]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LATCH:.*]] ]
+; CHECK-NEXT:    br i1 [[COND]], label %[[THEN:.*]], label %[[LATCH]]
+; CHECK:       [[THEN]]:
+; CHECK-NEXT:    [[RET:%.*]] = call i32 @readonly_maythrow(ptr [[LOC]])
+; CHECK-NEXT:    store volatile i32 [[RET]], ptr [[SINK]], align 4
+; CHECK-NEXT:    br label %[[LATCH]]
+; CHECK:       [[LATCH]]:
+; CHECK-NEXT:    [[IV_NEXT]] = add i32 [[IV]], 1
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[IV]], 200
+; CHECK-NEXT:    br i1 [[CMP]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret void
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi i32 [0, %entry], [%iv.next, %latch]
+  br i1 %cond, label %then, label %latch
+
+then:
+  %ret = call i32 @readonly_maythrow(ptr %loc)
+  store volatile i32 %ret, ptr %sink
+  br label %latch
+
+latch:
+  %iv.next = add i32 %iv, 1
+  %cmp = icmp slt i32 %iv, 200
+  br i1 %cmp, label %loop, label %exit
+
+exit:
+  ret void
+}
+
+declare void @maythrow_sideeffect()
+
+; A readonly call that may throw cannot be hoisted if preceded by another
+; may-throw instruction in the same block (not guaranteed to execute).
+define void @no_hoist_readonly_maythrow_after_icf(ptr noalias %loc, ptr noalias %sink) {
+; CHECK-LABEL: define void @no_hoist_readonly_maythrow_after_icf(
+; CHECK-SAME: ptr noalias [[LOC:%.*]], ptr noalias [[SINK:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    br label %[[LOOP:.*]]
+; CHECK:       [[LOOP]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT:    call void @maythrow_sideeffect()
+; CHECK-NEXT:    [[RET:%.*]] = call i32 @readonly_maythrow(ptr [[LOC]])
+; CHECK-NEXT:    store volatile i32 [[RET]], ptr [[SINK]], align 4
+; CHECK-NEXT:    [[IV_NEXT]] = add i32 [[IV]], 1
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[IV]], 200
+; CHECK-NEXT:    br i1 [[CMP]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret void
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi i32 [0, %entry], [%iv.next, %loop]
+  call void @maythrow_sideeffect()
+  %ret = call i32 @readonly_maythrow(ptr %loc)
+  store volatile i32 %ret, ptr %sink
+  %iv.next = add i32 %iv, 1
+  %cmp = icmp slt i32 %iv, 200
+  br i1 %cmp, label %loop, label %exit
+
+exit:
+  ret void
+}
diff --git a/llvm/test/Transforms/LICM/hoist-metadata.ll b/llvm/test/Transforms/LICM/hoist-metadata.ll
index f25cb966a37fc..d16fcf11bfb34 100644
--- a/llvm/test/Transforms/LICM/hoist-metadata.ll
+++ b/llvm/test/Transforms/LICM/hoist-metadata.ll
@@ -10,9 +10,9 @@ define void @test_unconditional(i1 %c, ptr dereferenceable(8) align 8 %p) {
 ; CHECK-NEXT:    [[V1:%.*]] = load i32, ptr [[P]], align 4, !range [[RNG0:![0-9]+]]
 ; CHECK-NEXT:    [[V2:%.*]] = load ptr, ptr [[P]], align 8, !nonnull [[META1:![0-9]+]], !noundef [[META1]]
 ; CHECK-NEXT:    [[V3:%.*]] = load ptr, ptr [[P]], align 8, !dereferenceable [[META2:![0-9]+]], !align [[META2]]
+; CHECK-NEXT:    call void @foo(i32 [[V1]], ptr [[V2]], ptr [[V3]])
 ; CHECK-NEXT:    br label [[LOOP:%.*]]
 ; CHECK:       loop:
-; CHECK-NEXT:    call void @foo(i32 [[V1]], ptr [[V2]], ptr [[V3]])
 ; CHECK-NEXT:    br i1 [[C]], label [[LOOP]], label [[EXIT:%.*]]
 ; CHECK:       exit:
 ; CHECK-NEXT:    ret void
diff --git a/llvm/test/Transforms/LICM/pr51333.ll b/llvm/test/Transforms/LICM/pr51333.ll
index 0ee4336c7fa5b..8764d14523c2f 100644
--- a/llvm/test/Transforms/LICM/pr51333.ll
+++ b/llvm/test/Transforms/LICM/pr51333.ll
@@ -9,10 +9,10 @@
 define void @test() {
 ; CHECK-LABEL: @test(
 ; CHECK-NEXT:  entry:
+; CHECK-NEXT:    call void @readnone_fn()
 ; CHECK-NEXT:    br label [[DO_BODY:%.*]]
 ; CHECK:       do.body:
 ; CHECK-NEXT:    store ptr @readnone_fn, ptr @fn_ptr, align 8
-; CHECK-NEXT:    call void @readnone_fn()
 ; CHECK-NEXT:    call void @foo()
 ; CHECK-NEXT:    br label [[DO_BODY]]
 ;
diff --git a/llvm/test/Transforms/LICM/preheader-safe.ll b/llvm/test/Transforms/LICM/preheader-safe.ll
index a96e847c51d9f..997894c8d0ba9 100644
--- a/llvm/test/Transforms/LICM/preheader-safe.ll
+++ b/llvm/test/Transforms/LICM/preheader-safe.ll
@@ -1,3 +1,4 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
 ; RUN: opt -S -passes=licm < %s | FileCheck %s
 ; RUN: opt -aa-pipeline=basic-aa -passes='require<aa>,require<target-ir>,require<scalar-evolution>,require<opt-remark-emit>,loop-mssa(licm)' -S %s | FileCheck %s
 
@@ -6,11 +7,17 @@ declare void @use(i64 %a)
 declare void @maythrow()
 
 define void @nothrow(i64 %x, i64 %y, ptr %cond) {
-; CHECK-LABEL: nothrow
-; CHECK-LABEL: entry
-; CHECK: %div = udiv i64 %x, %y
-; CHECK-LABEL: loop
-; CHECK: call void @use_nothrow(i64 %div)
+; CHECK-LABEL: define void @nothrow(
+; CHECK-SAME: i64 [[X:%.*]], i64 [[Y:%.*]], ptr [[COND:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[DIV:%.*]] = udiv i64 [[X]], [[Y]]
+; CHECK-NEXT:    br label %[[LOOP:.*]]
+; CHECK:       [[LOOP]]:
+; CHECK-NEXT:    br label %[[LOOP2:.*]]
+; CHECK:       [[LOOP2]]:
+; CHECK-NEXT:    call void @use_nothrow(i64 [[DIV]])
+; CHECK-NEXT:    br label %[[LOOP]]
+;
 entry:
   br label %loop
 
@@ -25,10 +32,15 @@ loop2:
 
 ; The udiv is guarantee to execute if the loop is
 define void @throw_header_after(i64 %x, i64 %y, ptr %cond) {
-; CHECK-LABEL: throw_header_after
-; CHECK: %div = udiv i64 %x, %y
-; CHECK-LABEL: loop
-; CHECK: call void @use(i64 %div)
+; CHECK-LABEL: define void @throw_header_after(
+; CHECK-SAME: i64 [[X:%.*]], i64 [[Y:%.*]], ptr [[COND:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[DIV:%.*]] = udiv i64 [[X]], [[Y]]
+; CHECK-NEXT:    br label %[[LOOP:.*]]
+; CHECK:       [[LOOP]]:
+; CHECK-NEXT:    call void @use(i64 [[DIV]])
+; CHECK-NEXT:    br label %[[LOOP]]
+;
 entry:
   br label %loop
 
@@ -37,13 +49,20 @@ loop:                                         ; preds = %entry, %for.inc
   call void @use(i64 %div)
   br label %loop
 }
-define void @throw_header_after_rec(ptr %xp, ptr %yp, ptr %cond) {
-; CHECK-LABEL: throw_header_after_rec
-; CHECK: %x = load i64, ptr %xp
-; CHECK: %y = load i64, ptr %yp
-; CHECK: %div = udiv i64 %x, %y
-; CHECK-LABEL: loop
-; CHECK: call void @use(i64 %div)
+; The may-throw call (readwrite) cannot be hoisted, but the loads and udiv
+; before it are guaranteed to execute and can hoist past it.
+define void @throw_header_after_rec(ptr noalias %xp, ptr noalias %yp, ptr %cond) {
+; CHECK-LABEL: define void @throw_header_after_rec(
+; CHECK-SAME: ptr noalias [[XP:%.*]], ptr noalias [[YP:%.*]], ptr [[COND:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[X:%.*]] = load i64, ptr [[XP]], align 4
+; CHECK-NEXT:    [[Y:%.*]] = load i64, ptr [[YP]], align 4
+; CHECK-NEXT:    [[DIV:%.*]] = udiv i64 [[X]], [[Y]]
+; CHECK-NEXT:    br label %[[LOOP:.*]]
+; CHECK:       [[LOOP]]:
+; CHECK-NEXT:    call void @use(i64 [[DIV]])
+; CHECK-NEXT:    br label %[[LOOP]]
+;
 entry:
   br label %loop
 
@@ -51,19 +70,26 @@ loop:                                         ; preds = %entry, %for.inc
   %x = load i64, ptr %xp
   %y = load i64, ptr %yp
   %div = udiv i64 %x, %y
-  call void @use(i64 %div) readonly
+  call void @use(i64 %div)
   br label %loop
 }
 
 ; Similiar to the above, but the hoistable instruction (%y in this case)
 ; happens not to be the first instruction in the block.
 define void @throw_header_after_nonfirst(ptr %xp, ptr %yp, ptr %cond) {
-; CHECK-LABEL: throw_header_after_nonfirst
-; CHECK: %y = load i64, ptr %yp
-; CHECK-LABEL: loop
-; CHECK: %x = load i64, ptr %gep
-; CHECK: %div = udiv i64 %x, %y
-; CHECK: call void @use(i64 %div)
+; CHECK-LABEL: define void @throw_header_after_nonfirst(
+; CHECK-SAME: ptr [[XP:%.*]], ptr [[YP:%.*]], ptr [[COND:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    [[Y:%.*]] = load i64, ptr [[YP]], align 4
+; CHECK-NEXT:    br label %[[LOOP:.*]]
+; CHECK:       [[LOOP]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[DIV:%.*]], %[[LOOP]] ]
+; CHECK-NEXT:    [[GEP:%.*]] = getelementptr i64, ptr [[XP]], i64 [[IV]]
+; CHECK-NEXT:    [[X:%.*]] = load i64, ptr [[GEP]], align 4
+; CHECK-NEXT:    [[DIV]] = udiv i64 [[X]], [[Y]]
+; CHECK-NEXT:    call void @use(i64 [[DIV]]) #[[ATTR1:[0-9]+]]
+; CHECK-NEXT:    br label %[[LOOP]]
+;
 entry:
   br label %loop
 
@@ -79,10 +105,16 @@ loop:                                         ; preds = %entry, %for.inc
 
 ; Negative test
 define void @throw_header_before(i64 %x, i64 %y, ptr %cond) {
-; CHECK-LABEL: throw_header_before
-; CHECK-LABEL: loop
-; CHECK: %div = udiv i64 %x, %y
-; CHECK: call void @use(i64 %div)
+; CHECK-LABEL: define void @throw_header_before(
+; CHECK-SAME: i64 [[X:%.*]], i64 [[Y:%.*]], ptr [[COND:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    br label %[[LOOP:.*]]
+; CHECK:       [[LOOP]]:
+; CHECK-NEXT:    call void @maythrow()
+; CHECK-NEXT:    [[DIV:%.*]] = udiv i64 [[X]], [[Y]]
+; CHECK-NEXT:    call void @use(i64 [[DIV]])
+; CHECK-NEXT:    br label %[[LOOP]]
+;
 entry:
   br label %loop
 
@@ -96,11 +128,19 @@ loop:                                         ; preds = %entry, %for.inc
 ; The header is known no throw, but the loop is not.  We can
 ; still lift out of the header.
 define void @nothrow_header(i64 %x, i64 %y, i1 %cond) {
-; CHECK-LABEL: nothrow_header
-; CHECK-LABEL: entry
-; CHECK: %div = udiv i64 %x, %y
-; CHECK-LABEL: loop
-  ; CHECK: call void @use(i64 %div)
+; CHECK-LABEL: define void @nothrow_header(
+; CHECK-SAME: i64 [[X:%.*]], i64 [[Y:%.*]], i1 [[COND:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[DIV:%.*]] = udiv i64 [[X]], [[Y]]
+; CHECK-NEXT:    br label %[[LOOP:.*]]
+; CHECK:       [[LOOP]]:
+; CHECK-NEXT:    br i1 [[COND]], label %[[LOOP_IF:.*]], label %[[EXIT:.*]]
+; CHECK:       [[LOOP_IF]]:
+; CHECK-NEXT:    call void @use(i64 [[DIV]])
+; CHECK-NEXT:    br label %[[LOOP]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret void
+;
 entry:
   br label %loop
 loop:                                         ; preds = %entry, %for.inc
@@ -115,11 +155,17 @@ exit:
 
 ; Positive test - can hoist something that happens before thrower.
 define void @nothrow_header_pos(i64 %x, i64 %y, i1 %cond) {
-; CHECK-LABEL: nothrow_header_pos
-; CHECK-LABEL: entry
-; CHECK: %div = udiv i64 %x, %y
-; CHECK-LABEL: loop
-; CHECK: call void @use(i64 %div)
+; CHECK-LABEL: define void @nothrow_header_pos(
+; CHECK-SAME: i64 [[X:%.*]], i64 [[Y:%.*]], i1 [[COND:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[DIV:%.*]] = udiv i64 [[X]], [[Y]]
+; CHECK-NEXT:    br label %[[LOOP:.*]]
+; CHECK:       [[LOOP]]:
+; CHECK-NEXT:    br label %[[LOOP_IF:.*]]
+; CHECK:       [[LOOP_IF]]:
+; CHECK-NEXT:    call void @use(i64 [[DIV]])
+; CHECK-NEXT:    br label %[[LOOP]]
+;
 entry:
   br label %loop
 loop:                                         ; preds = %entry, %for.inc
@@ -133,12 +179,18 @@ loop-if:
 
 ; Negative test - can't move out of throwing block
 define void @nothrow_header_neg(i64 %x, i64 %y, i1 %cond) {
-; CHECK-LABEL: nothrow_header_neg
-; CHECK-LABEL: entry
-; CHECK-LABEL: loop
-; CHECK: call void @maythrow()
-; CHECK: %div = udiv i64 %x, %y
-; CHECK: call void @use(i64 %div)
+; CHECK-LABEL: define void @nothrow_header_neg(
+; CHECK-SAME: i64 [[X:%.*]], i64 [[Y:%.*]], i1 [[COND:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    br label %[[LOOP:.*]]
+; CHECK:       [[LOOP]]:
+; CHECK-NEXT:    br label %[[LOOP_IF:.*]]
+; CHECK:       [[LOOP_IF]]:
+; CHECK-NEXT:    call void @maythrow()
+; CHECK-NEXT:    [[DIV:%.*]] = udiv i64 [[X]], [[Y]]
+; CHECK-NEXT:    call void @use(i64 [[DIV]])
+; CHECK-NEXT:    br label %[[LOOP]]
+;
 entry:
   br label %loop
 loop:                                         ; preds = %entry, %for.inc
diff --git a/llvm/test/Transforms/LICM/read-only-calls.ll b/llvm/test/Transforms/LICM/read-only-calls.ll
index 87899045f1655..a03cf9baf55c5 100644
--- a/llvm/test/Transforms/LICM/read-only-calls.ll
+++ b/llvm/test/Transforms/LICM/read-only-calls.ll
@@ -1,3 +1,4 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
 ; RUN: opt -aa-pipeline=basic-aa -passes='require<aa>,require<target-ir>,require<scalar-evolution>,require<opt-remark-emit>,loop-mssa(licm)' < %s -S | FileCheck %s
 
 ; We should be able to hoist loads in presence of read only calls and stores
@@ -15,10 +16,19 @@ declare void @foo(i64, ptr) readonly
 ; default AST mechanism clumps all memory locations in one set because of the
 ; readonly call
 define void @test1(ptr %ptr) {
-; CHECK-LABEL: @test1(
-; CHECK-LABEL: entry:
-; CHECK:         %val = load i32, ptr %ptr
-; CHECK-LABEL: loop:
+; CHECK-LABEL: define void @test1(
+; CHECK-SAME: ptr [[PTR:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    [[VAL:%.*]] = load i32, ptr [[PTR]], align 4
+; CHECK-NEXT:    [[P2:%.*]] = getelementptr i32, ptr [[PTR]], i32 1
+; CHECK-NEXT:    br label %[[LOOP:.*]]
+; CHECK:       [[LOOP]]:
+; CHECK-NEXT:    [[X:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[X_INC:%.*]], %[[LOOP]] ]
+; CHECK-NEXT:    call void @foo(i64 4, ptr [[PTR]])
+; CHECK-NEXT:    store volatile i32 0, ptr [[P2]], align 4
+; CHECK-NEXT:    [[X_INC]] = add i32 [[X]], [[VAL]]
+; CHECK-NEXT:    br label %[[LOOP]]
+;
 entry:
   br label %loop
 
@@ -34,10 +44,17 @@ loop:
 
 ; can hoist out load with the default AST and the alias analysis mechanism.
 define void @test2(ptr %ptr) {
-; CHECK-LABEL: @test2(
-; CHECK-LABEL: entry:
-; CHECK:         %val = load i32, ptr %ptr
-; CHECK-LABEL: loop:
+; CHECK-LABEL: define void @test2(
+; CHECK-SAME: ptr [[PTR:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    [[VAL:%.*]] = load i32, ptr [[PTR]], align 4
+; CHECK-NEXT:    call void @foo(i64 4, ptr [[PTR]])
+; CHECK-NEXT:    br label %[[LOOP:.*]]
+; CHECK:       [[LOOP]]:
+; CHECK-NEXT:    [[X:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[X_INC:%.*]], %[[LOOP]] ]
+; CHECK-NEXT:    [[X_INC]] = add i32 [[X]], [[VAL]]
+; CHECK-NEXT:    br label %[[LOOP]]
+;
 entry:
   br label %loop
 
@@ -...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/189388


More information about the llvm-commits mailing list