[llvm] [Attributor] Distinguish COHERENT accesses in addition to exact ones (PR #207857)

via llvm-commits llvm-commits at lists.llvm.org
Mon Jul 6 15:49:46 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Johannes Doerfert (jdoerfert)

<details>
<summary>Changes</summary>

When we find overlapping accesses, an access is exact when it will hit the same memory. Otherwise we treated it like an unknown access that could overwrite any part of the overlapping value. Using size and alignment we can introduce a new category, COHERENT, which means the access migth not be at the exact address but if it is affecting the initial range it will do so with the proper value, e.g., it will not write part of the range but all or nothing.

---
Full diff: https://github.com/llvm/llvm-project/pull/207857.diff


5 Files Affected:

- (modified) llvm/include/llvm/Transforms/IPO/Attributor.h (+12-3) 
- (modified) llvm/lib/Transforms/IPO/Attributor.cpp (+9-7) 
- (modified) llvm/lib/Transforms/IPO/AttributorAttributes.cpp (+39-10) 
- (modified) llvm/test/Transforms/Attributor/multiple-offsets-pointer-info.ll (+1-7) 
- (modified) llvm/test/Transforms/Attributor/nocapture-2.ll (+119) 


``````````diff
diff --git a/llvm/include/llvm/Transforms/IPO/Attributor.h b/llvm/include/llvm/Transforms/IPO/Attributor.h
index f342a8381b8af..a61cd2023f75f 100644
--- a/llvm/include/llvm/Transforms/IPO/Attributor.h
+++ b/llvm/include/llvm/Transforms/IPO/Attributor.h
@@ -6243,12 +6243,21 @@ struct AAPointerInfo : public AbstractAttribute {
   virtual bool reachesReturn() const = 0;
   virtual void addReturnedOffsetsTo(OffsetInfo &) const = 0;
 
+  enum AccessOverlapTy {
+    EXACT,    /// Matches the exact location and size of the access.
+    COHERENT, /// Matches the size and has sufficient alignment to be not broken
+              /// apart but stay a coherent value if it matches the target
+              /// range.
+    UNKNOWN,  /// No known overlap.
+  };
+
   /// Call \p CB on all accesses that might interfere with \p Range and return
   /// true if all such accesses were known and the callback returned true for
   /// all of them, false otherwise. An access interferes with an offset-size
   /// pair if it might read or write that memory region.
   virtual bool forallInterferingAccesses(
-      AA::RangeTy Range, function_ref<bool(const Access &, bool)> CB) const = 0;
+      AA::RangeTy Range,
+      function_ref<bool(const Access &, AccessOverlapTy)> CB) const = 0;
 
   /// Call \p CB on all accesses that might interfere with \p I and
   /// return true if all such accesses were known and the callback returned true
@@ -6262,8 +6271,8 @@ struct AAPointerInfo : public AbstractAttribute {
   virtual bool forallInterferingAccesses(
       Attributor &A, const AbstractAttribute &QueryingAA, Instruction &I,
       bool FindInterferingWrites, bool FindInterferingReads,
-      function_ref<bool(const Access &, bool)> CB, bool &HasBeenWrittenTo,
-      AA::RangeTy &Range,
+      function_ref<bool(const Access &, AccessOverlapTy)> CB,
+      bool &HasBeenWrittenTo, AA::RangeTy &Range,
       function_ref<bool(const Access &)> SkipCB = nullptr) const = 0;
 
   /// This function should return true if the type of the \p AA is AAPointerInfo
diff --git a/llvm/lib/Transforms/IPO/Attributor.cpp b/llvm/lib/Transforms/IPO/Attributor.cpp
index c74647557f4d1..d50f5cf7696ea 100644
--- a/llvm/lib/Transforms/IPO/Attributor.cpp
+++ b/llvm/lib/Transforms/IPO/Attributor.cpp
@@ -487,13 +487,13 @@ static bool getPotentialCopiesOfMemoryValue(
     bool NullOnly = true;
     bool NullRequired = false;
     auto CheckForNullOnlyAndUndef = [&](std::optional<Value *> V,
-                                        bool IsExact) {
+                                        AAPointerInfo::AccessOverlapTy AOTy) {
       if (!V || *V == nullptr)
         NullOnly = false;
       else if (isa<UndefValue>(*V))
         /* No op */;
       else if (isa<Constant>(*V) && cast<Constant>(*V)->isNullValue())
-        NullRequired = !IsExact;
+        NullRequired = AOTy == AAPointerInfo::AccessOverlapTy::UNKNOWN;
       else
         NullOnly = false;
     };
@@ -534,14 +534,15 @@ static bool getPotentialCopiesOfMemoryValue(
       return false;
     };
 
-    auto CheckAccess = [&](const AAPointerInfo::Access &Acc, bool IsExact) {
+    auto CheckAccess = [&](const AAPointerInfo::Access &Acc,
+                           AAPointerInfo::AccessOverlapTy AOTy) {
       if ((IsLoad && !Acc.isWriteOrAssumption()) || (!IsLoad && !Acc.isRead()))
         return true;
       if (IsLoad && Acc.isWrittenValueYetUndetermined())
         return true;
-      CheckForNullOnlyAndUndef(Acc.getContent(), IsExact);
-      if (OnlyExact && !IsExact && !NullOnly &&
-          !isa_and_nonnull<UndefValue>(Acc.getWrittenValue())) {
+      CheckForNullOnlyAndUndef(Acc.getContent(), AOTy);
+      if (OnlyExact && AOTy == AAPointerInfo::AccessOverlapTy::UNKNOWN &&
+          !NullOnly && !isa_and_nonnull<UndefValue>(Acc.getWrittenValue())) {
         LLVM_DEBUG(dbgs() << "Non exact access " << *Acc.getRemoteInst()
                           << ", abort!\n");
         return false;
@@ -618,7 +619,8 @@ static bool getPotentialCopiesOfMemoryValue(
                              "underlying object, abort!\n");
         return false;
       }
-      CheckForNullOnlyAndUndef(InitialValue, /* IsExact */ true);
+      CheckForNullOnlyAndUndef(InitialValue,
+                               AAPointerInfo::AccessOverlapTy::EXACT);
       if (NullRequired && !NullOnly) {
         LLVM_DEBUG(dbgs() << "Non exact access but initial value that is not "
                              "null or undef, abort!\n");
diff --git a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
index 004bd3f741dd8..1b58d2f4e25fa 100644
--- a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
+++ b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
@@ -887,9 +887,21 @@ struct AA::PointerInfo::State : public AbstractState {
   /// invalidation.
   AAPointerInfo::OffsetInfo ReturnedOffsets;
 
+  static void getSizeAndAlignment(Instruction &I, const DataLayout &DL,
+                                  uint32_t &Size, uint32_t &Alignment) {
+    if (auto *LI = dyn_cast<LoadInst>(&I)) {
+      Size = DL.getTypeStoreSize(LI->getType());
+      Alignment = LI->getAlign().value();
+    } else if (auto *SI = dyn_cast<StoreInst>(&I)) {
+      Size = DL.getTypeStoreSize(SI->getPointerOperandType());
+      Alignment = SI->getAlign().value();
+    }
+  }
+
   /// See AAPointerInfo::forallInterferingAccesses.
   template <typename F>
-  bool forallInterferingAccesses(AA::RangeTy Range, F CB) const {
+  bool forallInterferingAccesses(AA::RangeTy Range, F CB, uint32_t Size = 0,
+                                 uint32_t Alignment = 0) const {
     if (!isValidState() || !ReturnedOffsets.isUnassigned())
       return false;
 
@@ -898,9 +910,21 @@ struct AA::PointerInfo::State : public AbstractState {
       if (!Range.mayOverlap(ItRange))
         continue;
       bool IsExact = Range == ItRange && !Range.offsetOrSizeAreUnknown();
+      bool IsCoherent = false;
       for (auto Index : It.getSecond()) {
         auto &Access = AccessList[Index];
-        if (!CB(Access, IsExact))
+        if (!IsExact) {
+          auto *AccI = Access.getRemoteInst();
+          uint32_t AccSize = 0, AccAlignment = 0;
+          const DataLayout &DL = AccI->getDataLayout();
+          getSizeAndAlignment(*AccI, DL, AccSize, AccAlignment);
+          if (AccSize && AccSize == Size && AccAlignment && Alignment >= Size &&
+              AccAlignment >= AccSize)
+            IsCoherent = true;
+        }
+        if (!CB(Access, IsExact      ? AAPointerInfo::EXACT
+                        : IsCoherent ? AAPointerInfo::COHERENT
+                                     : AAPointerInfo::UNKNOWN))
           return false;
       }
     }
@@ -926,7 +950,10 @@ struct AA::PointerInfo::State : public AbstractState {
           break;
       }
     }
-    return forallInterferingAccesses(Range, CB);
+    uint32_t Size = 0, Alignment = 0;
+    const DataLayout &DL = I.getDataLayout();
+    getSizeAndAlignment(I, DL, Size, Alignment);
+    return forallInterferingAccesses(Range, CB, Size, Alignment);
   }
 
 private:
@@ -1078,7 +1105,7 @@ struct AAPointerInfoImpl
 
   bool forallInterferingAccesses(
       AA::RangeTy Range,
-      function_ref<bool(const AAPointerInfo::Access &, bool)> CB)
+      function_ref<bool(const AAPointerInfo::Access &, AccessOverlapTy)> CB)
       const override {
     return State::forallInterferingAccesses(Range, CB);
   }
@@ -1086,13 +1113,14 @@ struct AAPointerInfoImpl
   bool forallInterferingAccesses(
       Attributor &A, const AbstractAttribute &QueryingAA, Instruction &I,
       bool FindInterferingWrites, bool FindInterferingReads,
-      function_ref<bool(const Access &, bool)> UserCB, bool &HasBeenWrittenTo,
-      AA::RangeTy &Range,
+      function_ref<bool(const Access &, AccessOverlapTy)> UserCB,
+      bool &HasBeenWrittenTo, AA::RangeTy &Range,
       function_ref<bool(const Access &)> SkipCB) const override {
     HasBeenWrittenTo = false;
 
     SmallPtrSet<const Access *, 8> DominatingWrites;
-    SmallVector<std::pair<const Access *, bool>, 8> InterferingAccesses;
+    SmallVector<std::pair<const Access *, AccessOverlapTy>, 8>
+        InterferingAccesses;
 
     Function &Scope = *I.getFunction();
     bool IsKnownNoSync;
@@ -1221,7 +1249,8 @@ struct AAPointerInfoImpl
     // therefore blockers in the reachability traversal.
     AA::InstExclusionSetTy ExclusionSet;
 
-    auto AccessCB = [&](const Access &Acc, bool Exact) {
+    auto AccessCB = [&](const Access &Acc, AccessOverlapTy AOTy) {
+      bool Exact = AOTy == AccessOverlapTy::EXACT;
       Function *AccScope = Acc.getRemoteInst()->getFunction();
       bool AccInSameScope = AccScope == &Scope;
 
@@ -1250,7 +1279,7 @@ struct AAPointerInfoImpl
       // the given instruction.
       AllInSameNoSyncFn &= Acc.getRemoteInst()->getFunction() == &Scope;
 
-      InterferingAccesses.push_back({&Acc, Exact});
+      InterferingAccesses.push_back({&Acc, AOTy});
       return true;
     };
     if (!State::forallInterferingAccesses(I, AccessCB, Range))
@@ -1270,7 +1299,7 @@ struct AAPointerInfoImpl
     }
 
     // Helper to determine if we can skip a specific write access.
-    auto CanSkipAccess = [&](const Access &Acc, bool Exact) {
+    auto CanSkipAccess = [&](const Access &Acc, AccessOverlapTy AOTy) {
       if (SkipCB && SkipCB(Acc))
         return true;
       if (!CanIgnoreThreading(Acc))
diff --git a/llvm/test/Transforms/Attributor/multiple-offsets-pointer-info.ll b/llvm/test/Transforms/Attributor/multiple-offsets-pointer-info.ll
index f04ac4d73340f..9173e5013686b 100644
--- a/llvm/test/Transforms/Attributor/multiple-offsets-pointer-info.ll
+++ b/llvm/test/Transforms/Attributor/multiple-offsets-pointer-info.ll
@@ -407,19 +407,13 @@ define i8 @phi_gep_not_simplifiable_1(i1 %cnd1, i1 %cnd2) {
 ; CHECK-LABEL: define {{[^@]+}}@phi_gep_not_simplifiable_1
 ; CHECK-SAME: (i1 noundef [[CND1:%.*]], i1 [[CND2:%.*]]) #[[ATTR1]] {
 ; CHECK-NEXT:  entry:
-; CHECK-NEXT:    [[BYTES:%.*]] = alloca [1024 x i8], align 16
-; CHECK-NEXT:    [[GEP23:%.*]] = getelementptr inbounds [1024 x i8], ptr [[BYTES]], i64 0, i64 23
 ; CHECK-NEXT:    br i1 [[CND1]], label [[THEN:%.*]], label [[ELSE:%.*]]
 ; CHECK:       then:
 ; CHECK-NEXT:    br label [[JOIN:%.*]]
 ; CHECK:       else:
-; CHECK-NEXT:    [[GEP31:%.*]] = getelementptr inbounds [1024 x i8], ptr [[BYTES]], i64 0, i64 31
 ; CHECK-NEXT:    br label [[JOIN]]
 ; CHECK:       join:
-; CHECK-NEXT:    [[PHI_PTR:%.*]] = phi ptr [ [[GEP23]], [[THEN]] ], [ [[GEP31]], [[ELSE]] ]
-; CHECK-NEXT:    store i8 42, ptr [[GEP23]], align 4
-; CHECK-NEXT:    [[I:%.*]] = load i8, ptr [[PHI_PTR]], align 4
-; CHECK-NEXT:    ret i8 [[I]]
+; CHECK-NEXT:    ret i8 42
 ;
 entry:
   %Bytes = alloca [1024 x i8], align 16
diff --git a/llvm/test/Transforms/Attributor/nocapture-2.ll b/llvm/test/Transforms/Attributor/nocapture-2.ll
index 49669c51ebb1f..a0f12bae7b3bc 100644
--- a/llvm/test/Transforms/Attributor/nocapture-2.ll
+++ b/llvm/test/Transforms/Attributor/nocapture-2.ll
@@ -802,6 +802,125 @@ define void @b64613_positive(ptr noundef %p, i32 %i) {
   %q = call ptr @b64613_b(ptr %r, i32 %i)
   ret void
 }
+define ptr @b64613_c(ptr noundef %p, i32 %i) {
+; TUNIT: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
+; TUNIT-LABEL: define ptr @b64613_c
+; TUNIT-SAME: (ptr nofree noundef readnone returned "no-capture-maybe-returned" [[P:%.*]], i32 [[I:%.*]]) #[[ATTR11]] {
+; TUNIT-NEXT:    [[P_ADDR:%.*]] = alloca <2 x ptr>, align 8
+; TUNIT-NEXT:    [[G:%.*]] = getelementptr i8, ptr [[P_ADDR]], i32 [[I]]
+; TUNIT-NEXT:    store ptr [[P]], ptr [[G]], align 8
+; TUNIT-NEXT:    ret ptr [[P]]
+;
+; CGSCC: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
+; CGSCC-LABEL: define ptr @b64613_c
+; CGSCC-SAME: (ptr nofree noundef readnone returned "no-capture-maybe-returned" [[P:%.*]], i32 [[I:%.*]]) #[[ATTR12]] {
+; CGSCC-NEXT:    [[P_ADDR:%.*]] = alloca <2 x ptr>, align 8
+; CGSCC-NEXT:    [[G:%.*]] = getelementptr i8, ptr [[P_ADDR]], i32 [[I]]
+; CGSCC-NEXT:    store ptr [[P]], ptr [[G]], align 8
+; CGSCC-NEXT:    ret ptr [[P]]
+;
+  %p.addr = alloca <2 x ptr>, align 8
+  %g = getelementptr i8, ptr %p.addr, i32 %i
+  store ptr %p, ptr %g, align 8
+  %r = load ptr, ptr %p.addr, align 8
+  ret ptr %r
+}
+define ptr @b64613_d(ptr noundef %p, i32 %i) {
+; TUNIT: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
+; TUNIT-LABEL: define ptr @b64613_d
+; TUNIT-SAME: (ptr nofree noundef [[P:%.*]], i32 [[I:%.*]]) #[[ATTR11]] {
+; TUNIT-NEXT:    [[P_ADDR:%.*]] = alloca <2 x ptr>, align 8
+; TUNIT-NEXT:    [[G:%.*]] = getelementptr i8, ptr [[P_ADDR]], i32 [[I]]
+; TUNIT-NEXT:    store ptr [[P]], ptr [[G]], align 4
+; TUNIT-NEXT:    [[R:%.*]] = load ptr, ptr [[P_ADDR]], align 8
+; TUNIT-NEXT:    ret ptr [[R]]
+;
+; CGSCC: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
+; CGSCC-LABEL: define ptr @b64613_d
+; CGSCC-SAME: (ptr nofree noundef [[P:%.*]], i32 [[I:%.*]]) #[[ATTR12]] {
+; CGSCC-NEXT:    [[P_ADDR:%.*]] = alloca <2 x ptr>, align 8
+; CGSCC-NEXT:    [[G:%.*]] = getelementptr i8, ptr [[P_ADDR]], i32 [[I]]
+; CGSCC-NEXT:    store ptr [[P]], ptr [[G]], align 4
+; CGSCC-NEXT:    [[R:%.*]] = load ptr, ptr [[P_ADDR]], align 8
+; CGSCC-NEXT:    ret ptr [[R]]
+;
+  %p.addr = alloca <2 x ptr>, align 8
+  %g = getelementptr i8, ptr %p.addr, i32 %i
+  store ptr %p, ptr %g, align 4
+  %r = load ptr, ptr %p.addr, align 8
+  ret ptr %r
+}
+define ptr @b64613_e(ptr noundef %p, i32 %i) {
+; TUNIT: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
+; TUNIT-LABEL: define ptr @b64613_e
+; TUNIT-SAME: (ptr nofree noundef [[P:%.*]], i32 [[I:%.*]]) #[[ATTR11]] {
+; TUNIT-NEXT:    [[P_ADDR:%.*]] = alloca <2 x ptr>, align 8
+; TUNIT-NEXT:    [[G:%.*]] = getelementptr i8, ptr [[P_ADDR]], i32 [[I]]
+; TUNIT-NEXT:    store ptr [[P]], ptr [[G]], align 8
+; TUNIT-NEXT:    [[R:%.*]] = load ptr, ptr [[P_ADDR]], align 8
+; TUNIT-NEXT:    ret ptr [[R]]
+;
+; CGSCC: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
+; CGSCC-LABEL: define ptr @b64613_e
+; CGSCC-SAME: (ptr nofree noundef [[P:%.*]], i32 [[I:%.*]]) #[[ATTR12]] {
+; CGSCC-NEXT:    [[P_ADDR:%.*]] = alloca <2 x ptr>, align 8
+; CGSCC-NEXT:    [[G:%.*]] = getelementptr i8, ptr [[P_ADDR]], i32 [[I]]
+; CGSCC-NEXT:    store ptr [[P]], ptr [[G]], align 8
+; CGSCC-NEXT:    [[R:%.*]] = load ptr, ptr [[P_ADDR]], align 8
+; CGSCC-NEXT:    ret ptr [[R]]
+;
+  %p.addr = alloca <2 x ptr>, align 8
+  %g = getelementptr i8, ptr %p.addr, i32 %i
+  store ptr %p, ptr %g, align 8
+  %r = load ptr, ptr %p.addr, align 4
+  ret ptr %r
+}
+define ptr @b64613_f(ptr noundef %p, i32 %i) {
+; TUNIT: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
+; TUNIT-LABEL: define ptr @b64613_f
+; TUNIT-SAME: (ptr nofree noundef readnone returned "no-capture-maybe-returned" [[P:%.*]], i32 [[I:%.*]]) #[[ATTR11]] {
+; TUNIT-NEXT:    [[P_ADDR:%.*]] = alloca <2 x ptr>, align 8
+; TUNIT-NEXT:    [[G:%.*]] = getelementptr i8, ptr [[P_ADDR]], i32 [[I]]
+; TUNIT-NEXT:    store ptr [[P]], ptr [[G]], align 8
+; TUNIT-NEXT:    ret ptr [[P]]
+;
+; CGSCC: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
+; CGSCC-LABEL: define ptr @b64613_f
+; CGSCC-SAME: (ptr nofree noundef readnone returned "no-capture-maybe-returned" [[P:%.*]], i32 [[I:%.*]]) #[[ATTR12]] {
+; CGSCC-NEXT:    [[P_ADDR:%.*]] = alloca <2 x ptr>, align 8
+; CGSCC-NEXT:    [[G:%.*]] = getelementptr i8, ptr [[P_ADDR]], i32 [[I]]
+; CGSCC-NEXT:    store ptr [[P]], ptr [[G]], align 8
+; CGSCC-NEXT:    ret ptr [[P]]
+;
+  %p.addr = alloca <2 x ptr>, align 8
+  %g = getelementptr i8, ptr %p.addr, i32 %i
+  store ptr %p, ptr %g, align 8
+  %r = load ptr, ptr %p.addr, align 16
+  ret ptr %r
+}
+define ptr @b64613_g(ptr noundef %p, i32 %i) {
+; TUNIT: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
+; TUNIT-LABEL: define ptr @b64613_g
+; TUNIT-SAME: (ptr nofree noundef readnone returned "no-capture-maybe-returned" [[P:%.*]], i32 [[I:%.*]]) #[[ATTR11]] {
+; TUNIT-NEXT:    [[P_ADDR:%.*]] = alloca <2 x ptr>, align 8
+; TUNIT-NEXT:    [[G:%.*]] = getelementptr i8, ptr [[P_ADDR]], i32 [[I]]
+; TUNIT-NEXT:    store ptr [[P]], ptr [[G]], align 16
+; TUNIT-NEXT:    ret ptr [[P]]
+;
+; CGSCC: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
+; CGSCC-LABEL: define ptr @b64613_g
+; CGSCC-SAME: (ptr nofree noundef readnone returned "no-capture-maybe-returned" [[P:%.*]], i32 [[I:%.*]]) #[[ATTR12]] {
+; CGSCC-NEXT:    [[P_ADDR:%.*]] = alloca <2 x ptr>, align 8
+; CGSCC-NEXT:    [[G:%.*]] = getelementptr i8, ptr [[P_ADDR]], i32 [[I]]
+; CGSCC-NEXT:    store ptr [[P]], ptr [[G]], align 16
+; CGSCC-NEXT:    ret ptr [[P]]
+;
+  %p.addr = alloca <2 x ptr>, align 8
+  %g = getelementptr i8, ptr %p.addr, i32 %i
+  store ptr %p, ptr %g, align 16
+  %r = load ptr, ptr %p.addr, align 8
+  ret ptr %r
+}
 
 attributes #0 = { noinline nounwind uwtable }
 ;.

``````````

</details>


https://github.com/llvm/llvm-project/pull/207857


More information about the llvm-commits mailing list