[llvm] [InstCombine] Canonicalize nsz NaN-fallback float clamp to minnum/maxnum (PR #213690)

via llvm-commits llvm-commits at lists.llvm.org
Mon Aug 3 08:00:46 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Jun Yeong Kim (junyeong0619)

<details>
<summary>Changes</summary>

Canonicalize an `nsz` NaN-fallback `[LO, HI]` float clamp built from
`fcmp`/`select` into `llvm.minnum`/`llvm.maxnum` plus an explicit NaN select.

The matcher is deliberately narrow. It requires:
- the exact predicate/operand/bound/fallback/`nsz` relationships,
- `LO <= HI` (constant bounds),
- one-use intermediate nodes so the original tree is fully removed,
- `denormal-fp-math = ieee`.

It never infers `nnan`/`ninf`; NaN is handled explicitly by the `fcmp uno`
select. Scalar and fixed-vector `float` are covered. Negative tests break each
precondition (missing `nsz` on each select, wrong predicate, mismatched bound,
mismatched operand, `LO > HI`, non-`ieee` denormals, and a multi-use
intermediate).

Proof: https://alive2.llvm.org/ce/z/NL6Xsh

### On introducing non-`nnan` `minnum`/`maxnum`

This deliberately creates `minnum`/`maxnum` without `nnan`, which the existing
`fcmp`+`select` canonicalization avoids (see the note referencing #<!-- -->54554),
because on targets without a native min/max such intrinsics can lower to a
libcall. This is a narrow, whole-tree canonicalization for a specific
NaN-fallback clamp idiom (found in a hot pixel-processing loop), not a
relaxation of the existing `nnan` safeguard. On the evaluated targets the
canonical form lowers to native instructions with no libcalls:

- x86-64: `minss`/`maxss`
- AArch64: `fminnm`/`fmaxnm`
- RISC-V (Zbb): `fmin`/`fmax`

Feedback on whether this should stay a target-independent canonicalization or be
gated is welcome (see the issue's open question).

Fixes #<!-- -->213189.


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


2 Files Affected:

- (modified) llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp (+97) 
- (added) llvm/test/Transforms/InstCombine/clamp-nan-fallback.ll (+250) 


``````````diff
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index f109d3fd61640..4d3623c729ae8 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -4511,6 +4511,100 @@ static Instruction *foldSelectNegNot(SelectInst &SI,
   return nullptr;
 }
 
+/// Canonicalize an `nsz` NaN-fallback float clamp to `minnum`/`maxnum`:
+///
+///   below_lo_or_nan = fcmp nsz ult %x, LO
+///   below_hi        = fcmp nsz olt %x, HI
+///   upper           = select nsz below_hi, %x, HI
+///   ordered         = fcmp ord %x, LO
+///   lower_or_fb     = select nsz ordered, LO, %fallback
+///   result          = select nsz below_lo_or_nan, lower_or_fb, upper
+///
+/// =>
+///
+///   isnan   = fcmp uno %x, %x
+///   upperV  = call nsz @llvm.minnum(%x, HI)
+///   clamped = call nsz @llvm.maxnum(upperV, LO)
+///   result  = select isnan, %fallback, clamped
+///
+/// The match is deliberately narrow: exact predicates/operands/bounds, `nsz` on
+/// every value-producing select, `denormal-fp-math = ieee`, and one-use
+/// intermediate nodes so the original tree is fully removed. `nnan`/`ninf` are
+/// never inferred; NaN is handled explicitly.
+static Value *foldClampWithNaNFallback(SelectInst &Outer,
+                                       InstCombiner::BuilderTy &Builder) {
+  // hasNoSignedZeros() is only valid on FP operations; bail out on non-FP
+  // selects before querying fast-math flags or FP semantics.
+  if (!isa<FPMathOperator>(&Outer) || !Outer.hasNoSignedZeros())
+    return nullptr;
+
+  // The minnum/maxnum form treats denormals per IEEE. If the function may flush
+  // denormals to zero (FTZ/DAZ, e.g. from -ffast-math / crtfastmath.o), the
+  // rewrite is not value-equivalent.
+  Type *Ty = Outer.getType()->getScalarType();
+  const fltSemantics &FPSem = Ty->getFltSemantics();
+  if (Outer.getFunction()->getDenormalMode(FPSem) != DenormalMode::getIEEE())
+    return nullptr;
+
+  Value *BelowLoOrNan, *LowerOrFallback, *Upper;
+  if (!match(&Outer, m_Select(m_Value(BelowLoOrNan), m_Value(LowerOrFallback),
+                              m_Value(Upper))))
+    return nullptr;
+
+  // belowLoOrNan: fcmp nsz ult %x, LO
+  auto *Cmp1 = dyn_cast<FCmpInst>(BelowLoOrNan);
+  if (!Cmp1 || !Cmp1->hasOneUse() ||
+      Cmp1->getPredicate() != FCmpInst::FCMP_ULT || !Cmp1->hasNoSignedZeros())
+    return nullptr;
+  Value *X = Cmp1->getOperand(0);
+  auto *Lo = dyn_cast<Constant>(Cmp1->getOperand(1));
+  if (!Lo)
+    return nullptr;
+
+  // upper: select nsz (fcmp nsz olt %x, HI), %x, HI
+  auto *UpperSel = dyn_cast<SelectInst>(Upper);
+  if (!UpperSel || !UpperSel->hasOneUse() || !UpperSel->hasNoSignedZeros())
+    return nullptr;
+  auto *Cmp2 = dyn_cast<FCmpInst>(UpperSel->getCondition());
+  if (!Cmp2 || !Cmp2->hasOneUse() ||
+      Cmp2->getPredicate() != FCmpInst::FCMP_OLT || !Cmp2->hasNoSignedZeros() ||
+      Cmp2->getOperand(0) != X)
+    return nullptr;
+  auto *Hi = dyn_cast<Constant>(Cmp2->getOperand(1));
+  if (!Hi || UpperSel->getTrueValue() != X || UpperSel->getFalseValue() != Hi)
+    return nullptr;
+
+  // lowerOrFallback: select nsz (fcmp ord %x, *), LO, %fallback
+  auto *LowSel = dyn_cast<SelectInst>(LowerOrFallback);
+  if (!LowSel || !LowSel->hasOneUse() || !LowSel->hasNoSignedZeros())
+    return nullptr;
+  auto *Cmp3 = dyn_cast<FCmpInst>(LowSel->getCondition());
+  if (!Cmp3 || !Cmp3->hasOneUse() ||
+      Cmp3->getPredicate() != FCmpInst::FCMP_ORD || Cmp3->getOperand(0) != X)
+    return nullptr;
+  if (LowSel->getTrueValue() != Lo)
+    return nullptr;
+  Value *Fallback = LowSel->getFalseValue();
+
+  // Require LO <= HI (as constants) so the clamp is well-formed. For LO > HI
+  // the source tree and the minnum/maxnum form disagree, so bail out.
+  Constant *LeCmp = ConstantFoldCompareInstOperands(FCmpInst::FCMP_OLE, Lo, Hi,
+                                                    Outer.getDataLayout());
+  if (!LeCmp || !match(LeCmp, m_One()))
+    return nullptr;
+
+  // Match succeeded -- build the normalized form.
+  Value *IsNaN = Builder.CreateFCmpUNO(X, X, "isnan");
+
+  Value *UpperV = Builder.CreateBinaryIntrinsic(Intrinsic::minnum, X, Hi);
+  cast<Instruction>(UpperV)->setHasNoSignedZeros(true);
+
+  Value *Clamped = Builder.CreateBinaryIntrinsic(Intrinsic::maxnum, UpperV, Lo);
+  cast<Instruction>(Clamped)->setHasNoSignedZeros(true);
+
+  return Builder.CreateSelect(IsNaN, Fallback, Clamped, "clamp.or.fallback");
+}
+
 Instruction *InstCombinerImpl::visitSelectInst(SelectInst &SI) {
   Value *CondVal = SI.getCondition();
   Value *TrueVal = SI.getTrueValue();
@@ -5341,5 +5435,8 @@ Instruction *InstCombinerImpl::visitSelectInst(SelectInst &SI) {
     return CallInst::Create(Scmp, {CmpLHS, ConstantInt::get(SI.getType(), 0)});
   }
 
+  if (Value *V = foldClampWithNaNFallback(SI, Builder))
+    return replaceInstUsesWith(SI, V);
+
   return nullptr;
 }
diff --git a/llvm/test/Transforms/InstCombine/clamp-nan-fallback.ll b/llvm/test/Transforms/InstCombine/clamp-nan-fallback.ll
new file mode 100644
index 0000000000000..906691f903f00
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/clamp-nan-fallback.ll
@@ -0,0 +1,250 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes=instcombine -S < %s | FileCheck %s
+
+declare float @llvm.minnum.f32(float, float)
+declare float @llvm.maxnum.f32(float, float)
+
+; ---- POS: exact clamp + NaN-fallback pattern (folds) ----
+define float @pos_scalar(float %x, float %fallback) {
+; CHECK-LABEL: define float @pos_scalar(
+; CHECK-SAME: float [[X:%.*]], float [[FALLBACK:%.*]]) {
+; CHECK-NEXT:    [[ISNAN:%.*]] = fcmp uno float [[X]], 0.000000e+00
+; CHECK-NEXT:    [[TMP1:%.*]] = call nsz float @llvm.minnum.f32(float [[X]], float 1.000000e+00)
+; CHECK-NEXT:    [[TMP2:%.*]] = call nsz float @llvm.maxnum.f32(float [[TMP1]], float 0.000000e+00)
+; CHECK-NEXT:    [[RESULT:%.*]] = select i1 [[ISNAN]], float [[FALLBACK]], float [[TMP2]]
+; CHECK-NEXT:    ret float [[RESULT]]
+;
+  %below_lo_or_nan = fcmp nsz ult float %x, 0.0
+  %below_hi = fcmp nsz olt float %x, 1.0
+  %upper = select nsz i1 %below_hi, float %x, float 1.0
+  %ordered = fcmp ord float %x, 0.0
+  %lower_or_fallback = select nsz i1 %ordered, float 0.0, float %fallback
+  %result = select nsz i1 %below_lo_or_nan, float %lower_or_fallback, float %upper
+  ret float %result
+}
+
+; ---- POS vector: fixed <8 x float> clamp+NaN-fallback ----
+define <8 x float> @pos_vector(<8 x float> %x, <8 x float> %fallback) {
+; CHECK-LABEL: define <8 x float> @pos_vector(
+; CHECK-SAME: <8 x float> [[X:%.*]], <8 x float> [[FALLBACK:%.*]]) {
+; CHECK-NEXT:    [[ISNAN:%.*]] = fcmp uno <8 x float> [[X]], zeroinitializer
+; CHECK-NEXT:    [[TMP1:%.*]] = call nsz <8 x float> @llvm.minnum.v8f32(<8 x float> [[X]], <8 x float> splat (float 1.000000e+00))
+; CHECK-NEXT:    [[TMP2:%.*]] = call nsz <8 x float> @llvm.maxnum.v8f32(<8 x float> [[TMP1]], <8 x float> zeroinitializer)
+; CHECK-NEXT:    [[CLAMP_OR_FALLBACK:%.*]] = select <8 x i1> [[ISNAN]], <8 x float> [[FALLBACK]], <8 x float> [[TMP2]]
+; CHECK-NEXT:    ret <8 x float> [[CLAMP_OR_FALLBACK]]
+;
+  %below_lo_or_nan = fcmp nsz ult <8 x float> %x, splat (float 0.0)
+  %below_hi = fcmp nsz olt <8 x float> %x, splat (float 1.0)
+  %upper = select nsz <8 x i1> %below_hi, <8 x float> %x, <8 x float> splat (float 1.0)
+  %ordered = fcmp ord <8 x float> %x, splat (float 0.0)
+  %lower_or_fallback = select nsz <8 x i1> %ordered, <8 x float> splat (float 0.0), <8 x float> %fallback
+  %result = select nsz <8 x i1> %below_lo_or_nan, <8 x float> %lower_or_fallback, <8 x float> %upper
+  ret <8 x float> %result
+}
+
+; ---- N1: outer select missing nsz ----
+define float @n1_outer_no_nsz(float %x, float %fallback) {
+; CHECK-LABEL: define float @n1_outer_no_nsz(
+; CHECK-SAME: float [[X:%.*]], float [[FALLBACK:%.*]]) {
+; CHECK-NEXT:    [[BELOW_LO_OR_NAN:%.*]] = fcmp nsz ult float [[X]], 0.000000e+00
+; CHECK-NEXT:    [[BELOW_HI:%.*]] = fcmp nsz olt float [[X]], 1.000000e+00
+; CHECK-NEXT:    [[UPPER:%.*]] = select nsz i1 [[BELOW_HI]], float [[X]], float 1.000000e+00
+; CHECK-NEXT:    [[ORDERED:%.*]] = fcmp ord float [[X]], 0.000000e+00
+; CHECK-NEXT:    [[LOWER_OR_FALLBACK:%.*]] = select nsz i1 [[ORDERED]], float 0.000000e+00, float [[FALLBACK]]
+; CHECK-NEXT:    [[RESULT:%.*]] = select i1 [[BELOW_LO_OR_NAN]], float [[LOWER_OR_FALLBACK]], float [[UPPER]]
+; CHECK-NEXT:    ret float [[RESULT]]
+;
+  %below_lo_or_nan = fcmp nsz ult float %x, 0.0
+  %below_hi = fcmp nsz olt float %x, 1.0
+  %upper = select nsz i1 %below_hi, float %x, float 1.0
+  %ordered = fcmp ord float %x, 0.0
+  %lower_or_fallback = select nsz i1 %ordered, float 0.0, float %fallback
+  %result = select i1 %below_lo_or_nan, float %lower_or_fallback, float %upper
+  ret float %result
+}
+
+; ---- N2: upper select missing nsz ----
+define float @n2_upper_no_nsz(float %x, float %fallback) {
+; CHECK-LABEL: define float @n2_upper_no_nsz(
+; CHECK-SAME: float [[X:%.*]], float [[FALLBACK:%.*]]) {
+; CHECK-NEXT:    [[BELOW_LO_OR_NAN:%.*]] = fcmp nsz ult float [[X]], 0.000000e+00
+; CHECK-NEXT:    [[BELOW_HI:%.*]] = fcmp nsz olt float [[X]], 1.000000e+00
+; CHECK-NEXT:    [[UPPER:%.*]] = select i1 [[BELOW_HI]], float [[X]], float 1.000000e+00
+; CHECK-NEXT:    [[ORDERED:%.*]] = fcmp ord float [[X]], 0.000000e+00
+; CHECK-NEXT:    [[LOWER_OR_FALLBACK:%.*]] = select nsz i1 [[ORDERED]], float 0.000000e+00, float [[FALLBACK]]
+; CHECK-NEXT:    [[RESULT:%.*]] = select nsz i1 [[BELOW_LO_OR_NAN]], float [[LOWER_OR_FALLBACK]], float [[UPPER]]
+; CHECK-NEXT:    ret float [[RESULT]]
+;
+  %below_lo_or_nan = fcmp nsz ult float %x, 0.0
+  %below_hi = fcmp nsz olt float %x, 1.0
+  %upper = select i1 %below_hi, float %x, float 1.0
+  %ordered = fcmp ord float %x, 0.0
+  %lower_or_fallback = select nsz i1 %ordered, float 0.0, float %fallback
+  %result = select nsz i1 %below_lo_or_nan, float %lower_or_fallback, float %upper
+  ret float %result
+}
+
+; ---- N3: ult -> olt ----
+define float @n3_ult_to_olt(float %x, float %fallback) {
+; CHECK-LABEL: define float @n3_ult_to_olt(
+; CHECK-SAME: float [[X:%.*]], float [[FALLBACK:%.*]]) {
+; CHECK-NEXT:    [[BELOW_LO_OR_NAN:%.*]] = fcmp nsz olt float [[X]], 0.000000e+00
+; CHECK-NEXT:    [[BELOW_HI:%.*]] = fcmp nsz olt float [[X]], 1.000000e+00
+; CHECK-NEXT:    [[UPPER:%.*]] = select nsz i1 [[BELOW_HI]], float [[X]], float 1.000000e+00
+; CHECK-NEXT:    [[RESULT:%.*]] = select nsz i1 [[BELOW_LO_OR_NAN]], float 0.000000e+00, float [[UPPER]]
+; CHECK-NEXT:    ret float [[RESULT]]
+;
+  %below_lo_or_nan = fcmp nsz olt float %x, 0.0
+  %below_hi = fcmp nsz olt float %x, 1.0
+  %upper = select nsz i1 %below_hi, float %x, float 1.0
+  %ordered = fcmp ord float %x, 0.0
+  %lower_or_fallback = select nsz i1 %ordered, float 0.0, float %fallback
+  %result = select nsz i1 %below_lo_or_nan, float %lower_or_fallback, float %upper
+  ret float %result
+}
+
+; ---- N4: lower-bound constant mismatch (0.0 -> 2.0) ----
+define float @n4_lo_mismatch(float %x, float %fallback) {
+; CHECK-LABEL: define float @n4_lo_mismatch(
+; CHECK-SAME: float [[X:%.*]], float [[FALLBACK:%.*]]) {
+; CHECK-NEXT:    [[BELOW_LO_OR_NAN:%.*]] = fcmp nsz ult float [[X]], 0.000000e+00
+; CHECK-NEXT:    [[BELOW_HI:%.*]] = fcmp nsz olt float [[X]], 1.000000e+00
+; CHECK-NEXT:    [[UPPER:%.*]] = select nsz i1 [[BELOW_HI]], float [[X]], float 1.000000e+00
+; CHECK-NEXT:    [[ORDERED:%.*]] = fcmp ord float [[X]], 0.000000e+00
+; CHECK-NEXT:    [[LOWER_OR_FALLBACK:%.*]] = select nsz i1 [[ORDERED]], float 2.000000e+00, float [[FALLBACK]]
+; CHECK-NEXT:    [[RESULT:%.*]] = select nsz i1 [[BELOW_LO_OR_NAN]], float [[LOWER_OR_FALLBACK]], float [[UPPER]]
+; CHECK-NEXT:    ret float [[RESULT]]
+;
+  %below_lo_or_nan = fcmp nsz ult float %x, 0.0
+  %below_hi = fcmp nsz olt float %x, 1.0
+  %upper = select nsz i1 %below_hi, float %x, float 1.0
+  %ordered = fcmp ord float %x, 0.0
+  %lower_or_fallback = select nsz i1 %ordered, float 2.0, float %fallback
+  %result = select nsz i1 %below_lo_or_nan, float %lower_or_fallback, float %upper
+  ret float %result
+}
+
+; ---- N5: compared operand mismatch (%x -> %y) ----
+define float @n5_operand_mismatch(float %x, float %y, float %fallback) {
+; CHECK-LABEL: define float @n5_operand_mismatch(
+; CHECK-SAME: float [[X:%.*]], float [[Y:%.*]], float [[FALLBACK:%.*]]) {
+; CHECK-NEXT:    [[BELOW_LO_OR_NAN:%.*]] = fcmp nsz ult float [[X]], 0.000000e+00
+; CHECK-NEXT:    [[BELOW_HI:%.*]] = fcmp nsz olt float [[Y]], 1.000000e+00
+; CHECK-NEXT:    [[UPPER:%.*]] = select nsz i1 [[BELOW_HI]], float [[X]], float 1.000000e+00
+; CHECK-NEXT:    [[ORDERED:%.*]] = fcmp ord float [[X]], 0.000000e+00
+; CHECK-NEXT:    [[LOWER_OR_FALLBACK:%.*]] = select nsz i1 [[ORDERED]], float 0.000000e+00, float [[FALLBACK]]
+; CHECK-NEXT:    [[RESULT:%.*]] = select nsz i1 [[BELOW_LO_OR_NAN]], float [[LOWER_OR_FALLBACK]], float [[UPPER]]
+; CHECK-NEXT:    ret float [[RESULT]]
+;
+  %below_lo_or_nan = fcmp nsz ult float %x, 0.0
+  %below_hi = fcmp nsz olt float %y, 1.0
+  %upper = select nsz i1 %below_hi, float %x, float 1.0
+  %ordered = fcmp ord float %x, 0.0
+  %lower_or_fallback = select nsz i1 %ordered, float 0.0, float %fallback
+  %result = select nsz i1 %below_lo_or_nan, float %lower_or_fallback, float %upper
+  ret float %result
+}
+
+; ---- N6: ord -> uno ----
+define float @n6_ord_to_uno(float %x, float %fallback) {
+; CHECK-LABEL: define float @n6_ord_to_uno(
+; CHECK-SAME: float [[X:%.*]], float [[FALLBACK:%.*]]) {
+; CHECK-NEXT:    [[BELOW_LO_OR_NAN:%.*]] = fcmp nsz ult float [[X]], 0.000000e+00
+; CHECK-NEXT:    [[BELOW_HI:%.*]] = fcmp nsz olt float [[X]], 1.000000e+00
+; CHECK-NEXT:    [[UPPER:%.*]] = select nsz i1 [[BELOW_HI]], float [[X]], float 1.000000e+00
+; CHECK-NEXT:    [[ORDERED:%.*]] = fcmp uno float [[X]], 0.000000e+00
+; CHECK-NEXT:    [[LOWER_OR_FALLBACK:%.*]] = select nsz i1 [[ORDERED]], float 0.000000e+00, float [[FALLBACK]]
+; CHECK-NEXT:    [[RESULT:%.*]] = select nsz i1 [[BELOW_LO_OR_NAN]], float [[LOWER_OR_FALLBACK]], float [[UPPER]]
+; CHECK-NEXT:    ret float [[RESULT]]
+;
+  %below_lo_or_nan = fcmp nsz ult float %x, 0.0
+  %below_hi = fcmp nsz olt float %x, 1.0
+  %upper = select nsz i1 %below_hi, float %x, float 1.0
+  %ordered = fcmp uno float %x, 0.0
+  %lower_or_fallback = select nsz i1 %ordered, float 0.0, float %fallback
+  %result = select nsz i1 %below_lo_or_nan, float %lower_or_fallback, float %upper
+  ret float %result
+}
+
+; ---- N7: non-ieee denormal-fp-math (otherwise same as POS) ----
+define float @n7_non_ieee_denormal(float %x, float %fallback) denormal_fpenv(preservesign) {
+; CHECK-LABEL: define float @n7_non_ieee_denormal(
+; CHECK-SAME: float [[X:%.*]], float [[FALLBACK:%.*]]) #[[ATTR1:[0-9]+]] {
+; CHECK-NEXT:    [[BELOW_LO_OR_NAN:%.*]] = fcmp nsz ult float [[X]], 0.000000e+00
+; CHECK-NEXT:    [[BELOW_HI:%.*]] = fcmp nsz olt float [[X]], 1.000000e+00
+; CHECK-NEXT:    [[UPPER:%.*]] = select nsz i1 [[BELOW_HI]], float [[X]], float 1.000000e+00
+; CHECK-NEXT:    [[ORDERED:%.*]] = fcmp ord float [[X]], 0.000000e+00
+; CHECK-NEXT:    [[LOWER_OR_FALLBACK:%.*]] = select nsz i1 [[ORDERED]], float 0.000000e+00, float [[FALLBACK]]
+; CHECK-NEXT:    [[RESULT:%.*]] = select nsz i1 [[BELOW_LO_OR_NAN]], float [[LOWER_OR_FALLBACK]], float [[UPPER]]
+; CHECK-NEXT:    ret float [[RESULT]]
+;
+  %below_lo_or_nan = fcmp nsz ult float %x, 0.0
+  %below_hi = fcmp nsz olt float %x, 1.0
+  %upper = select nsz i1 %below_hi, float %x, float 1.0
+  %ordered = fcmp ord float %x, 0.0
+  %lower_or_fallback = select nsz i1 %ordered, float 0.0, float %fallback
+  %result = select nsz i1 %below_lo_or_nan, float %lower_or_fallback, float %upper
+  ret float %result
+}
+; ---- N8: intermediate %upper has an extra use -> not one-use, no fold ----
+define float @n8_multi_use(float %x, float %fallback, ptr %p) {
+; CHECK-LABEL: define float @n8_multi_use(
+; CHECK-SAME: float [[X:%.*]], float [[FALLBACK:%.*]], ptr [[P:%.*]]) {
+; CHECK-NEXT:    [[BELOW_LO_OR_NAN:%.*]] = fcmp nsz ult float [[X]], 0.000000e+00
+; CHECK-NEXT:    [[BELOW_HI:%.*]] = fcmp nsz olt float [[X]], 1.000000e+00
+; CHECK-NEXT:    [[UPPER:%.*]] = select nsz i1 [[BELOW_HI]], float [[X]], float 1.000000e+00
+; CHECK-NEXT:    store float [[UPPER]], ptr [[P]], align 4
+; CHECK-NEXT:    [[ORDERED:%.*]] = fcmp ord float [[X]], 0.000000e+00
+; CHECK-NEXT:    [[LOWER_OR_FALLBACK:%.*]] = select nsz i1 [[ORDERED]], float 0.000000e+00, float [[FALLBACK]]
+; CHECK-NEXT:    [[RESULT:%.*]] = select nsz i1 [[BELOW_LO_OR_NAN]], float [[LOWER_OR_FALLBACK]], float [[UPPER]]
+; CHECK-NEXT:    ret float [[RESULT]]
+;
+  %below_lo_or_nan = fcmp nsz ult float %x, 0.0
+  %below_hi = fcmp nsz olt float %x, 1.0
+  %upper = select nsz i1 %below_hi, float %x, float 1.0
+  store float %upper, ptr %p
+  %ordered = fcmp ord float %x, 0.0
+  %lower_or_fallback = select nsz i1 %ordered, float 0.0, float %fallback
+  %result = select nsz i1 %below_lo_or_nan, float %lower_or_fallback, float %upper
+  ret float %result
+}
+
+; ---- N9: LO > HI (ill-formed clamp), must not fold ----
+define float @n9_lo_gt_hi(float %x, float %fallback) {
+; CHECK-LABEL: define float @n9_lo_gt_hi(
+; CHECK-SAME: float [[X:%.*]], float [[FALLBACK:%.*]]) {
+; CHECK-NEXT:    [[BELOW_LO_OR_NAN:%.*]] = fcmp nsz ult float [[X]], 1.000000e+00
+; CHECK-NEXT:    [[ORDERED:%.*]] = fcmp ord float [[X]], 0.000000e+00
+; CHECK-NEXT:    [[LOWER_OR_FALLBACK:%.*]] = select nsz i1 [[ORDERED]], float 1.000000e+00, float [[FALLBACK]]
+; CHECK-NEXT:    [[RESULT:%.*]] = select nsz i1 [[BELOW_LO_OR_NAN]], float [[LOWER_OR_FALLBACK]], float 0.000000e+00
+; CHECK-NEXT:    ret float [[RESULT]]
+;
+  %below_lo_or_nan = fcmp nsz ult float %x, 1.0
+  %below_hi = fcmp nsz olt float %x, 0.0
+  %upper = select nsz i1 %below_hi, float %x, float 0.0
+  %ordered = fcmp ord float %x, 1.0
+  %lower_or_fallback = select nsz i1 %ordered, float 1.0, float %fallback
+  %result = select nsz i1 %below_lo_or_nan, float %lower_or_fallback, float %upper
+  ret float %result
+}
+
+; ---- N10: lower select missing nsz ----
+define float @n10_lower_no_nsz(float %x, float %fallback) {
+; CHECK-LABEL: define float @n10_lower_no_nsz(
+; CHECK-SAME: float [[X:%.*]], float [[FALLBACK:%.*]]) {
+; CHECK-NEXT:    [[BELOW_LO_OR_NAN:%.*]] = fcmp nsz ult float [[X]], 0.000000e+00
+; CHECK-NEXT:    [[BELOW_HI:%.*]] = fcmp nsz olt float [[X]], 1.000000e+00
+; CHECK-NEXT:    [[UPPER:%.*]] = select nsz i1 [[BELOW_HI]], float [[X]], float 1.000000e+00
+; CHECK-NEXT:    [[ORDERED:%.*]] = fcmp ord float [[X]], 0.000000e+00
+; CHECK-NEXT:    [[LOWER_OR_FALLBACK:%.*]] = select i1 [[ORDERED]], float 0.000000e+00, float [[FALLBACK]]
+; CHECK-NEXT:    [[RESULT:%.*]] = select nsz i1 [[BELOW_LO_OR_NAN]], float [[LOWER_OR_FALLBACK]], float [[UPPER]]
+; CHECK-NEXT:    ret float [[RESULT]]
+;
+  %below_lo_or_nan = fcmp nsz ult float %x, 0.0
+  %below_hi = fcmp nsz olt float %x, 1.0
+  %upper = select nsz i1 %below_hi, float %x, float 1.0
+  %ordered = fcmp ord float %x, 0.0
+  %lower_or_fallback = select i1 %ordered, float 0.0, float %fallback
+  %result = select nsz i1 %below_lo_or_nan, float %lower_or_fallback, float %upper
+  ret float %result
+}

``````````

</details>


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


More information about the llvm-commits mailing list