[llvm-branch-commits] [llvm] Attributor: Deduce noundef returns if poison-generating attributes hold (PR #231797)
Matt Arsenault via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Wed Oct 14 01:10:47 PDT 2026
https://github.com/arsenm created https://github.com/llvm/llvm-project/pull/231797
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 at anthropic.com>
---
<sub>Stack created with <a href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>
>From 41b91ffdadf9384aecb45f57d804f07fa7fa7df5 Mon Sep 17 00:00:00 2001
From: Matt Arsenault <Matthew.Arsenault at amd.com>
Date: Tue, 13 Oct 2026 14:42:38 -0700
Subject: [PATCH] Attributor: Deduce noundef returns if poison-generating
attributes hold
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 at anthropic.com>
---
llvm/include/llvm/Analysis/ValueTracking.h | 6 +
llvm/lib/Analysis/ValueTracking.cpp | 21 +++
.../Transforms/IPO/AttributorAttributes.cpp | 25 ++-
llvm/lib/Transforms/IPO/FunctionAttrs.cpp | 22 +--
llvm/test/Transforms/Attributor/align.ll | 2 +-
.../Attributor/noundef-return-poison-attrs.ll | 158 ++++++++++++++++--
6 files changed, 194 insertions(+), 40 deletions(-)
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
+}
More information about the llvm-branch-commits
mailing list