[llvm-branch-commits] [llvm] Attributor: Deduce noundef returns if poison-generating attributes hold (PR #231797)

via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Wed Oct 14 01:11:30 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-analysis

Author: Matt Arsenault (arsenm)

<details>
<summary>Changes</summary>

Instead of giving up on noundef for returns with poison-generating
attributes, check that the attributes hold for all returned values, like
FunctionAttrs already does.

Co-authored-by: Claude Opus 5.5 <noreply@<!-- -->anthropic.com>

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


6 Files Affected:

- (modified) llvm/include/llvm/Analysis/ValueTracking.h (+6) 
- (modified) llvm/lib/Analysis/ValueTracking.cpp (+21) 
- (modified) llvm/lib/Transforms/IPO/AttributorAttributes.cpp (+21-4) 
- (modified) llvm/lib/Transforms/IPO/FunctionAttrs.cpp (+2-20) 
- (modified) llvm/test/Transforms/Attributor/align.ll (+1-1) 
- (modified) llvm/test/Transforms/Attributor/noundef-return-poison-attrs.ll (+143-15) 


``````````diff
diff --git a/llvm/include/llvm/Analysis/ValueTracking.h b/llvm/include/llvm/Analysis/ValueTracking.h
index b3c8a894ef056..1892249ffd8bc 100644
--- a/llvm/include/llvm/Analysis/ValueTracking.h
+++ b/llvm/include/llvm/Analysis/ValueTracking.h
@@ -844,6 +844,12 @@ LLVM_ABI bool isGuaranteedNotToBePoison(const Value *V,
                                         const DominatorTree *DT = nullptr,
                                         unsigned Depth = 0);
 
+/// Returns true if \p V is known to not violate the poison-generating
+/// attributes in \p Attrs.
+LLVM_ABI bool
+isKnownToSatisfyPoisonGeneratingAttributes(const Value *V, AttributeSet Attrs,
+                                           const SimplifyQuery &SQ);
+
 inline bool isGuaranteedNotToBePoison(const Value *V, AssumptionCache *AC,
                                       BasicBlock::iterator CtxI,
                                       const DominatorTree *DT = nullptr,
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index a3612e2807612..471be0a84e356 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -8389,6 +8389,27 @@ bool llvm::isGuaranteedNotToBePoison(const Value *V, AssumptionCache *AC,
                                             UndefPoisonKind::PoisonOnly);
 }
 
+bool llvm::isKnownToSatisfyPoisonGeneratingAttributes(const Value *V,
+                                                      AttributeSet Attrs,
+                                                      const SimplifyQuery &SQ) {
+  if (Attrs.hasAttribute(Attribute::NonNull) && !isKnownNonZero(V, SQ))
+    return false;
+
+  if (MaybeAlign Align = Attrs.getAlignment()) {
+    if (V->getPointerAlignment(SQ.DL) < *Align)
+      return false;
+  }
+
+  Attribute Range = Attrs.getAttribute(Attribute::Range);
+  if (Range.isValid() && !Range.getRange().contains(
+                             computeConstantRange(V, /*ForSigned=*/false, SQ)))
+    return false;
+
+  FPClassTest NoFPClass = Attrs.getNoFPClass();
+  return NoFPClass == fcNone ||
+         computeKnownFPClass(V, NoFPClass, SQ).isKnownNever(NoFPClass);
+}
+
 bool llvm::isGuaranteedNotToBeUndef(const Value *V, AssumptionCache *AC,
                                     const Instruction *CtxI,
                                     const DominatorTree *DT, unsigned Depth) {
diff --git a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
index a94871815c4f3..e4c3328352b0c 100644
--- a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
+++ b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
@@ -10477,11 +10477,28 @@ struct AANoUndefReturned final
       return;
 
     // Returning a value that violates a return attribute, e.g., nonnull, is
-    // poison.
+    // poison, so the attributes must hold for all returned values.
     AttributeMask AM = AttributeFuncs::getPoisonGeneratingAttributes();
-    AttributeSet RetAttrs =
-        getAssociatedFunction()->getAttributes().getRetAttrs();
-    if (any_of(RetAttrs, [&](Attribute Attr) { return AM.contains(Attr); }))
+    Function *F = getAssociatedFunction();
+    AttributeSet RetAttrs = F->getAttributes().getRetAttrs();
+    if (none_of(RetAttrs, [&](Attribute Attr) { return AM.contains(Attr); }))
+      return;
+
+    InformationCache &InfoCache = A.getInfoCache();
+    SimplifyQuery Q(
+        F->getDataLayout(), InfoCache.getTargetLibraryInfoForFunction(*F),
+        InfoCache.getAnalysisResultForFunction<DominatorTreeAnalysis>(*F),
+        InfoCache.getAnalysisResultForFunction<AssumptionAnalysis>(*F));
+    auto AttrsHold = [&](Instruction &I) {
+      return isKnownToSatisfyPoisonGeneratingAttributes(
+          cast<ReturnInst>(I).getReturnValue(), RetAttrs,
+          Q.getWithInstruction(&I));
+    };
+    bool UsedAssumedInformation = false;
+    if (!A.checkForAllInstructions(AttrsHold, *this, {Instruction::Ret},
+                                   UsedAssumedInformation,
+                                   /*CheckBBLivenessOnly=*/false,
+                                   /*CheckPotentiallyDead=*/true))
       indicatePessimisticFixpoint();
   }
 
diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
index ac7ae381b1b57..b495cfb207ef0 100644
--- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
+++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
@@ -1702,27 +1702,9 @@ static void addNoUndefAttrs(const SCCNodeSet &SCCNodes,
             // We know the original return value is not poison now, but it
             // could still be converted to poison by another return attribute.
             // Try to explicitly re-prove the relevant attributes.
-            if (Attrs.hasRetAttr(Attribute::NonNull) &&
-                !isKnownNonZero(RetVal, DL))
+            if (!isKnownToSatisfyPoisonGeneratingAttributes(
+                    RetVal, Attrs.getRetAttrs(), SimplifyQuery(DL)))
               return false;
-
-            if (MaybeAlign Align = Attrs.getRetAlignment())
-              if (RetVal->getPointerAlignment(DL) < *Align)
-                return false;
-
-            Attribute Attr = Attrs.getRetAttr(Attribute::Range);
-            if (Attr.isValid() &&
-                !Attr.getRange().contains(
-                    computeConstantRange(RetVal, /*ForSigned=*/false,
-                                         SimplifyQuery(F->getDataLayout()))))
-              return false;
-
-            FPClassTest AttrFPClass = Attrs.getRetNoFPClass();
-            if (AttrFPClass != fcNone) {
-              KnownFPClass ComputedFPClass = computeKnownFPClass(RetVal, DL);
-              if (!ComputedFPClass.isKnownNever(AttrFPClass))
-                return false;
-            }
           }
           return true;
         })) {
diff --git a/llvm/test/Transforms/Attributor/align.ll b/llvm/test/Transforms/Attributor/align.ll
index 986c0ed056181..904233408c144 100644
--- a/llvm/test/Transforms/Attributor/align.ll
+++ b/llvm/test/Transforms/Attributor/align.ll
@@ -235,7 +235,7 @@ define align 4 ptr @test7() #0 {
 ; CGSCC: Function Attrs: mustprogress nofree noinline nosync nounwind willreturn memory(none) uwtable
 ; CGSCC-LABEL: define {{[^@]+}}@test7
 ; CGSCC-SAME: () #[[ATTR1:[0-9]+]] {
-; CGSCC-NEXT:    [[C:%.*]] = tail call nonnull align 8 dereferenceable(1) ptr @f1() #[[ATTR15:[0-9]+]]
+; CGSCC-NEXT:    [[C:%.*]] = tail call noundef nonnull align 8 dereferenceable(1) ptr @f1() #[[ATTR15:[0-9]+]]
 ; CGSCC-NEXT:    ret ptr [[C]]
 ;
   %c = tail call ptr @f1(ptr align 8 dereferenceable(1) @a1)
diff --git a/llvm/test/Transforms/Attributor/noundef-return-poison-attrs.ll b/llvm/test/Transforms/Attributor/noundef-return-poison-attrs.ll
index f45ed6cb0a42d..929f2c9daf194 100644
--- a/llvm/test/Transforms/Attributor/noundef-return-poison-attrs.ll
+++ b/llvm/test/Transforms/Attributor/noundef-return-poison-attrs.ll
@@ -1,44 +1,79 @@
 ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
-; RUN: opt -S -passes=attributor %s | FileCheck %s
+; RUN: opt -S -passes=attributor %s | FileCheck %s --check-prefixes=CHECK,ATTRIBUTOR
+; RUN: opt -S -passes=function-attrs %s | FileCheck %s --check-prefixes=CHECK,FNATTRS
 
 ; A well defined return value can still be poison if it violates an existing
-; return attribute, so noundef is not deduced for returns with such attributes,
-; even if they hold.
+; return attribute, so only deduce noundef if the attribute holds.
 
 @g = global i32 0, align 8
+ at g16 = global i32 0, align 16
 
 define nonnull ptr @nonnull(ptr noundef %p) {
-; CHECK-LABEL: define nonnull ptr @nonnull(
-; CHECK-SAME: ptr nofree noundef readnone returned [[P:%.*]]) #[[ATTR0:[0-9]+]] {
-; CHECK-NEXT:    ret ptr [[P]]
+; ATTRIBUTOR-LABEL: define nonnull ptr @nonnull(
+; ATTRIBUTOR-SAME: ptr nofree noundef readnone returned [[P:%.*]]) #[[ATTR0:[0-9]+]] {
+; ATTRIBUTOR-NEXT:    ret ptr [[P]]
+;
+; FNATTRS-LABEL: define nonnull ptr @nonnull(
+; FNATTRS-SAME: ptr nofree noundef readnone returned captures(ret: address, provenance) [[P:%.*]]) #[[ATTR0:[0-9]+]] {
+; FNATTRS-NEXT:    ret ptr [[P]]
 ;
   ret ptr %p
 }
 
 define nonnull ptr @nonnull_holds() {
-; CHECK-LABEL: define nonnull align 8 dereferenceable(4) ptr @nonnull_holds(
-; CHECK-SAME: ) #[[ATTR0]] {
-; CHECK-NEXT:    ret ptr @g
+; ATTRIBUTOR-LABEL: define noundef nonnull align 8 dereferenceable(4) ptr @nonnull_holds(
+; ATTRIBUTOR-SAME: ) #[[ATTR0]] {
+; ATTRIBUTOR-NEXT:    ret ptr @g
+;
+; FNATTRS-LABEL: define noundef nonnull ptr @nonnull_holds(
+; FNATTRS-SAME: ) #[[ATTR0]] {
+; FNATTRS-NEXT:    ret ptr @g
 ;
   ret ptr @g
 }
 
-define align 16 ptr @align() {
-; CHECK-LABEL: define nonnull align 16 dereferenceable(4) ptr @align(
-; CHECK-SAME: ) #[[ATTR0]] {
-; CHECK-NEXT:    ret ptr @g
+define align 16 ptr @align(ptr noundef %p) {
+; ATTRIBUTOR-LABEL: define align 16 ptr @align(
+; ATTRIBUTOR-SAME: ptr nofree noundef readnone returned [[P:%.*]]) #[[ATTR0]] {
+; ATTRIBUTOR-NEXT:    ret ptr [[P]]
 ;
-  ret ptr @g
+; FNATTRS-LABEL: define align 16 ptr @align(
+; FNATTRS-SAME: ptr nofree noundef readnone returned captures(ret: address, provenance) [[P:%.*]]) #[[ATTR0]] {
+; FNATTRS-NEXT:    ret ptr [[P]]
+;
+  ret ptr %p
+}
+
+define align 16 ptr @align_holds() {
+; ATTRIBUTOR-LABEL: define noundef nonnull align 16 dereferenceable(4) ptr @align_holds(
+; ATTRIBUTOR-SAME: ) #[[ATTR0]] {
+; ATTRIBUTOR-NEXT:    ret ptr @g16
+;
+; FNATTRS-LABEL: define noundef nonnull align 16 ptr @align_holds(
+; FNATTRS-SAME: ) #[[ATTR0]] {
+; FNATTRS-NEXT:    ret ptr @g16
+;
+  ret ptr @g16
 }
 
 define range(i32 0, 10) i32 @range(i32 noundef %x) {
 ; CHECK-LABEL: define range(i32 0, 10) i32 @range(
-; CHECK-SAME: i32 noundef returned [[X:%.*]]) #[[ATTR0]] {
+; CHECK-SAME: i32 noundef returned [[X:%.*]]) #[[ATTR0:[0-9]+]] {
 ; CHECK-NEXT:    ret i32 [[X]]
 ;
   ret i32 %x
 }
 
+define range(i32 0, 10) i32 @range_holds(i32 noundef %x) {
+; CHECK-LABEL: define noundef range(i32 0, 10) i32 @range_holds(
+; CHECK-SAME: i32 noundef [[X:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:    [[R:%.*]] = and i32 [[X]], 7
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %r = and i32 %x, 7
+  ret i32 %r
+}
+
 define nofpclass(nan) float @nofpclass(float noundef %x) {
 ; CHECK-LABEL: define nofpclass(nan) float @nofpclass(
 ; CHECK-SAME: float noundef returned [[X:%.*]]) #[[ATTR0]] {
@@ -46,3 +81,96 @@ define nofpclass(nan) float @nofpclass(float noundef %x) {
 ;
   ret float %x
 }
+
+define nofpclass(nan) float @nofpclass_holds() {
+; ATTRIBUTOR-LABEL: define noundef nofpclass(nan inf zero sub nnorm) float @nofpclass_holds(
+; ATTRIBUTOR-SAME: ) #[[ATTR0]] {
+; ATTRIBUTOR-NEXT:    ret float 1.000000e+00
+;
+; FNATTRS-LABEL: define noundef nofpclass(nan) float @nofpclass_holds(
+; FNATTRS-SAME: ) #[[ATTR0]] {
+; FNATTRS-NEXT:    ret float 1.000000e+00
+;
+  ret float 1.0
+}
+
+; The attribute only holds for one of the returned values.
+define nonnull ptr @nonnull_one_return(i1 noundef %c, ptr noundef %p) {
+; ATTRIBUTOR-LABEL: define nonnull ptr @nonnull_one_return(
+; ATTRIBUTOR-SAME: i1 noundef [[C:%.*]], ptr nofree noundef readnone [[P:%.*]]) #[[ATTR0]] {
+; ATTRIBUTOR-NEXT:    br i1 [[C]], label %[[A:.*]], label %[[B:.*]]
+; ATTRIBUTOR:       [[A]]:
+; ATTRIBUTOR-NEXT:    ret ptr @g
+; ATTRIBUTOR:       [[B]]:
+; ATTRIBUTOR-NEXT:    ret ptr [[P]]
+;
+; FNATTRS-LABEL: define nonnull ptr @nonnull_one_return(
+; FNATTRS-SAME: i1 noundef [[C:%.*]], ptr nofree noundef readnone captures(ret: address, provenance) [[P:%.*]]) #[[ATTR0]] {
+; FNATTRS-NEXT:    br i1 [[C]], label %[[A:.*]], label %[[B:.*]]
+; FNATTRS:       [[A]]:
+; FNATTRS-NEXT:    ret ptr @g
+; FNATTRS:       [[B]]:
+; FNATTRS-NEXT:    ret ptr [[P]]
+;
+  br i1 %c, label %a, label %b
+a:
+  ret ptr @g
+b:
+  ret ptr %p
+}
+
+; The assume makes %p nonnull at both returns.
+define nonnull ptr @nonnull_assume_all_returns(i1 noundef %c, ptr noundef %p) {
+; ATTRIBUTOR-LABEL: define noundef nonnull ptr @nonnull_assume_all_returns(
+; ATTRIBUTOR-SAME: i1 noundef [[C:%.*]], ptr nofree noundef nonnull readnone returned [[P:%.*]]) #[[ATTR1:[0-9]+]] {
+; ATTRIBUTOR-NEXT:    call void @llvm.assume(i1 noundef true) #[[ATTR3:[0-9]+]] [ "nonnull"(ptr [[P]]) ]
+; ATTRIBUTOR-NEXT:    br i1 [[C]], label %[[A:.*]], label %[[B:.*]]
+; ATTRIBUTOR:       [[A]]:
+; ATTRIBUTOR-NEXT:    ret ptr [[P]]
+; ATTRIBUTOR:       [[B]]:
+; ATTRIBUTOR-NEXT:    ret ptr [[P]]
+;
+; FNATTRS-LABEL: define nonnull ptr @nonnull_assume_all_returns(
+; FNATTRS-SAME: i1 noundef [[C:%.*]], ptr nofree noundef readnone returned captures(ret: address, provenance) [[P:%.*]]) #[[ATTR1:[0-9]+]] {
+; FNATTRS-NEXT:    call void @llvm.assume(i1 true) [ "nonnull"(ptr [[P]]) ]
+; FNATTRS-NEXT:    br i1 [[C]], label %[[A:.*]], label %[[B:.*]]
+; FNATTRS:       [[A]]:
+; FNATTRS-NEXT:    ret ptr [[P]]
+; FNATTRS:       [[B]]:
+; FNATTRS-NEXT:    ret ptr [[P]]
+;
+  call void @llvm.assume(i1 true) [ "nonnull"(ptr %p) ]
+  br i1 %c, label %a, label %b
+a:
+  ret ptr %p
+b:
+  ret ptr %p
+}
+
+; The assume only makes %p nonnull at one of the returns.
+define nonnull ptr @nonnull_assume_one_return(i1 noundef %c, ptr noundef %p) {
+; ATTRIBUTOR-LABEL: define nonnull ptr @nonnull_assume_one_return(
+; ATTRIBUTOR-SAME: i1 noundef [[C:%.*]], ptr nofree noundef readnone returned [[P:%.*]]) #[[ATTR1]] {
+; ATTRIBUTOR-NEXT:    br i1 [[C]], label %[[A:.*]], label %[[B:.*]]
+; ATTRIBUTOR:       [[A]]:
+; ATTRIBUTOR-NEXT:    call void @llvm.assume(i1 noundef true) #[[ATTR3]] [ "nonnull"(ptr [[P]]) ]
+; ATTRIBUTOR-NEXT:    ret ptr [[P]]
+; ATTRIBUTOR:       [[B]]:
+; ATTRIBUTOR-NEXT:    ret ptr [[P]]
+;
+; FNATTRS-LABEL: define nonnull ptr @nonnull_assume_one_return(
+; FNATTRS-SAME: i1 noundef [[C:%.*]], ptr nofree noundef readnone returned captures(ret: address, provenance) [[P:%.*]]) #[[ATTR1]] {
+; FNATTRS-NEXT:    br i1 [[C]], label %[[A:.*]], label %[[B:.*]]
+; FNATTRS:       [[A]]:
+; FNATTRS-NEXT:    call void @llvm.assume(i1 true) [ "nonnull"(ptr [[P]]) ]
+; FNATTRS-NEXT:    ret ptr [[P]]
+; FNATTRS:       [[B]]:
+; FNATTRS-NEXT:    ret ptr [[P]]
+;
+  br i1 %c, label %a, label %b
+a:
+  call void @llvm.assume(i1 true) [ "nonnull"(ptr %p) ]
+  ret ptr %p
+b:
+  ret ptr %p
+}

``````````

</details>


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


More information about the llvm-branch-commits mailing list