[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