[llvm] InstCombine: Handle canonicalize in SimplifyDemandedFPClass (PR #173189)
Matt Arsenault via llvm-commits
llvm-commits at lists.llvm.org
Wed Dec 24 13:48:48 PST 2025
https://github.com/arsenm updated https://github.com/llvm/llvm-project/pull/173189
>From e52a8a400b5ff9c2e747eae9dfa45c7fbff48ddd Mon Sep 17 00:00:00 2001
From: Matt Arsenault <Matthew.Arsenault at amd.com>
Date: Sat, 20 Dec 2025 11:18:24 +0100
Subject: [PATCH 1/3] InstCombine: Handle canonicalize in
SimplifyDemandedFPClass
Doesn't try to handle PositiveZero flushing mode, but I
don't believe it is incorrect with it.
---
llvm/include/llvm/Support/KnownFPClass.h | 5 ++
llvm/lib/Analysis/ValueTracking.cpp | 55 ++------------
llvm/lib/Support/KnownFPClass.cpp | 48 ++++++++++++
.../InstCombineSimplifyDemanded.cpp | 73 +++++++++++++++++++
.../simplify-demanded-fpclass-canonicalize.ll | 73 ++++++-------------
5 files changed, 156 insertions(+), 98 deletions(-)
diff --git a/llvm/include/llvm/Support/KnownFPClass.h b/llvm/include/llvm/Support/KnownFPClass.h
index b3c18bcf6b34b..e50b97ac7f00d 100644
--- a/llvm/include/llvm/Support/KnownFPClass.h
+++ b/llvm/include/llvm/Support/KnownFPClass.h
@@ -155,6 +155,11 @@ struct KnownFPClass {
signBitMustBeZero();
}
+ /// Apply the canonicalize intrinsic to this value. This is essentially a
+ /// stronger form of propagateCanonicalizingSrc.
+ LLVM_ABI void
+ canonicalize(DenormalMode DenormMode = DenormalMode::getDynamic());
+
/// Return true if the sign bit must be 0, ignoring the sign of nans.
bool signBitIsZeroOrNaN() const { return isKnownNever(fcNegative); }
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 6d8c966deb7e0..a2dcfb3e905a5 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -5272,58 +5272,15 @@ void computeKnownFPClass(const Value *V, const APInt &DemandedElts,
break;
}
case Intrinsic::canonicalize: {
- KnownFPClass KnownSrc;
computeKnownFPClass(II->getArgOperand(0), DemandedElts, InterestedClasses,
- KnownSrc, Q, Depth + 1);
-
- // This is essentially a stronger form of
- // propagateCanonicalizingSrc. Other "canonicalizing" operations don't
- // actually have an IR canonicalization guarantee.
-
- // Canonicalize may flush denormals to zero, so we have to consider the
- // denormal mode to preserve known-not-0 knowledge.
- Known.KnownFPClasses = KnownSrc.KnownFPClasses | fcZero | fcQNan;
-
- // Stronger version of propagateNaN
- // Canonicalize is guaranteed to quiet signaling nans.
- if (KnownSrc.isKnownNeverNaN())
- Known.knownNot(fcNan);
- else
- Known.knownNot(fcSNan);
+ Known, Q, Depth + 1);
const Function *F = II->getFunction();
- if (!F)
- break;
-
- // If the parent function flushes denormals, the canonical output cannot
- // be a denormal.
- const fltSemantics &FPType =
- II->getType()->getScalarType()->getFltSemantics();
- DenormalMode DenormMode = F->getDenormalMode(FPType);
- if (DenormMode == DenormalMode::getIEEE()) {
- if (KnownSrc.isKnownNever(fcPosZero))
- Known.knownNot(fcPosZero);
- if (KnownSrc.isKnownNever(fcNegZero))
- Known.knownNot(fcNegZero);
- break;
- }
-
- if (DenormMode.inputsAreZero() || DenormMode.outputsAreZero())
- Known.knownNot(fcSubnormal);
-
- if (DenormMode == DenormalMode::getPreserveSign()) {
- if (KnownSrc.isKnownNever(fcPosZero | fcPosSubnormal))
- Known.knownNot(fcPosZero);
- if (KnownSrc.isKnownNever(fcNegZero | fcNegSubnormal))
- Known.knownNot(fcNegZero);
- break;
- }
-
- if (DenormMode.Input == DenormalMode::PositiveZero ||
- (DenormMode.Output == DenormalMode::PositiveZero &&
- DenormMode.Input == DenormalMode::IEEE))
- Known.knownNot(fcNegZero);
-
+ DenormalMode DenormMode =
+ F ? F->getDenormalMode(
+ II->getType()->getScalarType()->getFltSemantics())
+ : DenormalMode::getDynamic();
+ Known.canonicalize(DenormMode);
break;
}
case Intrinsic::vector_reduce_fmax:
diff --git a/llvm/lib/Support/KnownFPClass.cpp b/llvm/lib/Support/KnownFPClass.cpp
index 43fb2e7108d2b..cb9563e0e52c3 100644
--- a/llvm/lib/Support/KnownFPClass.cpp
+++ b/llvm/lib/Support/KnownFPClass.cpp
@@ -87,6 +87,54 @@ void KnownFPClass::propagateDenormal(const KnownFPClass &Src,
}
}
+void KnownFPClass::canonicalize(DenormalMode DenormMode) {
+ KnownFPClass KnownSrc = *this;
+ *this = KnownFPClass();
+
+ // This is essentially a stronger form of
+ // propagateCanonicalizingSrc. Other "canonicalizing" operations don't
+ // actually have an IR canonicalization guarantee.
+
+ // Canonicalize may flush denormals to zero, so we have to consider the
+ // denormal mode to preserve known-not-0 knowledge.
+ KnownFPClasses = KnownSrc.KnownFPClasses | fcZero | fcQNan;
+
+ // Stronger version of propagateNaN
+ // Canonicalize is guaranteed to quiet signaling nans.
+ if (KnownSrc.isKnownNeverNaN())
+ knownNot(fcNan);
+ else
+ knownNot(fcSNan);
+
+ // FIXME: Missing check of IEEE like types.
+
+ // If the parent function flushes denormals, the canonical output cannot be a
+ // denormal.
+ if (DenormMode == DenormalMode::getIEEE()) {
+ if (KnownSrc.isKnownNever(fcPosZero))
+ knownNot(fcPosZero);
+ if (KnownSrc.isKnownNever(fcNegZero))
+ knownNot(fcNegZero);
+ return;
+ }
+
+ if (DenormMode.inputsAreZero() || DenormMode.outputsAreZero())
+ knownNot(fcSubnormal);
+
+ if (DenormMode == DenormalMode::getPreserveSign()) {
+ if (KnownSrc.isKnownNever(fcPosZero | fcPosSubnormal))
+ knownNot(fcPosZero);
+ if (KnownSrc.isKnownNever(fcNegZero | fcNegSubnormal))
+ knownNot(fcNegZero);
+ return;
+ }
+
+ if (DenormMode.Input == DenormalMode::PositiveZero ||
+ (DenormMode.Output == DenormalMode::PositiveZero &&
+ DenormMode.Input == DenormalMode::IEEE))
+ knownNot(fcNegZero);
+}
+
void KnownFPClass::propagateCanonicalizingSrc(const KnownFPClass &Src,
DenormalMode Mode) {
propagateDenormal(Src, Mode);
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
index 550dfc57a348b..3df89c930967e 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
@@ -2106,6 +2106,79 @@ Value *InstCombinerImpl::SimplifyDemandedUseFPClass(Value *V,
Known.copysign(KnownSign);
break;
}
+ case Intrinsic::canonicalize: {
+ Type *EltTy = VTy->getScalarType();
+
+ // TODO: This could have more refined support for PositiveZero denormal
+ // mode.
+ if (EltTy->isIEEELikeFPTy()) {
+ DenormalMode Mode = F.getDenormalMode(EltTy->getFltSemantics());
+
+ FPClassTest SrcDemandedMask = DemandedMask;
+
+ // A demanded quiet nan result may have come from a signaling nan, so we
+ // need to expand the demanded mask.
+ if ((DemandedMask & fcQNan) != fcNone)
+ SrcDemandedMask |= fcSNan;
+
+ // This cannot have produced a signaling nan, so we can trim any snan
+ // inputs.
+ if ((DemandedMask & fcSNan) != fcNone)
+ SrcDemandedMask &= ~fcSNan;
+
+ if (Mode != DenormalMode::getIEEE()) {
+ // Any zero results may have come from flushed denormals.
+ if (DemandedMask & fcPosZero)
+ SrcDemandedMask |= fcPosSubnormal;
+ if (DemandedMask & fcNegZero)
+ SrcDemandedMask |= fcNegSubnormal;
+ }
+
+ if (Mode == DenormalMode::getPreserveSign()) {
+ // If a denormal input will be flushed, and we don't need zeros, we
+ // don't need denormals either.
+ if ((DemandedMask & fcPosZero) == fcNone)
+ SrcDemandedMask &= ~fcPosSubnormal;
+
+ if ((DemandedMask & fcNegZero) == fcNone)
+ SrcDemandedMask &= ~fcNegSubnormal;
+ }
+
+ KnownFPClass KnownSrc;
+
+ // Simplify upstream operations before trying to simplify this call.
+ if (SimplifyDemandedFPClass(I, 0, SrcDemandedMask, KnownSrc, Depth + 1))
+ return I;
+
+ Known = KnownSrc;
+
+ // Perform the canonicalization to see if this folded to a constant.
+ Known.canonicalize(Mode);
+
+ if (Constant *SingleVal =
+ getFPClassConstant(VTy, DemandedMask & Known.KnownFPClasses))
+ return SingleVal;
+
+ if (Mode == DenormalMode::getIEEE()) {
+ // For IEEE handling, there is only a bit change for nan inputs, so we
+ // can drop it if we do not demand nan results or we know the input
+ // isn't a nan.
+ if (KnownSrc.isKnownNeverNaN() || (DemandedMask & fcNan) == fcNone)
+ return CI->getArgOperand(0);
+ } else {
+ // Nan handling is the same as the IEEE case, but we also need to
+ // avoid denormal inputs to drop the canonicalize.
+ if ((KnownSrc.isKnownNeverNaN() ||
+ (DemandedMask & fcNan) == fcNone) &&
+ KnownSrc.isKnownNeverSubnormal())
+ return CI->getArgOperand(0);
+ }
+
+ return nullptr;
+ }
+
+ [[fallthrough]];
+ }
default:
Known = computeKnownFPClass(I, ~DemandedMask, CxtI, Depth + 1);
break;
diff --git a/llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-canonicalize.ll b/llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-canonicalize.ll
index b23e0a5e7c615..0c305762d5c36 100644
--- a/llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-canonicalize.ll
+++ b/llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-canonicalize.ll
@@ -71,8 +71,7 @@ define nofpclass(nan pzero) float @ret_nofpclass_nan_pzero__canonicalize_select_
; CHECK-LABEL: define nofpclass(nan pzero) float @ret_nofpclass_nan_pzero__canonicalize_select_psub_daz(
; CHECK-SAME: float [[X:%.*]], i1 [[COND:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[PSUB:%.*]] = call float @returns_psub()
-; CHECK-NEXT: [[SELECT:%.*]] = select i1 [[COND]], float [[X]], float [[PSUB]]
-; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[SELECT]])
+; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[X]])
; CHECK-NEXT: ret float [[CANON]]
;
%psub = call float @returns_psub()
@@ -85,8 +84,7 @@ define nofpclass(nan nzero) float @ret_nofpclass_nan_nzero__canonicalize_select_
; CHECK-LABEL: define nofpclass(nan nzero) float @ret_nofpclass_nan_nzero__canonicalize_select_nsub_daz(
; CHECK-SAME: float [[X:%.*]], i1 [[COND:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[NSUB:%.*]] = call float @returns_nsub()
-; CHECK-NEXT: [[SELECT:%.*]] = select i1 [[COND]], float [[X]], float [[NSUB]]
-; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[SELECT]])
+; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[X]])
; CHECK-NEXT: ret float [[CANON]]
;
%nsub = call float @returns_nsub()
@@ -99,8 +97,7 @@ define nofpclass(nan zero) float @ret_nofpclass_nan_zero__canonicalize_select_su
; CHECK-LABEL: define nofpclass(nan zero) float @ret_nofpclass_nan_zero__canonicalize_select_sub_daz(
; CHECK-SAME: float [[X:%.*]], i1 [[COND:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[SUB:%.*]] = call float @returns_sub()
-; CHECK-NEXT: [[SELECT:%.*]] = select i1 [[COND]], float [[X]], float [[SUB]]
-; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[SELECT]])
+; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[X]])
; CHECK-NEXT: ret float [[CANON]]
;
%sub = call float @returns_sub()
@@ -112,8 +109,7 @@ define nofpclass(nan zero) float @ret_nofpclass_nan_zero__canonicalize_select_su
define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_psub_ieee() {
; CHECK-LABEL: define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_psub_ieee() {
; CHECK-NEXT: [[PSUB:%.*]] = call float @returns_psub()
-; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[PSUB]])
-; CHECK-NEXT: ret float [[CANON]]
+; CHECK-NEXT: ret float [[PSUB]]
;
%psub = call float @returns_psub()
%canon = call float @llvm.canonicalize.f32(float %psub)
@@ -123,8 +119,7 @@ define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_psub_ieee() {
define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_nsub_ieee() {
; CHECK-LABEL: define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_nsub_ieee() {
; CHECK-NEXT: [[NSUB:%.*]] = call float @returns_nsub()
-; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[NSUB]])
-; CHECK-NEXT: ret float [[CANON]]
+; CHECK-NEXT: ret float [[NSUB]]
;
%nsub = call float @returns_nsub()
%canon = call float @llvm.canonicalize.f32(float %nsub)
@@ -134,8 +129,7 @@ define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_nsub_ieee() {
define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_sub_ieee() {
; CHECK-LABEL: define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_sub_ieee() {
; CHECK-NEXT: [[SUB:%.*]] = call float @returns_sub()
-; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[SUB]])
-; CHECK-NEXT: ret float [[CANON]]
+; CHECK-NEXT: ret float [[SUB]]
;
%sub = call float @returns_sub()
%canon = call float @llvm.canonicalize.f32(float %sub)
@@ -180,8 +174,7 @@ define nofpclass(zero) <2 x float> @ret_nofpclass_zero__canonicalize_daz_vec(<2
; CHECK-LABEL: define nofpclass(zero) <2 x float> @ret_nofpclass_zero__canonicalize_daz_vec(
; CHECK-SAME: <2 x float> [[X:%.*]], <2 x i1> [[COND:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[SUB:%.*]] = call <2 x float> @returns_sub_vec()
-; CHECK-NEXT: [[SELECT:%.*]] = select <2 x i1> [[COND]], <2 x float> [[X]], <2 x float> [[SUB]]
-; CHECK-NEXT: [[CANON:%.*]] = call <2 x float> @llvm.canonicalize.v2f32(<2 x float> [[SELECT]])
+; CHECK-NEXT: [[CANON:%.*]] = call <2 x float> @llvm.canonicalize.v2f32(<2 x float> [[X]])
; CHECK-NEXT: ret <2 x float> [[CANON]]
;
%sub = call <2 x float> @returns_sub_vec()
@@ -194,8 +187,7 @@ define nofpclass(zero sub) float @ret_nofpclass_sub_zero__canonicalize_daz(float
; CHECK-LABEL: define nofpclass(zero sub) float @ret_nofpclass_sub_zero__canonicalize_daz(
; CHECK-SAME: float [[X:%.*]], i1 [[COND:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[SUB_OR_ZERO:%.*]] = call float @returns_sub_zero()
-; CHECK-NEXT: [[SELECT:%.*]] = select i1 [[COND]], float [[X]], float [[SUB_OR_ZERO]]
-; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[SELECT]])
+; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[X]])
; CHECK-NEXT: ret float [[CANON]]
;
%sub_or_zero = call float @returns_sub_zero()
@@ -207,8 +199,7 @@ define nofpclass(zero sub) float @ret_nofpclass_sub_zero__canonicalize_daz(float
define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_ieee(float %unknown) {
; CHECK-LABEL: define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_ieee(
; CHECK-SAME: float [[UNKNOWN:%.*]]) {
-; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[UNKNOWN]])
-; CHECK-NEXT: ret float [[CANON]]
+; CHECK-NEXT: ret float [[UNKNOWN]]
;
%canon = call float @llvm.canonicalize.f32(float %unknown)
ret float %canon
@@ -308,8 +299,7 @@ define nofpclass(qnan) float @ret_nofpclass_qnan__canonicalize_select_unknown_or
; CHECK-LABEL: define nofpclass(qnan) float @ret_nofpclass_qnan__canonicalize_select_unknown_or_snan(
; CHECK-SAME: float [[X:%.*]], i1 [[COND:%.*]]) {
; CHECK-NEXT: [[SNAN:%.*]] = call float @returns_snan()
-; CHECK-NEXT: [[SELECT:%.*]] = select i1 [[COND]], float [[X]], float [[SNAN]]
-; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[SELECT]])
+; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[X]])
; CHECK-NEXT: ret float [[CANON]]
;
%snan = call float @returns_snan()
@@ -323,9 +313,7 @@ define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_select_unknown_or_s
; CHECK-LABEL: define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_select_unknown_or_snan(
; CHECK-SAME: float [[X:%.*]], i1 [[COND:%.*]]) {
; CHECK-NEXT: [[SNAN:%.*]] = call float @returns_snan()
-; CHECK-NEXT: [[SELECT:%.*]] = select i1 [[COND]], float [[X]], float [[SNAN]]
-; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[SELECT]])
-; CHECK-NEXT: ret float [[CANON]]
+; CHECK-NEXT: ret float [[X]]
;
%snan = call float @returns_snan()
%select = select i1 %cond, float %x, float %snan
@@ -337,8 +325,7 @@ define nofpclass(zero) float @ret_nofpclass_zero_nnan_flag__canonicalize_select_
; CHECK-LABEL: define nofpclass(zero) float @ret_nofpclass_zero_nnan_flag__canonicalize_select_unknown_or_snan(
; CHECK-SAME: float [[X:%.*]], i1 [[COND:%.*]]) {
; CHECK-NEXT: [[SNAN:%.*]] = call float @returns_snan()
-; CHECK-NEXT: [[SELECT:%.*]] = select i1 [[COND]], float [[X]], float [[SNAN]]
-; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[SELECT]])
+; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[X]])
; CHECK-NEXT: ret float [[CANON]]
;
%snan = call float @returns_snan()
@@ -354,8 +341,7 @@ define nofpclass(zero) float @ret_nofpclass_zero_nnan_flag__canonicalize_select_
define nofpclass(zero) float @ret_nofpclass_zero__canonicalize_nnan_src_ieee(float nofpclass(nan) %x, i1 %cond) {
; CHECK-LABEL: define nofpclass(zero) float @ret_nofpclass_zero__canonicalize_nnan_src_ieee(
; CHECK-SAME: float nofpclass(nan) [[X:%.*]], i1 [[COND:%.*]]) {
-; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[X]])
-; CHECK-NEXT: ret float [[CANON]]
+; CHECK-NEXT: ret float [[X]]
;
%canon = call float @llvm.canonicalize.f32(float %x)
ret float %canon
@@ -385,8 +371,7 @@ define nofpclass(zero) float @ret_nofpclass_zero__canonicalize_no_sub_src_daz(fl
define nofpclass(zero) float @ret_nofpclass_zero__canonicalize_no_sub_no_nan_src_daz(float nofpclass(nan sub) %x, i1 %cond) #0 {
; CHECK-LABEL: define nofpclass(zero) float @ret_nofpclass_zero__canonicalize_no_sub_no_nan_src_daz(
; CHECK-SAME: float nofpclass(nan sub) [[X:%.*]], i1 [[COND:%.*]]) #[[ATTR0]] {
-; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[X]])
-; CHECK-NEXT: ret float [[CANON]]
+; CHECK-NEXT: ret float [[X]]
;
%canon = call float @llvm.canonicalize.f32(float %x)
ret float %canon
@@ -395,8 +380,7 @@ define nofpclass(zero) float @ret_nofpclass_zero__canonicalize_no_sub_no_nan_src
define nofpclass(zero) float @ret_nofpclass_zero__canonicalize_no_sub_no_nan_src_dynamic(float nofpclass(nan sub) %x, i1 %cond) #1 {
; CHECK-LABEL: define nofpclass(zero) float @ret_nofpclass_zero__canonicalize_no_sub_no_nan_src_dynamic(
; CHECK-SAME: float nofpclass(nan sub) [[X:%.*]], i1 [[COND:%.*]]) #[[ATTR1]] {
-; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[X]])
-; CHECK-NEXT: ret float [[CANON]]
+; CHECK-NEXT: ret float [[X]]
;
%canon = call float @llvm.canonicalize.f32(float %x)
ret float %canon
@@ -406,8 +390,7 @@ define nofpclass(zero) float @ret_nofpclass_zero__canonicalize_no_sub_no_nan_src
define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_no_sub_src_daz(float nofpclass(sub) %x, i1 %cond) #0 {
; CHECK-LABEL: define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_no_sub_src_daz(
; CHECK-SAME: float nofpclass(sub) [[X:%.*]], i1 [[COND:%.*]]) #[[ATTR0]] {
-; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[X]])
-; CHECK-NEXT: ret float [[CANON]]
+; CHECK-NEXT: ret float [[X]]
;
%canon = call float @llvm.canonicalize.f32(float %x)
ret float %canon
@@ -438,8 +421,7 @@ define nofpclass(snan) float @ret_nofpclass_snan__canonicalize_no_sub_src_daz(fl
define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_no_sub_src_dynamic(float nofpclass(sub) %x, i1 %cond) #1 {
; CHECK-LABEL: define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_no_sub_src_dynamic(
; CHECK-SAME: float nofpclass(sub) [[X:%.*]], i1 [[COND:%.*]]) #[[ATTR1]] {
-; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[X]])
-; CHECK-NEXT: ret float [[CANON]]
+; CHECK-NEXT: ret float [[X]]
;
%canon = call float @llvm.canonicalize.f32(float %x)
ret float %canon
@@ -517,8 +499,7 @@ define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_sub_dynamic()
define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_ninf__dynamic(i1 %cond, float nofpclass(sub norm zero pinf) %must.be.ninf.or.nan) #1 {
; CHECK-LABEL: define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_ninf__dynamic(
; CHECK-SAME: i1 [[COND:%.*]], float nofpclass(pinf zero sub norm) [[MUST_BE_NINF_OR_NAN:%.*]]) #[[ATTR1]] {
-; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[MUST_BE_NINF_OR_NAN]])
-; CHECK-NEXT: ret float [[CANON]]
+; CHECK-NEXT: ret float 0xFFF0000000000000
;
%canon = call float @llvm.canonicalize.f32(float %must.be.ninf.or.nan)
ret float %canon
@@ -527,8 +508,7 @@ define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_ninf__dynamic(
define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_pinf__dynamic(i1 %cond, float nofpclass(sub norm zero pinf) %must.be.pinf.or.nan) #1 {
; CHECK-LABEL: define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_pinf__dynamic(
; CHECK-SAME: i1 [[COND:%.*]], float nofpclass(pinf zero sub norm) [[MUST_BE_PINF_OR_NAN:%.*]]) #[[ATTR1]] {
-; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[MUST_BE_PINF_OR_NAN]])
-; CHECK-NEXT: ret float [[CANON]]
+; CHECK-NEXT: ret float 0xFFF0000000000000
;
%canon = call float @llvm.canonicalize.f32(float %must.be.pinf.or.nan)
ret float %canon
@@ -537,8 +517,7 @@ define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_pinf__dynamic(
define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_inf__dynamic(i1 %cond, float nofpclass(sub norm zero) %must.be.inf.or.nan) #1 {
; CHECK-LABEL: define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_inf__dynamic(
; CHECK-SAME: i1 [[COND:%.*]], float nofpclass(zero sub norm) [[MUST_BE_INF_OR_NAN:%.*]]) #[[ATTR1]] {
-; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[MUST_BE_INF_OR_NAN]])
-; CHECK-NEXT: ret float [[CANON]]
+; CHECK-NEXT: ret float [[MUST_BE_INF_OR_NAN]]
;
%canon = call float @llvm.canonicalize.f32(float %must.be.inf.or.nan)
ret float %canon
@@ -547,8 +526,7 @@ define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_inf__dynamic(i
define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_pzero__dynamic(i1 %cond, float nofpclass(sub norm nzero inf) %must.be.pzero.or.nan) #1 {
; CHECK-LABEL: define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_pzero__dynamic(
; CHECK-SAME: i1 [[COND:%.*]], float nofpclass(inf nzero sub norm) [[MUST_BE_PZERO_OR_NAN:%.*]]) #[[ATTR1]] {
-; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[MUST_BE_PZERO_OR_NAN]])
-; CHECK-NEXT: ret float [[CANON]]
+; CHECK-NEXT: ret float 0.000000e+00
;
%canon = call float @llvm.canonicalize.f32(float %must.be.pzero.or.nan)
ret float %canon
@@ -557,8 +535,7 @@ define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_pzero__dynamic
define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_nzero__dynamic(i1 %cond, float nofpclass(sub norm pzero inf) %must.be.nzero.or.nan) #1 {
; CHECK-LABEL: define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_nzero__dynamic(
; CHECK-SAME: i1 [[COND:%.*]], float nofpclass(inf pzero sub norm) [[MUST_BE_NZERO_OR_NAN:%.*]]) #[[ATTR1]] {
-; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[MUST_BE_NZERO_OR_NAN]])
-; CHECK-NEXT: ret float [[CANON]]
+; CHECK-NEXT: ret float -0.000000e+00
;
%canon = call float @llvm.canonicalize.f32(float %must.be.nzero.or.nan)
ret float %canon
@@ -567,8 +544,7 @@ define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_nzero__dynamic
define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_zero__ieee(i1 %cond, float nofpclass(sub norm inf) %must.be.zero.or.nan) {
; CHECK-LABEL: define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_zero__ieee(
; CHECK-SAME: i1 [[COND:%.*]], float nofpclass(inf sub norm) [[MUST_BE_ZERO_OR_NAN:%.*]]) {
-; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[MUST_BE_ZERO_OR_NAN]])
-; CHECK-NEXT: ret float [[CANON]]
+; CHECK-NEXT: ret float [[MUST_BE_ZERO_OR_NAN]]
;
%canon = call float @llvm.canonicalize.f32(float %must.be.zero.or.nan)
ret float %canon
@@ -577,8 +553,7 @@ define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_zero__ieee(i1
define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_zero__dynamic(i1 %cond, float nofpclass(sub norm inf) %must.be.zero.or.nan) #1 {
; CHECK-LABEL: define nofpclass(nan) float @ret_nofpclass_nan__canonicalize_only_zero__dynamic(
; CHECK-SAME: i1 [[COND:%.*]], float nofpclass(inf sub norm) [[MUST_BE_ZERO_OR_NAN:%.*]]) #[[ATTR1]] {
-; CHECK-NEXT: [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[MUST_BE_ZERO_OR_NAN]])
-; CHECK-NEXT: ret float [[CANON]]
+; CHECK-NEXT: ret float [[MUST_BE_ZERO_OR_NAN]]
;
%canon = call float @llvm.canonicalize.f32(float %must.be.zero.or.nan)
ret float %canon
>From 5e35cbb23ddf840a9f17b2efdf011ae78065ab6a Mon Sep 17 00:00:00 2001
From: Matt Arsenault <Matthew.Arsenault at amd.com>
Date: Tue, 23 Dec 2025 20:20:25 +0100
Subject: [PATCH 2/3] Make KnownFPClass::canonicalize not side-effecting
---
llvm/include/llvm/Support/KnownFPClass.h | 5 ++--
llvm/lib/Analysis/ValueTracking.cpp | 5 ++--
llvm/lib/Support/KnownFPClass.cpp | 30 ++++++++++---------
.../InstCombineSimplifyDemanded.cpp | 4 +--
4 files changed, 23 insertions(+), 21 deletions(-)
diff --git a/llvm/include/llvm/Support/KnownFPClass.h b/llvm/include/llvm/Support/KnownFPClass.h
index e50b97ac7f00d..7fe6197cb84aa 100644
--- a/llvm/include/llvm/Support/KnownFPClass.h
+++ b/llvm/include/llvm/Support/KnownFPClass.h
@@ -157,8 +157,9 @@ struct KnownFPClass {
/// Apply the canonicalize intrinsic to this value. This is essentially a
/// stronger form of propagateCanonicalizingSrc.
- LLVM_ABI void
- canonicalize(DenormalMode DenormMode = DenormalMode::getDynamic());
+ LLVM_ABI static KnownFPClass
+ canonicalize(const KnownFPClass &Src,
+ DenormalMode DenormMode = DenormalMode::getDynamic());
/// Return true if the sign bit must be 0, ignoring the sign of nans.
bool signBitIsZeroOrNaN() const { return isKnownNever(fcNegative); }
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index a2dcfb3e905a5..d866446051fe2 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -5272,15 +5272,16 @@ void computeKnownFPClass(const Value *V, const APInt &DemandedElts,
break;
}
case Intrinsic::canonicalize: {
+ KnownFPClass KnownSrc;
computeKnownFPClass(II->getArgOperand(0), DemandedElts, InterestedClasses,
- Known, Q, Depth + 1);
+ KnownSrc, Q, Depth + 1);
const Function *F = II->getFunction();
DenormalMode DenormMode =
F ? F->getDenormalMode(
II->getType()->getScalarType()->getFltSemantics())
: DenormalMode::getDynamic();
- Known.canonicalize(DenormMode);
+ Known = KnownFPClass::canonicalize(KnownSrc, DenormMode);
break;
}
case Intrinsic::vector_reduce_fmax:
diff --git a/llvm/lib/Support/KnownFPClass.cpp b/llvm/lib/Support/KnownFPClass.cpp
index cb9563e0e52c3..556a3b165d80d 100644
--- a/llvm/lib/Support/KnownFPClass.cpp
+++ b/llvm/lib/Support/KnownFPClass.cpp
@@ -87,9 +87,9 @@ void KnownFPClass::propagateDenormal(const KnownFPClass &Src,
}
}
-void KnownFPClass::canonicalize(DenormalMode DenormMode) {
- KnownFPClass KnownSrc = *this;
- *this = KnownFPClass();
+KnownFPClass KnownFPClass::canonicalize(const KnownFPClass &KnownSrc,
+ DenormalMode DenormMode) {
+ KnownFPClass Known;
// This is essentially a stronger form of
// propagateCanonicalizingSrc. Other "canonicalizing" operations don't
@@ -97,14 +97,14 @@ void KnownFPClass::canonicalize(DenormalMode DenormMode) {
// Canonicalize may flush denormals to zero, so we have to consider the
// denormal mode to preserve known-not-0 knowledge.
- KnownFPClasses = KnownSrc.KnownFPClasses | fcZero | fcQNan;
+ Known.KnownFPClasses = KnownSrc.KnownFPClasses | fcZero | fcQNan;
// Stronger version of propagateNaN
// Canonicalize is guaranteed to quiet signaling nans.
if (KnownSrc.isKnownNeverNaN())
- knownNot(fcNan);
+ Known.knownNot(fcNan);
else
- knownNot(fcSNan);
+ Known.knownNot(fcSNan);
// FIXME: Missing check of IEEE like types.
@@ -112,27 +112,29 @@ void KnownFPClass::canonicalize(DenormalMode DenormMode) {
// denormal.
if (DenormMode == DenormalMode::getIEEE()) {
if (KnownSrc.isKnownNever(fcPosZero))
- knownNot(fcPosZero);
+ Known.knownNot(fcPosZero);
if (KnownSrc.isKnownNever(fcNegZero))
- knownNot(fcNegZero);
- return;
+ Known.knownNot(fcNegZero);
+ return Known;
}
if (DenormMode.inputsAreZero() || DenormMode.outputsAreZero())
- knownNot(fcSubnormal);
+ Known.knownNot(fcSubnormal);
if (DenormMode == DenormalMode::getPreserveSign()) {
if (KnownSrc.isKnownNever(fcPosZero | fcPosSubnormal))
- knownNot(fcPosZero);
+ Known.knownNot(fcPosZero);
if (KnownSrc.isKnownNever(fcNegZero | fcNegSubnormal))
- knownNot(fcNegZero);
- return;
+ Known.knownNot(fcNegZero);
+ return Known;
}
if (DenormMode.Input == DenormalMode::PositiveZero ||
(DenormMode.Output == DenormalMode::PositiveZero &&
DenormMode.Input == DenormalMode::IEEE))
- knownNot(fcNegZero);
+ Known.knownNot(fcNegZero);
+
+ return Known;
}
void KnownFPClass::propagateCanonicalizingSrc(const KnownFPClass &Src,
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
index 3df89c930967e..579cbd07fbc0f 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
@@ -2150,10 +2150,8 @@ Value *InstCombinerImpl::SimplifyDemandedUseFPClass(Value *V,
if (SimplifyDemandedFPClass(I, 0, SrcDemandedMask, KnownSrc, Depth + 1))
return I;
- Known = KnownSrc;
-
// Perform the canonicalization to see if this folded to a constant.
- Known.canonicalize(Mode);
+ Known = KnownFPClass::canonicalize(KnownSrc, Mode);
if (Constant *SingleVal =
getFPClassConstant(VTy, DemandedMask & Known.KnownFPClasses))
>From 3c70f790267dc0e5c26ed75c341aff3e1434c01e Mon Sep 17 00:00:00 2001
From: Matt Arsenault <Matthew.Arsenault at amd.com>
Date: Wed, 24 Dec 2025 20:30:49 +0100
Subject: [PATCH 3/3] Address comment
---
.../InstCombineSimplifyDemanded.cpp | 23 ++++++++-----------
1 file changed, 9 insertions(+), 14 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
index 579cbd07fbc0f..4cd0e4fe45c7f 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
@@ -2157,20 +2157,15 @@ Value *InstCombinerImpl::SimplifyDemandedUseFPClass(Value *V,
getFPClassConstant(VTy, DemandedMask & Known.KnownFPClasses))
return SingleVal;
- if (Mode == DenormalMode::getIEEE()) {
- // For IEEE handling, there is only a bit change for nan inputs, so we
- // can drop it if we do not demand nan results or we know the input
- // isn't a nan.
- if (KnownSrc.isKnownNeverNaN() || (DemandedMask & fcNan) == fcNone)
- return CI->getArgOperand(0);
- } else {
- // Nan handling is the same as the IEEE case, but we also need to
- // avoid denormal inputs to drop the canonicalize.
- if ((KnownSrc.isKnownNeverNaN() ||
- (DemandedMask & fcNan) == fcNone) &&
- KnownSrc.isKnownNeverSubnormal())
- return CI->getArgOperand(0);
- }
+ // For IEEE handling, there is only a bit change for nan inputs, so we
+ // can drop it if we do not demand nan results or we know the input
+ // isn't a nan.
+ // Otherwise, we also need to avoid denormal inputs to drop the
+ // canonicalize.
+ if ((KnownSrc.isKnownNeverNaN() || (DemandedMask & fcNan) == fcNone) &&
+ (Mode == DenormalMode::getIEEE() ||
+ KnownSrc.isKnownNeverSubnormal()))
+ return CI->getArgOperand(0);
return nullptr;
}
More information about the llvm-commits
mailing list