[llvm] [LLVM][LICM] Skip unrelated accesses when looking for hoist/sink conflicting instructions. (PR #195132)

Paul Walker via llvm-commits llvm-commits at lists.llvm.org
Thu May 7 04:52:54 PDT 2026


https://github.com/paulwalker-arm updated https://github.com/llvm/llvm-project/pull/195132

>From 5102d94c541eed102fa737f43e381befaa45cd2e Mon Sep 17 00:00:00 2001
From: Paul Walker <paul.walker at arm.com>
Date: Tue, 5 May 2026 15:21:14 +0100
Subject: [PATCH 1/3] Add Tests.

---
 .../LICM/hoist-inaccesiblemem-call.ll         | 37 +++++++++++++++++++
 1 file changed, 37 insertions(+)

diff --git a/llvm/test/Transforms/LICM/hoist-inaccesiblemem-call.ll b/llvm/test/Transforms/LICM/hoist-inaccesiblemem-call.ll
index fd73080365993..ad73840480503 100644
--- a/llvm/test/Transforms/LICM/hoist-inaccesiblemem-call.ll
+++ b/llvm/test/Transforms/LICM/hoist-inaccesiblemem-call.ll
@@ -158,6 +158,43 @@ loop:
   br label %loop
 }
 
+define i32 @non_dominated_load_that_does_not_alias_inaccessible_mem(ptr %dst, i64 %x, ptr %start) {
+; CHECK-LABEL: define i32 @non_dominated_load_that_does_not_alias_inaccessible_mem(
+; CHECK-SAME: ptr [[DST:%.*]], i64 [[X:%.*]], ptr [[START:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    br label %[[LOOP:.*]]
+; CHECK:       [[LOOP]]:
+; CHECK-NEXT:    [[PHI:%.*]] = phi ptr [ [[START]], %[[ENTRY]] ], [ [[GEP:%.*]], %[[LOOP]] ]
+; CHECK-NEXT:    [[LOAD:%.*]] = load i32, ptr [[PHI]], align 4
+; CHECK-NEXT:    [[VAL:%.*]] = call i32 @fn_args(i32 [[LOAD]])
+; CHECK-NEXT:    call void @fn_write_inaccessible_mem()
+; CHECK-NEXT:    [[RES:%.*]] = call i32 @fn_read_inaccessible_mem_2(i32 [[LOAD]])
+; CHECK-NEXT:    store i32 [[RES]], ptr [[DST]], align 4
+; CHECK-NEXT:    [[GEP]] = getelementptr inbounds nuw i32, ptr [[PHI]], i64 [[X]]
+; CHECK-NEXT:    [[ACC:%.*]] = add nuw nsw i32 [[VAL]], 1
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i32 [[ACC]], 10
+; CHECK-NEXT:    br i1 [[CMP]], label %[[LOOP]], label %[[AFTER_LOOP:.*]]
+; CHECK:       [[AFTER_LOOP]]:
+; CHECK-NEXT:    [[ACC_LCSSA:%.*]] = phi i32 [ [[ACC]], %[[LOOP]] ]
+; CHECK-NEXT:    ret i32 [[ACC_LCSSA]]
+;
+entry:
+  br label %loop
+loop:
+  %phi = phi ptr [ %start, %entry ], [ %gep, %loop ]
+  %load = load i32, ptr %phi
+  %val = call i32 @fn_args(i32 %load)
+  call void @fn_write_inaccessible_mem()
+  %res = call i32 @fn_read_inaccessible_mem_2(i32 %load)
+  store i32 %res, ptr %dst
+  %gep = getelementptr inbounds nuw i32, ptr %phi, i64 %x
+  %acc = add nuw nsw i32 %val, 1
+  %cmp = icmp ult i32 %acc, 10
+  br i1 %cmp, label %loop, label %after_loop
+after_loop:
+  ret i32 %acc
+}
+
 declare i32 @fn_args(i32) nounwind willreturn memory(argmem: read)
 declare i32 @fn_read_inaccessible_mem_2(i32) nounwind willreturn memory(inaccessiblemem: read)
 declare void @fn_write_inaccessible_mem() nounwind memory(inaccessiblemem: write)

>From 9fa258294c0137bcf7d5e97e4ad0c5d25b348d17 Mon Sep 17 00:00:00 2001
From: Paul Walker <paul.walker at arm.com>
Date: Thu, 30 Apr 2026 17:40:41 +0100
Subject: [PATCH 2/3] [LLVM][LICM] Break as soon as we find an access that a
 hoistable instruction dominates.

---
 llvm/lib/Transforms/Scalar/LICM.cpp | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Transforms/Scalar/LICM.cpp b/llvm/lib/Transforms/Scalar/LICM.cpp
index 9f3535a506b7e..95ab7e82e9004 100644
--- a/llvm/lib/Transforms/Scalar/LICM.cpp
+++ b/llvm/lib/Transforms/Scalar/LICM.cpp
@@ -2329,8 +2329,10 @@ static bool noConflictingReadWrites(Instruction *I, MemorySSA *MSSA,
     if (!Accesses)
       continue;
     for (const auto &MA : *Accesses) {
+      // Accesses are ordered. If we find one that I dominates we can stop.
       if (!Flags.getIsSink() && MSSA->dominates(IMD, &MA))
-        continue;
+        break;
+
       if (const auto *MU = dyn_cast<MemoryUse>(&MA)) {
         auto *MD = getClobberingMemoryAccess(*MSSA, BAA, Flags,
                                              const_cast<MemoryUse *>(MU));

>From 432f73a39e6b22eede6b8fd3e6164aa85ce2fe36 Mon Sep 17 00:00:00 2001
From: Paul Walker <paul.walker at arm.com>
Date: Thu, 30 Apr 2026 17:42:15 +0100
Subject: [PATCH 3/3] [LLVM][LICM] Skip unrelated accesses when looking for
 hoist/sink conflicting instructions.

---
 llvm/include/llvm/Analysis/AliasAnalysis.h    |  3 ++
 llvm/lib/Transforms/Scalar/LICM.cpp           | 47 +++----------------
 llvm/test/Transforms/LICM/call-hoisting.ll    |  3 +-
 .../LICM/hoist-inaccesiblemem-call.ll         |  6 +--
 llvm/test/Transforms/LICM/pr54495.ll          |  2 +-
 5 files changed, 14 insertions(+), 47 deletions(-)

diff --git a/llvm/include/llvm/Analysis/AliasAnalysis.h b/llvm/include/llvm/Analysis/AliasAnalysis.h
index 845ccdbfce776..799dd7a482b2c 100644
--- a/llvm/include/llvm/Analysis/AliasAnalysis.h
+++ b/llvm/include/llvm/Analysis/AliasAnalysis.h
@@ -689,6 +689,9 @@ class BatchAAResults {
   ModRefInfo getModRefInfo(const Instruction *I, const CallBase *Call2) {
     return AA.getModRefInfo(I, Call2, AAQI);
   }
+  ModRefInfo getModRefInfo(const Instruction *I, const Instruction *I2) {
+    return AA.getModRefInfo(I, I2, AAQI);
+  }
   ModRefInfo getArgModRefInfo(const CallBase *Call, unsigned ArgIdx) {
     return AA.getArgModRefInfo(Call, ArgIdx);
   }
diff --git a/llvm/lib/Transforms/Scalar/LICM.cpp b/llvm/lib/Transforms/Scalar/LICM.cpp
index 95ab7e82e9004..2ceadb1ac30e2 100644
--- a/llvm/lib/Transforms/Scalar/LICM.cpp
+++ b/llvm/lib/Transforms/Scalar/LICM.cpp
@@ -2319,9 +2319,7 @@ static bool noConflictingReadWrites(Instruction *I, MemorySSA *MSSA,
   if (!MSSA->isLiveOnEntryDef(Source) && CurLoop->contains(Source->getBlock()))
     return false;
 
-  // If there are interfering Uses (i.e. their defining access is in the
-  // loop), or ordered loads (stored as Defs!), don't move this store.
-  // Could do better here, but this is conservatively correct.
+  // If there are interfering Uses don't move this store.
   // TODO: Cache set of Uses on the first walk in runOnLoop, update when
   // moving accesses. Can also extend to dominating uses.
   for (auto *BB : CurLoop->getBlocks()) {
@@ -2333,43 +2331,12 @@ static bool noConflictingReadWrites(Instruction *I, MemorySSA *MSSA,
       if (!Flags.getIsSink() && MSSA->dominates(IMD, &MA))
         break;
 
-      if (const auto *MU = dyn_cast<MemoryUse>(&MA)) {
-        auto *MD = getClobberingMemoryAccess(*MSSA, BAA, Flags,
-                                             const_cast<MemoryUse *>(MU));
-        if (!MSSA->isLiveOnEntryDef(MD) && CurLoop->contains(MD->getBlock()))
-          return false;
-        // Disable hoisting past potentially interfering loads. Optimized
-        // Uses may point to an access outside the loop, as getClobbering
-        // checks the previous iteration when walking the backedge.
-        // FIXME: More precise: no Uses that alias I.
-        if (!Flags.getIsSink() && !MSSA->dominates(IMD, MU))
-          return false;
-      } else if (const auto *MD = dyn_cast<MemoryDef>(&MA)) {
-        if (auto *LI = dyn_cast<LoadInst>(MD->getMemoryInst())) {
-          (void)LI; // Silence warning.
-          assert(!LI->isUnordered() && "Expected unordered load");
-          return false;
-        }
-        // Any call, while it may not be clobbering I, it may be a use.
-        if (auto *CI = dyn_cast<CallInst>(MD->getMemoryInst())) {
-          // Check if the call may read from the memory location written
-          // to by I. Check CI's attributes and arguments; the number of
-          // such checks performed is limited above by NoOfMemAccTooLarge.
-          if (auto *SI = dyn_cast<StoreInst>(I)) {
-            ModRefInfo MRI = BAA.getModRefInfo(CI, MemoryLocation::get(SI));
-            if (isModOrRefSet(MRI))
-              return false;
-          } else {
-            auto *SCI = cast<CallInst>(I);
-            // If the instruction we are wanting to hoist is also a call
-            // instruction then we need not check mod/ref info with itself
-            if (SCI == CI)
-              continue;
-            ModRefInfo MRI = BAA.getModRefInfo(CI, SCI);
-            if (isModOrRefSet(MRI))
-              return false;
-          }
-        }
+      if (const auto *MemUseOrDef = dyn_cast<MemoryUseOrDef>(&MA)) {
+        // Skip unrelated accesses.
+        if (isNoModRef(BAA.getModRefInfo(MemUseOrDef->getMemoryInst(), I)))
+          continue;
+
+        return false;
       }
     }
   }
diff --git a/llvm/test/Transforms/LICM/call-hoisting.ll b/llvm/test/Transforms/LICM/call-hoisting.ll
index 748810be3a709..2520e2277cf56 100644
--- a/llvm/test/Transforms/LICM/call-hoisting.ll
+++ b/llvm/test/Transforms/LICM/call-hoisting.ll
@@ -277,17 +277,16 @@ exit:
   ret i32 %val
 }
 
-; FIXME: It's safe to hoist @store(), because @load() does not alias.
 define i32 @unrelated_read(ptr noalias %loc, ptr noalias %otherloc) {
 ; CHECK-LABEL: define i32 @unrelated_read(
 ; CHECK-SAME: ptr noalias [[LOC:%.*]], ptr noalias [[OTHERLOC:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    call void @store(i32 0, ptr [[LOC]])
 ; CHECK-NEXT:    br label %[[LOOP:.*]]
 ; CHECK:       [[LOOP]]:
 ; CHECK-NEXT:    [[IV:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
 ; CHECK-NEXT:    [[OTHERLOC_GEP:%.*]] = getelementptr i32, ptr [[OTHERLOC]], i32 [[IV]]
 ; CHECK-NEXT:    [[VAL:%.*]] = call i32 @load(ptr [[OTHERLOC_GEP]])
-; CHECK-NEXT:    call void @store(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:.*]]
diff --git a/llvm/test/Transforms/LICM/hoist-inaccesiblemem-call.ll b/llvm/test/Transforms/LICM/hoist-inaccesiblemem-call.ll
index ad73840480503..89d3cc9d37033 100644
--- a/llvm/test/Transforms/LICM/hoist-inaccesiblemem-call.ll
+++ b/llvm/test/Transforms/LICM/hoist-inaccesiblemem-call.ll
@@ -4,18 +4,16 @@
 ;; It should hoist fn_write_inaccessible_mem
 ;; because there is no conflict between inaccessible memory
 ;; fn_read_inaccessible_mem is a nice side effect
-; FIXME: fn_write_inaccessible_mem is currently not hoisted due to the preceding
-; load, even though it does not alias.
 define i32 @loop_alias(i64 %x, ptr %start) {
 ; CHECK-LABEL: define i32 @loop_alias(
 ; CHECK-SAME: i64 [[X:%.*]], ptr [[START:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    call void @fn_write_inaccessible_mem()
 ; CHECK-NEXT:    br label %[[LOOP:.*]]
 ; CHECK:       [[LOOP]]:
 ; CHECK-NEXT:    [[PHI:%.*]] = phi ptr [ [[START]], %[[ENTRY]] ], [ [[GEP:%.*]], %[[LOOP]] ]
 ; CHECK-NEXT:    [[LOAD:%.*]] = load i32, ptr [[PHI]], align 4
 ; CHECK-NEXT:    [[VAL:%.*]] = call i32 @fn_args(i32 [[LOAD]])
-; CHECK-NEXT:    call void @fn_write_inaccessible_mem()
 ; CHECK-NEXT:    call void @fn_read_inaccessible_mem(i32 [[LOAD]])
 ; CHECK-NEXT:    [[GEP]] = getelementptr inbounds nuw i32, ptr [[PHI]], i64 [[X]]
 ; CHECK-NEXT:    [[ACC:%.*]] = add nuw nsw i32 [[VAL]], 1
@@ -162,12 +160,12 @@ define i32 @non_dominated_load_that_does_not_alias_inaccessible_mem(ptr %dst, i6
 ; CHECK-LABEL: define i32 @non_dominated_load_that_does_not_alias_inaccessible_mem(
 ; CHECK-SAME: ptr [[DST:%.*]], i64 [[X:%.*]], ptr [[START:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    call void @fn_write_inaccessible_mem()
 ; CHECK-NEXT:    br label %[[LOOP:.*]]
 ; CHECK:       [[LOOP]]:
 ; CHECK-NEXT:    [[PHI:%.*]] = phi ptr [ [[START]], %[[ENTRY]] ], [ [[GEP:%.*]], %[[LOOP]] ]
 ; CHECK-NEXT:    [[LOAD:%.*]] = load i32, ptr [[PHI]], align 4
 ; CHECK-NEXT:    [[VAL:%.*]] = call i32 @fn_args(i32 [[LOAD]])
-; CHECK-NEXT:    call void @fn_write_inaccessible_mem()
 ; CHECK-NEXT:    [[RES:%.*]] = call i32 @fn_read_inaccessible_mem_2(i32 [[LOAD]])
 ; CHECK-NEXT:    store i32 [[RES]], ptr [[DST]], align 4
 ; CHECK-NEXT:    [[GEP]] = getelementptr inbounds nuw i32, ptr [[PHI]], i64 [[X]]
diff --git a/llvm/test/Transforms/LICM/pr54495.ll b/llvm/test/Transforms/LICM/pr54495.ll
index d01ca69d55242..5e66758257ef0 100644
--- a/llvm/test/Transforms/LICM/pr54495.ll
+++ b/llvm/test/Transforms/LICM/pr54495.ll
@@ -6,6 +6,7 @@
 define void @test(ptr %p1, ptr %p2, ptr noalias %p3) {
 ; CHECK-LABEL: @test(
 ; CHECK-NEXT:  entry:
+; CHECK-NEXT:    store ptr [[P3:%.*]], ptr [[P3]], align 8
 ; CHECK-NEXT:    br label [[LOOP:%.*]]
 ; CHECK:       loop:
 ; CHECK-NEXT:    [[P:%.*]] = phi ptr [ [[P1:%.*]], [[ENTRY:%.*]] ], [ [[P2:%.*]], [[LOOP]] ]
@@ -13,7 +14,6 @@ define void @test(ptr %p1, ptr %p2, ptr noalias %p3) {
 ; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i64 [[V]], 0
 ; CHECK-NEXT:    br i1 [[CMP]], label [[LOOP]], label [[LOOP_EXIT:%.*]]
 ; CHECK:       loop.exit:
-; CHECK-NEXT:    store ptr [[P3:%.*]], ptr [[P3]], align 8
 ; CHECK-NEXT:    ret void
 ;
 entry:



More information about the llvm-commits mailing list