[llvm] [LICM] Don't promote stores past non-nosync calls that may synchronize (#64188) (PR #213282)

Saksham Kapoor via llvm-commits llvm-commits at lists.llvm.org
Fri Jul 31 07:26:05 PDT 2026


https://github.com/SakshamKapoor2911 created https://github.com/llvm/llvm-project/pull/213282

#ai-generated

Fixes: https://github.com/llvm/llvm-project/issues/64188

## Problem
LICM's store promotion (`collectPromotionCandidates`) promotes a non-atomic store to a `noalias` pointer out of a loop containing a non-`nosync` call. If the call contains an atomic release, this breaks the happens-before ordering required by the C/C++ memory model (§6.9.3.1 [intro.races]): a concurrent thread that synchronises via the release may observe the store *after* the release has completed, when it should observe it *before*. Confirmed on ARM (Apple M1, Cavium ThunderX2) and x86 hardware.

Reduced example:
```llvm
for.body:
  store i32 %i, ptr %p32     ; LICM promotes this
  call void @may_sync(...)    ; past this call (release inside)
```
After promotion the store lands in `for.end.loopexit` — after the release chain that was supposed to make it visible.

## Fix
In `collectPromotionCandidates`, scan the loop for non-`nosync` calls that may write accessible memory. If found, discard candidate sets whose base pointer is not provably non-escaping (not an AllocaInst). Alloca-backed stores are unaffected because an alloca cannot be observed by another thread.

This is the conservative, targeted LICM fix discussed on the issue. The broader AA fix (using captures instead of captures-before for non-nosync calls in `getModRefInfo`) can be a follow-up.

## Test Plan
New test: `llvm/test/Transforms/LICM/atomic-hoist.ll`
```
opt -S -passes='licm' llvm/test/Transforms/LICM/atomic-hoist.ll | FileCheck atomic-hoist.ll
```
PASSES with this fix, FAILS without it.

Existing LICM tests: 97 pass, no regressions. (56 tests fail identically on both this branch and main — they require build artifacts not configured in this tree.)

>From 6da87469ae107119ce8c6f7f426016d4f6aad41a Mon Sep 17 00:00:00 2001
From: SakshamKapoor2911 <sakshamkapoor2911 at gmail.com>
Date: Fri, 31 Jul 2026 10:22:32 -0400
Subject: [PATCH] [LICM] Don't promote stores past non-nosync calls that may
 sync (#64188)

LICM's store promotion (collectPromotionCandidates) promoted a
non-atomic store to a noalias pointer out of a loop containing a
non-nosync call, reordering the store past the call.  If the call
contains an atomic release, this breaks the happens-before ordering
required by the C/C++ memory model: a concurrent thread that
synchronises via the release may observe the store after the release
has completed, when it should observe it before.

The fix discards promotion candidate sets whose base pointer is not
provably non-escaping (i.e., not an alloca) when the loop contains
a non-nosync call that may write accessible memory.  Alloca-backed
stores are unaffected because an alloca cannot be observed by another
thread.

Test Plan:
  opt -S -passes='licm' llvm/test/Transforms/LICM/atomic-hoist.ll | FileCheck atomic-hoist.ll
---
 llvm/lib/Transforms/Scalar/LICM.cpp       | 25 +++++++++++++++++
 llvm/test/Transforms/LICM/atomic-hoist.ll | 34 +++++++++++++++++++++++
 2 files changed, 59 insertions(+)
 create mode 100644 llvm/test/Transforms/LICM/atomic-hoist.ll

diff --git a/llvm/lib/Transforms/Scalar/LICM.cpp b/llvm/lib/Transforms/Scalar/LICM.cpp
index 53b9a04e5c5bb..336a71e6078af 100644
--- a/llvm/lib/Transforms/Scalar/LICM.cpp
+++ b/llvm/lib/Transforms/Scalar/LICM.cpp
@@ -2369,6 +2369,31 @@ collectPromotionCandidates(MemorySSA *MSSA, AliasAnalysis *AA, Loop *L) {
   if (Sets.empty())
     return {}; // Nothing to promote...
 
+
+  // If the loop contains a non-nosync call that may write accessible memory,
+  // do not promote stores whose pointers are not provably non-escaping (i.e.,
+  // not allocas).  Such a call may contain atomic operations with release
+  // semantics; promoting a store to a captured location past it can make a
+  // write visible to another thread after the release has synchronised,
+  // violating the happens-before ordering required by the C/C++ memory model.
+  // See llvm/llvm-project#64188.
+  bool HasNonNoSyncCall = false;
+  foreachMemoryAccess(MSSA, L, [&](Instruction *I) {
+    if (isa<CallInst>(I) && !cast<CallInst>(I)->onlyReadsMemory() &&
+        !cast<CallInst>(I)->doesNotAccessMemory() &&
+        !cast<CallInst>(I)->hasFnAttr(Attribute::NoSync))
+      HasNonNoSyncCall = true;
+  });
+  if (HasNonNoSyncCall) {
+    llvm::erase_if(Sets, [](PointerIntPair<const AliasSet *, 1, bool> &Pair) {
+      for (auto &MemLoc : *Pair.getPointer()) {
+        if (!isa<AllocaInst>(MemLoc.Ptr))
+          return true;
+      }
+      return false;
+    });
+  }
+
   // Discard any sets for which there is an aliasing non-promotable access.
   foreachMemoryAccess(MSSA, L, [&](Instruction *I) {
     if (AttemptingPromotion.contains(I))
diff --git a/llvm/test/Transforms/LICM/atomic-hoist.ll b/llvm/test/Transforms/LICM/atomic-hoist.ll
new file mode 100644
index 0000000000000..108b038ad89d3
--- /dev/null
+++ b/llvm/test/Transforms/LICM/atomic-hoist.ll
@@ -0,0 +1,34 @@
+; RUN: opt -S -passes='licm' < %s | FileCheck %s
+
+; Regression test for llvm/llvm-project#64188.
+; LICM must not promote the non-atomic store out of the loop when the
+; loop contains a non-nosync call that may write accessible memory.
+
+ at a = dso_local global i32 0, align 4
+
+define ptr @hoist_store_past_non_nosync_call(i32 %N) {
+; CHECK-LABEL: @hoist_store_past_non_nosync_call(
+; CHECK:       for.body:
+; CHECK:         store i32 %i, ptr %p32
+; CHECK:         call void @may_sync
+entry:
+  %p = call noalias ptr @malloc(i64 4)
+  %p32 = bitcast ptr %p to ptr
+  %cmp = icmp slt i32 0, %N
+  br i1 %cmp, label %for.body, label %for.end
+
+for.body:
+  %i = phi i32 [ 1, %entry ], [ %inc, %for.body ]
+  store i32 %i, ptr %p32, align 4
+  call void @may_sync(ptr @a, i32 %i)
+  %inc = add i32 %i, 1
+  %cmp2 = icmp sle i32 %inc, %N
+  br i1 %cmp2, label %for.body, label %for.end
+
+for.end:
+  store i32 %N, ptr %p32, align 4
+  ret ptr %p
+}
+
+declare noalias ptr @malloc(i64)
+declare void @may_sync(ptr, i32)



More information about the llvm-commits mailing list