Skip to content

Conversation

@sivakusayan
Copy link
Contributor

@sivakusayan sivakusayan commented Nov 10, 2025

Fixes issue #74368.

This patch enables us to constant fold nextafter and nexttoward as long as we know that errno won't be written. In the latter case, we should keep the function call so the programmer can observe the side effect.

Apologies for the large patch, the majority of it are lit tests. I wasn't sure how to cut down on the repetitiveness here, let me know if you have any better ideas. I got the idea for reusing the constants across .ll files from the LLVM Discord.

@github-actions
Copy link

github-actions bot commented Nov 10, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@sivakusayan sivakusayan force-pushed the fix/constant-fold-nextafter-and-nexttoward branch from cc01e5a to 5b9786f Compare November 10, 2025 14:53
@sivakusayan sivakusayan force-pushed the fix/constant-fold-nextafter-and-nexttoward branch from 5b9786f to e94017f Compare November 10, 2025 19:00
@sivakusayan sivakusayan marked this pull request as ready for review November 10, 2025 19:03
@llvmbot llvmbot added llvm:instcombine Covers the InstCombine, InstSimplify and AggressiveInstCombine passes llvm:analysis Includes value tracking, cost tables and constant folding llvm:transforms llvm:adt labels Nov 10, 2025
@llvmbot
Copy link
Member

llvmbot commented Nov 10, 2025

@llvm/pr-subscribers-llvm-analysis

Author: Sayan Sivakumaran (sivakusayan)

Changes

Fixes issue #74368.

This patch enables us to constant fold nextafter and nexttoward as long as we know that errno won't be written. In the latter case, we should keep the function call so the programmer can observe the side effect.

Apologies for the large patch, the majority of it are lit tests. I wasn't sure how to cut down on the repetitiveness here, let me know if you have any better ideas. I got the idea for reusing the constants across .ll files from the LLVM Discord.


Patch is 38.99 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/167324.diff

7 Files Affected:

  • (modified) llvm/include/llvm/ADT/APFloat.h (+13)
  • (modified) llvm/lib/Analysis/ConstantFolding.cpp (+39-1)
  • (added) llvm/test/Transforms/InstCombine/constant-fold-nextafter.ll (+171)
  • (added) llvm/test/Transforms/InstCombine/constant-fold-nexttoward-fp128.ll (+187)
  • (added) llvm/test/Transforms/InstCombine/constant-fold-nexttoward-ppc-fp128.ll (+187)
  • (added) llvm/test/Transforms/InstCombine/constant-fold-nexttoward-x86-fp80.ll (+187)
  • (added) llvm/test/Transforms/InstCombine/floating-point-constants.ll (+48)
