[llvm] [llvm] Hoisting writeonly calls (PR #143799)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Jun 11 15:50:08 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
Author: Jiachen (Yangyang) Wang (WanderingAura)
<details>
<summary>Changes</summary>
Adds support for hoisting `writeonly` calls in LICM.
This patch adds a missing optimization that allows hoisting of `writeonly` function calls out of loops when it is safe to do so. Previously, such calls were conservatively retained inside the loop body, and the redundant calls were only reduced through unrolling, relying on target-dependent heuristics.
Closes #<!-- -->143267
Testing:
- Modified previously negative tests for hoisting writeonly calls to be instead positive
- Added test cases for hoisting of two writeonly calls where the pointers do/do not alias
- Added a test case for not argmemonly writeonly calls.
---
Full diff: https://github.com/llvm/llvm-project/pull/143799.diff
2 Files Affected:
- (modified) llvm/lib/Transforms/Scalar/LICM.cpp (+37-5)
- (modified) llvm/test/Transforms/LICM/call-hoisting.ll (+80-7)
``````````diff
diff --git a/llvm/lib/Transforms/Scalar/LICM.cpp b/llvm/lib/Transforms/Scalar/LICM.cpp
index 7965ed76a81b7..372a79edeb593 100644
--- a/llvm/lib/Transforms/Scalar/LICM.cpp
+++ b/llvm/lib/Transforms/Scalar/LICM.cpp
@@ -186,6 +186,9 @@ static bool isSafeToExecuteUnconditionally(
const Loop *CurLoop, const LoopSafetyInfo *SafetyInfo,
OptimizationRemarkEmitter *ORE, const Instruction *CtxI,
AssumptionCache *AC, bool AllowSpeculation);
+static bool memoryDefInvalidatedByLoop(MemorySSA *MSSA, MemoryDef *MD,
+ Loop *CurLoop,
+ SinkAndHoistLICMFlags &Flags);
static bool pointerInvalidatedByLoop(MemorySSA *MSSA, MemoryUse *MU,
Loop *CurLoop, Instruction &I,
SinkAndHoistLICMFlags &Flags,
@@ -1032,8 +1035,8 @@ bool llvm::hoistRegion(DomTreeNode *N, AAResults *AA, LoopInfo *LI,
if (VerifyMemorySSA)
MSSAU.getMemorySSA()->verifyMemorySSA();
- // Now that we've finished hoisting make sure that LI and DT are still
- // valid.
+ // Now that we've finished hoisting make sure that LI and DT are still
+ // valid.
#ifdef EXPENSIVE_CHECKS
if (Changed) {
assert(DT->verify(DominatorTree::VerificationLevel::Fast) &&
@@ -1258,6 +1261,21 @@ bool llvm::canSinkOrHoistInst(Instruction &I, AAResults *AA, DominatorTree *DT,
return true;
}
+ if (Behavior.onlyWritesMemory()) {
+ // If it's the only memory access then there is nothing
+ // stopping us from hoisting it.
+ if (isOnlyMemoryAccess(CI, CurLoop, MSSAU))
+ return true;
+
+ if (Behavior.onlyAccessesArgPointees()) {
+ if (memoryDefInvalidatedByLoop(
+ MSSA, cast<MemoryDef>(MSSA->getMemoryAccess(CI)), CurLoop,
+ Flags))
+ return false;
+ return true;
+ }
+ }
+
// FIXME: This should use mod/ref information to see if we can hoist or
// sink the call.
@@ -1287,7 +1305,7 @@ bool llvm::canSinkOrHoistInst(Instruction &I, AAResults *AA, DominatorTree *DT,
auto *Source = getClobberingMemoryAccess(*MSSA, BAA, Flags, SIMD);
// Make sure there are no clobbers inside the loop.
if (!MSSA->isLiveOnEntryDef(Source) &&
- CurLoop->contains(Source->getBlock()))
+ CurLoop->contains(Source->getBlock()))
return false;
// If there are interfering Uses (i.e. their defining access is in the
@@ -1300,7 +1318,7 @@ bool llvm::canSinkOrHoistInst(Instruction &I, AAResults *AA, DominatorTree *DT,
for (const auto &MA : *Accesses)
if (const auto *MU = dyn_cast<MemoryUse>(&MA)) {
auto *MD = getClobberingMemoryAccess(*MSSA, BAA, Flags,
- const_cast<MemoryUse *>(MU));
+ const_cast<MemoryUse *>(MU));
if (!MSSA->isLiveOnEntryDef(MD) &&
CurLoop->contains(MD->getBlock()))
return false;
@@ -1352,7 +1370,7 @@ static bool isTriviallyReplaceablePHI(const PHINode &PN, const Instruction &I) {
/// Return true if the instruction is foldable in the loop.
static bool isFoldableInLoop(const Instruction &I, const Loop *CurLoop,
- const TargetTransformInfo *TTI) {
+ const TargetTransformInfo *TTI) {
if (auto *GEP = dyn_cast<GetElementPtrInst>(&I)) {
InstructionCost CostI =
TTI->getInstructionCost(&I, TargetTransformInfo::TCK_SizeAndLatency);
@@ -2354,6 +2372,20 @@ collectPromotionCandidates(MemorySSA *MSSA, AliasAnalysis *AA, Loop *L) {
return Result;
}
+// This function checks if a given MemoryDef gets clobbered by
+// any memory accesses within the loop.
+static bool memoryDefInvalidatedByLoop(MemorySSA *MSSA, MemoryDef *MD,
+ Loop *CurLoop,
+ SinkAndHoistLICMFlags &Flags) {
+ if (Flags.tooManyMemoryAccesses()) {
+ return true;
+ }
+ BatchAAResults BAA(MSSA->getAA());
+ MemoryAccess *Source = getClobberingMemoryAccess(*MSSA, BAA, Flags, MD);
+ return !MSSA->isLiveOnEntryDef(Source) &&
+ CurLoop->contains(Source->getBlock());
+}
+
static bool pointerInvalidatedByLoop(MemorySSA *MSSA, MemoryUse *MU,
Loop *CurLoop, Instruction &I,
SinkAndHoistLICMFlags &Flags,
diff --git a/llvm/test/Transforms/LICM/call-hoisting.ll b/llvm/test/Transforms/LICM/call-hoisting.ll
index e6d2e42e34e81..e9b19db4861d4 100644
--- a/llvm/test/Transforms/LICM/call-hoisting.ll
+++ b/llvm/test/Transforms/LICM/call-hoisting.ll
@@ -64,10 +64,13 @@ exit:
declare void @store(i32 %val, ptr %p) argmemonly writeonly nounwind
+; loop invariant calls to writeonly functions such as the above
+; should be hoisted
define void @test(ptr %loc) {
; CHECK-LABEL: @test
-; CHECK-LABEL: loop:
+; CHECK-LABEL: entry:
; CHECK: call void @store
+; CHECK-LABEL: loop:
; CHECK-LABEL: exit:
entry:
br label %loop
@@ -85,8 +88,9 @@ exit:
define void @test_multiexit(ptr %loc, i1 %earlycnd) {
; CHECK-LABEL: @test_multiexit
-; CHECK-LABEL: loop:
+; CHECK-LABEL: entry:
; CHECK: call void @store
+; CHECK-LABEL: loop:
; CHECK-LABEL: backedge:
entry:
br label %loop
@@ -107,6 +111,50 @@ exit2:
ret void
}
+; cannot be hoisted because the two pointers can alias one another
+define void @neg_two_pointer(ptr %loc, ptr %otherloc) {
+; CHECK-LABEL: @neg_two_pointer
+; CHECK-LABEL: entry:
+; CHECK-LABEL: loop:
+; CHECK: call void @store
+; CHECK: call void @store
+; CHECK-LABEL: exit:
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i32 [0, %entry], [%iv.next, %loop]
+ call void @store(i32 0, ptr %loc)
+ call void @store(i32 1, ptr %otherloc)
+ %iv.next = add i32 %iv, 1
+ %cmp = icmp slt i32 %iv, 200
+ br i1 %cmp, label %loop, label %exit
+exit:
+ ret void
+}
+
+; hoisted due to pointers not aliasing
+define void @two_pointer_noalias(ptr noalias %loc, ptr %otherloc) {
+; CHECK-LABEL: @two_pointer_noalias
+; CHECK-LABEL: entry:
+; CHECK: call void @store
+; CHECK: call void @store
+; CHECK-LABEL: loop:
+; CHECK-LABEL: exit:
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i32 [0, %entry], [%iv.next, %loop]
+ call void @store(i32 0, ptr %loc)
+ call void @store(i32 1, ptr %otherloc)
+ %iv.next = add i32 %iv, 1
+ %cmp = icmp slt i32 %iv, 200
+ br i1 %cmp, label %loop, label %exit
+exit:
+ ret void
+}
+
define void @neg_lv_value(ptr %loc) {
; CHECK-LABEL: @neg_lv_value
; CHECK-LABEL: loop:
@@ -166,10 +214,11 @@ exit:
ret void
}
-define void @neg_ref(ptr %loc) {
-; CHECK-LABEL: @neg_ref
-; CHECK-LABEL: loop:
+define void @ref(ptr %loc) {
+; CHECK-LABEL: @ref
+; CHECK-LABEL: entry:
; CHECK: call void @store
+; CHECK-LABEL: loop:
; CHECK-LABEL: exit1:
entry:
br label %loop
@@ -257,8 +306,31 @@ exit:
ret void
}
-define void @neg_not_argmemonly(ptr %loc) {
-; CHECK-LABEL: @neg_not_argmemonly
+; not argmemonly calls can be hoisted if it's the only memory access in loop
+define void @not_argmemonly_only_access(ptr %loc) {
+; CHECK-LABEL: @not_argmemonly_only_access
+; CHECK-LABEL: entry:
+; CHECK: call void @not_argmemonly
+; CHECK-LABEL: loop:
+; CHECK-LABEL: exit:
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i32 [0, %entry], [%iv.next, %loop]
+ call void @not_argmemonly(i32 0, ptr %loc)
+ %iv.next = add i32 %iv, 1
+ %cmp = icmp slt i32 %iv, 200
+ br i1 %cmp, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+; not argmemonly calls cannot be hoisted if there are other memory accesses
+define void @neg_not_argmemonly_multiple_access(ptr noalias %loc, ptr noalias %loc2) {
+; CHECK-LABEL: @neg_not_argmemonly_multiple_access
+; CHECK-LABEL: entry:
; CHECK-LABEL: loop:
; CHECK: call void @not_argmemonly
; CHECK-LABEL: exit:
@@ -268,6 +340,7 @@ entry:
loop:
%iv = phi i32 [0, %entry], [%iv.next, %loop]
call void @not_argmemonly(i32 0, ptr %loc)
+ call void @load(i32 0, ptr %loc2)
%iv.next = add i32 %iv, 1
%cmp = icmp slt i32 %iv, 200
br i1 %cmp, label %loop, label %exit
``````````
</details>
https://github.com/llvm/llvm-project/pull/143799
More information about the llvm-commits
mailing list