[llvm] [LoopInfo] Don't recognize loop as parallel if it stores to out-of-loop alloca (PR #180551)

Julius Ikkala via llvm-commits llvm-commits at lists.llvm.org
Mon Feb 16 09:27:16 PST 2026


https://github.com/juliusikkala updated https://github.com/llvm/llvm-project/pull/180551

>From 1ccb84711e97b8aa7db8812e807cff28a09998dd Mon Sep 17 00:00:00 2001
From: Julius Ikkala <julius.ikkala at tuni.fi>
Date: Mon, 9 Feb 2026 17:30:54 +0200
Subject: [PATCH 01/10] [LoopInfo] Don't recognize loop as parallel if it
 stores to out-of-loop alloca

---
 llvm/lib/Analysis/LoopInfo.cpp                | 15 +++++
 .../LoopInfo/annotated-parallel-alloca.ll     | 57 +++++++++++++++++++
 2 files changed, 72 insertions(+)
 create mode 100644 llvm/test/Analysis/LoopInfo/annotated-parallel-alloca.ll

diff --git a/llvm/lib/Analysis/LoopInfo.cpp b/llvm/lib/Analysis/LoopInfo.cpp
index a364b21c64b01..d5203a20c8c6c 100644
--- a/llvm/lib/Analysis/LoopInfo.cpp
+++ b/llvm/lib/Analysis/LoopInfo.cpp
@@ -591,6 +591,21 @@ bool Loop::isAnnotatedParallel() const {
       if (!I.mayReadOrWriteMemory())
         continue;
 
+      // If the loop contains a store instruction into an alloca that is outside
+      // of the loop, it is possible that the alloca was initially related to a
+      // loop-local variable but got hoisted outside during e.g. inlining or
+      // some other parallel-loop-unaware pass.
+      //
+      // TODO: Allow metadata to mark 'alloca' as safe to vectorize and
+      // separately handle such allocas in the loop vectorizer, either by
+      // sinking the `alloca` into the loop body or by otherwise "privatizing"
+      // the allocation for each vector lane.
+      if (StoreInst *SI = dyn_cast<StoreInst>(&I)) {
+        AllocaInst *AI = findAllocaForValue(SI->getPointerOperand());
+        if (AI && !contains(AI))
+          return false;
+      }
+
       if (MDNode *AccessGroup = I.getMetadata(LLVMContext::MD_access_group)) {
         auto ContainsAccessGroup = [&ParallelAccessGroups](MDNode *AG) -> bool {
           if (AG->getNumOperands() == 0) {
diff --git a/llvm/test/Analysis/LoopInfo/annotated-parallel-alloca.ll b/llvm/test/Analysis/LoopInfo/annotated-parallel-alloca.ll
new file mode 100644
index 0000000000000..b4e5af07950c6
--- /dev/null
+++ b/llvm/test/Analysis/LoopInfo/annotated-parallel-alloca.ll
@@ -0,0 +1,57 @@
+; RUN: opt -passes='print<loops>' -disable-output %s 2>&1 | FileCheck %s
+;
+; void func(long n, long *A) {
+;   #pragma clang loop vectorize(assume_safety)
+;   for (long i = 0; i < n; i += 1) {
+;     long t[32];
+;     for (long j = 0; j < 32; j += 1)
+;       t[j] = i;
+;     A[i] = t[i];
+;   }
+; }
+;
+; The alloca for `t` usually gets hoisted outside of the loop (either by Clang
+; itself, or by an inlining pass if the loop body is in a function, etc.) and
+; gets incorrectly shared between iterations. Check that isAnnotatedParallel is
+; blocking this kind of usage, as it will not get vectorized correctly unless
+; mem2reg converts the array.
+;
+target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
+
+define void @func(i64 %n, ptr noalias nonnull %A) {
+entry:
+  %t = alloca [32 x i64], align 16
+  %cmp17 = icmp sgt i64 %n, 0
+  br i1 %cmp17, label %for.body, label %for.cond.cleanup
+
+for.body:
+  %i.018 = phi i64 [ %add8, %for.cond.cleanup3 ], [ 0, %entry ]
+  br label %for.body4
+
+for.body4:
+  %j.016 = phi i64 [ 0, %for.body ], [ %add, %for.body4 ]
+  %arrayidx = getelementptr inbounds nuw i64, ptr %t, i64 %j.016
+  store i64 %i.018, ptr %arrayidx, align 8, !llvm.access.group !9
+  %add = add nuw nsw i64 %j.016, 1
+  %exitcond.not = icmp eq i64 %add, 32
+  br i1 %exitcond.not, label %for.cond.cleanup3, label %for.body4
+
+for.cond.cleanup3:
+  %arrayidx5 = getelementptr inbounds nuw i64, ptr %t, i64 %i.018
+  %0 = load i64, ptr %arrayidx5, align 8, !llvm.access.group !9
+  %arrayidx6 = getelementptr inbounds nuw i64, ptr %A, i64 %i.018
+  store i64 %0, ptr %arrayidx6, align 8, !llvm.access.group !9
+  %add8 = add nuw nsw i64 %i.018, 1
+  %exitcond19.not = icmp eq i64 %add8, %n
+  br i1 %exitcond19.not, label %for.cond.cleanup, label %for.body, !llvm.loop !10
+
+for.cond.cleanup:
+  ret void
+}
+
+!9 = distinct !{}
+!10 = distinct !{!10, !11}
+!11 = !{!"llvm.loop.parallel_accesses", !9}
+
+; CHECK: Loop info for function 'func':
+; CHECK-NOT: Parallel Loop at depth 1 containing:

>From 420ae4aa3590bc8dad61aff658789399e76e999f Mon Sep 17 00:00:00 2001
From: Julius Ikkala <julius.ikkala at tuni.fi>
Date: Mon, 9 Feb 2026 18:15:01 +0200
Subject: [PATCH 02/10] Retain parallel metadata on alloca

---
 llvm/lib/Transforms/Utils/InlineFunction.cpp | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/InlineFunction.cpp b/llvm/lib/Transforms/Utils/InlineFunction.cpp
index 3230b306f17d1..896802b43ef43 100644
--- a/llvm/lib/Transforms/Utils/InlineFunction.cpp
+++ b/llvm/lib/Transforms/Utils/InlineFunction.cpp
@@ -946,8 +946,9 @@ static void PropagateCallSiteMetadata(CallBase &CB, Function::iterator FStart,
 
   for (BasicBlock &BB : make_range(FStart, FEnd)) {
     for (Instruction &I : BB) {
-      // This metadata is only relevant for instructions that access memory.
-      if (!I.mayReadOrWriteMemory())
+      // This metadata is only relevant for instructions that access memory and
+      // alloca.
+      if (!I.mayReadOrWriteMemory() && !dyn_cast<AllocaInst>(&I))
         continue;
 
       if (MemParallelLoopAccess) {
@@ -963,6 +964,11 @@ static void PropagateCallSiteMetadata(CallBase &CB, Function::iterator FStart,
         I.setMetadata(LLVMContext::MD_access_group, uniteAccessGroups(
             I.getMetadata(LLVMContext::MD_access_group), AccessGroup));
 
+      // The rest of the metadata is only relevant for instructions accessing
+      // memory.
+      if (!I.mayReadOrWriteMemory())
+        continue;
+
       if (AliasScope)
         I.setMetadata(LLVMContext::MD_alias_scope, MDNode::concatenate(
             I.getMetadata(LLVMContext::MD_alias_scope), AliasScope));

>From 2373b184b0433e10a2570105c9a74e5f1d645628 Mon Sep 17 00:00:00 2001
From: Julius Ikkala <julius.ikkala at tuni.fi>
Date: Mon, 9 Feb 2026 18:34:09 +0200
Subject: [PATCH 03/10] Allow alloca if access.group metadata is present

---
 llvm/lib/Analysis/LoopInfo.cpp | 49 +++++++++++++++++-----------------
 1 file changed, 25 insertions(+), 24 deletions(-)

diff --git a/llvm/lib/Analysis/LoopInfo.cpp b/llvm/lib/Analysis/LoopInfo.cpp
index d5203a20c8c6c..5db4f0771d5bd 100644
--- a/llvm/lib/Analysis/LoopInfo.cpp
+++ b/llvm/lib/Analysis/LoopInfo.cpp
@@ -591,38 +591,39 @@ bool Loop::isAnnotatedParallel() const {
       if (!I.mayReadOrWriteMemory())
         continue;
 
+      auto ContainsAccessGroup = [&ParallelAccessGroups](MDNode *AG) -> bool {
+        if (AG->getNumOperands() == 0) {
+          assert(isValidAsAccessGroup(AG) && "Item must be an access group");
+          return ParallelAccessGroups.count(AG);
+        }
+
+        for (const MDOperand &AccessListItem : AG->operands()) {
+          MDNode *AccGroup = cast<MDNode>(AccessListItem.get());
+          assert(isValidAsAccessGroup(AccGroup) &&
+                 "List item must be an access group");
+          if (ParallelAccessGroups.count(AccGroup))
+            return true;
+        }
+        return false;
+      };
+
       // If the loop contains a store instruction into an alloca that is outside
       // of the loop, it is possible that the alloca was initially related to a
       // loop-local variable but got hoisted outside during e.g. inlining or
-      // some other parallel-loop-unaware pass.
-      //
-      // TODO: Allow metadata to mark 'alloca' as safe to vectorize and
-      // separately handle such allocas in the loop vectorizer, either by
-      // sinking the `alloca` into the loop body or by otherwise "privatizing"
-      // the allocation for each vector lane.
+      // some other parallel-loop-unaware pass. However, if the alloca itself
+      // has been marked with the access group metadata, this usage has to be
+      // assumed to be valid.
       if (StoreInst *SI = dyn_cast<StoreInst>(&I)) {
         AllocaInst *AI = findAllocaForValue(SI->getPointerOperand());
-        if (AI && !contains(AI))
-          return false;
+        if (AI) {
+          MDNode *AccessGroup = AI->getMetadata(LLVMContext::MD_access_group);
+          if (AI && !contains(AI) &&
+                  (!AccessGroup || !ContainsAccessGroup(AccessGroup)))
+            return false;
+        }
       }
 
       if (MDNode *AccessGroup = I.getMetadata(LLVMContext::MD_access_group)) {
-        auto ContainsAccessGroup = [&ParallelAccessGroups](MDNode *AG) -> bool {
-          if (AG->getNumOperands() == 0) {
-            assert(isValidAsAccessGroup(AG) && "Item must be an access group");
-            return ParallelAccessGroups.count(AG);
-          }
-
-          for (const MDOperand &AccessListItem : AG->operands()) {
-            MDNode *AccGroup = cast<MDNode>(AccessListItem.get());
-            assert(isValidAsAccessGroup(AccGroup) &&
-                   "List item must be an access group");
-            if (ParallelAccessGroups.count(AccGroup))
-              return true;
-          }
-          return false;
-        };
-
         if (ContainsAccessGroup(AccessGroup))
           continue;
       }

>From b34a9d72f21e72ca846bb094f585308935c7eec4 Mon Sep 17 00:00:00 2001
From: Julius Ikkala <julius.ikkala at tuni.fi>
Date: Tue, 10 Feb 2026 18:09:18 +0200
Subject: [PATCH 04/10] Make LAA only recognize loads&stores to alloca in
 parallel loops

---
 llvm/include/llvm/Analysis/LoopInfo.h    |  4 ++
 llvm/lib/Analysis/LoopAccessAnalysis.cpp | 44 ++++++++++---
 llvm/lib/Analysis/LoopInfo.cpp           | 80 ++++++++++--------------
 3 files changed, 75 insertions(+), 53 deletions(-)

diff --git a/llvm/include/llvm/Analysis/LoopInfo.h b/llvm/include/llvm/Analysis/LoopInfo.h
index 0ecb1141dc1be..4f8b31d11b4ca 100644
--- a/llvm/include/llvm/Analysis/LoopInfo.h
+++ b/llvm/include/llvm/Analysis/LoopInfo.h
@@ -341,6 +341,10 @@ class LLVM_ABI Loop : public LoopBase<BasicBlock, Loop> {
   /// iterations.
   bool isAnnotatedParallel() const;
 
+  /// Returns true if the loop's parallel_accesses metadata contains the given
+  /// access group.
+  bool containsAccessGroup(MDNode* AG) const;
+
   /// Return the llvm.loop loop id metadata node for this loop if it is present.
   ///
   /// If this loop contains the same llvm.loop metadata on each branch to the
diff --git a/llvm/lib/Analysis/LoopAccessAnalysis.cpp b/llvm/lib/Analysis/LoopAccessAnalysis.cpp
index eae645ab84fff..a4d3016a0fb2a 100644
--- a/llvm/lib/Analysis/LoopAccessAnalysis.cpp
+++ b/llvm/lib/Analysis/LoopAccessAnalysis.cpp
@@ -2524,6 +2524,12 @@ bool LoopAccessInfo::analyzeLoop(AAResults *AA, const LoopInfo *LI,
 
   const bool IsAnnotatedParallel = TheLoop->isAnnotatedParallel();
 
+  if (IsAnnotatedParallel) {
+    LLVM_DEBUG(
+        dbgs() << "LAA: A loop annotated parallel, ignore memory dependency "
+               << "checks.\n");
+  }
+
   const bool EnableMemAccessVersioningOfLoop =
       EnableMemAccessVersioning &&
       !TheLoop->getHeader()->getParent()->hasOptSize();
@@ -2593,6 +2599,29 @@ bool LoopAccessInfo::analyzeLoop(AAResults *AA, const LoopInfo *LI,
           HasComplexMemInst = true;
           continue;
         }
+
+        // For parallel loops, we only want to analyze alloca-based addresses.
+        // If the loop accesses an alloca that is outside of the loop, it is
+        // possible that the alloca was initially related to a loop-local
+        // variable but got hoisted outside during e.g. inlining or some other
+        // parallel-loop-unaware pass. However, if the alloca itself has been
+        // marked with the access group metadata, this usage has to be assumed
+        // to be valid.
+        if (IsAnnotatedParallel) {
+          AllocaInst *AI = findAllocaForValue(Ld->getPointerOperand());
+          // Not accessing alloca, or the alloca is inside the loop, so no race
+          // condition there.
+          if (!AI || TheLoop->contains(AI))
+            continue;
+
+          MDNode *AG = AI->getMetadata(LLVMContext::MD_access_group);
+          // Access group is annotated properly for this loop, assume no race
+          // condition.
+          if (AG && TheLoop->containsAccessGroup(AG))
+            continue;
+
+          // Otherwise, proceed handling the load as if the loop isn't parallel.
+        }
         NumLoads++;
         Loads.push_back(Ld);
         DepChecker->addAccess(Ld);
@@ -2617,6 +2646,14 @@ bool LoopAccessInfo::analyzeLoop(AAResults *AA, const LoopInfo *LI,
           HasComplexMemInst = true;
           continue;
         }
+        if (IsAnnotatedParallel) {
+          AllocaInst *AI = findAllocaForValue(St->getPointerOperand());
+          if (!AI || TheLoop->contains(AI))
+            continue;
+          MDNode *AG = AI->getMetadata(LLVMContext::MD_access_group);
+          if (AG && TheLoop->containsAccessGroup(AG))
+            continue;
+        }
         NumStores++;
         Stores.push_back(St);
         DepChecker->addAccess(St);
@@ -2685,13 +2722,6 @@ bool LoopAccessInfo::analyzeLoop(AAResults *AA, const LoopInfo *LI,
     }
   }
 
-  if (IsAnnotatedParallel) {
-    LLVM_DEBUG(
-        dbgs() << "LAA: A loop annotated parallel, ignore memory dependency "
-               << "checks.\n");
-    return true;
-  }
-
   for (LoadInst *LD : Loads) {
     Value *Ptr = LD->getPointerOperand();
     // If we did *not* see this pointer before, insert it to the
diff --git a/llvm/lib/Analysis/LoopInfo.cpp b/llvm/lib/Analysis/LoopInfo.cpp
index 5db4f0771d5bd..1012001a4cc87 100644
--- a/llvm/lib/Analysis/LoopInfo.cpp
+++ b/llvm/lib/Analysis/LoopInfo.cpp
@@ -568,19 +568,6 @@ bool Loop::isAnnotatedParallel() const {
   if (!DesiredLoopIdMetadata)
     return false;
 
-  MDNode *ParallelAccesses =
-      findOptionMDForLoop(this, "llvm.loop.parallel_accesses");
-  SmallPtrSet<MDNode *, 4>
-      ParallelAccessGroups; // For scalable 'contains' check.
-  if (ParallelAccesses) {
-    for (const MDOperand &MD : drop_begin(ParallelAccesses->operands())) {
-      MDNode *AccGroup = cast<MDNode>(MD.get());
-      assert(isValidAsAccessGroup(AccGroup) &&
-             "List item must be an access group");
-      ParallelAccessGroups.insert(AccGroup);
-    }
-  }
-
   // The loop branch contains the parallel loop metadata. In order to ensure
   // that any parallel-loop-unaware optimization pass hasn't added loop-carried
   // dependencies (thus converted the loop back to a sequential loop), check
@@ -591,40 +578,8 @@ bool Loop::isAnnotatedParallel() const {
       if (!I.mayReadOrWriteMemory())
         continue;
 
-      auto ContainsAccessGroup = [&ParallelAccessGroups](MDNode *AG) -> bool {
-        if (AG->getNumOperands() == 0) {
-          assert(isValidAsAccessGroup(AG) && "Item must be an access group");
-          return ParallelAccessGroups.count(AG);
-        }
-
-        for (const MDOperand &AccessListItem : AG->operands()) {
-          MDNode *AccGroup = cast<MDNode>(AccessListItem.get());
-          assert(isValidAsAccessGroup(AccGroup) &&
-                 "List item must be an access group");
-          if (ParallelAccessGroups.count(AccGroup))
-            return true;
-        }
-        return false;
-      };
-
-      // If the loop contains a store instruction into an alloca that is outside
-      // of the loop, it is possible that the alloca was initially related to a
-      // loop-local variable but got hoisted outside during e.g. inlining or
-      // some other parallel-loop-unaware pass. However, if the alloca itself
-      // has been marked with the access group metadata, this usage has to be
-      // assumed to be valid.
-      if (StoreInst *SI = dyn_cast<StoreInst>(&I)) {
-        AllocaInst *AI = findAllocaForValue(SI->getPointerOperand());
-        if (AI) {
-          MDNode *AccessGroup = AI->getMetadata(LLVMContext::MD_access_group);
-          if (AI && !contains(AI) &&
-                  (!AccessGroup || !ContainsAccessGroup(AccessGroup)))
-            return false;
-        }
-      }
-
       if (MDNode *AccessGroup = I.getMetadata(LLVMContext::MD_access_group)) {
-        if (ContainsAccessGroup(AccessGroup))
+        if (containsAccessGroup(AccessGroup))
           continue;
       }
 
@@ -645,6 +600,39 @@ bool Loop::isAnnotatedParallel() const {
   return true;
 }
 
+bool Loop::containsAccessGroup(MDNode* AG) const
+{
+  MDNode *ParallelAccesses =
+      findOptionMDForLoop(this, "llvm.loop.parallel_accesses");
+  auto MetadataContainsGroup = [ParallelAccesses](MDNode *AccGroup) -> bool {
+    if (ParallelAccesses) {
+      for (const MDOperand &MD : drop_begin(ParallelAccesses->operands())) {
+        MDNode *Group = cast<MDNode>(MD.get());
+        assert(isValidAsAccessGroup(Group) &&
+               "List item must be an access group");
+
+        if (AccGroup == Group)
+          return true;
+      }
+    }
+    return false;
+  };
+
+  if (AG->getNumOperands() == 0) {
+    assert(isValidAsAccessGroup(AG) && "Item must be an access group");
+    return MetadataContainsGroup(AG);
+  }
+
+  for (const MDOperand &AccessListItem : AG->operands()) {
+    MDNode *AccGroup = cast<MDNode>(AccessListItem.get());
+    assert(isValidAsAccessGroup(AccGroup) &&
+           "List item must be an access group");
+    if (MetadataContainsGroup(AccGroup))
+      return true;
+  }
+  return false;
+}
+
 DebugLoc Loop::getStartLoc() const { return getLocRange().getStart(); }
 
 Loop::LocRange Loop::getLocRange() const {

>From 812386d02ab4d4bb557c0a9bd16bf927620aba32 Mon Sep 17 00:00:00 2001
From: Julius Ikkala <julius.ikkala at tuni.fi>
Date: Thu, 12 Feb 2026 13:47:58 +0200
Subject: [PATCH 05/10] Revert "Make LAA only recognize loads&stores to alloca
 in parallel loops"

This reverts commit 4888a4c9660e557d6b797778b667286ca836f75c.
---
 llvm/include/llvm/Analysis/LoopInfo.h    |  4 --
 llvm/lib/Analysis/LoopAccessAnalysis.cpp | 44 +++----------
 llvm/lib/Analysis/LoopInfo.cpp           | 80 ++++++++++++++----------
 3 files changed, 53 insertions(+), 75 deletions(-)

diff --git a/llvm/include/llvm/Analysis/LoopInfo.h b/llvm/include/llvm/Analysis/LoopInfo.h
index 4f8b31d11b4ca..0ecb1141dc1be 100644
--- a/llvm/include/llvm/Analysis/LoopInfo.h
+++ b/llvm/include/llvm/Analysis/LoopInfo.h
@@ -341,10 +341,6 @@ class LLVM_ABI Loop : public LoopBase<BasicBlock, Loop> {
   /// iterations.
   bool isAnnotatedParallel() const;
 
-  /// Returns true if the loop's parallel_accesses metadata contains the given
-  /// access group.
-  bool containsAccessGroup(MDNode* AG) const;
-
   /// Return the llvm.loop loop id metadata node for this loop if it is present.
   ///
   /// If this loop contains the same llvm.loop metadata on each branch to the
diff --git a/llvm/lib/Analysis/LoopAccessAnalysis.cpp b/llvm/lib/Analysis/LoopAccessAnalysis.cpp
index a4d3016a0fb2a..eae645ab84fff 100644
--- a/llvm/lib/Analysis/LoopAccessAnalysis.cpp
+++ b/llvm/lib/Analysis/LoopAccessAnalysis.cpp
@@ -2524,12 +2524,6 @@ bool LoopAccessInfo::analyzeLoop(AAResults *AA, const LoopInfo *LI,
 
   const bool IsAnnotatedParallel = TheLoop->isAnnotatedParallel();
 
-  if (IsAnnotatedParallel) {
-    LLVM_DEBUG(
-        dbgs() << "LAA: A loop annotated parallel, ignore memory dependency "
-               << "checks.\n");
-  }
-
   const bool EnableMemAccessVersioningOfLoop =
       EnableMemAccessVersioning &&
       !TheLoop->getHeader()->getParent()->hasOptSize();
@@ -2599,29 +2593,6 @@ bool LoopAccessInfo::analyzeLoop(AAResults *AA, const LoopInfo *LI,
           HasComplexMemInst = true;
           continue;
         }
-
-        // For parallel loops, we only want to analyze alloca-based addresses.
-        // If the loop accesses an alloca that is outside of the loop, it is
-        // possible that the alloca was initially related to a loop-local
-        // variable but got hoisted outside during e.g. inlining or some other
-        // parallel-loop-unaware pass. However, if the alloca itself has been
-        // marked with the access group metadata, this usage has to be assumed
-        // to be valid.
-        if (IsAnnotatedParallel) {
-          AllocaInst *AI = findAllocaForValue(Ld->getPointerOperand());
-          // Not accessing alloca, or the alloca is inside the loop, so no race
-          // condition there.
-          if (!AI || TheLoop->contains(AI))
-            continue;
-
-          MDNode *AG = AI->getMetadata(LLVMContext::MD_access_group);
-          // Access group is annotated properly for this loop, assume no race
-          // condition.
-          if (AG && TheLoop->containsAccessGroup(AG))
-            continue;
-
-          // Otherwise, proceed handling the load as if the loop isn't parallel.
-        }
         NumLoads++;
         Loads.push_back(Ld);
         DepChecker->addAccess(Ld);
@@ -2646,14 +2617,6 @@ bool LoopAccessInfo::analyzeLoop(AAResults *AA, const LoopInfo *LI,
           HasComplexMemInst = true;
           continue;
         }
-        if (IsAnnotatedParallel) {
-          AllocaInst *AI = findAllocaForValue(St->getPointerOperand());
-          if (!AI || TheLoop->contains(AI))
-            continue;
-          MDNode *AG = AI->getMetadata(LLVMContext::MD_access_group);
-          if (AG && TheLoop->containsAccessGroup(AG))
-            continue;
-        }
         NumStores++;
         Stores.push_back(St);
         DepChecker->addAccess(St);
@@ -2722,6 +2685,13 @@ bool LoopAccessInfo::analyzeLoop(AAResults *AA, const LoopInfo *LI,
     }
   }
 
+  if (IsAnnotatedParallel) {
+    LLVM_DEBUG(
+        dbgs() << "LAA: A loop annotated parallel, ignore memory dependency "
+               << "checks.\n");
+    return true;
+  }
+
   for (LoadInst *LD : Loads) {
     Value *Ptr = LD->getPointerOperand();
     // If we did *not* see this pointer before, insert it to the
diff --git a/llvm/lib/Analysis/LoopInfo.cpp b/llvm/lib/Analysis/LoopInfo.cpp
index 1012001a4cc87..5db4f0771d5bd 100644
--- a/llvm/lib/Analysis/LoopInfo.cpp
+++ b/llvm/lib/Analysis/LoopInfo.cpp
@@ -568,6 +568,19 @@ bool Loop::isAnnotatedParallel() const {
   if (!DesiredLoopIdMetadata)
     return false;
 
+  MDNode *ParallelAccesses =
+      findOptionMDForLoop(this, "llvm.loop.parallel_accesses");
+  SmallPtrSet<MDNode *, 4>
+      ParallelAccessGroups; // For scalable 'contains' check.
+  if (ParallelAccesses) {
+    for (const MDOperand &MD : drop_begin(ParallelAccesses->operands())) {
+      MDNode *AccGroup = cast<MDNode>(MD.get());
+      assert(isValidAsAccessGroup(AccGroup) &&
+             "List item must be an access group");
+      ParallelAccessGroups.insert(AccGroup);
+    }
+  }
+
   // The loop branch contains the parallel loop metadata. In order to ensure
   // that any parallel-loop-unaware optimization pass hasn't added loop-carried
   // dependencies (thus converted the loop back to a sequential loop), check
@@ -578,8 +591,40 @@ bool Loop::isAnnotatedParallel() const {
       if (!I.mayReadOrWriteMemory())
         continue;
 
+      auto ContainsAccessGroup = [&ParallelAccessGroups](MDNode *AG) -> bool {
+        if (AG->getNumOperands() == 0) {
+          assert(isValidAsAccessGroup(AG) && "Item must be an access group");
+          return ParallelAccessGroups.count(AG);
+        }
+
+        for (const MDOperand &AccessListItem : AG->operands()) {
+          MDNode *AccGroup = cast<MDNode>(AccessListItem.get());
+          assert(isValidAsAccessGroup(AccGroup) &&
+                 "List item must be an access group");
+          if (ParallelAccessGroups.count(AccGroup))
+            return true;
+        }
+        return false;
+      };
+
+      // If the loop contains a store instruction into an alloca that is outside
+      // of the loop, it is possible that the alloca was initially related to a
+      // loop-local variable but got hoisted outside during e.g. inlining or
+      // some other parallel-loop-unaware pass. However, if the alloca itself
+      // has been marked with the access group metadata, this usage has to be
+      // assumed to be valid.
+      if (StoreInst *SI = dyn_cast<StoreInst>(&I)) {
+        AllocaInst *AI = findAllocaForValue(SI->getPointerOperand());
+        if (AI) {
+          MDNode *AccessGroup = AI->getMetadata(LLVMContext::MD_access_group);
+          if (AI && !contains(AI) &&
+                  (!AccessGroup || !ContainsAccessGroup(AccessGroup)))
+            return false;
+        }
+      }
+
       if (MDNode *AccessGroup = I.getMetadata(LLVMContext::MD_access_group)) {
-        if (containsAccessGroup(AccessGroup))
+        if (ContainsAccessGroup(AccessGroup))
           continue;
       }
 
@@ -600,39 +645,6 @@ bool Loop::isAnnotatedParallel() const {
   return true;
 }
 
-bool Loop::containsAccessGroup(MDNode* AG) const
-{
-  MDNode *ParallelAccesses =
-      findOptionMDForLoop(this, "llvm.loop.parallel_accesses");
-  auto MetadataContainsGroup = [ParallelAccesses](MDNode *AccGroup) -> bool {
-    if (ParallelAccesses) {
-      for (const MDOperand &MD : drop_begin(ParallelAccesses->operands())) {
-        MDNode *Group = cast<MDNode>(MD.get());
-        assert(isValidAsAccessGroup(Group) &&
-               "List item must be an access group");
-
-        if (AccGroup == Group)
-          return true;
-      }
-    }
-    return false;
-  };
-
-  if (AG->getNumOperands() == 0) {
-    assert(isValidAsAccessGroup(AG) && "Item must be an access group");
-    return MetadataContainsGroup(AG);
-  }
-
-  for (const MDOperand &AccessListItem : AG->operands()) {
-    MDNode *AccGroup = cast<MDNode>(AccessListItem.get());
-    assert(isValidAsAccessGroup(AccGroup) &&
-           "List item must be an access group");
-    if (MetadataContainsGroup(AccGroup))
-      return true;
-  }
-  return false;
-}
-
 DebugLoc Loop::getStartLoc() const { return getLocRange().getStart(); }
 
 Loop::LocRange Loop::getLocRange() const {

>From 3ffe6c6786f5300ea1c0cdfbead3500827187de9 Mon Sep 17 00:00:00 2001
From: Julius Ikkala <julius.ikkala at tuni.fi>
Date: Fri, 13 Feb 2026 16:52:41 +0200
Subject: [PATCH 06/10] Revert inliner marking alloca's with llvm.access.group

---
 llvm/lib/Transforms/Utils/InlineFunction.cpp | 10 ++--------
 1 file changed, 2 insertions(+), 8 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/InlineFunction.cpp b/llvm/lib/Transforms/Utils/InlineFunction.cpp
index 896802b43ef43..3230b306f17d1 100644
--- a/llvm/lib/Transforms/Utils/InlineFunction.cpp
+++ b/llvm/lib/Transforms/Utils/InlineFunction.cpp
@@ -946,9 +946,8 @@ static void PropagateCallSiteMetadata(CallBase &CB, Function::iterator FStart,
 
   for (BasicBlock &BB : make_range(FStart, FEnd)) {
     for (Instruction &I : BB) {
-      // This metadata is only relevant for instructions that access memory and
-      // alloca.
-      if (!I.mayReadOrWriteMemory() && !dyn_cast<AllocaInst>(&I))
+      // This metadata is only relevant for instructions that access memory.
+      if (!I.mayReadOrWriteMemory())
         continue;
 
       if (MemParallelLoopAccess) {
@@ -964,11 +963,6 @@ static void PropagateCallSiteMetadata(CallBase &CB, Function::iterator FStart,
         I.setMetadata(LLVMContext::MD_access_group, uniteAccessGroups(
             I.getMetadata(LLVMContext::MD_access_group), AccessGroup));
 
-      // The rest of the metadata is only relevant for instructions accessing
-      // memory.
-      if (!I.mayReadOrWriteMemory())
-        continue;
-
       if (AliasScope)
         I.setMetadata(LLVMContext::MD_alias_scope, MDNode::concatenate(
             I.getMetadata(LLVMContext::MD_alias_scope), AliasScope));

>From 605121050fe35977d9fa44f1fbd3222e63911a84 Mon Sep 17 00:00:00 2001
From: Julius Ikkala <julius.ikkala at tuni.fi>
Date: Fri, 13 Feb 2026 17:49:30 +0200
Subject: [PATCH 07/10] Update LangRef to add llvm.access.group for alloca

---
 llvm/docs/LangRef.rst | 24 ++++++++++++------------
 1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 00a4a00c5bf95..646ff2daf3d64 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -8176,10 +8176,10 @@ as it is not affected by the ``llvm.loop.disable_nonforced`` metadata.
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 ``llvm.access.group`` metadata can be attached to any instruction that
-potentially accesses memory. It can point to a single distinct metadata
-node, which we call access group. This node represents all memory access
-instructions referring to it via ``llvm.access.group``. When an
-instruction belongs to multiple access groups, it can also point to a
+potentially accesses or allocates memory. It can point to a single distinct
+metadata node, which we call access group. This node represents all memory
+access or allocation instructions referring to it via ``llvm.access.group``.
+When an instruction belongs to multiple access groups, it can also point to a
 list of accesses groups, illustrated by the following example.
 
 .. code-block:: llvm
@@ -8201,8 +8201,8 @@ situation that the content must be updated which, because metadata is
 immutable by design, would required finding and updating all references
 to the access group node.
 
-The access group can be used to refer to a memory access instruction
-without pointing to it directly (which is not possible in global
+The access group can be used to refer to a memory access or allocation
+instruction without pointing to it directly (which is not possible in global
 metadata). Currently, the only metadata making use of it is
 ``llvm.loop.parallel_accesses``.
 
@@ -8223,12 +8223,12 @@ this loop. Instructions that belong to multiple access groups are
 considered having this property if at least one of the access groups
 matches the ``llvm.loop.parallel_accesses`` list.
 
-If all memory-accessing instructions in a loop have
-``llvm.access.group`` metadata that each refer to one of the access
-groups of a loop's ``llvm.loop.parallel_accesses`` metadata, then the
-loop has no loop carried memory dependencies and is considered to be a
-parallel loop. If there is a loop-carried dependency, the behavior is
-undefined.
+If all memory-accessing instructions in a loop and all ``alloca`` instructions
+whose address range is being written to by instructions in the loop have
+``llvm.access.group`` metadata referring to one of the access groups of a loop's
+``llvm.loop.parallel_accesses`` metadata, then the loop has no loop carried
+memory dependencies and is considered to be a parallel loop. If there is a
+loop-carried dependency, the behavior is undefined.
 
 Note that if not all memory access instructions belong to an access
 group referred to by ``llvm.loop.parallel_accesses``, then the loop must

>From 10d75ab168a4ea38267ea0685623cedee6f73e43 Mon Sep 17 00:00:00 2001
From: Julius Ikkala <julius.ikkala at tuni.fi>
Date: Fri, 13 Feb 2026 17:54:15 +0200
Subject: [PATCH 08/10] Fix formatting

---
 llvm/lib/Analysis/LoopInfo.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Analysis/LoopInfo.cpp b/llvm/lib/Analysis/LoopInfo.cpp
index 5db4f0771d5bd..4bad9381f4b38 100644
--- a/llvm/lib/Analysis/LoopInfo.cpp
+++ b/llvm/lib/Analysis/LoopInfo.cpp
@@ -618,7 +618,7 @@ bool Loop::isAnnotatedParallel() const {
         if (AI) {
           MDNode *AccessGroup = AI->getMetadata(LLVMContext::MD_access_group);
           if (AI && !contains(AI) &&
-                  (!AccessGroup || !ContainsAccessGroup(AccessGroup)))
+              (!AccessGroup || !ContainsAccessGroup(AccessGroup)))
             return false;
         }
       }

>From d4f763ee16ce5c721ebbea6d99fa5665e8d5605d Mon Sep 17 00:00:00 2001
From: Julius Ikkala <julius.ikkala at gmail.com>
Date: Mon, 16 Feb 2026 17:13:26 +0200
Subject: [PATCH 09/10] Add release note

---
 llvm/docs/ReleaseNotes.md | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index 257d962ba8941..6d82d6969febb 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -69,6 +69,12 @@ Changes to the LLVM IR
 * The `"nooutline"` attribute is now writen as `nooutline`. Existing IR and
   bitcode will be automatically updated.
 
+* To be considered parallel, [loops with `llvm.loop.parallel_accesses` metadata
+  now require corresponding `llvm.access.group` metadata to be present on all
+  `alloca` instructions whose address range is being written to in the loop.](https://discourse.llvm.org/t/semantics-of-llvm-loop-parallel-accesses-and-interaction-with-alloca/89714)
+  If this metadata is not present, such loops are no longer considered parallel
+  and memory dependency checks are not skipped.
+
 Changes to LLVM infrastructure
 ------------------------------
 

>From ad201b416c46962e23432458fd43b7d239e975ed Mon Sep 17 00:00:00 2001
From: Julius Ikkala <julius.ikkala at gmail.com>
Date: Mon, 16 Feb 2026 19:26:37 +0200
Subject: [PATCH 10/10] Clarify LangRef on llvm.loop.parallel_accesses

---
 llvm/docs/LangRef.rst | 64 +++++++++++++++++++++++++++++--------------
 1 file changed, 43 insertions(+), 21 deletions(-)

diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 646ff2daf3d64..b2364c22423df 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -8209,34 +8209,56 @@ metadata). Currently, the only metadata making use of it is
 '``llvm.loop.parallel_accesses``' Metadata
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-The ``llvm.loop.parallel_accesses`` metadata refers to one or more
-access group metadata nodes (see ``llvm.access.group``). It denotes that
-no loop-carried memory dependence exist between it and other instructions
-in the loop with this metadata.
+The ``llvm.loop.parallel_accesses`` metadata is used to explicitly declare a
+loop as "trivially parallel", indicating that there are no memory dependencies
+between iterations. If a loop has this metadata but has memory dependencies,
+the behavior is undefined.
+
+``llvm.loop.parallel_accesses`` refers to one or more access group metadata
+nodes (see ``llvm.access.group``). In a loop with this metadata, instructions
+may have the ``llvm.access.group`` metadata to denote that no loop-carried
+memory dependence exist between those instructions, as long as the access group
+is listed in ``llvm.loop.parallel_accesses``.
 
 Let ``m1`` and ``m2`` be two instructions that both have the
-``llvm.access.group`` metadata to the access group ``g1``, respectively
-``g2`` (which might be identical). If a loop contains both access groups
-in its ``llvm.loop.parallel_accesses`` metadata, then the compiler can
+``llvm.access.group`` metadata to the access groups ``g1`` and ``g2``
+respectively (the groups can be identical). If a loop contains both access
+groups in its ``llvm.loop.parallel_accesses`` metadata, then the compiler can
 assume that there is no dependency between ``m1`` and ``m2`` carried by
 this loop. Instructions that belong to multiple access groups are
 considered having this property if at least one of the access groups
 matches the ``llvm.loop.parallel_accesses`` list.
 
-If all memory-accessing instructions in a loop and all ``alloca`` instructions
-whose address range is being written to by instructions in the loop have
-``llvm.access.group`` metadata referring to one of the access groups of a loop's
-``llvm.loop.parallel_accesses`` metadata, then the loop has no loop carried
-memory dependencies and is considered to be a parallel loop. If there is a
-loop-carried dependency, the behavior is undefined.
-
-Note that if not all memory access instructions belong to an access
-group referred to by ``llvm.loop.parallel_accesses``, then the loop must
-not be considered trivially parallel. Additional
-memory dependence analysis is required to make that determination. As a
-fail-safe mechanism, this causes loops that were originally parallel to be considered
-sequential (if optimization passes that are unaware of the parallel semantics
-insert new memory instructions into the loop body).
+A loop is declared to be trivially parallel (as in, there are no memory
+dependencies between loop iterations) if it has the
+``llvm.loop.parallel_accesses`` metadata with referring to a set of access
+groups ``G`` and fulfills the following conditions:
+
+- All memory-accessing instructions in the loop must have ``llvm.access.group``
+  metadata referring to at least one access group in ``G``.
+- If the loop has instructions that write to memory allocated via ``alloca``,
+  the corresponding ``alloca`` instruction must have the ``llvm.access.group``
+  metadata.
+
+If there is a loop-carried memory dependency in spite of the metadata, the
+behavior is undefined. If the above conditions are not fulfilled, the loop must
+not be considered as trivially parallel without further memory dependence
+analysis.
+
+These conditions exist as a fail-safe mechanism to cause loops that were
+originally parallel to be considered sequential when optimization passes that
+are unaware of the parallel semantics perform transformations or insert new
+instructions into the loop body. Note that even if the loop is no longer
+considered trivially parallel, it may still be vectorizable. It must be treated
+as if this metadata was not present.
+
+For example, if an unaware pass adds a new memory-accessing instruction into the
+loop body without being aware of the parallel semantics, that instruction does
+not have the corresponding ``llvm.access.group`` metadata, thereby demoting the
+loop into a sequential one. Another example where the fail-safe triggers is
+when a pass hoists an ``alloca`` instruction outside of the loop body. Without
+the fail-safe, this would cause the same allocation to be shared across
+iterations, introducing race conditions.
 
 Example of a loop that is considered parallel due to its correct use of
 both ``llvm.access.group`` and ``llvm.loop.parallel_accesses``



More information about the llvm-commits mailing list