diff --git a/llvm/include/llvm/ADT/APFloat.h b/llvm/include/llvm/ADT/APFloat.h
index 82ac9a3a1ef80..32e5c1aafc245 100644
--- a/llvm/include/llvm/ADT/APFloat.h
+++ b/llvm/include/llvm/ADT/APFloat.h
@@ -1149,6 +1149,19 @@ class APFloat : public APFloatBase {
   /// \param Semantics - type float semantics
   LLVM_ABI static APFloat getAllOnesValue(const fltSemantics &Semantics);
 
+  /// Returns a copy of this APFloat with the requested semantics.
+  /// The requested semantics should be equal to or stronger
+  /// than the semantics of the current instance.
+  APFloat getPromoted(const fltSemantics &Sem) const {
+    assert(isRepresentableBy(this->getSemantics(), Sem) &&
+           "Target semantics will lose information.");
+    APFloat Val(*this);
+    bool LosesInfo;
+    Val.convert(Sem, rmNearestTiesToEven, &LosesInfo);
+    assert(!LosesInfo);
+    return Val;
+  }
+
   /// Returns true if the given semantics has actual significand.
   ///
   /// \param Sem - type float semantics
diff --git a/llvm/lib/Analysis/ConstantFolding.cpp b/llvm/lib/Analysis/ConstantFolding.cpp
index da32542cf7870..fadd3e7896c3a 100755
--- a/llvm/lib/Analysis/ConstantFolding.cpp
+++ b/llvm/lib/Analysis/ConstantFolding.cpp
@@ -1996,7 +1996,9 @@ bool llvm::canConstantFoldCallTo(const CallBase *Call, const Function *F) {
            Name == "log10f" || Name == "logb" || Name == "logbf" ||
            Name == "log1p" || Name == "log1pf";
   case 'n':
-    return Name == "nearbyint" || Name == "nearbyintf";
+    return Name == "nearbyint" || Name == "nearbyintf" || Name == "nextafter" ||
+           Name == "nextafterf" || Name == "nexttoward" ||
+           Name == "nexttowardf";
   case 'p':
     return Name == "pow" || Name == "powf";
   case 'r':
@@ -3221,6 +3223,26 @@ static Constant *ConstantFoldLibCall2(StringRef Name, Type *Ty,
     if (TLI->has(Func))
       return ConstantFoldBinaryFP(atan2, Op1V, Op2V, Ty);
     break;
+  case LibFunc_nextafter:
+  case LibFunc_nextafterf:
+  case LibFunc_nexttoward:
+  case LibFunc_nexttowardf:
+    if (TLI->has(Func)) {
+      if (Op1V.isNaN() || Op2V.isNaN()) {
+        return ConstantFP::get(Ty->getContext(),
+                               APFloat::getNaN(Ty->getFltSemantics()));
+      }
+
+      APFloat PromotedOp1V = Op1V.getPromoted(APFloat::IEEEquad());
+      APFloat PromotedOp2V = Op2V.getPromoted(APFloat::IEEEquad());
+      if (PromotedOp1V == PromotedOp2V) {
+        return ConstantFP::get(Ty->getContext(), Op1V);
+      }
+
+      APFloat Next(Op1V);
+      Next.next(/*nextDown=*/PromotedOp1V > PromotedOp2V);
+      return ConstantFP::get(Ty->getContext(), Next);
+    }
   }
 
   return nullptr;
@@ -4655,6 +4677,22 @@ bool llvm::isMathLibCallNoop(const CallBase *Call,
         // may occur, so allow for that possibility.
         return !Op0.isZero() || !Op1.isZero();
 
+      case LibFunc_nextafter:
+      case LibFunc_nextafterf:
+      case LibFunc_nextafterl:
+      case LibFunc_nexttoward:
+      case LibFunc_nexttowardf:
+      case LibFunc_nexttowardl: {
+        APFloat PromotedOp0 = Op0.getPromoted(APFloat::IEEEquad());
+        APFloat PromotedOp1 = Op1.getPromoted(APFloat::IEEEquad());
+        if (PromotedOp0 == PromotedOp1)
+          return true;
+
+        APFloat Next(Op0);
+        Next.next(/*nextDown=*/PromotedOp0 > PromotedOp1);
+        bool DidOverflow = Op0.isLargest() && Next.isInfinity();
+        return !Next.isZero() && !Next.isDenormal() && !DidOverflow;
+      }
       default:
         break;
       }
diff --git a/llvm/test/Transforms/InstCombine/constant-fold-nextafter.ll b/llvm/test/Transforms/InstCombine/constant-fold-nextafter.ll
new file mode 100644
index 0000000000000..9af0a5aeba871
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/constant-fold-nextafter.ll
@@ -0,0 +1,171 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: cat %S/floating-point-constants.ll %s | opt -passes=instcombine -S | FileCheck %s
+
+declare double @nextafter(double noundef, double noundef) #0
+declare float @nextafterf(float noundef, float noundef) #0
+
+attributes #0 = { willreturn memory(errnomem: write) }
+
+; ==================
+; nextafter tests
+; ==================
+
+define double @nextafter_can_constant_fold_up_direction() {
+; CHECK-LABEL: define double @nextafter_can_constant_fold_up_direction() {
+; CHECK-NEXT:    ret double 0x3FF0000000000001
+;
+  %next = call double @nextafter(double noundef 1.0, double noundef 2.0)
+  ret double %next
+}
+define double @nextafter_can_constant_fold_down_direction() {
+; CHECK-LABEL: define double @nextafter_can_constant_fold_down_direction() {
+; CHECK-NEXT:    ret double 0x3FEFFFFFFFFFFFFF
+;
+  %next = call double @nextafter(double noundef 1.0, double noundef 0.0)
+  ret double %next
+}
+define double @nextafter_can_constant_fold_equal_args() {
+; CHECK-LABEL: define double @nextafter_can_constant_fold_equal_args() {
+; CHECK-NEXT:    ret double 1.000000e+00
+;
+  %next = call double @nextafter(double noundef 1.0, double noundef 1.0)
+  ret double %next
+}
+define double @nextafter_can_constant_fold_with_nan_arg() {
+; CHECK-LABEL: define double @nextafter_can_constant_fold_with_nan_arg() {
+; CHECK-NEXT:    ret double 0x7FF8000000000000
+;
+  %arg = load double, double* @dbl_nan
+  %next = call double @nextafter(double 1.0, double %arg)
+  ret double %next
+}
+define double @nextafter_not_marked_dead_on_pos_overflow () {
+; CHECK-LABEL: define double @nextafter_not_marked_dead_on_pos_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nextafter(double 0x7FEFFFFFFFFFFFFF, double 0x7FF0000000000000)
+; CHECK-NEXT:    ret double 0x7FF0000000000000
+;
+  %arg1 = load double, double* @dbl_pos_max
+  %arg2 = load double, double* @dbl_pos_infinity
+  %next = call double @nextafter(double %arg1, double %arg2)
+  ret double %next
+}
+define double @nextafter_not_marked_dead_on_neg_overflow() {
+; CHECK-LABEL: define double @nextafter_not_marked_dead_on_neg_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nextafter(double 0xFFEFFFFFFFFFFFFF, double 0xFFF0000000000000)
+; CHECK-NEXT:    ret double 0xFFF0000000000000
+;
+  %arg1 = load double, double* @dbl_neg_max
+  %arg2 = load double, double* @dbl_neg_infinity
+  %next = call double @nextafter(double %arg1, double %arg2)
+  ret double %next
+}
+define double @nextafter_not_marked_dead_on_zero_from_above() {
+; CHECK-LABEL: define double @nextafter_not_marked_dead_on_zero_from_above() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nextafter(double 4.940660e-324, double 0.000000e+00)
+; CHECK-NEXT:    ret double 0.000000e+00
+;
+  %arg = load double, double* @dbl_pos_min_subnormal
+  %next = call double @nextafter(double %arg, double 0.0)
+  ret double %next
+}
+define double @nextafter_not_marked_dead_on_zero_from_below() {
+; CHECK-LABEL: define double @nextafter_not_marked_dead_on_zero_from_below() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nextafter(double -4.940660e-324, double 0.000000e+00)
+; CHECK-NEXT:    ret double -0.000000e+00
+;
+  %arg = load double, double* @dbl_neg_min_subnormal
+  %next = call double @nextafter(double %arg, double 0.0)
+  ret double %next
+}
+define double @nextafter_not_marked_dead_on_subnormal() {
+; CHECK-LABEL: define double @nextafter_not_marked_dead_on_subnormal() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nextafter(double 4.940660e-324, double 0x7FF0000000000000)
+; CHECK-NEXT:    ret double 9.881310e-324
+;
+  %subnormal = load double, double* @dbl_pos_min_subnormal
+  %infinity = load double, double* @dbl_pos_infinity
+  %next = call double @nextafter(double %subnormal, double %infinity)
+  ret double %next
+}
+
+; ==================
+; nextafterf tests
+; ==================
+
+define float @nextafterf_can_constant_fold_up_direction() {
+; CHECK-LABEL: define float @nextafterf_can_constant_fold_up_direction() {
+; CHECK-NEXT:    ret float 0x3FF0000020000000
+;
+  %next = call float @nextafterf(float noundef 1.0, float noundef 2.0)
+  ret float %next
+}
+define float @nextafterf_can_constant_fold_down_direction() {
+; CHECK-LABEL: define float @nextafterf_can_constant_fold_down_direction() {
+; CHECK-NEXT:    ret float 0x3FEFFFFFE0000000
+;
+  %next = call float @nextafterf(float noundef 1.0, float noundef 0.0)
+  ret float %next
+}
+define float @nextafterf_can_constant_fold_equal_args() {
+; CHECK-LABEL: define float @nextafterf_can_constant_fold_equal_args() {
+; CHECK-NEXT:    ret float 1.000000e+00
+;
+  %next = call float @nextafterf(float noundef 1.0, float noundef 1.0)
+  ret float %next
+}
+define float @nextafterf_can_constant_fold_with_nan_arg() {
+; CHECK-LABEL: define float @nextafterf_can_constant_fold_with_nan_arg() {
+; CHECK-NEXT:    ret float 0x7FF8000000000000
+;
+  %arg = load float, float* @flt_nan
+  %next = call float @nextafterf(float 1.0, float %arg)
+  ret float %next
+}
+define float @nextafterf_not_marked_dead_on_pos_overflow() {
+; CHECK-LABEL: define float @nextafterf_not_marked_dead_on_pos_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nextafterf(float 0x47EFFFFFE0000000, float 0x7FF0000000000000)
+; CHECK-NEXT:    ret float 0x7FF0000000000000
+;
+  %arg1 = load float, float* @flt_pos_max
+  %arg2 = load float, float* @flt_pos_infinity
+  %next = call float @nextafterf(float %arg1, float %arg2)
+  ret float %next
+}
+define float @nextafterf_not_marked_dead_on_neg_overflow() {
+; CHECK-LABEL: define float @nextafterf_not_marked_dead_on_neg_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nextafterf(float 0xC7EFFFFFE0000000, float 0xFFF0000000000000)
+; CHECK-NEXT:    ret float 0xFFF0000000000000
+;
+  %arg1 = load float, float* @flt_neg_max
+  %arg2 = load float, float* @flt_neg_infinity
+  %next = call float @nextafterf(float %arg1, float %arg2)
+  ret float %next
+}
+define float @nextafterf_not_marked_dead_on_zero_from_above() {
+; CHECK-LABEL: define float @nextafterf_not_marked_dead_on_zero_from_above() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nextafterf(float 0x36A0000000000000, float 0.000000e+00)
+; CHECK-NEXT:    ret float 0.000000e+00
+;
+  %arg = load float, float* @flt_pos_min_subnormal
+  %next = call float @nextafterf(float %arg, float 0.0)
+  ret float %next
+}
+define float @nextafterf_not_marked_dead_on_zero_from_below() {
+; CHECK-LABEL: define float @nextafterf_not_marked_dead_on_zero_from_below() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nextafterf(float 0xB6A0000000000000, float 0.000000e+00)
+; CHECK-NEXT:    ret float -0.000000e+00
+;
+  %arg = load float, float* @flt_neg_min_subnormal
+  %next = call float @nextafterf(float %arg, float 0.0)
+  ret float %next
+}
+define float @nextafterf_not_marked_dead_on_subnormal() {
+; CHECK-LABEL: define float @nextafterf_not_marked_dead_on_subnormal() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nextafterf(float 0x36A0000000000000, float 0x7FF0000000000000)
+; CHECK-NEXT:    ret float 0x36B0000000000000
+;
+  %subnormal = load float, float* @flt_pos_min_subnormal
+  %infinity = load float, float* @flt_pos_infinity
+  %next = call float @nextafterf(float %subnormal, float %infinity)
+  ret float %next
+}
diff --git a/llvm/test/Transforms/InstCombine/constant-fold-nexttoward-fp128.ll b/llvm/test/Transforms/InstCombine/constant-fold-nexttoward-fp128.ll
new file mode 100644
index 0000000000000..5f044241fbce8
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/constant-fold-nexttoward-fp128.ll
@@ -0,0 +1,187 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: cat %S/floating-point-constants.ll %s | opt -passes=instcombine -S | FileCheck %s
+
+declare double @nexttoward(double noundef, fp128 noundef) #0
+declare float @nexttowardf(float noundef, fp128 noundef) #0
+
+attributes #0 = { willreturn memory(errnomem: write) }
+
+; ==================
+; nexttoward tests
+; ==================
+
+define double @nexttoward_can_constant_fold_up_direction() {
+; CHECK-LABEL: define double @nexttoward_can_constant_fold_up_direction() {
+; CHECK-NEXT:    ret double 0x3FF0000000000001
+;
+  %arg = fpext double 2.0 to fp128
+  %next = call double @nexttoward(double noundef 1.0, fp128 %arg)
+  ret double %next
+}
+define double @nexttoward_can_constant_fold_down_direction() {
+; CHECK-LABEL: define double @nexttoward_can_constant_fold_down_direction() {
+; CHECK-NEXT:    ret double 0x3FEFFFFFFFFFFFFF
+;
+  %arg = fpext double 0.0 to fp128
+  %next = call double @nexttoward(double noundef 1.0, fp128 %arg)
+  ret double %next
+}
+define double @nexttoward_can_constant_fold_equal_args() {
+; CHECK-LABEL: define double @nexttoward_can_constant_fold_equal_args() {
+; CHECK-NEXT:    ret double 1.000000e+00
+;
+  %arg = fpext double 1.0 to fp128
+  %next = call double @nexttoward(double 1.0, fp128 %arg)
+  ret double %next
+}
+define double @nexttoward_can_constant_fold_with_nan_arg() {
+; CHECK-LABEL: define double @nexttoward_can_constant_fold_with_nan_arg() {
+; CHECK-NEXT:    ret double 0x7FF8000000000000
+;
+  %nan = load double, double* @dbl_nan
+  %arg = fpext double %nan to fp128
+  %next = call double @nexttoward(double 1.0, fp128 %arg)
+  ret double %next
+}
+define double @nexttoward_not_marked_dead_on_pos_overflow() {
+; CHECK-LABEL: define double @nexttoward_not_marked_dead_on_pos_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nexttoward(double 0x7FEFFFFFFFFFFFFF, fp128 0xL00000000000000007FFF000000000000)
+; CHECK-NEXT:    ret double 0x7FF0000000000000
+;
+  %pos_max = load double, double* @dbl_pos_max
+  %pos_inf = load double, double* @dbl_pos_infinity
+  %ext_pos_inf = fpext double %pos_inf to fp128
+  %next = call double @nexttoward(double %pos_max, fp128 %ext_pos_inf)
+  ret double %next
+}
+define double @nexttoward_not_marked_dead_on_neg_overflow() {
+; CHECK-LABEL: define double @nexttoward_not_marked_dead_on_neg_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nexttoward(double 0xFFEFFFFFFFFFFFFF, fp128 0xL0000000000000000FFFF000000000000)
+; CHECK-NEXT:    ret double 0xFFF0000000000000
+;
+  %neg_max = load double, double* @dbl_neg_max
+  %neg_inf = load double, double* @dbl_neg_infinity
+  %ext_neg_inf = fpext double %neg_inf to fp128
+  %next = call double @nexttoward(double %neg_max, fp128 %ext_neg_inf)
+  ret double %next
+}
+define double @nexttoward_not_marked_dead_on_zero_from_above() {
+; CHECK-LABEL: define double @nexttoward_not_marked_dead_on_zero_from_above() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nexttoward(double 4.940660e-324, fp128 0xL00000000000000000000000000000000)
+; CHECK-NEXT:    ret double 0.000000e+00
+;
+  %subnormal = load double, double* @dbl_pos_min_subnormal
+  %zero = fpext double 0.0 to fp128
+  %next = call double @nexttoward(double %subnormal, fp128 %zero)
+  ret double %next
+}
+define double @nexttoward_not_marked_dead_on_zero_from_below() {
+; CHECK-LABEL: define double @nexttoward_not_marked_dead_on_zero_from_below() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nexttoward(double -4.940660e-324, fp128 0xL00000000000000000000000000000000)
+; CHECK-NEXT:    ret double -0.000000e+00
+;
+  %subnormal = load double, double* @dbl_neg_min_subnormal
+  %zero = fpext double 0.0 to fp128
+  %next = call double @nexttoward(double %subnormal, fp128 %zero)
+  ret double %next
+}
+define double @nexttoward_not_marked_dead_on_subnormal() {
+; CHECK-LABEL: define double @nexttoward_not_marked_dead_on_subnormal() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nexttoward(double 4.940660e-324, fp128 0xL00000000000000003FFF000000000000)
+; CHECK-NEXT:    ret double 9.881310e-324
+;
+  %subnormal = load double, double* @dbl_pos_min_subnormal
+  %target = fpext double 1.0 to fp128
+  %next = call double @nexttoward(double %subnormal, fp128 %target)
+  ret double %next
+}
+
+; ==================
+; nexttowardf tests
+; ==================
+
+define float @nexttowardf_can_constant_fold_up_direction() {
+; CHECK-LABEL: define float @nexttowardf_can_constant_fold_up_direction() {
+; CHECK-NEXT:    ret float 0x3FF0000020000000
+;
+  %arg = fpext float 2.0 to fp128
+  %next = call float @nexttowardf(float noundef 1.0, fp128 %arg)
+  ret float %next
+}
+define float @nexttowardf_can_constant_fold_down_direction() {
+; CHECK-LABEL: define float @nexttowardf_can_constant_fold_down_direction() {
+; CHECK-NEXT:    ret float 0x3FEFFFFFE0000000
+;
+  %arg = fpext float 0.0 to fp128
+  %next = call float @nexttowardf(float noundef 1.0, fp128 %arg)
+  ret float %next
+}
+define float @nexttowardf_can_constant_fold_equal_args() {
+; CHECK-LABEL: define float @nexttowardf_can_constant_fold_equal_args() {
+; CHECK-NEXT:    ret float 1.000000e+00
+;
+  %arg = fpext float 1.0 to fp128
+  %next = call float @nexttowardf(float 1.0, fp128 %arg)
+  ret float %next
+}
+define float @nexttowardf_can_constant_fold_with_nan_arg() {
+; CHECK-LABEL: define float @nexttowardf_can_constant_fold_with_nan_arg() {
+; CHECK-NEXT:    ret float 0x7FF8000000000000
+;
+  %nan = load float, float* @flt_nan
+  %ext_nan = fpext float %nan to fp128
+  %next = call float @nexttowardf(float 1.0, fp128 %ext_nan)
+  ret float %next
+}
+define float @nexttowardf_not_marked_dead_on_pos_overflow () {
+; CHECK-LABEL: define float @nexttowardf_not_marked_dead_on_pos_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nexttowardf(float 0x47EFFFFFE0000000, fp128 0xL00000000000000007FFF000000000000)
+; CHECK-NEXT:    ret float 0x7FF0000000000000
+;
+  %pos_max = load float, float* @flt_pos_max
+  %pos_inf = load float, float* @flt_pos_infinity
+  %ext_pos_inf = fpext float %pos_inf to fp128
+  %next = call float @nexttowardf(float %pos_max, fp128 %ext_pos_inf)
+  ret float %next
+}
+define float @nexttowardf_not_marked_dead_on_neg_overflow() {
+; CHECK-LABEL: define float @nexttowardf_not_marked_dead_on_neg_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nexttowardf(float 0xC7EFFFFFE0000000, fp128 0xL0000000000000000FFFF000000000000)
+; CHECK-NEXT:    ret float 0xFFF0000000000000
+;
+  %neg_max = load float, float* @flt_neg_max
+  %neg_inf = load float, float* @flt_neg_infinity
+  %ext_neg_inf = fpext float %neg_inf to fp128
+  %next = call float @nexttowardf(float %neg_max, fp128 %ext_neg_inf)
+  ret float %next
+}
+define float @nexttowardf_not_marked_dead_on_zero_from_above() {
+; CHECK-LABEL: define float @nexttowardf_not_marked_dead_on_zero_from_above() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nexttowardf(float 0x36A0000000000000, fp128 0xL00000000000000000000000000000000)
+; CHECK-NEXT:    ret float 0.000000e+00
+;
+  %min_subnormal = load float, float* @flt_pos_min_subnormal
+  %zero = fpext float 0.0 to fp128
+  %next = call float @nexttowardf(float %min_subnormal, fp128 %zero)
+  ret float %next
+}
+define float @nexttowardf_not_marked_dead_on_zero_from_below() {
+; CHECK-LABEL: define float @nexttowardf_not_marked_dead_on_zero_from_below() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nexttowardf(float 0xB6A0000000000000, fp128 0xL00000000000000000000000000000000)
+; CHECK-NEXT:    ret float -0.000000e+00
+;
+  %min_subnormal = load float, float* @flt_neg_min_subnormal
+  %zero = fpext float 0.0 to fp128
+  %next = call float @nexttowardf(float %min_subnormal, fp128 %zero)
+  ret float %next
+}
+define float @nexttowardf_not_marked_dead_on_subnormal() {
+; CHECK-LABEL: define float @nexttowardf_not_marked_dead_on_subnormal() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nexttowardf(float 0x36A0000000000000, fp128 0xL00000000000000003FFF000000000000)
+; CHECK-NEXT:    ret float 0x36B0000000000000
+;
+  %subnormal = load float, float* @flt_pos_min_subnormal
+  %target = fpext float 1.0 to fp128
+  %next = call float @nexttowardf(float %subnormal, fp128 %target)
+  ret float %next
+}
diff --git a/llvm/test/Transforms/InstCombine/constant-fold-nexttoward-ppc-fp128.ll b/llvm/test/Transforms/InstCombine/constant-fold-nexttoward-ppc-fp128.ll
new file mode 100644
index 0000000000000..30da81b1bd01f
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/constant-fold-nexttoward-ppc-fp128.ll
@@ -0,0 +1,187 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: cat %S/floating-point-constants.ll %s | opt -passes=instcombine -S | FileCheck %s
+
+declare double @nexttoward(double noundef, ppc_fp128 noundef) #0
+declare float @nexttowardf(float noundef, ppc_fp128 noundef) #0
+
+attribut...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Nov 10, 2025

@llvm/pr-subscribers-llvm-transforms

Author: Sayan Sivakumaran (sivakusayan)

Changes

Fixes issue #74368.

This patch enables us to constant fold nextafter and nexttoward as long as we know that errno won't be written. In the latter case, we should keep the function call so the programmer can observe the side effect.

Apologies for the large patch, the majority of it are lit tests. I wasn't sure how to cut down on the repetitiveness here, let me know if you have any better ideas. I got the idea for reusing the constants across .ll files from the LLVM Discord.


Patch is 38.99 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/167324.diff

7 Files Affected:

  • (modified) llvm/include/llvm/ADT/APFloat.h (+13)
  • (modified) llvm/lib/Analysis/ConstantFolding.cpp (+39-1)
  • (added) llvm/test/Transforms/InstCombine/constant-fold-nextafter.ll (+171)
  • (added) llvm/test/Transforms/InstCombine/constant-fold-nexttoward-fp128.ll (+187)
  • (added) llvm/test/Transforms/InstCombine/constant-fold-nexttoward-ppc-fp128.ll (+187)
  • (added) llvm/test/Transforms/InstCombine/constant-fold-nexttoward-x86-fp80.ll (+187)
  • (added) llvm/test/Transforms/InstCombine/floating-point-constants.ll (+48)
diff --git a/llvm/include/llvm/ADT/APFloat.h b/llvm/include/llvm/ADT/APFloat.h
index 82ac9a3a1ef80..32e5c1aafc245 100644
--- a/llvm/include/llvm/ADT/APFloat.h
+++ b/llvm/include/llvm/ADT/APFloat.h
@@ -1149,6 +1149,19 @@ class APFloat : public APFloatBase {
   /// \param Semantics - type float semantics
   LLVM_ABI static APFloat getAllOnesValue(const fltSemantics &Semantics);
 
+  /// Returns a copy of this APFloat with the requested semantics.
+  /// The requested semantics should be equal to or stronger
+  /// than the semantics of the current instance.
+  APFloat getPromoted(const fltSemantics &Sem) const {
+    assert(isRepresentableBy(this->getSemantics(), Sem) &&
+           "Target semantics will lose information.");
+    APFloat Val(*this);
+    bool LosesInfo;
+    Val.convert(Sem, rmNearestTiesToEven, &LosesInfo);
+    assert(!LosesInfo);
+    return Val;
+  }
+
   /// Returns true if the given semantics has actual significand.
   ///
   /// \param Sem - type float semantics
diff --git a/llvm/lib/Analysis/ConstantFolding.cpp b/llvm/lib/Analysis/ConstantFolding.cpp
index da32542cf7870..fadd3e7896c3a 100755
--- a/llvm/lib/Analysis/ConstantFolding.cpp
+++ b/llvm/lib/Analysis/ConstantFolding.cpp
@@ -1996,7 +1996,9 @@ bool llvm::canConstantFoldCallTo(const CallBase *Call, const Function *F) {
            Name == "log10f" || Name == "logb" || Name == "logbf" ||
            Name == "log1p" || Name == "log1pf";
   case 'n':
-    return Name == "nearbyint" || Name == "nearbyintf";
+    return Name == "nearbyint" || Name == "nearbyintf" || Name == "nextafter" ||
+           Name == "nextafterf" || Name == "nexttoward" ||
+           Name == "nexttowardf";
   case 'p':
     return Name == "pow" || Name == "powf";
   case 'r':
@@ -3221,6 +3223,26 @@ static Constant *ConstantFoldLibCall2(StringRef Name, Type *Ty,
     if (TLI->has(Func))
       return ConstantFoldBinaryFP(atan2, Op1V, Op2V, Ty);
     break;
+  case LibFunc_nextafter:
+  case LibFunc_nextafterf:
+  case LibFunc_nexttoward:
+  case LibFunc_nexttowardf:
+    if (TLI->has(Func)) {
+      if (Op1V.isNaN() || Op2V.isNaN()) {
+        return ConstantFP::get(Ty->getContext(),
+                               APFloat::getNaN(Ty->getFltSemantics()));
+      }
+
+      APFloat PromotedOp1V = Op1V.getPromoted(APFloat::IEEEquad());
+      APFloat PromotedOp2V = Op2V.getPromoted(APFloat::IEEEquad());
+      if (PromotedOp1V == PromotedOp2V) {
+        return ConstantFP::get(Ty->getContext(), Op1V);
+      }
+
+      APFloat Next(Op1V);
+      Next.next(/*nextDown=*/PromotedOp1V > PromotedOp2V);
+      return ConstantFP::get(Ty->getContext(), Next);
+    }
   }
 
   return nullptr;
@@ -4655,6 +4677,22 @@ bool llvm::isMathLibCallNoop(const CallBase *Call,
         // may occur, so allow for that possibility.
         return !Op0.isZero() || !Op1.isZero();
 
+      case LibFunc_nextafter:
+      case LibFunc_nextafterf:
+      case LibFunc_nextafterl:
+      case LibFunc_nexttoward:
+      case LibFunc_nexttowardf:
+      case LibFunc_nexttowardl: {
+        APFloat PromotedOp0 = Op0.getPromoted(APFloat::IEEEquad());
+        APFloat PromotedOp1 = Op1.getPromoted(APFloat::IEEEquad());
+        if (PromotedOp0 == PromotedOp1)
+          return true;
+
+        APFloat Next(Op0);
+        Next.next(/*nextDown=*/PromotedOp0 > PromotedOp1);
+        bool DidOverflow = Op0.isLargest() && Next.isInfinity();
+        return !Next.isZero() && !Next.isDenormal() && !DidOverflow;
+      }
       default:
         break;
       }
diff --git a/llvm/test/Transforms/InstCombine/constant-fold-nextafter.ll b/llvm/test/Transforms/InstCombine/constant-fold-nextafter.ll
new file mode 100644
index 0000000000000..9af0a5aeba871
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/constant-fold-nextafter.ll
@@ -0,0 +1,171 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: cat %S/floating-point-constants.ll %s | opt -passes=instcombine -S | FileCheck %s
+
+declare double @nextafter(double noundef, double noundef) #0
+declare float @nextafterf(float noundef, float noundef) #0
+
+attributes #0 = { willreturn memory(errnomem: write) }
+
+; ==================
+; nextafter tests
+; ==================
+
+define double @nextafter_can_constant_fold_up_direction() {
+; CHECK-LABEL: define double @nextafter_can_constant_fold_up_direction() {
+; CHECK-NEXT:    ret double 0x3FF0000000000001
+;
+  %next = call double @nextafter(double noundef 1.0, double noundef 2.0)
+  ret double %next
+}
+define double @nextafter_can_constant_fold_down_direction() {
+; CHECK-LABEL: define double @nextafter_can_constant_fold_down_direction() {
+; CHECK-NEXT:    ret double 0x3FEFFFFFFFFFFFFF
+;
+  %next = call double @nextafter(double noundef 1.0, double noundef 0.0)
+  ret double %next
+}
+define double @nextafter_can_constant_fold_equal_args() {
+; CHECK-LABEL: define double @nextafter_can_constant_fold_equal_args() {
+; CHECK-NEXT:    ret double 1.000000e+00
+;
+  %next = call double @nextafter(double noundef 1.0, double noundef 1.0)
+  ret double %next
+}
+define double @nextafter_can_constant_fold_with_nan_arg() {
+; CHECK-LABEL: define double @nextafter_can_constant_fold_with_nan_arg() {
+; CHECK-NEXT:    ret double 0x7FF8000000000000
+;
+  %arg = load double, double* @dbl_nan
+  %next = call double @nextafter(double 1.0, double %arg)
+  ret double %next
+}
+define double @nextafter_not_marked_dead_on_pos_overflow () {
+; CHECK-LABEL: define double @nextafter_not_marked_dead_on_pos_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nextafter(double 0x7FEFFFFFFFFFFFFF, double 0x7FF0000000000000)
+; CHECK-NEXT:    ret double 0x7FF0000000000000
+;
+  %arg1 = load double, double* @dbl_pos_max
+  %arg2 = load double, double* @dbl_pos_infinity
+  %next = call double @nextafter(double %arg1, double %arg2)
+  ret double %next
+}
+define double @nextafter_not_marked_dead_on_neg_overflow() {
+; CHECK-LABEL: define double @nextafter_not_marked_dead_on_neg_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nextafter(double 0xFFEFFFFFFFFFFFFF, double 0xFFF0000000000000)
+; CHECK-NEXT:    ret double 0xFFF0000000000000
+;
+  %arg1 = load double, double* @dbl_neg_max
+  %arg2 = load double, double* @dbl_neg_infinity
+  %next = call double @nextafter(double %arg1, double %arg2)
+  ret double %next
+}
+define double @nextafter_not_marked_dead_on_zero_from_above() {
+; CHECK-LABEL: define double @nextafter_not_marked_dead_on_zero_from_above() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nextafter(double 4.940660e-324, double 0.000000e+00)
+; CHECK-NEXT:    ret double 0.000000e+00
+;
+  %arg = load double, double* @dbl_pos_min_subnormal
+  %next = call double @nextafter(double %arg, double 0.0)
+  ret double %next
+}
+define double @nextafter_not_marked_dead_on_zero_from_below() {
+; CHECK-LABEL: define double @nextafter_not_marked_dead_on_zero_from_below() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nextafter(double -4.940660e-324, double 0.000000e+00)
+; CHECK-NEXT:    ret double -0.000000e+00
+;
+  %arg = load double, double* @dbl_neg_min_subnormal
+  %next = call double @nextafter(double %arg, double 0.0)
+  ret double %next
+}
+define double @nextafter_not_marked_dead_on_subnormal() {
+; CHECK-LABEL: define double @nextafter_not_marked_dead_on_subnormal() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nextafter(double 4.940660e-324, double 0x7FF0000000000000)
+; CHECK-NEXT:    ret double 9.881310e-324
+;
+  %subnormal = load double, double* @dbl_pos_min_subnormal
+  %infinity = load double, double* @dbl_pos_infinity
+  %next = call double @nextafter(double %subnormal, double %infinity)
+  ret double %next
+}
+
+; ==================
+; nextafterf tests
+; ==================
+
+define float @nextafterf_can_constant_fold_up_direction() {
+; CHECK-LABEL: define float @nextafterf_can_constant_fold_up_direction() {
+; CHECK-NEXT:    ret float 0x3FF0000020000000
+;
+  %next = call float @nextafterf(float noundef 1.0, float noundef 2.0)
+  ret float %next
+}
+define float @nextafterf_can_constant_fold_down_direction() {
+; CHECK-LABEL: define float @nextafterf_can_constant_fold_down_direction() {
+; CHECK-NEXT:    ret float 0x3FEFFFFFE0000000
+;
+  %next = call float @nextafterf(float noundef 1.0, float noundef 0.0)
+  ret float %next
+}
+define float @nextafterf_can_constant_fold_equal_args() {
+; CHECK-LABEL: define float @nextafterf_can_constant_fold_equal_args() {
+; CHECK-NEXT:    ret float 1.000000e+00
+;
+  %next = call float @nextafterf(float noundef 1.0, float noundef 1.0)
+  ret float %next
+}
+define float @nextafterf_can_constant_fold_with_nan_arg() {
+; CHECK-LABEL: define float @nextafterf_can_constant_fold_with_nan_arg() {
+; CHECK-NEXT:    ret float 0x7FF8000000000000
+;
+  %arg = load float, float* @flt_nan
+  %next = call float @nextafterf(float 1.0, float %arg)
+  ret float %next
+}
+define float @nextafterf_not_marked_dead_on_pos_overflow() {
+; CHECK-LABEL: define float @nextafterf_not_marked_dead_on_pos_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nextafterf(float 0x47EFFFFFE0000000, float 0x7FF0000000000000)
+; CHECK-NEXT:    ret float 0x7FF0000000000000
+;
+  %arg1 = load float, float* @flt_pos_max
+  %arg2 = load float, float* @flt_pos_infinity
+  %next = call float @nextafterf(float %arg1, float %arg2)
+  ret float %next
+}
+define float @nextafterf_not_marked_dead_on_neg_overflow() {
+; CHECK-LABEL: define float @nextafterf_not_marked_dead_on_neg_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nextafterf(float 0xC7EFFFFFE0000000, float 0xFFF0000000000000)
+; CHECK-NEXT:    ret float 0xFFF0000000000000
+;
+  %arg1 = load float, float* @flt_neg_max
+  %arg2 = load float, float* @flt_neg_infinity
+  %next = call float @nextafterf(float %arg1, float %arg2)
+  ret float %next
+}
+define float @nextafterf_not_marked_dead_on_zero_from_above() {
+; CHECK-LABEL: define float @nextafterf_not_marked_dead_on_zero_from_above() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nextafterf(float 0x36A0000000000000, float 0.000000e+00)
+; CHECK-NEXT:    ret float 0.000000e+00
+;
+  %arg = load float, float* @flt_pos_min_subnormal
+  %next = call float @nextafterf(float %arg, float 0.0)
+  ret float %next
+}
+define float @nextafterf_not_marked_dead_on_zero_from_below() {
+; CHECK-LABEL: define float @nextafterf_not_marked_dead_on_zero_from_below() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nextafterf(float 0xB6A0000000000000, float 0.000000e+00)
+; CHECK-NEXT:    ret float -0.000000e+00
+;
+  %arg = load float, float* @flt_neg_min_subnormal
+  %next = call float @nextafterf(float %arg, float 0.0)
+  ret float %next
+}
+define float @nextafterf_not_marked_dead_on_subnormal() {
+; CHECK-LABEL: define float @nextafterf_not_marked_dead_on_subnormal() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nextafterf(float 0x36A0000000000000, float 0x7FF0000000000000)
+; CHECK-NEXT:    ret float 0x36B0000000000000
+;
+  %subnormal = load float, float* @flt_pos_min_subnormal
+  %infinity = load float, float* @flt_pos_infinity
+  %next = call float @nextafterf(float %subnormal, float %infinity)
+  ret float %next
+}
diff --git a/llvm/test/Transforms/InstCombine/constant-fold-nexttoward-fp128.ll b/llvm/test/Transforms/InstCombine/constant-fold-nexttoward-fp128.ll
new file mode 100644
index 0000000000000..5f044241fbce8
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/constant-fold-nexttoward-fp128.ll
@@ -0,0 +1,187 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: cat %S/floating-point-constants.ll %s | opt -passes=instcombine -S | FileCheck %s
+
+declare double @nexttoward(double noundef, fp128 noundef) #0
+declare float @nexttowardf(float noundef, fp128 noundef) #0
+
+attributes #0 = { willreturn memory(errnomem: write) }
+
+; ==================
+; nexttoward tests
+; ==================
+
+define double @nexttoward_can_constant_fold_up_direction() {
+; CHECK-LABEL: define double @nexttoward_can_constant_fold_up_direction() {
+; CHECK-NEXT:    ret double 0x3FF0000000000001
+;
+  %arg = fpext double 2.0 to fp128
+  %next = call double @nexttoward(double noundef 1.0, fp128 %arg)
+  ret double %next
+}
+define double @nexttoward_can_constant_fold_down_direction() {
+; CHECK-LABEL: define double @nexttoward_can_constant_fold_down_direction() {
+; CHECK-NEXT:    ret double 0x3FEFFFFFFFFFFFFF
+;
+  %arg = fpext double 0.0 to fp128
+  %next = call double @nexttoward(double noundef 1.0, fp128 %arg)
+  ret double %next
+}
+define double @nexttoward_can_constant_fold_equal_args() {
+; CHECK-LABEL: define double @nexttoward_can_constant_fold_equal_args() {
+; CHECK-NEXT:    ret double 1.000000e+00
+;
+  %arg = fpext double 1.0 to fp128
+  %next = call double @nexttoward(double 1.0, fp128 %arg)
+  ret double %next
+}
+define double @nexttoward_can_constant_fold_with_nan_arg() {
+; CHECK-LABEL: define double @nexttoward_can_constant_fold_with_nan_arg() {
+; CHECK-NEXT:    ret double 0x7FF8000000000000
+;
+  %nan = load double, double* @dbl_nan
+  %arg = fpext double %nan to fp128
+  %next = call double @nexttoward(double 1.0, fp128 %arg)
+  ret double %next
+}
+define double @nexttoward_not_marked_dead_on_pos_overflow() {
+; CHECK-LABEL: define double @nexttoward_not_marked_dead_on_pos_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nexttoward(double 0x7FEFFFFFFFFFFFFF, fp128 0xL00000000000000007FFF000000000000)
+; CHECK-NEXT:    ret double 0x7FF0000000000000
+;
+  %pos_max = load double, double* @dbl_pos_max
+  %pos_inf = load double, double* @dbl_pos_infinity
+  %ext_pos_inf = fpext double %pos_inf to fp128
+  %next = call double @nexttoward(double %pos_max, fp128 %ext_pos_inf)
+  ret double %next
+}
+define double @nexttoward_not_marked_dead_on_neg_overflow() {
+; CHECK-LABEL: define double @nexttoward_not_marked_dead_on_neg_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nexttoward(double 0xFFEFFFFFFFFFFFFF, fp128 0xL0000000000000000FFFF000000000000)
+; CHECK-NEXT:    ret double 0xFFF0000000000000
+;
+  %neg_max = load double, double* @dbl_neg_max
+  %neg_inf = load double, double* @dbl_neg_infinity
+  %ext_neg_inf = fpext double %neg_inf to fp128
+  %next = call double @nexttoward(double %neg_max, fp128 %ext_neg_inf)
+  ret double %next
+}
+define double @nexttoward_not_marked_dead_on_zero_from_above() {
+; CHECK-LABEL: define double @nexttoward_not_marked_dead_on_zero_from_above() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nexttoward(double 4.940660e-324, fp128 0xL00000000000000000000000000000000)
+; CHECK-NEXT:    ret double 0.000000e+00
+;
+  %subnormal = load double, double* @dbl_pos_min_subnormal
+  %zero = fpext double 0.0 to fp128
+  %next = call double @nexttoward(double %subnormal, fp128 %zero)
+  ret double %next
+}
+define double @nexttoward_not_marked_dead_on_zero_from_below() {
+; CHECK-LABEL: define double @nexttoward_not_marked_dead_on_zero_from_below() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nexttoward(double -4.940660e-324, fp128 0xL00000000000000000000000000000000)
+; CHECK-NEXT:    ret double -0.000000e+00
+;
+  %subnormal = load double, double* @dbl_neg_min_subnormal
+  %zero = fpext double 0.0 to fp128
+  %next = call double @nexttoward(double %subnormal, fp128 %zero)
+  ret double %next
+}
+define double @nexttoward_not_marked_dead_on_subnormal() {
+; CHECK-LABEL: define double @nexttoward_not_marked_dead_on_subnormal() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nexttoward(double 4.940660e-324, fp128 0xL00000000000000003FFF000000000000)
+; CHECK-NEXT:    ret double 9.881310e-324
+;
+  %subnormal = load double, double* @dbl_pos_min_subnormal
+  %target = fpext double 1.0 to fp128
+  %next = call double @nexttoward(double %subnormal, fp128 %target)
+  ret double %next
+}
+
+; ==================
+; nexttowardf tests
+; ==================
+
+define float @nexttowardf_can_constant_fold_up_direction() {
+; CHECK-LABEL: define float @nexttowardf_can_constant_fold_up_direction() {
+; CHECK-NEXT:    ret float 0x3FF0000020000000
+;
+  %arg = fpext float 2.0 to fp128
+  %next = call float @nexttowardf(float noundef 1.0, fp128 %arg)
+  ret float %next
+}
+define float @nexttowardf_can_constant_fold_down_direction() {
+; CHECK-LABEL: define float @nexttowardf_can_constant_fold_down_direction() {
+; CHECK-NEXT:    ret float 0x3FEFFFFFE0000000
+;
+  %arg = fpext float 0.0 to fp128
+  %next = call float @nexttowardf(float noundef 1.0, fp128 %arg)
+  ret float %next
+}
+define float @nexttowardf_can_constant_fold_equal_args() {
+; CHECK-LABEL: define float @nexttowardf_can_constant_fold_equal_args() {
+; CHECK-NEXT:    ret float 1.000000e+00
+;
+  %arg = fpext float 1.0 to fp128
+  %next = call float @nexttowardf(float 1.0, fp128 %arg)
+  ret float %next
+}
+define float @nexttowardf_can_constant_fold_with_nan_arg() {
+; CHECK-LABEL: define float @nexttowardf_can_constant_fold_with_nan_arg() {
+; CHECK-NEXT:    ret float 0x7FF8000000000000
+;
+  %nan = load float, float* @flt_nan
+  %ext_nan = fpext float %nan to fp128
+  %next = call float @nexttowardf(float 1.0, fp128 %ext_nan)
+  ret float %next
+}
+define float @nexttowardf_not_marked_dead_on_pos_overflow () {
+; CHECK-LABEL: define float @nexttowardf_not_marked_dead_on_pos_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nexttowardf(float 0x47EFFFFFE0000000, fp128 0xL00000000000000007FFF000000000000)
+; CHECK-NEXT:    ret float 0x7FF0000000000000
+;
+  %pos_max = load float, float* @flt_pos_max
+  %pos_inf = load float, float* @flt_pos_infinity
+  %ext_pos_inf = fpext float %pos_inf to fp128
+  %next = call float @nexttowardf(float %pos_max, fp128 %ext_pos_inf)
+  ret float %next
+}
+define float @nexttowardf_not_marked_dead_on_neg_overflow() {
+; CHECK-LABEL: define float @nexttowardf_not_marked_dead_on_neg_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nexttowardf(float 0xC7EFFFFFE0000000, fp128 0xL0000000000000000FFFF000000000000)
+; CHECK-NEXT:    ret float 0xFFF0000000000000
+;
+  %neg_max = load float, float* @flt_neg_max
+  %neg_inf = load float, float* @flt_neg_infinity
+  %ext_neg_inf = fpext float %neg_inf to fp128
+  %next = call float @nexttowardf(float %neg_max, fp128 %ext_neg_inf)
+  ret float %next
+}
+define float @nexttowardf_not_marked_dead_on_zero_from_above() {
+; CHECK-LABEL: define float @nexttowardf_not_marked_dead_on_zero_from_above() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nexttowardf(float 0x36A0000000000000, fp128 0xL00000000000000000000000000000000)
+; CHECK-NEXT:    ret float 0.000000e+00
+;
+  %min_subnormal = load float, float* @flt_pos_min_subnormal
+  %zero = fpext float 0.0 to fp128
+  %next = call float @nexttowardf(float %min_subnormal, fp128 %zero)
+  ret float %next
+}
+define float @nexttowardf_not_marked_dead_on_zero_from_below() {
+; CHECK-LABEL: define float @nexttowardf_not_marked_dead_on_zero_from_below() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nexttowardf(float 0xB6A0000000000000, fp128 0xL00000000000000000000000000000000)
+; CHECK-NEXT:    ret float -0.000000e+00
+;
+  %min_subnormal = load float, float* @flt_neg_min_subnormal
+  %zero = fpext float 0.0 to fp128
+  %next = call float @nexttowardf(float %min_subnormal, fp128 %zero)
+  ret float %next
+}
+define float @nexttowardf_not_marked_dead_on_subnormal() {
+; CHECK-LABEL: define float @nexttowardf_not_marked_dead_on_subnormal() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nexttowardf(float 0x36A0000000000000, fp128 0xL00000000000000003FFF000000000000)
+; CHECK-NEXT:    ret float 0x36B0000000000000
+;
+  %subnormal = load float, float* @flt_pos_min_subnormal
+  %target = fpext float 1.0 to fp128
+  %next = call float @nexttowardf(float %subnormal, fp128 %target)
+  ret float %next
+}
diff --git a/llvm/test/Transforms/InstCombine/constant-fold-nexttoward-ppc-fp128.ll b/llvm/test/Transforms/InstCombine/constant-fold-nexttoward-ppc-fp128.ll
new file mode 100644
index 0000000000000..30da81b1bd01f
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/constant-fold-nexttoward-ppc-fp128.ll
@@ -0,0 +1,187 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: cat %S/floating-point-constants.ll %s | opt -passes=instcombine -S | FileCheck %s
+
+declare double @nexttoward(double noundef, ppc_fp128 noundef) #0
+declare float @nexttowardf(float noundef, ppc_fp128 noundef) #0
+
+attribut...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Nov 10, 2025

@llvm/pr-subscribers-llvm-adt

Author: Sayan Sivakumaran (sivakusayan)

Changes

Fixes issue #74368.

This patch enables us to constant fold nextafter and nexttoward as long as we know that errno won't be written. In the latter case, we should keep the function call so the programmer can observe the side effect.

Apologies for the large patch, the majority of it are lit tests. I wasn't sure how to cut down on the repetitiveness here, let me know if you have any better ideas. I got the idea for reusing the constants across .ll files from the LLVM Discord.


Patch is 38.99 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/167324.diff

7 Files Affected:

  • (modified) llvm/include/llvm/ADT/APFloat.h (+13)
  • (modified) llvm/lib/Analysis/ConstantFolding.cpp (+39-1)
  • (added) llvm/test/Transforms/InstCombine/constant-fold-nextafter.ll (+171)
  • (added) llvm/test/Transforms/InstCombine/constant-fold-nexttoward-fp128.ll (+187)
  • (added) llvm/test/Transforms/InstCombine/constant-fold-nexttoward-ppc-fp128.ll (+187)
  • (added) llvm/test/Transforms/InstCombine/constant-fold-nexttoward-x86-fp80.ll (+187)
  • (added) llvm/test/Transforms/InstCombine/floating-point-constants.ll (+48)
diff --git a/llvm/include/llvm/ADT/APFloat.h b/llvm/include/llvm/ADT/APFloat.h
index 82ac9a3a1ef80..32e5c1aafc245 100644
--- a/llvm/include/llvm/ADT/APFloat.h
+++ b/llvm/include/llvm/ADT/APFloat.h
@@ -1149,6 +1149,19 @@ class APFloat : public APFloatBase {
   /// \param Semantics - type float semantics
   LLVM_ABI static APFloat getAllOnesValue(const fltSemantics &Semantics);
 
+  /// Returns a copy of this APFloat with the requested semantics.
+  /// The requested semantics should be equal to or stronger
+  /// than the semantics of the current instance.
+  APFloat getPromoted(const fltSemantics &Sem) const {
+    assert(isRepresentableBy(this->getSemantics(), Sem) &&
+           "Target semantics will lose information.");
+    APFloat Val(*this);
+    bool LosesInfo;
+    Val.convert(Sem, rmNearestTiesToEven, &LosesInfo);
+    assert(!LosesInfo);
+    return Val;
+  }
+
   /// Returns true if the given semantics has actual significand.
   ///
   /// \param Sem - type float semantics
diff --git a/llvm/lib/Analysis/ConstantFolding.cpp b/llvm/lib/Analysis/ConstantFolding.cpp
index da32542cf7870..fadd3e7896c3a 100755
--- a/llvm/lib/Analysis/ConstantFolding.cpp
+++ b/llvm/lib/Analysis/ConstantFolding.cpp
@@ -1996,7 +1996,9 @@ bool llvm::canConstantFoldCallTo(const CallBase *Call, const Function *F) {
            Name == "log10f" || Name == "logb" || Name == "logbf" ||
            Name == "log1p" || Name == "log1pf";
   case 'n':
-    return Name == "nearbyint" || Name == "nearbyintf";
+    return Name == "nearbyint" || Name == "nearbyintf" || Name == "nextafter" ||
+           Name == "nextafterf" || Name == "nexttoward" ||
+           Name == "nexttowardf";
   case 'p':
     return Name == "pow" || Name == "powf";
   case 'r':
@@ -3221,6 +3223,26 @@ static Constant *ConstantFoldLibCall2(StringRef Name, Type *Ty,
     if (TLI->has(Func))
       return ConstantFoldBinaryFP(atan2, Op1V, Op2V, Ty);
     break;
+  case LibFunc_nextafter:
+  case LibFunc_nextafterf:
+  case LibFunc_nexttoward:
+  case LibFunc_nexttowardf:
+    if (TLI->has(Func)) {
+      if (Op1V.isNaN() || Op2V.isNaN()) {
+        return ConstantFP::get(Ty->getContext(),
+                               APFloat::getNaN(Ty->getFltSemantics()));
+      }
+
+      APFloat PromotedOp1V = Op1V.getPromoted(APFloat::IEEEquad());
+      APFloat PromotedOp2V = Op2V.getPromoted(APFloat::IEEEquad());
+      if (PromotedOp1V == PromotedOp2V) {
+        return ConstantFP::get(Ty->getContext(), Op1V);
+      }
+
+      APFloat Next(Op1V);
+      Next.next(/*nextDown=*/PromotedOp1V > PromotedOp2V);
+      return ConstantFP::get(Ty->getContext(), Next);
+    }
   }
 
   return nullptr;
@@ -4655,6 +4677,22 @@ bool llvm::isMathLibCallNoop(const CallBase *Call,
         // may occur, so allow for that possibility.
         return !Op0.isZero() || !Op1.isZero();
 
+      case LibFunc_nextafter:
+      case LibFunc_nextafterf:
+      case LibFunc_nextafterl:
+      case LibFunc_nexttoward:
+      case LibFunc_nexttowardf:
+      case LibFunc_nexttowardl: {
+        APFloat PromotedOp0 = Op0.getPromoted(APFloat::IEEEquad());
+        APFloat PromotedOp1 = Op1.getPromoted(APFloat::IEEEquad());
+        if (PromotedOp0 == PromotedOp1)
+          return true;
+
+        APFloat Next(Op0);
+        Next.next(/*nextDown=*/PromotedOp0 > PromotedOp1);
+        bool DidOverflow = Op0.isLargest() && Next.isInfinity();
+        return !Next.isZero() && !Next.isDenormal() && !DidOverflow;
+      }
       default:
         break;
       }
diff --git a/llvm/test/Transforms/InstCombine/constant-fold-nextafter.ll b/llvm/test/Transforms/InstCombine/constant-fold-nextafter.ll
new file mode 100644
index 0000000000000..9af0a5aeba871
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/constant-fold-nextafter.ll
@@ -0,0 +1,171 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: cat %S/floating-point-constants.ll %s | opt -passes=instcombine -S | FileCheck %s
+
+declare double @nextafter(double noundef, double noundef) #0
+declare float @nextafterf(float noundef, float noundef) #0
+
+attributes #0 = { willreturn memory(errnomem: write) }
+
+; ==================
+; nextafter tests
+; ==================
+
+define double @nextafter_can_constant_fold_up_direction() {
+; CHECK-LABEL: define double @nextafter_can_constant_fold_up_direction() {
+; CHECK-NEXT:    ret double 0x3FF0000000000001
+;
+  %next = call double @nextafter(double noundef 1.0, double noundef 2.0)
+  ret double %next
+}
+define double @nextafter_can_constant_fold_down_direction() {
+; CHECK-LABEL: define double @nextafter_can_constant_fold_down_direction() {
+; CHECK-NEXT:    ret double 0x3FEFFFFFFFFFFFFF
+;
+  %next = call double @nextafter(double noundef 1.0, double noundef 0.0)
+  ret double %next
+}
+define double @nextafter_can_constant_fold_equal_args() {
+; CHECK-LABEL: define double @nextafter_can_constant_fold_equal_args() {
+; CHECK-NEXT:    ret double 1.000000e+00
+;
+  %next = call double @nextafter(double noundef 1.0, double noundef 1.0)
+  ret double %next
+}
+define double @nextafter_can_constant_fold_with_nan_arg() {
+; CHECK-LABEL: define double @nextafter_can_constant_fold_with_nan_arg() {
+; CHECK-NEXT:    ret double 0x7FF8000000000000
+;
+  %arg = load double, double* @dbl_nan
+  %next = call double @nextafter(double 1.0, double %arg)
+  ret double %next
+}
+define double @nextafter_not_marked_dead_on_pos_overflow () {
+; CHECK-LABEL: define double @nextafter_not_marked_dead_on_pos_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nextafter(double 0x7FEFFFFFFFFFFFFF, double 0x7FF0000000000000)
+; CHECK-NEXT:    ret double 0x7FF0000000000000
+;
+  %arg1 = load double, double* @dbl_pos_max
+  %arg2 = load double, double* @dbl_pos_infinity
+  %next = call double @nextafter(double %arg1, double %arg2)
+  ret double %next
+}
+define double @nextafter_not_marked_dead_on_neg_overflow() {
+; CHECK-LABEL: define double @nextafter_not_marked_dead_on_neg_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nextafter(double 0xFFEFFFFFFFFFFFFF, double 0xFFF0000000000000)
+; CHECK-NEXT:    ret double 0xFFF0000000000000
+;
+  %arg1 = load double, double* @dbl_neg_max
+  %arg2 = load double, double* @dbl_neg_infinity
+  %next = call double @nextafter(double %arg1, double %arg2)
+  ret double %next
+}
+define double @nextafter_not_marked_dead_on_zero_from_above() {
+; CHECK-LABEL: define double @nextafter_not_marked_dead_on_zero_from_above() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nextafter(double 4.940660e-324, double 0.000000e+00)
+; CHECK-NEXT:    ret double 0.000000e+00
+;
+  %arg = load double, double* @dbl_pos_min_subnormal
+  %next = call double @nextafter(double %arg, double 0.0)
+  ret double %next
+}
+define double @nextafter_not_marked_dead_on_zero_from_below() {
+; CHECK-LABEL: define double @nextafter_not_marked_dead_on_zero_from_below() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nextafter(double -4.940660e-324, double 0.000000e+00)
+; CHECK-NEXT:    ret double -0.000000e+00
+;
+  %arg = load double, double* @dbl_neg_min_subnormal
+  %next = call double @nextafter(double %arg, double 0.0)
+  ret double %next
+}
+define double @nextafter_not_marked_dead_on_subnormal() {
+; CHECK-LABEL: define double @nextafter_not_marked_dead_on_subnormal() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nextafter(double 4.940660e-324, double 0x7FF0000000000000)
+; CHECK-NEXT:    ret double 9.881310e-324
+;
+  %subnormal = load double, double* @dbl_pos_min_subnormal
+  %infinity = load double, double* @dbl_pos_infinity
+  %next = call double @nextafter(double %subnormal, double %infinity)
+  ret double %next
+}
+
+; ==================
+; nextafterf tests
+; ==================
+
+define float @nextafterf_can_constant_fold_up_direction() {
+; CHECK-LABEL: define float @nextafterf_can_constant_fold_up_direction() {
+; CHECK-NEXT:    ret float 0x3FF0000020000000
+;
+  %next = call float @nextafterf(float noundef 1.0, float noundef 2.0)
+  ret float %next
+}
+define float @nextafterf_can_constant_fold_down_direction() {
+; CHECK-LABEL: define float @nextafterf_can_constant_fold_down_direction() {
+; CHECK-NEXT:    ret float 0x3FEFFFFFE0000000
+;
+  %next = call float @nextafterf(float noundef 1.0, float noundef 0.0)
+  ret float %next
+}
+define float @nextafterf_can_constant_fold_equal_args() {
+; CHECK-LABEL: define float @nextafterf_can_constant_fold_equal_args() {
+; CHECK-NEXT:    ret float 1.000000e+00
+;
+  %next = call float @nextafterf(float noundef 1.0, float noundef 1.0)
+  ret float %next
+}
+define float @nextafterf_can_constant_fold_with_nan_arg() {
+; CHECK-LABEL: define float @nextafterf_can_constant_fold_with_nan_arg() {
+; CHECK-NEXT:    ret float 0x7FF8000000000000
+;
+  %arg = load float, float* @flt_nan
+  %next = call float @nextafterf(float 1.0, float %arg)
+  ret float %next
+}
+define float @nextafterf_not_marked_dead_on_pos_overflow() {
+; CHECK-LABEL: define float @nextafterf_not_marked_dead_on_pos_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nextafterf(float 0x47EFFFFFE0000000, float 0x7FF0000000000000)
+; CHECK-NEXT:    ret float 0x7FF0000000000000
+;
+  %arg1 = load float, float* @flt_pos_max
+  %arg2 = load float, float* @flt_pos_infinity
+  %next = call float @nextafterf(float %arg1, float %arg2)
+  ret float %next
+}
+define float @nextafterf_not_marked_dead_on_neg_overflow() {
+; CHECK-LABEL: define float @nextafterf_not_marked_dead_on_neg_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nextafterf(float 0xC7EFFFFFE0000000, float 0xFFF0000000000000)
+; CHECK-NEXT:    ret float 0xFFF0000000000000
+;
+  %arg1 = load float, float* @flt_neg_max
+  %arg2 = load float, float* @flt_neg_infinity
+  %next = call float @nextafterf(float %arg1, float %arg2)
+  ret float %next
+}
+define float @nextafterf_not_marked_dead_on_zero_from_above() {
+; CHECK-LABEL: define float @nextafterf_not_marked_dead_on_zero_from_above() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nextafterf(float 0x36A0000000000000, float 0.000000e+00)
+; CHECK-NEXT:    ret float 0.000000e+00
+;
+  %arg = load float, float* @flt_pos_min_subnormal
+  %next = call float @nextafterf(float %arg, float 0.0)
+  ret float %next
+}
+define float @nextafterf_not_marked_dead_on_zero_from_below() {
+; CHECK-LABEL: define float @nextafterf_not_marked_dead_on_zero_from_below() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nextafterf(float 0xB6A0000000000000, float 0.000000e+00)
+; CHECK-NEXT:    ret float -0.000000e+00
+;
+  %arg = load float, float* @flt_neg_min_subnormal
+  %next = call float @nextafterf(float %arg, float 0.0)
+  ret float %next
+}
+define float @nextafterf_not_marked_dead_on_subnormal() {
+; CHECK-LABEL: define float @nextafterf_not_marked_dead_on_subnormal() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nextafterf(float 0x36A0000000000000, float 0x7FF0000000000000)
+; CHECK-NEXT:    ret float 0x36B0000000000000
+;
+  %subnormal = load float, float* @flt_pos_min_subnormal
+  %infinity = load float, float* @flt_pos_infinity
+  %next = call float @nextafterf(float %subnormal, float %infinity)
+  ret float %next
+}
diff --git a/llvm/test/Transforms/InstCombine/constant-fold-nexttoward-fp128.ll b/llvm/test/Transforms/InstCombine/constant-fold-nexttoward-fp128.ll
new file mode 100644
index 0000000000000..5f044241fbce8
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/constant-fold-nexttoward-fp128.ll
@@ -0,0 +1,187 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: cat %S/floating-point-constants.ll %s | opt -passes=instcombine -S | FileCheck %s
+
+declare double @nexttoward(double noundef, fp128 noundef) #0
+declare float @nexttowardf(float noundef, fp128 noundef) #0
+
+attributes #0 = { willreturn memory(errnomem: write) }
+
+; ==================
+; nexttoward tests
+; ==================
+
+define double @nexttoward_can_constant_fold_up_direction() {
+; CHECK-LABEL: define double @nexttoward_can_constant_fold_up_direction() {
+; CHECK-NEXT:    ret double 0x3FF0000000000001
+;
+  %arg = fpext double 2.0 to fp128
+  %next = call double @nexttoward(double noundef 1.0, fp128 %arg)
+  ret double %next
+}
+define double @nexttoward_can_constant_fold_down_direction() {
+; CHECK-LABEL: define double @nexttoward_can_constant_fold_down_direction() {
+; CHECK-NEXT:    ret double 0x3FEFFFFFFFFFFFFF
+;
+  %arg = fpext double 0.0 to fp128
+  %next = call double @nexttoward(double noundef 1.0, fp128 %arg)
+  ret double %next
+}
+define double @nexttoward_can_constant_fold_equal_args() {
+; CHECK-LABEL: define double @nexttoward_can_constant_fold_equal_args() {
+; CHECK-NEXT:    ret double 1.000000e+00
+;
+  %arg = fpext double 1.0 to fp128
+  %next = call double @nexttoward(double 1.0, fp128 %arg)
+  ret double %next
+}
+define double @nexttoward_can_constant_fold_with_nan_arg() {
+; CHECK-LABEL: define double @nexttoward_can_constant_fold_with_nan_arg() {
+; CHECK-NEXT:    ret double 0x7FF8000000000000
+;
+  %nan = load double, double* @dbl_nan
+  %arg = fpext double %nan to fp128
+  %next = call double @nexttoward(double 1.0, fp128 %arg)
+  ret double %next
+}
+define double @nexttoward_not_marked_dead_on_pos_overflow() {
+; CHECK-LABEL: define double @nexttoward_not_marked_dead_on_pos_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nexttoward(double 0x7FEFFFFFFFFFFFFF, fp128 0xL00000000000000007FFF000000000000)
+; CHECK-NEXT:    ret double 0x7FF0000000000000
+;
+  %pos_max = load double, double* @dbl_pos_max
+  %pos_inf = load double, double* @dbl_pos_infinity
+  %ext_pos_inf = fpext double %pos_inf to fp128
+  %next = call double @nexttoward(double %pos_max, fp128 %ext_pos_inf)
+  ret double %next
+}
+define double @nexttoward_not_marked_dead_on_neg_overflow() {
+; CHECK-LABEL: define double @nexttoward_not_marked_dead_on_neg_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nexttoward(double 0xFFEFFFFFFFFFFFFF, fp128 0xL0000000000000000FFFF000000000000)
+; CHECK-NEXT:    ret double 0xFFF0000000000000
+;
+  %neg_max = load double, double* @dbl_neg_max
+  %neg_inf = load double, double* @dbl_neg_infinity
+  %ext_neg_inf = fpext double %neg_inf to fp128
+  %next = call double @nexttoward(double %neg_max, fp128 %ext_neg_inf)
+  ret double %next
+}
+define double @nexttoward_not_marked_dead_on_zero_from_above() {
+; CHECK-LABEL: define double @nexttoward_not_marked_dead_on_zero_from_above() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nexttoward(double 4.940660e-324, fp128 0xL00000000000000000000000000000000)
+; CHECK-NEXT:    ret double 0.000000e+00
+;
+  %subnormal = load double, double* @dbl_pos_min_subnormal
+  %zero = fpext double 0.0 to fp128
+  %next = call double @nexttoward(double %subnormal, fp128 %zero)
+  ret double %next
+}
+define double @nexttoward_not_marked_dead_on_zero_from_below() {
+; CHECK-LABEL: define double @nexttoward_not_marked_dead_on_zero_from_below() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nexttoward(double -4.940660e-324, fp128 0xL00000000000000000000000000000000)
+; CHECK-NEXT:    ret double -0.000000e+00
+;
+  %subnormal = load double, double* @dbl_neg_min_subnormal
+  %zero = fpext double 0.0 to fp128
+  %next = call double @nexttoward(double %subnormal, fp128 %zero)
+  ret double %next
+}
+define double @nexttoward_not_marked_dead_on_subnormal() {
+; CHECK-LABEL: define double @nexttoward_not_marked_dead_on_subnormal() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call double @nexttoward(double 4.940660e-324, fp128 0xL00000000000000003FFF000000000000)
+; CHECK-NEXT:    ret double 9.881310e-324
+;
+  %subnormal = load double, double* @dbl_pos_min_subnormal
+  %target = fpext double 1.0 to fp128
+  %next = call double @nexttoward(double %subnormal, fp128 %target)
+  ret double %next
+}
+
+; ==================
+; nexttowardf tests
+; ==================
+
+define float @nexttowardf_can_constant_fold_up_direction() {
+; CHECK-LABEL: define float @nexttowardf_can_constant_fold_up_direction() {
+; CHECK-NEXT:    ret float 0x3FF0000020000000
+;
+  %arg = fpext float 2.0 to fp128
+  %next = call float @nexttowardf(float noundef 1.0, fp128 %arg)
+  ret float %next
+}
+define float @nexttowardf_can_constant_fold_down_direction() {
+; CHECK-LABEL: define float @nexttowardf_can_constant_fold_down_direction() {
+; CHECK-NEXT:    ret float 0x3FEFFFFFE0000000
+;
+  %arg = fpext float 0.0 to fp128
+  %next = call float @nexttowardf(float noundef 1.0, fp128 %arg)
+  ret float %next
+}
+define float @nexttowardf_can_constant_fold_equal_args() {
+; CHECK-LABEL: define float @nexttowardf_can_constant_fold_equal_args() {
+; CHECK-NEXT:    ret float 1.000000e+00
+;
+  %arg = fpext float 1.0 to fp128
+  %next = call float @nexttowardf(float 1.0, fp128 %arg)
+  ret float %next
+}
+define float @nexttowardf_can_constant_fold_with_nan_arg() {
+; CHECK-LABEL: define float @nexttowardf_can_constant_fold_with_nan_arg() {
+; CHECK-NEXT:    ret float 0x7FF8000000000000
+;
+  %nan = load float, float* @flt_nan
+  %ext_nan = fpext float %nan to fp128
+  %next = call float @nexttowardf(float 1.0, fp128 %ext_nan)
+  ret float %next
+}
+define float @nexttowardf_not_marked_dead_on_pos_overflow () {
+; CHECK-LABEL: define float @nexttowardf_not_marked_dead_on_pos_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nexttowardf(float 0x47EFFFFFE0000000, fp128 0xL00000000000000007FFF000000000000)
+; CHECK-NEXT:    ret float 0x7FF0000000000000
+;
+  %pos_max = load float, float* @flt_pos_max
+  %pos_inf = load float, float* @flt_pos_infinity
+  %ext_pos_inf = fpext float %pos_inf to fp128
+  %next = call float @nexttowardf(float %pos_max, fp128 %ext_pos_inf)
+  ret float %next
+}
+define float @nexttowardf_not_marked_dead_on_neg_overflow() {
+; CHECK-LABEL: define float @nexttowardf_not_marked_dead_on_neg_overflow() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nexttowardf(float 0xC7EFFFFFE0000000, fp128 0xL0000000000000000FFFF000000000000)
+; CHECK-NEXT:    ret float 0xFFF0000000000000
+;
+  %neg_max = load float, float* @flt_neg_max
+  %neg_inf = load float, float* @flt_neg_infinity
+  %ext_neg_inf = fpext float %neg_inf to fp128
+  %next = call float @nexttowardf(float %neg_max, fp128 %ext_neg_inf)
+  ret float %next
+}
+define float @nexttowardf_not_marked_dead_on_zero_from_above() {
+; CHECK-LABEL: define float @nexttowardf_not_marked_dead_on_zero_from_above() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nexttowardf(float 0x36A0000000000000, fp128 0xL00000000000000000000000000000000)
+; CHECK-NEXT:    ret float 0.000000e+00
+;
+  %min_subnormal = load float, float* @flt_pos_min_subnormal
+  %zero = fpext float 0.0 to fp128
+  %next = call float @nexttowardf(float %min_subnormal, fp128 %zero)
+  ret float %next
+}
+define float @nexttowardf_not_marked_dead_on_zero_from_below() {
+; CHECK-LABEL: define float @nexttowardf_not_marked_dead_on_zero_from_below() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nexttowardf(float 0xB6A0000000000000, fp128 0xL00000000000000000000000000000000)
+; CHECK-NEXT:    ret float -0.000000e+00
+;
+  %min_subnormal = load float, float* @flt_neg_min_subnormal
+  %zero = fpext float 0.0 to fp128
+  %next = call float @nexttowardf(float %min_subnormal, fp128 %zero)
+  ret float %next
+}
+define float @nexttowardf_not_marked_dead_on_subnormal() {
+; CHECK-LABEL: define float @nexttowardf_not_marked_dead_on_subnormal() {
+; CHECK-NEXT:    [[NEXT:%.*]] = call float @nexttowardf(float 0x36A0000000000000, fp128 0xL00000000000000003FFF000000000000)
+; CHECK-NEXT:    ret float 0x36B0000000000000
+;
+  %subnormal = load float, float* @flt_pos_min_subnormal
+  %target = fpext float 1.0 to fp128
+  %next = call float @nexttowardf(float %subnormal, fp128 %target)
+  ret float %next
+}
diff --git a/llvm/test/Transforms/InstCombine/constant-fold-nexttoward-ppc-fp128.ll b/llvm/test/Transforms/InstCombine/constant-fold-nexttoward-ppc-fp128.ll
new file mode 100644
index 0000000000000..30da81b1bd01f
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/constant-fold-nexttoward-ppc-fp128.ll
@@ -0,0 +1,187 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: cat %S/floating-point-constants.ll %s | opt -passes=instcombine -S | FileCheck %s
+
+declare double @nexttoward(double noundef, ppc_fp128 noundef) #0
+declare float @nexttowardf(float noundef, ppc_fp128 noundef) #0
+
+attribut...
[truncated]

Comment on lines 3236 to 3237
APFloat PromotedOp1V = Op1V.getPromoted(APFloat::IEEEquad());
APFloat PromotedOp2V = Op2V.getPromoted(APFloat::IEEEquad());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you promoting these values here?

Copy link
Contributor Author

@sivakusayan sivakusayan Nov 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I should have added a comment here.

The problem is that nexttoward has different types for their first and second payloads, so we can't compare the operands without them having the same semantics. I initially tried promoting the first operand to the type of the second operand, because I believe long double should be able to hold all of the values of float and double. However, I ran into an assertion error on PowerPC stating that semIEEEDouble isn't representably by semPPCDoubleDoubleLegacy, presumably because the minimum exponent of the former is less than the minimum exponent of the latter.

I might have used the APIs incorrectly, but I decided to keep things simple and convert everything to IEEEQuad because its semantics is presumably large enough for me to compare the two operands no matter the floating point type.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PPC long double is a double + double representation, which means every double is trivially representable in it. PPC long double is not representable in an fp128, however, since there can be effectively a pretty wide mantissa, much longer than the mantissa of a fp128.

(The problems of the PPC long double type are one of the reasons long double optimization is somewhat disfavored).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, okay. It sounds like the getPromoted function I added is broken then, since it refuses to do a legal promotion of double to long double on PPC, and would permit an invalid "promotion" from ppc_fp128 to fp128.

I'm not immediately sure if there is an equivalent function that would work when comparing IEEEFloat and DoubleAPFloat layouts, since if I understand correctly isRepresentablyBy can only compare IEEEFloat layouts. It's not something I have the knowledge to work with yet, and I probably wouldn't want to touch that in this MR even if I did.

I think it should be sufficient then to just use the APFloat::convert API to upgrade the first argument's semantics to the second argument's, which is always safe. It sounds like I never needed to touch the ADT module, sorry about that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should also add, C and C++ both guarantee that the set of values for long double is a (not necessarily strict) superset of the set of values representable for double.

Comment on lines 3232 to 3233
return ConstantFP::get(Ty->getContext(),
APFloat::getNaN(Ty->getFltSemantics()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we're constant folding NaNs, we should be making some effort to do payload propagation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this should be done. I also added lit tests to assert that we propagate NaN payloads.

I decided to not propagate payloads if the NaN is the second argument to nexttoward, since the payload could be cut off if we convert to the return type. Hopefully that seems reasonable. Propagating the payload of the second argument in the case of nextafter should be safe.

Comment on lines 4686 to 4687
APFloat PromotedOp0 = Op0.getPromoted(APFloat::IEEEquad());
APFloat PromotedOp1 = Op1.getPromoted(APFloat::IEEEquad());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, why are you promoting these values here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same reason as mentioned in this reply.

@sivakusayan sivakusayan force-pushed the fix/constant-fold-nextafter-and-nexttoward branch from 8f6fa1b to 678ba1e Compare November 11, 2025 17:10
Copy link
Member

@kuhar kuhar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ADT changes look fine % a nit.

I haven't reviewed the other code and test cases -- please wait for a second approval before landing.

Comment on lines +1999 to +2001
return Name == "nearbyint" || Name == "nearbyintf" || Name == "nextafter" ||
Name == "nextafterf" || Name == "nexttoward" ||
Name == "nexttowardf";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, all of the surrounding code can be simplified with llvm::is_contained, but that's for another PR

%subnormal = load double, double* @dbl_pos_min_subnormal
%next = call double @nextafter(double %subnormal, double 1.0) readnone
ret double %next
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused why adding readnone didn't kill the call to nextafter, but I see it didn't do that either for ilogb so maybe this is expected for now?

@sivakusayan
Copy link
Contributor Author

sivakusayan commented Nov 12, 2025

Taking this MR into draft to do a small rework of the implementation. After talking with Joshua Cranmer, it seems like I never needed to touch APFloat at all, and I should probably undo those changes.

I'm guessing that only people from the llvm-adt mailing list reviewed this so far (I might be wrong), and their review won't be needed after I remove the APFloat changes. Sorry for the confusion, I appreciate the review time you two gave me.

Let me know if you would prefer that I close this MR and start another one with the simpler implementation. I think it would make sure you two don't get emails about changes you don't care about, and would give me a chance to clean up the messy git history.

@sivakusayan sivakusayan marked this pull request as draft November 12, 2025 20:34
@sivakusayan
Copy link
Contributor Author

Closing this for a simpler PR I'll open after I get back from vacation early next week. It might also be smart to wait to see what discussion comes out of #167475, since it seems like there is some interest in not constant folding NaNs at all.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

llvm:adt llvm:analysis Includes value tracking, cost tables and constant folding llvm:instcombine Covers the InstCombine, InstSimplify and AggressiveInstCombine passes llvm:transforms

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants