[llvm] [DA] Fix for missed anti-dependence due to iteration-local alias scopes (PR #223299)
via llvm-commits
llvm-commits at lists.llvm.org
Sun Sep 13 21:35:05 PDT 2026
https://github.com/sp4s-s updated https://github.com/llvm/llvm-project/pull/223299
>From 01156d7739cbdcaa6b17e41f10575b3bc63398dc Mon Sep 17 00:00:00 2001
From: sp4s-s <spass.ni42+github at gmail.com>
Date: Mon, 14 Sep 2026 10:04:33 +0530
Subject: [PATCH 1/2] [DA] Ignore iteration-local alias scopes in
cross-iteration queries
noalias.scope.decl inside a loop is only valid for that iteration.
DA was still using those !alias.scope / !noalias tags under
enableCrossIterationMode(), so ScopedNoAliasAA could return NoAlias
and hide a real anti dependence.
Drop the same scopes LAA already drops.
Fixes #223173
---
.../llvm/Analysis/DependenceAnalysis.h | 7 ++
llvm/lib/Analysis/DependenceAnalysis.cpp | 72 +++++++++++++++++--
2 files changed, 72 insertions(+), 7 deletions(-)
diff --git a/llvm/include/llvm/Analysis/DependenceAnalysis.h b/llvm/include/llvm/Analysis/DependenceAnalysis.h
index 338bbef674ba3..69d6c08564d32 100644
--- a/llvm/include/llvm/Analysis/DependenceAnalysis.h
+++ b/llvm/include/llvm/Analysis/DependenceAnalysis.h
@@ -40,6 +40,7 @@
#define LLVM_ANALYSIS_DEPENDENCEANALYSIS_H
#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/Analysis/ScalarEvolution.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/PassManager.h"
@@ -51,6 +52,7 @@ class AAResults;
template <typename T> class ArrayRef;
class Loop;
class LoopInfo;
+class MDNode;
class SCEVConstant;
class raw_ostream;
@@ -331,6 +333,11 @@ class DependenceInfo {
LoopInfo *LI;
Function *F;
+ SmallPtrSet<const MDNode *, 4> LoopAliasScopes;
+ bool LoopAliasScopesPopulated = false;
+
+ const SmallPtrSetImpl<const MDNode *> &getLoopAliasScopes();
+
/// Subscript - This private struct represents a pair of subscripts from
/// a pair of potentially multi-dimensional array references. We use a
/// vector of them to guide subscript partitioning.
diff --git a/llvm/lib/Analysis/DependenceAnalysis.cpp b/llvm/lib/Analysis/DependenceAnalysis.cpp
index 7b0711c11429c..3139ac12f0faf 100644
--- a/llvm/lib/Analysis/DependenceAnalysis.cpp
+++ b/llvm/lib/Analysis/DependenceAnalysis.cpp
@@ -53,6 +53,7 @@
#include "llvm/Analysis/ScalarEvolutionExpressions.h"
#include "llvm/Analysis/ValueTracking.h"
#include "llvm/IR/InstIterator.h"
+#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/Module.h"
#include "llvm/InitializePasses.h"
#include "llvm/Support/CommandLine.h"
@@ -560,20 +561,48 @@ void Dependence::dumpImp(raw_ostream &OS, bool IsSameSD) const {
OS << "]";
}
-// Returns NoAlias/MayAliass/MustAlias for two memory locations based upon their
-// underlaying objects. If LocA and LocB are known to not alias (for any reason:
-// tbaa, non-overlapping regions etc), then it is known there is no dependecy.
+/// Drop alias scopes that are only valid within a single loop iteration.
+static MDNode *
+adjustAliasScopeList(MDNode *ScopeList,
+ const SmallPtrSetImpl<const MDNode *> &LoopAliasScopes) {
+ if (!ScopeList || LoopAliasScopes.empty())
+ return ScopeList;
+
+ // For the sake of simplicity, drop the whole scope list if any scope is
+ // iteration-local.
+ if (any_of(ScopeList->operands(), [&](Metadata *Scope) {
+ return LoopAliasScopes.contains(cast<MDNode>(Scope));
+ }))
+ return nullptr;
+
+ return ScopeList;
+}
+
+// Returns NoAlias/MayAlias/MustAlias for two memory locations based upon their
+// underlying objects. If LocA and LocB are known to not alias (for any reason:
+// tbaa, non-overlapping regions etc), then it is known there is no dependency.
// Otherwise the underlying objects are checked to see if they point to
// different identifiable objects.
-static AliasResult underlyingObjectsAlias(AAResults *AA, const DataLayout &DL,
- const MemoryLocation &LocA,
- const MemoryLocation &LocB) {
+static AliasResult
+underlyingObjectsAlias(AAResults *AA, const DataLayout &DL,
+ const MemoryLocation &LocA, const MemoryLocation &LocB,
+ const SmallPtrSetImpl<const MDNode *> *LoopAliasScopes) {
// Check the original locations (minus size) for noalias, which can happen for
// tbaa, incompatible underlying object locations, etc.
MemoryLocation LocAS =
MemoryLocation::getBeforeOrAfter(LocA.Ptr, LocA.AATags);
MemoryLocation LocBS =
MemoryLocation::getBeforeOrAfter(LocB.Ptr, LocB.AATags);
+ if (LoopAliasScopes) {
+ LocAS.AATags.Scope =
+ adjustAliasScopeList(LocAS.AATags.Scope, *LoopAliasScopes);
+ LocAS.AATags.NoAlias =
+ adjustAliasScopeList(LocAS.AATags.NoAlias, *LoopAliasScopes);
+ LocBS.AATags.Scope =
+ adjustAliasScopeList(LocBS.AATags.Scope, *LoopAliasScopes);
+ LocBS.AATags.NoAlias =
+ adjustAliasScopeList(LocBS.AATags.NoAlias, *LoopAliasScopes);
+ }
BatchAAResults BAA(*AA);
BAA.enableCrossIterationMode();
@@ -2648,6 +2677,29 @@ bool DependenceInfo::invalidate(Function &F, const PreservedAnalyses &PA,
Inv.invalidate<LoopAnalysis>(F, PA);
}
+const SmallPtrSetImpl<const MDNode *> &DependenceInfo::getLoopAliasScopes() {
+ if (!LoopAliasScopesPopulated) {
+ LoopAliasScopesPopulated = true;
+ Function *DeclFn =
+ F->getParent() ? F->getParent()->getFunction(Intrinsic::getName(
+ Intrinsic::experimental_noalias_scope_decl))
+ : nullptr;
+ if (!LI->empty() && (!F->getParent() || (DeclFn && !DeclFn->use_empty()))) {
+ for (const BasicBlock &BB : *F) {
+ if (!LI->getLoopFor(&BB))
+ continue;
+ for (const Instruction &I : BB) {
+ if (const auto *Decl = dyn_cast<NoAliasScopeDeclInst>(&I)) {
+ for (Metadata *Op : Decl->getScopeList()->operands())
+ LoopAliasScopes.insert(cast<MDNode>(Op));
+ }
+ }
+ }
+ }
+ }
+ return LoopAliasScopes;
+}
+
// depends -
// Returns NULL if there is no dependence.
// Otherwise, return a Dependence with as many details as possible.
@@ -2679,7 +2731,13 @@ DependenceInfo::depends(Instruction *Src, Instruction *Dst,
const MemoryLocation &DstLoc = MemoryLocation::get(Dst);
const MemoryLocation &SrcLoc = MemoryLocation::get(Src);
- switch (underlyingObjectsAlias(AA, F->getDataLayout(), DstLoc, SrcLoc)) {
+ bool HasAliasScopes = DstLoc.AATags.Scope || DstLoc.AATags.NoAlias ||
+ SrcLoc.AATags.Scope || SrcLoc.AATags.NoAlias;
+ const SmallPtrSetImpl<const MDNode *> *LoopAliasScopes =
+ HasAliasScopes ? &getLoopAliasScopes() : nullptr;
+
+ switch (underlyingObjectsAlias(AA, F->getDataLayout(), DstLoc, SrcLoc,
+ LoopAliasScopes)) {
case AliasResult::MayAlias:
case AliasResult::PartialAlias:
// cannot analyse objects if we don't understand their aliasing.
>From 6b08be873cea6c0ea4dcb43c8fccdb3cd6e6da89 Mon Sep 17 00:00:00 2001
From: sp4s-s <spass.ni42+github at gmail.com>
Date: Mon, 14 Sep 2026 10:04:37 +0530
Subject: [PATCH 2/2] [DA] Add tests for iteration-local scoped noalias
Cover decl-in-loop (anti), decl-outside-loop, and no-decl.
Also clang-format the previous change.
---
llvm/lib/Analysis/DependenceAnalysis.cpp | 8 +-
.../DependenceAnalysis/scoped-noalias.ll | 111 ++++++++++++++++++
2 files changed, 115 insertions(+), 4 deletions(-)
create mode 100644 llvm/test/Analysis/DependenceAnalysis/scoped-noalias.ll
diff --git a/llvm/lib/Analysis/DependenceAnalysis.cpp b/llvm/lib/Analysis/DependenceAnalysis.cpp
index 3139ac12f0faf..cd78e5998722c 100644
--- a/llvm/lib/Analysis/DependenceAnalysis.cpp
+++ b/llvm/lib/Analysis/DependenceAnalysis.cpp
@@ -2680,10 +2680,10 @@ bool DependenceInfo::invalidate(Function &F, const PreservedAnalyses &PA,
const SmallPtrSetImpl<const MDNode *> &DependenceInfo::getLoopAliasScopes() {
if (!LoopAliasScopesPopulated) {
LoopAliasScopesPopulated = true;
- Function *DeclFn =
- F->getParent() ? F->getParent()->getFunction(Intrinsic::getName(
- Intrinsic::experimental_noalias_scope_decl))
- : nullptr;
+ Function *DeclFn = F->getParent()
+ ? F->getParent()->getFunction(Intrinsic::getName(
+ Intrinsic::experimental_noalias_scope_decl))
+ : nullptr;
if (!LI->empty() && (!F->getParent() || (DeclFn && !DeclFn->use_empty()))) {
for (const BasicBlock &BB : *F) {
if (!LI->getLoopFor(&BB))
diff --git a/llvm/test/Analysis/DependenceAnalysis/scoped-noalias.ll b/llvm/test/Analysis/DependenceAnalysis/scoped-noalias.ll
new file mode 100644
index 0000000000000..049b2f414a898
--- /dev/null
+++ b/llvm/test/Analysis/DependenceAnalysis/scoped-noalias.ll
@@ -0,0 +1,111 @@
+; NOTE: Assertions have been autogenerated by utils/update_analyze_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -disable-output "-passes=print<da>" -aa-pipeline=basic-aa,scoped-noalias-aa 2>&1 | FileCheck %s
+
+; In this test, noalias.scope.decl is inside the loop, so the scope only
+; holds within a single iteration. DA must detect the anti dependence across
+; iterations and not report 'none'.
+define void @decl_in_loop(ptr %A, i64 %n) {
+; CHECK-LABEL: 'decl_in_loop'
+; CHECK-NEXT: Src: call void @llvm.experimental.noalias.scope.decl(metadata !5) --> Dst: call void @llvm.experimental.noalias.scope.decl(metadata !5)
+; CHECK-NEXT: da analyze - confused!
+; CHECK-NEXT: Src: call void @llvm.experimental.noalias.scope.decl(metadata !5) --> Dst: %v = load i32, ptr %src, align 4, !alias.scope !5
+; CHECK-NEXT: da analyze - confused!
+; CHECK-NEXT: Src: call void @llvm.experimental.noalias.scope.decl(metadata !5) --> Dst: store i32 %v1, ptr %dst, align 4, !noalias !5
+; CHECK-NEXT: da analyze - confused!
+; CHECK-NEXT: Src: %v = load i32, ptr %src, align 4, !alias.scope !5 --> Dst: %v = load i32, ptr %src, align 4, !alias.scope !5
+; CHECK-NEXT: da analyze - none!
+; CHECK-NEXT: Src: %v = load i32, ptr %src, align 4, !alias.scope !5 --> Dst: store i32 %v1, ptr %dst, align 4, !noalias !5
+; CHECK-NEXT: da analyze - anti [-1]!
+; CHECK-NEXT: Src: store i32 %v1, ptr %dst, align 4, !noalias !5 --> Dst: store i32 %v1, ptr %dst, align 4, !noalias !5
+; CHECK-NEXT: da analyze - none!
+;
+entry:
+ br label %loop
+
+loop:
+ %i = phi i64 [ 1, %entry ], [ %i.next, %loop ]
+ call void @llvm.experimental.noalias.scope.decl(metadata !2)
+ %im1 = add nsw i64 %i, -1
+ %src = getelementptr inbounds i32, ptr %A, i64 %im1
+ %v = load i32, ptr %src, align 4, !alias.scope !2
+ %v1 = add i32 %v, 1
+ %dst = getelementptr inbounds i32, ptr %A, i64 %i
+ store i32 %v1, ptr %dst, align 4, !noalias !2
+ %i.next = add nsw i64 %i, 1
+ %c = icmp slt i64 %i.next, %n
+ br i1 %c, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+; In this test, noalias.scope.decl is outside the loop, so the scope holds
+; across all iterations. DA can safely determine there is no dependence.
+define void @decl_outside_loop(ptr %A, i64 %n) {
+; CHECK-LABEL: 'decl_outside_loop'
+; CHECK-NEXT: Src: call void @llvm.experimental.noalias.scope.decl(metadata !5) --> Dst: call void @llvm.experimental.noalias.scope.decl(metadata !5)
+; CHECK-NEXT: da analyze - confused!
+; CHECK-NEXT: Src: call void @llvm.experimental.noalias.scope.decl(metadata !5) --> Dst: %v = load i32, ptr %src, align 4, !alias.scope !5
+; CHECK-NEXT: da analyze - confused!
+; CHECK-NEXT: Src: call void @llvm.experimental.noalias.scope.decl(metadata !5) --> Dst: store i32 %v1, ptr %dst, align 4, !noalias !5
+; CHECK-NEXT: da analyze - confused!
+; CHECK-NEXT: Src: %v = load i32, ptr %src, align 4, !alias.scope !5 --> Dst: %v = load i32, ptr %src, align 4, !alias.scope !5
+; CHECK-NEXT: da analyze - none!
+; CHECK-NEXT: Src: %v = load i32, ptr %src, align 4, !alias.scope !5 --> Dst: store i32 %v1, ptr %dst, align 4, !noalias !5
+; CHECK-NEXT: da analyze - none!
+; CHECK-NEXT: Src: store i32 %v1, ptr %dst, align 4, !noalias !5 --> Dst: store i32 %v1, ptr %dst, align 4, !noalias !5
+; CHECK-NEXT: da analyze - none!
+;
+entry:
+ call void @llvm.experimental.noalias.scope.decl(metadata !2)
+ br label %loop
+
+loop:
+ %i = phi i64 [ 1, %entry ], [ %i.next, %loop ]
+ %im1 = add nsw i64 %i, -1
+ %src = getelementptr inbounds i32, ptr %A, i64 %im1
+ %v = load i32, ptr %src, align 4, !alias.scope !2
+ %v1 = add i32 %v, 1
+ %dst = getelementptr inbounds i32, ptr %A, i64 %i
+ store i32 %v1, ptr %dst, align 4, !noalias !2
+ %i.next = add nsw i64 %i, 1
+ %c = icmp slt i64 %i.next, %n
+ br i1 %c, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+; In this test, there is no scope declaration, so the scope holds across
+; iterations. DA can safely determine there is no dependence.
+define void @no_decl(ptr %A, i64 %n) {
+; CHECK-LABEL: 'no_decl'
+; CHECK-NEXT: Src: %v = load i32, ptr %src, align 4, !alias.scope !5 --> Dst: %v = load i32, ptr %src, align 4, !alias.scope !5
+; CHECK-NEXT: da analyze - none!
+; CHECK-NEXT: Src: %v = load i32, ptr %src, align 4, !alias.scope !5 --> Dst: store i32 %v1, ptr %dst, align 4, !noalias !5
+; CHECK-NEXT: da analyze - none!
+; CHECK-NEXT: Src: store i32 %v1, ptr %dst, align 4, !noalias !5 --> Dst: store i32 %v1, ptr %dst, align 4, !noalias !5
+; CHECK-NEXT: da analyze - none!
+;
+entry:
+ br label %loop
+
+loop:
+ %i = phi i64 [ 1, %entry ], [ %i.next, %loop ]
+ %im1 = add nsw i64 %i, -1
+ %src = getelementptr inbounds i32, ptr %A, i64 %im1
+ %v = load i32, ptr %src, align 4, !alias.scope !2
+ %v1 = add i32 %v, 1
+ %dst = getelementptr inbounds i32, ptr %A, i64 %i
+ store i32 %v1, ptr %dst, align 4, !noalias !2
+ %i.next = add nsw i64 %i, 1
+ %c = icmp slt i64 %i.next, %n
+ br i1 %c, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+!0 = !{!0}
+!1 = !{!1, !0}
+!2 = !{!1}
More information about the llvm-commits
mailing list