[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