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

Gabriel Baraldi via llvm-commits llvm-commits at lists.llvm.org
Mon Mar 30 07:06:58 PDT 2026


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

## 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)

>From eeac91e77e41cae2ee93cd34b724081681eeb18e Mon Sep 17 00:00:00 2001
From: gbaraldi <baraldigabriel at gmail.com>
Date: Mon, 30 Mar 2026 11:05:54 -0300
Subject: [PATCH] [LICM] Allow hoisting may-throw calls when guaranteed to
 execute
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

canSinkOrHoistInst previously rejected all may-throw calls for both
sinking and hoisting. For hoisting, this is overly conservative: if a
call is guaranteed to execute on every loop iteration (checked by
isSafeToExecuteUnconditionally), hoisting it to the preheader is safe
even if it may throw — it would just throw on the first iteration
instead.

This adds an IsHoist parameter to canSinkOrHoistInst so the mayThrow
check only blocks sinking. The existing isSafeToExecuteUnconditionally
check at the hoisting call site ensures we only hoist when the
instruction is guaranteed to execute.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply at anthropic.com>
---
 .../include/llvm/Transforms/Utils/LoopUtils.h |   6 +-
 llvm/lib/Transforms/Scalar/LICM.cpp           |  13 +-
 llvm/test/Transforms/LICM/call-hoisting.ll    | 118 ++++++++++++++-
 llvm/test/Transforms/LICM/hoist-metadata.ll   |   2 +-
 llvm/test/Transforms/LICM/pr51333.ll          |   2 +-
 llvm/test/Transforms/LICM/preheader-safe.ll   | 138 ++++++++++++------
 llvm/test/Transforms/LICM/read-only-calls.ll  |  55 +++++--
 7 files changed, 267 insertions(+), 67 deletions(-)

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
 
@@ -49,18 +66,28 @@ loop:
   br label %loop
 }
 
-; cannot hoist load since not guaranteed to execute
+; cannot hoist load since not guaranteed to execute due to may-throw call before
+; it that is not hoistable (readwrite, so it stays in the loop as ICF).
+declare void @bar(i64, ptr)
+
 define void @test3(ptr %ptr) {
-; CHECK-LABEL: @test3(
-; CHECK-LABEL: entry:
-; CHECK-LABEL: loop:
-; CHECK:         %val = load i32, ptr %ptr
+; CHECK-LABEL: define void @test3(
+; CHECK-SAME: ptr [[PTR:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    br label %[[LOOP:.*]]
+; CHECK:       [[LOOP]]:
+; CHECK-NEXT:    [[X:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[X_INC:%.*]], %[[LOOP]] ]
+; CHECK-NEXT:    call void @bar(i64 4, ptr [[PTR]])
+; CHECK-NEXT:    [[VAL:%.*]] = load i32, ptr [[PTR]], align 4
+; CHECK-NEXT:    [[X_INC]] = add i32 [[X]], [[VAL]]
+; CHECK-NEXT:    br label %[[LOOP]]
+;
 entry:
   br label %loop
 
 loop:
   %x = phi i32 [ 0, %entry ], [ %x.inc, %loop ]
-  call void @foo(i64 4, ptr %ptr)
+  call void @bar(i64 4, ptr %ptr)
   %val = load i32, ptr %ptr
   %x.inc = add i32 %x, %val
   br label %loop



More information about the llvm-commits mailing list