[llvm] ValueTracking: Handle tracking nan through powi (PR #179311)
Matt Arsenault via llvm-commits
llvm-commits at lists.llvm.org
Thu Feb 5 06:35:40 PST 2026
https://github.com/arsenm updated https://github.com/llvm/llvm-project/pull/179311
>From 158ec8225f35323b003c6949fb6c270f7e682bfe Mon Sep 17 00:00:00 2001
From: Matt Arsenault <Matthew.Arsenault at amd.com>
Date: Mon, 2 Feb 2026 19:13:58 +0100
Subject: [PATCH 1/2] ValueTracking: Handle tracking nan through powi
Nans should propagate simply, the infinity cases are complicated.
---
llvm/lib/Support/KnownFPClass.cpp | 12 ++
.../Transforms/Attributor/nofpclass-powi.ll | 162 +++++++++++++++++-
2 files changed, 173 insertions(+), 1 deletion(-)
diff --git a/llvm/lib/Support/KnownFPClass.cpp b/llvm/lib/Support/KnownFPClass.cpp
index 99d29119b8d86..d2e7a60edb56a 100644
--- a/llvm/lib/Support/KnownFPClass.cpp
+++ b/llvm/lib/Support/KnownFPClass.cpp
@@ -675,9 +675,21 @@ KnownFPClass KnownFPClass::ldexp(const KnownFPClass &KnownSrc,
return Known;
}
+// TODO: Detect no-infinity cases
KnownFPClass KnownFPClass::powi(const KnownFPClass &KnownSrc,
const KnownBits &ExponentKnownBits) {
KnownFPClass Known;
+ Known.propagateNaN(KnownSrc);
+
+ if (ExponentKnownBits.isZero()) {
+ // powi(QNaN, 0) returns 1.0, and powi(SNaN, 0) may non-deterministically
+ // return 1.0 or a NaN.
+ FPClassTest PossibleVals =
+ KnownSrc.isKnownNever(fcSNan) ? fcPosNormal | fcNan : fcPosNormal;
+ Known.knownNot(~PossibleVals);
+ return Known;
+ }
+
if (ExponentKnownBits.isEven()) {
Known.knownNot(fcNegative);
return Known;
diff --git a/llvm/test/Transforms/Attributor/nofpclass-powi.ll b/llvm/test/Transforms/Attributor/nofpclass-powi.ll
index a3f5c5d3e08dc..f2288b6c65b4b 100644
--- a/llvm/test/Transforms/Attributor/nofpclass-powi.ll
+++ b/llvm/test/Transforms/Attributor/nofpclass-powi.ll
@@ -57,6 +57,166 @@ define float @ret_powi_f32_even_constant(float %arg0) #0 {
ret float %call
}
+define float @ret_powi_f32_0(float %arg0) #0 {
+; CHECK-LABEL: define nofpclass(nan inf zero sub nnorm) float @ret_powi_f32_0
+; CHECK-SAME: (float [[ARG0:%.*]]) #[[ATTR1]] {
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(nan inf zero sub nnorm) float @llvm.powi.f32.i32(float [[ARG0]], i32 noundef 0) #[[ATTR6]]
+; CHECK-NEXT: ret float [[CALL]]
+;
+ %call = call float @llvm.powi.f32.i32(float %arg0, i32 0)
+ ret float %call
+}
+
+define float @ret_powi_f32_0_no_snan(float nofpclass(snan) %arg0) #0 {
+; CHECK-LABEL: define nofpclass(nan inf zero sub nnorm) float @ret_powi_f32_0_no_snan
+; CHECK-SAME: (float nofpclass(snan) [[ARG0:%.*]]) #[[ATTR1]] {
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(nan inf zero sub nnorm) float @llvm.powi.f32.i32(float nofpclass(snan) [[ARG0]], i32 noundef 0) #[[ATTR6]]
+; CHECK-NEXT: ret float [[CALL]]
+;
+ %call = call float @llvm.powi.f32.i32(float %arg0, i32 0)
+ ret float %call
+}
+
+define float @ret_powi_f32_0_no_qnan(float nofpclass(qnan) %arg0) #0 {
+; CHECK-LABEL: define nofpclass(nan inf zero sub nnorm) float @ret_powi_f32_0_no_qnan
+; CHECK-SAME: (float nofpclass(qnan) [[ARG0:%.*]]) #[[ATTR1]] {
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(nan inf zero sub nnorm) float @llvm.powi.f32.i32(float nofpclass(qnan) [[ARG0]], i32 noundef 0) #[[ATTR6]]
+; CHECK-NEXT: ret float [[CALL]]
+;
+ %call = call float @llvm.powi.f32.i32(float %arg0, i32 0)
+ ret float %call
+}
+
+define float @ret_powi_f32_0_no_nan(float nofpclass(nan) %arg0) #0 {
+; CHECK-LABEL: define nofpclass(nan inf zero sub nnorm) float @ret_powi_f32_0_no_nan
+; CHECK-SAME: (float nofpclass(nan) [[ARG0:%.*]]) #[[ATTR1]] {
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(nan inf zero sub nnorm) float @llvm.powi.f32.i32(float nofpclass(nan) [[ARG0]], i32 noundef 0) #[[ATTR6]]
+; CHECK-NEXT: ret float [[CALL]]
+;
+ %call = call float @llvm.powi.f32.i32(float %arg0, i32 0)
+ ret float %call
+}
+
+define float @ret_powi_f32_1(float nofpclass(snan) %arg0) #0 {
+; CHECK-LABEL: define nofpclass(snan) float @ret_powi_f32_1
+; CHECK-SAME: (float nofpclass(snan) [[ARG0:%.*]]) #[[ATTR1]] {
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(snan) float @llvm.powi.f32.i32(float nofpclass(snan) [[ARG0]], i32 noundef 1) #[[ATTR6]]
+; CHECK-NEXT: ret float [[CALL]]
+;
+ %call = call float @llvm.powi.f32.i32(float %arg0, i32 1)
+ ret float %call
+}
+
+define float @ret_powi_f32_neg1(float nofpclass(snan) %arg0) #0 {
+; CHECK-LABEL: define nofpclass(snan) float @ret_powi_f32_neg1
+; CHECK-SAME: (float nofpclass(snan) [[ARG0:%.*]]) #[[ATTR1]] {
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(snan) float @llvm.powi.f32.i32(float nofpclass(snan) [[ARG0]], i32 noundef -1) #[[ATTR6]]
+; CHECK-NEXT: ret float [[CALL]]
+;
+ %call = call float @llvm.powi.f32.i32(float %arg0, i32 -1)
+ ret float %call
+}
+
+define float @ret_powi_f32_no_snan(float nofpclass(snan) %arg0, i32 %arg1) #0 {
+; CHECK-LABEL: define nofpclass(snan) float @ret_powi_f32_no_snan
+; CHECK-SAME: (float nofpclass(snan) [[ARG0:%.*]], i32 [[ARG1:%.*]]) #[[ATTR1]] {
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(snan) float @llvm.powi.f32.i32(float nofpclass(snan) [[ARG0]], i32 [[ARG1]]) #[[ATTR6]]
+; CHECK-NEXT: ret float [[CALL]]
+;
+ %call = call float @llvm.powi.f32.i32(float %arg0, i32 %arg1)
+ ret float %call
+}
+
+define float @ret_powi_f32_no_qnan(float nofpclass(qnan) %arg0, i32 %arg1) #0 {
+; CHECK-LABEL: define float @ret_powi_f32_no_qnan
+; CHECK-SAME: (float nofpclass(qnan) [[ARG0:%.*]], i32 [[ARG1:%.*]]) #[[ATTR1]] {
+; CHECK-NEXT: [[CALL:%.*]] = call float @llvm.powi.f32.i32(float nofpclass(qnan) [[ARG0]], i32 [[ARG1]]) #[[ATTR6]]
+; CHECK-NEXT: ret float [[CALL]]
+;
+ %call = call float @llvm.powi.f32.i32(float %arg0, i32 %arg1)
+ ret float %call
+}
+
+define float @ret_powi_f32_no_nan(float nofpclass(nan) %arg0, i32 %arg1) #0 {
+; CHECK-LABEL: define nofpclass(nan) float @ret_powi_f32_no_nan
+; CHECK-SAME: (float nofpclass(nan) [[ARG0:%.*]], i32 [[ARG1:%.*]]) #[[ATTR1]] {
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(nan) float @llvm.powi.f32.i32(float nofpclass(nan) [[ARG0]], i32 [[ARG1]]) #[[ATTR6]]
+; CHECK-NEXT: ret float [[CALL]]
+;
+ %call = call float @llvm.powi.f32.i32(float %arg0, i32 %arg1)
+ ret float %call
+}
+
+define float @ret_powi_f32_no_inf(float nofpclass(inf) %arg0, i32 %arg1) #0 {
+; CHECK-LABEL: define float @ret_powi_f32_no_inf
+; CHECK-SAME: (float nofpclass(inf) [[ARG0:%.*]], i32 [[ARG1:%.*]]) #[[ATTR1]] {
+; CHECK-NEXT: [[CALL:%.*]] = call float @llvm.powi.f32.i32(float nofpclass(inf) [[ARG0]], i32 [[ARG1]]) #[[ATTR6]]
+; CHECK-NEXT: ret float [[CALL]]
+;
+ %call = call float @llvm.powi.f32.i32(float %arg0, i32 %arg1)
+ ret float %call
+}
+
+define float @ret_powi_f32_no_nan_inf(float nofpclass(nan inf) %arg0, i32 %arg1) #0 {
+; CHECK-LABEL: define nofpclass(nan) float @ret_powi_f32_no_nan_inf
+; CHECK-SAME: (float nofpclass(nan inf) [[ARG0:%.*]], i32 [[ARG1:%.*]]) #[[ATTR1]] {
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(nan) float @llvm.powi.f32.i32(float nofpclass(nan inf) [[ARG0]], i32 [[ARG1]]) #[[ATTR6]]
+; CHECK-NEXT: ret float [[CALL]]
+;
+ %call = call float @llvm.powi.f32.i32(float %arg0, i32 %arg1)
+ ret float %call
+}
+
+define float @ret_powi_f32_no_nan__arg1_non0(float nofpclass(nan) %arg0, i32 range(i32 1, 256) %arg1) #0 {
+; CHECK-LABEL: define nofpclass(nan) float @ret_powi_f32_no_nan__arg1_non0
+; CHECK-SAME: (float nofpclass(nan) [[ARG0:%.*]], i32 range(i32 1, 256) [[ARG1:%.*]]) #[[ATTR1]] {
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(nan) float @llvm.powi.f32.i32(float nofpclass(nan) [[ARG0]], i32 [[ARG1]]) #[[ATTR6]]
+; CHECK-NEXT: ret float [[CALL]]
+;
+ %call = call float @llvm.powi.f32.i32(float %arg0, i32 %arg1)
+ ret float %call
+}
+
+define float @ret_powi_f32_no_nan_no_zero__arg1_non0(float nofpclass(nan zero) %arg0, i32 range(i32 1, 256) %arg1) #0 {
+; CHECK-LABEL: define nofpclass(nan) float @ret_powi_f32_no_nan_no_zero__arg1_non0
+; CHECK-SAME: (float nofpclass(nan zero) [[ARG0:%.*]], i32 range(i32 1, 256) [[ARG1:%.*]]) #[[ATTR1]] {
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(nan) float @llvm.powi.f32.i32(float nofpclass(nan zero) [[ARG0]], i32 [[ARG1]]) #[[ATTR6]]
+; CHECK-NEXT: ret float [[CALL]]
+;
+ %call = call float @llvm.powi.f32.i32(float %arg0, i32 %arg1)
+ ret float %call
+}
+
+define float @ret_powi_f32_no_nan_no_zero__arg1__ieee(float nofpclass(nan zero) %arg0, i32 %arg1) #0 {
+; CHECK-LABEL: define nofpclass(nan) float @ret_powi_f32_no_nan_no_zero__arg1__ieee
+; CHECK-SAME: (float nofpclass(nan zero) [[ARG0:%.*]], i32 [[ARG1:%.*]]) #[[ATTR1]] {
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(nan) float @llvm.powi.f32.i32(float nofpclass(nan zero) [[ARG0]], i32 [[ARG1]]) #[[ATTR6]]
+; CHECK-NEXT: ret float [[CALL]]
+;
+ %call = call float @llvm.powi.f32.i32(float %arg0, i32 %arg1)
+ ret float %call
+}
+
+define float @ret_powi_f32_no_nan_no_zero__arg1__daz(float nofpclass(nan zero) %arg0, i32 %arg1) #1 {
+; CHECK-LABEL: define nofpclass(nan) float @ret_powi_f32_no_nan_no_zero__arg1__daz
+; CHECK-SAME: (float nofpclass(nan zero) [[ARG0:%.*]], i32 [[ARG1:%.*]]) #[[ATTR2:[0-9]+]] {
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(nan) float @llvm.powi.f32.i32(float nofpclass(nan zero) [[ARG0]], i32 [[ARG1]]) #[[ATTR6]]
+; CHECK-NEXT: ret float [[CALL]]
+;
+ %call = call float @llvm.powi.f32.i32(float %arg0, i32 %arg1)
+ ret float %call
+}
+
+define float @ret_powi_f32_no_nan_no_zero_nosub__arg1__daz(float nofpclass(nan zero sub) %arg0, i32 %arg1) #1 {
+; CHECK-LABEL: define nofpclass(nan) float @ret_powi_f32_no_nan_no_zero_nosub__arg1__daz
+; CHECK-SAME: (float nofpclass(nan zero sub) [[ARG0:%.*]], i32 [[ARG1:%.*]]) #[[ATTR2]] {
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(nan) float @llvm.powi.f32.i32(float nofpclass(nan zero sub) [[ARG0]], i32 [[ARG1]]) #[[ATTR6]]
+; CHECK-NEXT: ret float [[CALL]]
+;
+ %call = call float @llvm.powi.f32.i32(float %arg0, i32 %arg1)
+ ret float %call
+}
+
define <2 x float> @ret_powi_v2f32_even_nonsplat(<2 x float> %arg0) #0 {
; CHECK-LABEL: define nofpclass(ninf nzero nsub nnorm) <2 x float> @ret_powi_v2f32_even_nonsplat
; CHECK-SAME: (<2 x float> [[ARG0:%.*]]) #[[ATTR1]] {
@@ -165,7 +325,7 @@ define float @ret_powi_f32_nopzero(float nofpclass(pzero) %arg0, i32 %arg1) #0 {
define float @ret_powi_f32_noneg_ftz_daz(float nofpclass(ninf nsub nnorm) %arg0, i32 %arg1) #1 {
; CHECK-LABEL: define float @ret_powi_f32_noneg_ftz_daz
-; CHECK-SAME: (float nofpclass(ninf nsub nnorm) [[ARG0:%.*]], i32 [[ARG1:%.*]]) #[[ATTR2:[0-9]+]] {
+; CHECK-SAME: (float nofpclass(ninf nsub nnorm) [[ARG0:%.*]], i32 [[ARG1:%.*]]) #[[ATTR2]] {
; CHECK-NEXT: [[CALL:%.*]] = call float @llvm.powi.f32.i32(float nofpclass(ninf nsub nnorm) [[ARG0]], i32 [[ARG1]]) #[[ATTR6]]
; CHECK-NEXT: ret float [[CALL]]
;
>From 79e0693c915516c059c915a8c3ee690de44ead21 Mon Sep 17 00:00:00 2001
From: Matt Arsenault <Matthew.Arsenault at amd.com>
Date: Thu, 5 Feb 2026 15:34:51 +0100
Subject: [PATCH 2/2] It was the right way the first time
---
llvm/lib/Support/KnownFPClass.cpp | 9 ++++++---
.../test/Transforms/Attributor/nofpclass-powi.ll | 16 ++++++++--------
2 files changed, 14 insertions(+), 11 deletions(-)
diff --git a/llvm/lib/Support/KnownFPClass.cpp b/llvm/lib/Support/KnownFPClass.cpp
index d2e7a60edb56a..305f85092b016 100644
--- a/llvm/lib/Support/KnownFPClass.cpp
+++ b/llvm/lib/Support/KnownFPClass.cpp
@@ -684,9 +684,12 @@ KnownFPClass KnownFPClass::powi(const KnownFPClass &KnownSrc,
if (ExponentKnownBits.isZero()) {
// powi(QNaN, 0) returns 1.0, and powi(SNaN, 0) may non-deterministically
// return 1.0 or a NaN.
- FPClassTest PossibleVals =
- KnownSrc.isKnownNever(fcSNan) ? fcPosNormal | fcNan : fcPosNormal;
- Known.knownNot(~PossibleVals);
+ if (KnownSrc.isKnownNever(fcSNan)) {
+ Known.knownNot(~fcPosNormal);
+ return Known;
+ }
+
+ Known.knownNot(~(fcPosNormal | fcNan));
return Known;
}
diff --git a/llvm/test/Transforms/Attributor/nofpclass-powi.ll b/llvm/test/Transforms/Attributor/nofpclass-powi.ll
index f2288b6c65b4b..0c1d638269eab 100644
--- a/llvm/test/Transforms/Attributor/nofpclass-powi.ll
+++ b/llvm/test/Transforms/Attributor/nofpclass-powi.ll
@@ -58,9 +58,9 @@ define float @ret_powi_f32_even_constant(float %arg0) #0 {
}
define float @ret_powi_f32_0(float %arg0) #0 {
-; CHECK-LABEL: define nofpclass(nan inf zero sub nnorm) float @ret_powi_f32_0
+; CHECK-LABEL: define nofpclass(inf zero sub nnorm) float @ret_powi_f32_0
; CHECK-SAME: (float [[ARG0:%.*]]) #[[ATTR1]] {
-; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(nan inf zero sub nnorm) float @llvm.powi.f32.i32(float [[ARG0]], i32 noundef 0) #[[ATTR6]]
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(inf zero sub nnorm) float @llvm.powi.f32.i32(float [[ARG0]], i32 noundef 0) #[[ATTR6]]
; CHECK-NEXT: ret float [[CALL]]
;
%call = call float @llvm.powi.f32.i32(float %arg0, i32 0)
@@ -68,9 +68,9 @@ define float @ret_powi_f32_0(float %arg0) #0 {
}
define float @ret_powi_f32_0_no_snan(float nofpclass(snan) %arg0) #0 {
-; CHECK-LABEL: define nofpclass(nan inf zero sub nnorm) float @ret_powi_f32_0_no_snan
+; CHECK-LABEL: define nofpclass(inf zero sub nnorm) float @ret_powi_f32_0_no_snan
; CHECK-SAME: (float nofpclass(snan) [[ARG0:%.*]]) #[[ATTR1]] {
-; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(nan inf zero sub nnorm) float @llvm.powi.f32.i32(float nofpclass(snan) [[ARG0]], i32 noundef 0) #[[ATTR6]]
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(inf zero sub nnorm) float @llvm.powi.f32.i32(float nofpclass(snan) [[ARG0]], i32 noundef 0) #[[ATTR6]]
; CHECK-NEXT: ret float [[CALL]]
;
%call = call float @llvm.powi.f32.i32(float %arg0, i32 0)
@@ -78,9 +78,9 @@ define float @ret_powi_f32_0_no_snan(float nofpclass(snan) %arg0) #0 {
}
define float @ret_powi_f32_0_no_qnan(float nofpclass(qnan) %arg0) #0 {
-; CHECK-LABEL: define nofpclass(nan inf zero sub nnorm) float @ret_powi_f32_0_no_qnan
+; CHECK-LABEL: define nofpclass(inf zero sub nnorm) float @ret_powi_f32_0_no_qnan
; CHECK-SAME: (float nofpclass(qnan) [[ARG0:%.*]]) #[[ATTR1]] {
-; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(nan inf zero sub nnorm) float @llvm.powi.f32.i32(float nofpclass(qnan) [[ARG0]], i32 noundef 0) #[[ATTR6]]
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(inf zero sub nnorm) float @llvm.powi.f32.i32(float nofpclass(qnan) [[ARG0]], i32 noundef 0) #[[ATTR6]]
; CHECK-NEXT: ret float [[CALL]]
;
%call = call float @llvm.powi.f32.i32(float %arg0, i32 0)
@@ -88,9 +88,9 @@ define float @ret_powi_f32_0_no_qnan(float nofpclass(qnan) %arg0) #0 {
}
define float @ret_powi_f32_0_no_nan(float nofpclass(nan) %arg0) #0 {
-; CHECK-LABEL: define nofpclass(nan inf zero sub nnorm) float @ret_powi_f32_0_no_nan
+; CHECK-LABEL: define nofpclass(inf zero sub nnorm) float @ret_powi_f32_0_no_nan
; CHECK-SAME: (float nofpclass(nan) [[ARG0:%.*]]) #[[ATTR1]] {
-; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(nan inf zero sub nnorm) float @llvm.powi.f32.i32(float nofpclass(nan) [[ARG0]], i32 noundef 0) #[[ATTR6]]
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(inf zero sub nnorm) float @llvm.powi.f32.i32(float nofpclass(nan) [[ARG0]], i32 noundef 0) #[[ATTR6]]
; CHECK-NEXT: ret float [[CALL]]
;
%call = call float @llvm.powi.f32.i32(float %arg0, i32 0)
More information about the llvm-commits
mailing list