Skip to content

Conversation

@valadaptive
Copy link
Contributor

@valadaptive valadaptive commented Dec 4, 2025

The fminimum_num, fmaximum_num, and roundeven libcalls (and in some cases, their corresponding intrinsics) were not being handled in various components, including constant folding, value tracking, and NumericalStabilitySanitizer. I discovered this in #170018.

This PR fills in the gaps, allowing those libcalls to be consistently lowered to their corresponding intrinsics and constant-folded.

(EDIT: I've also changed the round and ceil libcalls to be marked as memory(none) to match floor, nearbyint, and trunc; I can move that into a separate PR, but I want to avoid stacking too many PRs since I don't have commit access and hence cannot use Graphite.)

@valadaptive valadaptive requested a review from nikic as a code owner December 4, 2025 14:54
@llvmbot llvmbot added llvm:instcombine Covers the InstCombine, InstSimplify and AggressiveInstCombine passes llvm:SelectionDAG SelectionDAGISel as well compiler-rt:sanitizer llvm:analysis Includes value tracking, cost tables and constant folding llvm:transforms labels Dec 4, 2025
@llvmbot
Copy link
Member

llvmbot commented Dec 4, 2025

@llvm/pr-subscribers-llvm-transforms

@llvm/pr-subscribers-llvm-selectiondag

Author: None (valadaptive)

Changes

The fminimum_num, fmaximum_num, and roundeven libcalls (and in some cases, their corresponding intrinsics) were not being handled in various components, including constant folding, value tracking, and NumericalStabilitySanitizer. I discovered this in #170018.

This PR fills in the gaps, allowing those libcalls to be consistently lowered to their corresponding intrinsics and constant-folded.


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

11 Files Affected:

  • (modified) llvm/include/llvm/Analysis/TargetLibraryInfo.h (+5)
  • (modified) llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h (+1)
  • (modified) llvm/lib/Analysis/ConstantFolding.cpp (+8-2)
  • (modified) llvm/lib/Analysis/ValueTracking.cpp (+8)
  • (modified) llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp (+6)
  • (modified) llvm/lib/Transforms/Instrumentation/NumericalStabilitySanitizer.cpp (+9)
  • (modified) llvm/lib/Transforms/Utils/BuildLibCalls.cpp (+15-6)
  • (modified) llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp (+31)
  • (modified) llvm/test/Transforms/InferFunctionAttrs/annotate.ll (+6-6)
  • (modified) llvm/test/Transforms/InstCombine/float-shrink-compare.ll (+30)
  • (added) llvm/test/Transforms/InstSimplify/ConstProp/roundeven.ll (+157)
diff --git a/llvm/include/llvm/Analysis/TargetLibraryInfo.h b/llvm/include/llvm/Analysis/TargetLibraryInfo.h
index 0f98af69f12c6..022f0dbb04421 100644
--- a/llvm/include/llvm/Analysis/TargetLibraryInfo.h
+++ b/llvm/include/llvm/Analysis/TargetLibraryInfo.h
@@ -410,6 +410,10 @@ class TargetLibraryInfo {
     case LibFunc_floor:        case LibFunc_floorf:     case LibFunc_floorl:
     case LibFunc_fmax:         case LibFunc_fmaxf:      case LibFunc_fmaxl:
     case LibFunc_fmin:         case LibFunc_fminf:      case LibFunc_fminl:
+    case LibFunc_fmaximum_num: case LibFunc_fmaximum_numf:
+                                                  case LibFunc_fmaximum_numl:
+    case LibFunc_fminimum_num: case LibFunc_fminimum_numf:
+                                                  case LibFunc_fminimum_numl:
     case LibFunc_ldexp:        case LibFunc_ldexpf:     case LibFunc_ldexpl:
     case LibFunc_log2:         case LibFunc_log2f:      case LibFunc_log2l:
     case LibFunc_memcmp:       case LibFunc_bcmp:       case LibFunc_strcmp:
@@ -417,6 +421,7 @@ class TargetLibraryInfo {
     case LibFunc_nearbyint:    case LibFunc_nearbyintf: case LibFunc_nearbyintl:
     case LibFunc_rint:         case LibFunc_rintf:      case LibFunc_rintl:
     case LibFunc_round:        case LibFunc_roundf:     case LibFunc_roundl:
+    case LibFunc_roundeven:    case LibFunc_roundevenf: case LibFunc_roundevenl:
     case LibFunc_sin:          case LibFunc_sinf:       case LibFunc_sinl:
     case LibFunc_sinh:         case LibFunc_sinhf:      case LibFunc_sinhl:
     case LibFunc_sqrt:         case LibFunc_sqrtf:      case LibFunc_sqrtl:
diff --git a/llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h b/llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h
index 4e7c97194cc59..64d2512308935 100644
--- a/llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h
+++ b/llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h
@@ -205,6 +205,7 @@ class LibCallSimplifier {
   Value *replacePowWithSqrt(CallInst *Pow, IRBuilderBase &B);
   Value *optimizeExp2(CallInst *CI, IRBuilderBase &B);
   Value *optimizeFMinFMax(CallInst *CI, IRBuilderBase &B);
+  Value *optimizeFMinimumnumFMaximumnum(CallInst *CI, IRBuilderBase &B);
   Value *optimizeLog(CallInst *CI, IRBuilderBase &B);
   Value *optimizeSqrt(CallInst *CI, IRBuilderBase &B);
   Value *optimizeFMod(CallInst *CI, IRBuilderBase &B);
diff --git a/llvm/lib/Analysis/ConstantFolding.cpp b/llvm/lib/Analysis/ConstantFolding.cpp
index 63d12ee585e64..b39b32042dd2f 100644
--- a/llvm/lib/Analysis/ConstantFolding.cpp
+++ b/llvm/lib/Analysis/ConstantFolding.cpp
@@ -1984,6 +1984,7 @@ bool llvm::canConstantFoldCallTo(const CallBase *Call, const Function *F) {
   switch (Name[0]) {
   default:
     return false;
+    // clang-format off
   case 'a':
     return Name == "acos" || Name == "acosf" ||
            Name == "asin" || Name == "asinf" ||
@@ -2014,7 +2015,8 @@ bool llvm::canConstantFoldCallTo(const CallBase *Call, const Function *F) {
   case 'r':
     return Name == "remainder" || Name == "remainderf" ||
            Name == "rint" || Name == "rintf" ||
-           Name == "round" || Name == "roundf";
+           Name == "round" || Name == "roundf" ||
+           Name == "roundeven" || Name == "roundevenf";
   case 's':
     return Name == "sin" || Name == "sinf" ||
            Name == "sinh" || Name == "sinhf" ||
@@ -2052,6 +2054,7 @@ bool llvm::canConstantFoldCallTo(const CallBase *Call, const Function *F) {
     case 's':
       return Name == "__sinh_finite" || Name == "__sinhf_finite";
     }
+    // clang-format on
   }
 }
 
@@ -2516,7 +2519,8 @@ static Constant *ConstantFoldScalarCall1(StringRef Name,
 
     // Use internal versions of these intrinsics.
 
-    if (IntrinsicID == Intrinsic::nearbyint || IntrinsicID == Intrinsic::rint) {
+    if (IntrinsicID == Intrinsic::nearbyint || IntrinsicID == Intrinsic::rint ||
+        IntrinsicID == Intrinsic::roundeven) {
       U.roundToIntegral(APFloat::rmNearestTiesToEven);
       return ConstantFP::get(Ty->getContext(), U);
     }
@@ -2988,6 +2992,8 @@ static Constant *ConstantFoldScalarCall1(StringRef Name,
     case LibFunc_nearbyintf:
     case LibFunc_rint:
     case LibFunc_rintf:
+    case LibFunc_roundeven:
+    case LibFunc_roundevenf:
       if (TLI->has(Func)) {
         U.roundToIntegral(APFloat::rmNearestTiesToEven);
         return ConstantFP::get(Ty->getContext(), U);
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 9cb6f19b9340c..6de4f57f75273 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -4680,6 +4680,14 @@ Intrinsic::ID llvm::getIntrinsicForCallSite(const CallBase &CB,
   case LibFunc_fmaxf:
   case LibFunc_fmaxl:
     return Intrinsic::maxnum;
+  case LibFunc_fminimum_num:
+  case LibFunc_fminimum_numf:
+  case LibFunc_fminimum_numl:
+    return Intrinsic::minimumnum;
+  case LibFunc_fmaximum_num:
+  case LibFunc_fmaximum_numf:
+  case LibFunc_fmaximum_numl:
+    return Intrinsic::maximumnum;
   case LibFunc_copysign:
   case LibFunc_copysignf:
   case LibFunc_copysignl:
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
index 09a0673bfe1bb..6f22730c90efe 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
@@ -9710,6 +9710,12 @@ void SelectionDAGBuilder::visitCall(const CallInst &I) {
         if (visitUnaryFloatCall(I, ISD::FROUND))
           return;
         break;
+      case LibFunc_roundeven:
+      case LibFunc_roundevenf:
+      case LibFunc_roundevenl:
+        if (visitUnaryFloatCall(I, ISD::FROUNDEVEN))
+          return;
+        break;
       case LibFunc_trunc:
       case LibFunc_truncf:
       case LibFunc_truncl:
diff --git a/llvm/lib/Transforms/Instrumentation/NumericalStabilitySanitizer.cpp b/llvm/lib/Transforms/Instrumentation/NumericalStabilitySanitizer.cpp
index 66d570b3f831e..8b1d765d77818 100644
--- a/llvm/lib/Transforms/Instrumentation/NumericalStabilitySanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/NumericalStabilitySanitizer.cpp
@@ -1499,6 +1499,12 @@ const KnownIntrinsic::LFEntry KnownIntrinsic::kLibfuncIntrinsics[] = {
     {LibFunc_fminf, "llvm.minnum.f32"},
     {LibFunc_fmin, "llvm.minnum.f64"},
     {LibFunc_fminl, "llvm.minnum.f80"},
+    {LibFunc_fmaximum_numf, "llvm.maximumnum.f32"},
+    {LibFunc_fmaximum_num, "llvm.maximumnum.f64"},
+    {LibFunc_fmaximum_numl, "llvm.maximumnum.f80"},
+    {LibFunc_fminimum_numf, "llvm.minimumnum.f32"},
+    {LibFunc_fminimum_num, "llvm.minimumnum.f64"},
+    {LibFunc_fminimum_numl, "llvm.minimumnum.f80"},
     {LibFunc_ceilf, "llvm.ceil.f32"},
     {LibFunc_ceil, "llvm.ceil.f64"},
     {LibFunc_ceill, "llvm.ceil.f80"},
@@ -1514,6 +1520,9 @@ const KnownIntrinsic::LFEntry KnownIntrinsic::kLibfuncIntrinsics[] = {
     {LibFunc_roundf, "llvm.round.f32"},
     {LibFunc_round, "llvm.round.f64"},
     {LibFunc_roundl, "llvm.round.f80"},
+    {LibFunc_roundevenf, "llvm.roundeven.f32"},
+    {LibFunc_roundeven, "llvm.roundeven.f64"},
+    {LibFunc_roundevenl, "llvm.roundeven.f80"},
 };
 
 const char *KnownIntrinsic::get(LibFunc LFunc) {
diff --git a/llvm/lib/Transforms/Utils/BuildLibCalls.cpp b/llvm/lib/Transforms/Utils/BuildLibCalls.cpp
index 02b73e85d783f..a245b9405cfb7 100644
--- a/llvm/lib/Transforms/Utils/BuildLibCalls.cpp
+++ b/llvm/lib/Transforms/Utils/BuildLibCalls.cpp
@@ -1227,9 +1227,6 @@ bool llvm::inferNonMandatoryLibFuncAttrs(Function &F,
   case LibFunc_atanhf:
   case LibFunc_atanhl:
   case LibFunc_atanl:
-  case LibFunc_ceil:
-  case LibFunc_ceilf:
-  case LibFunc_ceill:
   case LibFunc_cos:
   case LibFunc_cosh:
   case LibFunc_coshf:
@@ -1298,9 +1295,6 @@ bool llvm::inferNonMandatoryLibFuncAttrs(Function &F,
   case LibFunc_rint:
   case LibFunc_rintf:
   case LibFunc_rintl:
-  case LibFunc_round:
-  case LibFunc_roundf:
-  case LibFunc_roundl:
   case LibFunc_scalbln:
   case LibFunc_scalblnf:
   case LibFunc_scalblnl:
@@ -1338,6 +1332,9 @@ bool llvm::inferNonMandatoryLibFuncAttrs(Function &F,
   case LibFunc_copysign:
   case LibFunc_copysignf:
   case LibFunc_copysignl:
+  case LibFunc_ceil:
+  case LibFunc_ceilf:
+  case LibFunc_ceill:
   case LibFunc_fabs:
   case LibFunc_fabsf:
   case LibFunc_fabsl:
@@ -1356,11 +1353,23 @@ bool llvm::inferNonMandatoryLibFuncAttrs(Function &F,
   case LibFunc_fmin:
   case LibFunc_fminf:
   case LibFunc_fminl:
+  case LibFunc_fmaximum_num:
+  case LibFunc_fmaximum_numf:
+  case LibFunc_fmaximum_numl:
+  case LibFunc_fminimum_num:
+  case LibFunc_fminimum_numf:
+  case LibFunc_fminimum_numl:
   case LibFunc_labs:
   case LibFunc_llabs:
   case LibFunc_nearbyint:
   case LibFunc_nearbyintf:
   case LibFunc_nearbyintl:
+  case LibFunc_round:
+  case LibFunc_roundf:
+  case LibFunc_roundl:
+  case LibFunc_roundeven:
+  case LibFunc_roundevenf:
+  case LibFunc_roundevenl:
   case LibFunc_toascii:
   case LibFunc_trunc:
   case LibFunc_truncf:
diff --git a/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp b/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp
index d1548694baa27..20d9b03bd87c8 100644
--- a/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp
@@ -2543,6 +2543,30 @@ Value *LibCallSimplifier::optimizeFMinFMax(CallInst *CI, IRBuilderBase &B) {
                                                 CI->getArgOperand(1), FMF));
 }
 
+Value *LibCallSimplifier::optimizeFMinimumnumFMaximumnum(CallInst *CI,
+                                                         IRBuilderBase &B) {
+  Module *M = CI->getModule();
+
+  // If we can shrink the call to a float function rather than a double
+  // function, do that first.
+  Function *Callee = CI->getCalledFunction();
+  StringRef Name = Callee->getName();
+  if ((Name == "fminimum_num" || Name == "fmaximum_num") &&
+      hasFloatVersion(M, Name))
+    if (Value *Ret = optimizeBinaryDoubleFP(CI, B, TLI))
+      return Ret;
+
+  // The new fminimum_num/fmaximum_num functions, unlike fmin/fmax, *are*
+  // sensitive to the sigh of zero, so we don't change the fast-math flags like
+  // we did for those.
+
+  Intrinsic::ID IID = Callee->getName().starts_with("fminimum_num")
+                          ? Intrinsic::minimumnum
+                          : Intrinsic::maximumnum;
+  return copyFlags(*CI, B.CreateBinaryIntrinsic(IID, CI->getArgOperand(0),
+                                                CI->getArgOperand(1), CI));
+}
+
 Value *LibCallSimplifier::optimizeLog(CallInst *Log, IRBuilderBase &B) {
   Function *LogFn = Log->getCalledFunction();
   StringRef LogNm = LogFn->getName();
@@ -4123,6 +4147,13 @@ Value *LibCallSimplifier::optimizeFloatingPointLibCall(CallInst *CI,
   case LibFunc_fmax:
   case LibFunc_fmaxl:
     return optimizeFMinFMax(CI, Builder);
+  case LibFunc_fminimum_numf:
+  case LibFunc_fminimum_num:
+  case LibFunc_fminimum_numl:
+  case LibFunc_fmaximum_numf:
+  case LibFunc_fmaximum_num:
+  case LibFunc_fmaximum_numl:
+    return optimizeFMinimumnumFMaximumnum(CI, Builder);
   case LibFunc_cabs:
   case LibFunc_cabsf:
   case LibFunc_cabsl:
diff --git a/llvm/test/Transforms/InferFunctionAttrs/annotate.ll b/llvm/test/Transforms/InferFunctionAttrs/annotate.ll
index 25a70a026a0b7..4a0a20b71dfde 100644
--- a/llvm/test/Transforms/InferFunctionAttrs/annotate.ll
+++ b/llvm/test/Transforms/InferFunctionAttrs/annotate.ll
@@ -304,13 +304,13 @@ declare float @cbrtf(float)
 ; CHECK: declare x86_fp80 @cbrtl(x86_fp80) [[MEMNONE_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
 declare x86_fp80 @cbrtl(x86_fp80)
 
-; CHECK: declare double @ceil(double) [[ERRNOMEMONLY_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
+; CHECK: declare double @ceil(double) [[MEMNONE_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
 declare double @ceil(double)
 
-; CHECK: declare float @ceilf(float) [[ERRNOMEMONLY_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
+; CHECK: declare float @ceilf(float) [[MEMNONE_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
 declare float @ceilf(float)
 
-; CHECK: declare x86_fp80 @ceill(x86_fp80) [[ERRNOMEMONLY_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
+; CHECK: declare x86_fp80 @ceill(x86_fp80) [[MEMNONE_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
 declare x86_fp80 @ceill(x86_fp80)
 
 ; The second argument of int chmod(FILE*, mode_t) is a 32-bit int on most
@@ -909,13 +909,13 @@ declare x86_fp80 @rintl(x86_fp80)
 ; CHECK: declare noundef i32 @rmdir(ptr noundef readonly captures(none)) [[NOFREE_NOUNWIND]]
 declare i32 @rmdir(ptr)
 
-; CHECK: declare double @round(double) [[ERRNOMEMONLY_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
+; CHECK: declare double @round(double) [[MEMNONE_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
 declare double @round(double)
 
-; CHECK: declare float @roundf(float) [[ERRNOMEMONLY_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
+; CHECK: declare float @roundf(float) [[MEMNONE_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
 declare float @roundf(float)
 
-; CHECK: declare x86_fp80 @roundl(x86_fp80) [[ERRNOMEMONLY_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
+; CHECK: declare x86_fp80 @roundl(x86_fp80) [[MEMNONE_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
 declare x86_fp80 @roundl(x86_fp80)
 
 ; CHECK: declare double @scalbln(double, i64) [[ERRNOMEMONLY_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
diff --git a/llvm/test/Transforms/InstCombine/float-shrink-compare.ll b/llvm/test/Transforms/InstCombine/float-shrink-compare.ll
index 77b6ed7c5abe8..6383feff3a6ee 100644
--- a/llvm/test/Transforms/InstCombine/float-shrink-compare.ll
+++ b/llvm/test/Transforms/InstCombine/float-shrink-compare.ll
@@ -463,6 +463,34 @@ define i1 @test18(float %x, float %y, float %z) {
   ret i1 %5
 }
 
+define i1 @test_fminimum_num(float %x, float %y, float %z) {
+; CHECK-LABEL: @test_fminimum_num(
+; CHECK-NEXT:    [[FMINIMUM_NUMF:%.*]] = call float @llvm.minimumnum.f32(float [[X:%.*]], float [[Y:%.*]])
+; CHECK-NEXT:    [[TMP5:%.*]] = fcmp oeq float [[FMINIMUM_NUMF]], [[Z:%.*]]
+; CHECK-NEXT:    ret i1 [[TMP5]]
+;
+  %1 = fpext float %x to double
+  %2 = fpext float %y to double
+  %3 = call double @fminimum_num(double %1, double %2) nounwind
+  %4 = fpext float %z to double
+  %5 = fcmp oeq double %3, %4
+  ret i1 %5
+}
+
+define i1 @test_fmaximum_num(float %x, float %y, float %z) {
+; CHECK-LABEL: @test_fmaximum_num(
+; CHECK-NEXT:    [[FMAXIMUM_NUMF:%.*]] = call float @llvm.maximumnum.f32(float [[X:%.*]], float [[Y:%.*]])
+; CHECK-NEXT:    [[TMP5:%.*]] = fcmp oeq float [[FMAXIMUM_NUMF]], [[Z:%.*]]
+; CHECK-NEXT:    ret i1 [[TMP5]]
+;
+  %1 = fpext float %x to double
+  %2 = fpext float %y to double
+  %3 = call double @fmaximum_num(double %1, double %2) nounwind
+  %4 = fpext float %z to double
+  %5 = fcmp oeq double %3, %4
+  ret i1 %5
+}
+
 define i1 @test19(float %x, float %y, float %z) {
 ; CHECK-LABEL: @test19(
 ; CHECK-NEXT:    [[COPYSIGNF:%.*]] = call float @copysignf(float [[X:%.*]], float [[Y:%.*]]) #[[ATTR0:[0-9]+]]
@@ -518,6 +546,8 @@ declare double @roundeven(double) nounwind readnone
 declare double @trunc(double) nounwind readnone
 declare double @fmin(double, double) nounwind readnone
 declare double @fmax(double, double) nounwind readnone
+declare double @fminimum_num(double, double) nounwind readnone
+declare double @fmaximum_num(double, double) nounwind readnone
 
 declare double @llvm.fabs.f64(double) nounwind readnone
 declare double @llvm.ceil.f64(double) nounwind readnone
diff --git a/llvm/test/Transforms/InstSimplify/ConstProp/roundeven.ll b/llvm/test/Transforms/InstSimplify/ConstProp/roundeven.ll
new file mode 100644
index 0000000000000..e6448847112fb
--- /dev/null
+++ b/llvm/test/Transforms/InstSimplify/ConstProp/roundeven.ll
@@ -0,0 +1,157 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -passes=early-cse -earlycse-debug-hash < %s | FileCheck %s
+
+declare float @roundevenf(float) #0
+declare float @llvm.roundeven.f32(float)
+declare double @roundeven(double) #0
+declare double @llvm.roundeven.f64(double)
+
+define float @constant_fold_roundeven_f32_01() #0 {
+; CHECK-LABEL: define float @constant_fold_roundeven_f32_01(
+; CHECK-SAME: ) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT:    ret float 1.000000e+00
+;
+  %x = call float @roundevenf(float 1.25) #0
+  ret float %x
+}
+
+define float @constant_fold_roundeven_f32_02() #0 {
+; CHECK-LABEL: define float @constant_fold_roundeven_f32_02(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret float -1.000000e+00
+;
+  %x = call float @llvm.roundeven.f32(float -1.25) #0
+  ret float %x
+}
+
+; roundeven rounds ties to even, so 1.5 -> 2.0 (nearest even)
+define float @constant_fold_roundeven_f32_03() #0 {
+; CHECK-LABEL: define float @constant_fold_roundeven_f32_03(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret float 2.000000e+00
+;
+  %x = call float @roundevenf(float 1.5) #0
+  ret float %x
+}
+
+; roundeven rounds ties to even, so -1.5 -> -2.0 (nearest even)
+define float @constant_fold_roundeven_f32_04() #0 {
+; CHECK-LABEL: define float @constant_fold_roundeven_f32_04(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret float -2.000000e+00
+;
+  %x = call float @llvm.roundeven.f32(float -1.5) #0
+  ret float %x
+}
+
+; roundeven rounds ties to even, so 2.5 -> 2.0 (nearest even)
+define float @constant_fold_roundeven_f32_05() #0 {
+; CHECK-LABEL: define float @constant_fold_roundeven_f32_05(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret float 2.000000e+00
+;
+  %x = call float @roundevenf(float 2.5) #0
+  ret float %x
+}
+
+; roundeven rounds ties to even, so -2.5 -> -2.0 (nearest even)
+define float @constant_fold_roundeven_f32_06() #0 {
+; CHECK-LABEL: define float @constant_fold_roundeven_f32_06(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret float -2.000000e+00
+;
+  %x = call float @llvm.roundeven.f32(float -2.5) #0
+  ret float %x
+}
+
+define float @constant_fold_roundeven_f32_07() #0 {
+; CHECK-LABEL: define float @constant_fold_roundeven_f32_07(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret float 3.000000e+00
+;
+  %x = call float @roundevenf(float 2.75) #0
+  ret float %x
+}
+
+define float @constant_fold_roundeven_f32_08() #0 {
+; CHECK-LABEL: define float @constant_fold_roundeven_f32_08(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret float -3.000000e+00
+;
+  %x = call float @llvm.roundeven.f32(float -2.75) #0
+  ret float %x
+}
+
+define double @constant_fold_roundeven_f64_01() #0 {
+; CHECK-LABEL: define double @constant_fold_roundeven_f64_01(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret double 1.000000e+00
+;
+  %x = call double @roundeven(double 1.3) #0
+  ret double %x
+}
+
+define double @constant_fold_roundeven_f64_02() #0 {
+; CHECK-LABEL: define double @constant_fold_roundeven_f64_02(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret double -1.000000e+00
+;
+  %x = call double @llvm.roundeven.f64(double -1.3) #0
+  ret double %x
+}
+
+define double @constant_fold_roundeven_f64_03() #0 {
+; CHECK-LABEL: define double @constant_fold_roundeven_f64_03(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret double 2.000000e+00
+;
+  %x = call double @roundeven(double 1.5) #0
+  ret double %x
+}
+
+define double @constant_fold_roundeven_f64_04() #0 {
+; CHECK-LABEL: define double @constant_fold_roundeven_f64_04(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret double -2.000000e+00
+;
+  %x = call double @llvm.roundeven.f64(double -1.5) #0
+  ret double %x
+}
+
+define double @constant_fold_roundeven_f64_05() #0 {
+; CHECK-LABEL: define double @constant_fold_roundeven_f64_05(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret double 2.000000e+00
+;
+  %x = call double @roundeven(double 2.5) #0
+  ret double %x
+}
+
+define double @constant_fold_roundeven_f64_06() #0 {
+; CHECK-LABEL: define double @constant_fold_roundeven_f64_06(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret double -2.000000e+00
+;
+  %x = call double @llvm.roundeven.f64(double -2.5) #0
+  ret double %x
+}
+
+define double @constant_fold_roundeven_f64_07() #0 {
+; CHECK-LABEL: define double @constant_fold_roundeven_f64_07(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret double 3.000000e+00
+;
+  %x = call double @roundeven(double 2.7) #0
+  ret double %x
+}
+
+define double @constant_fold_roundeven_f64_08() #0 {
+; CHECK-LABEL: define double @constant_fold_roundeven_f64_08(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret double -3.000000e+00
+;
+  %x = call double @llvm.roundeven.f64(double -2.7) #0
+  ...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Dec 4, 2025

@llvm/pr-subscribers-llvm-analysis

Author: None (valadaptive)

Changes

The fminimum_num, fmaximum_num, and roundeven libcalls (and in some cases, their corresponding intrinsics) were not being handled in various components, including constant folding, value tracking, and NumericalStabilitySanitizer. I discovered this in #170018.

This PR fills in the gaps, allowing those libcalls to be consistently lowered to their corresponding intrinsics and constant-folded.


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

11 Files Affected:

  • (modified) llvm/include/llvm/Analysis/TargetLibraryInfo.h (+5)
  • (modified) llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h (+1)
  • (modified) llvm/lib/Analysis/ConstantFolding.cpp (+8-2)
  • (modified) llvm/lib/Analysis/ValueTracking.cpp (+8)
  • (modified) llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp (+6)
  • (modified) llvm/lib/Transforms/Instrumentation/NumericalStabilitySanitizer.cpp (+9)
  • (modified) llvm/lib/Transforms/Utils/BuildLibCalls.cpp (+15-6)
  • (modified) llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp (+31)
  • (modified) llvm/test/Transforms/InferFunctionAttrs/annotate.ll (+6-6)
  • (modified) llvm/test/Transforms/InstCombine/float-shrink-compare.ll (+30)
  • (added) llvm/test/Transforms/InstSimplify/ConstProp/roundeven.ll (+157)
diff --git a/llvm/include/llvm/Analysis/TargetLibraryInfo.h b/llvm/include/llvm/Analysis/TargetLibraryInfo.h
index 0f98af69f12c6..022f0dbb04421 100644
--- a/llvm/include/llvm/Analysis/TargetLibraryInfo.h
+++ b/llvm/include/llvm/Analysis/TargetLibraryInfo.h
@@ -410,6 +410,10 @@ class TargetLibraryInfo {
     case LibFunc_floor:        case LibFunc_floorf:     case LibFunc_floorl:
     case LibFunc_fmax:         case LibFunc_fmaxf:      case LibFunc_fmaxl:
     case LibFunc_fmin:         case LibFunc_fminf:      case LibFunc_fminl:
+    case LibFunc_fmaximum_num: case LibFunc_fmaximum_numf:
+                                                  case LibFunc_fmaximum_numl:
+    case LibFunc_fminimum_num: case LibFunc_fminimum_numf:
+                                                  case LibFunc_fminimum_numl:
     case LibFunc_ldexp:        case LibFunc_ldexpf:     case LibFunc_ldexpl:
     case LibFunc_log2:         case LibFunc_log2f:      case LibFunc_log2l:
     case LibFunc_memcmp:       case LibFunc_bcmp:       case LibFunc_strcmp:
@@ -417,6 +421,7 @@ class TargetLibraryInfo {
     case LibFunc_nearbyint:    case LibFunc_nearbyintf: case LibFunc_nearbyintl:
     case LibFunc_rint:         case LibFunc_rintf:      case LibFunc_rintl:
     case LibFunc_round:        case LibFunc_roundf:     case LibFunc_roundl:
+    case LibFunc_roundeven:    case LibFunc_roundevenf: case LibFunc_roundevenl:
     case LibFunc_sin:          case LibFunc_sinf:       case LibFunc_sinl:
     case LibFunc_sinh:         case LibFunc_sinhf:      case LibFunc_sinhl:
     case LibFunc_sqrt:         case LibFunc_sqrtf:      case LibFunc_sqrtl:
diff --git a/llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h b/llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h
index 4e7c97194cc59..64d2512308935 100644
--- a/llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h
+++ b/llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h
@@ -205,6 +205,7 @@ class LibCallSimplifier {
   Value *replacePowWithSqrt(CallInst *Pow, IRBuilderBase &B);
   Value *optimizeExp2(CallInst *CI, IRBuilderBase &B);
   Value *optimizeFMinFMax(CallInst *CI, IRBuilderBase &B);
+  Value *optimizeFMinimumnumFMaximumnum(CallInst *CI, IRBuilderBase &B);
   Value *optimizeLog(CallInst *CI, IRBuilderBase &B);
   Value *optimizeSqrt(CallInst *CI, IRBuilderBase &B);
   Value *optimizeFMod(CallInst *CI, IRBuilderBase &B);
diff --git a/llvm/lib/Analysis/ConstantFolding.cpp b/llvm/lib/Analysis/ConstantFolding.cpp
index 63d12ee585e64..b39b32042dd2f 100644
--- a/llvm/lib/Analysis/ConstantFolding.cpp
+++ b/llvm/lib/Analysis/ConstantFolding.cpp
@@ -1984,6 +1984,7 @@ bool llvm::canConstantFoldCallTo(const CallBase *Call, const Function *F) {
   switch (Name[0]) {
   default:
     return false;
+    // clang-format off
   case 'a':
     return Name == "acos" || Name == "acosf" ||
            Name == "asin" || Name == "asinf" ||
@@ -2014,7 +2015,8 @@ bool llvm::canConstantFoldCallTo(const CallBase *Call, const Function *F) {
   case 'r':
     return Name == "remainder" || Name == "remainderf" ||
            Name == "rint" || Name == "rintf" ||
-           Name == "round" || Name == "roundf";
+           Name == "round" || Name == "roundf" ||
+           Name == "roundeven" || Name == "roundevenf";
   case 's':
     return Name == "sin" || Name == "sinf" ||
            Name == "sinh" || Name == "sinhf" ||
@@ -2052,6 +2054,7 @@ bool llvm::canConstantFoldCallTo(const CallBase *Call, const Function *F) {
     case 's':
       return Name == "__sinh_finite" || Name == "__sinhf_finite";
     }
+    // clang-format on
   }
 }
 
@@ -2516,7 +2519,8 @@ static Constant *ConstantFoldScalarCall1(StringRef Name,
 
     // Use internal versions of these intrinsics.
 
-    if (IntrinsicID == Intrinsic::nearbyint || IntrinsicID == Intrinsic::rint) {
+    if (IntrinsicID == Intrinsic::nearbyint || IntrinsicID == Intrinsic::rint ||
+        IntrinsicID == Intrinsic::roundeven) {
       U.roundToIntegral(APFloat::rmNearestTiesToEven);
       return ConstantFP::get(Ty->getContext(), U);
     }
@@ -2988,6 +2992,8 @@ static Constant *ConstantFoldScalarCall1(StringRef Name,
     case LibFunc_nearbyintf:
     case LibFunc_rint:
     case LibFunc_rintf:
+    case LibFunc_roundeven:
+    case LibFunc_roundevenf:
       if (TLI->has(Func)) {
         U.roundToIntegral(APFloat::rmNearestTiesToEven);
         return ConstantFP::get(Ty->getContext(), U);
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 9cb6f19b9340c..6de4f57f75273 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -4680,6 +4680,14 @@ Intrinsic::ID llvm::getIntrinsicForCallSite(const CallBase &CB,
   case LibFunc_fmaxf:
   case LibFunc_fmaxl:
     return Intrinsic::maxnum;
+  case LibFunc_fminimum_num:
+  case LibFunc_fminimum_numf:
+  case LibFunc_fminimum_numl:
+    return Intrinsic::minimumnum;
+  case LibFunc_fmaximum_num:
+  case LibFunc_fmaximum_numf:
+  case LibFunc_fmaximum_numl:
+    return Intrinsic::maximumnum;
   case LibFunc_copysign:
   case LibFunc_copysignf:
   case LibFunc_copysignl:
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
index 09a0673bfe1bb..6f22730c90efe 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
@@ -9710,6 +9710,12 @@ void SelectionDAGBuilder::visitCall(const CallInst &I) {
         if (visitUnaryFloatCall(I, ISD::FROUND))
           return;
         break;
+      case LibFunc_roundeven:
+      case LibFunc_roundevenf:
+      case LibFunc_roundevenl:
+        if (visitUnaryFloatCall(I, ISD::FROUNDEVEN))
+          return;
+        break;
       case LibFunc_trunc:
       case LibFunc_truncf:
       case LibFunc_truncl:
diff --git a/llvm/lib/Transforms/Instrumentation/NumericalStabilitySanitizer.cpp b/llvm/lib/Transforms/Instrumentation/NumericalStabilitySanitizer.cpp
index 66d570b3f831e..8b1d765d77818 100644
--- a/llvm/lib/Transforms/Instrumentation/NumericalStabilitySanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/NumericalStabilitySanitizer.cpp
@@ -1499,6 +1499,12 @@ const KnownIntrinsic::LFEntry KnownIntrinsic::kLibfuncIntrinsics[] = {
     {LibFunc_fminf, "llvm.minnum.f32"},
     {LibFunc_fmin, "llvm.minnum.f64"},
     {LibFunc_fminl, "llvm.minnum.f80"},
+    {LibFunc_fmaximum_numf, "llvm.maximumnum.f32"},
+    {LibFunc_fmaximum_num, "llvm.maximumnum.f64"},
+    {LibFunc_fmaximum_numl, "llvm.maximumnum.f80"},
+    {LibFunc_fminimum_numf, "llvm.minimumnum.f32"},
+    {LibFunc_fminimum_num, "llvm.minimumnum.f64"},
+    {LibFunc_fminimum_numl, "llvm.minimumnum.f80"},
     {LibFunc_ceilf, "llvm.ceil.f32"},
     {LibFunc_ceil, "llvm.ceil.f64"},
     {LibFunc_ceill, "llvm.ceil.f80"},
@@ -1514,6 +1520,9 @@ const KnownIntrinsic::LFEntry KnownIntrinsic::kLibfuncIntrinsics[] = {
     {LibFunc_roundf, "llvm.round.f32"},
     {LibFunc_round, "llvm.round.f64"},
     {LibFunc_roundl, "llvm.round.f80"},
+    {LibFunc_roundevenf, "llvm.roundeven.f32"},
+    {LibFunc_roundeven, "llvm.roundeven.f64"},
+    {LibFunc_roundevenl, "llvm.roundeven.f80"},
 };
 
 const char *KnownIntrinsic::get(LibFunc LFunc) {
diff --git a/llvm/lib/Transforms/Utils/BuildLibCalls.cpp b/llvm/lib/Transforms/Utils/BuildLibCalls.cpp
index 02b73e85d783f..a245b9405cfb7 100644
--- a/llvm/lib/Transforms/Utils/BuildLibCalls.cpp
+++ b/llvm/lib/Transforms/Utils/BuildLibCalls.cpp
@@ -1227,9 +1227,6 @@ bool llvm::inferNonMandatoryLibFuncAttrs(Function &F,
   case LibFunc_atanhf:
   case LibFunc_atanhl:
   case LibFunc_atanl:
-  case LibFunc_ceil:
-  case LibFunc_ceilf:
-  case LibFunc_ceill:
   case LibFunc_cos:
   case LibFunc_cosh:
   case LibFunc_coshf:
@@ -1298,9 +1295,6 @@ bool llvm::inferNonMandatoryLibFuncAttrs(Function &F,
   case LibFunc_rint:
   case LibFunc_rintf:
   case LibFunc_rintl:
-  case LibFunc_round:
-  case LibFunc_roundf:
-  case LibFunc_roundl:
   case LibFunc_scalbln:
   case LibFunc_scalblnf:
   case LibFunc_scalblnl:
@@ -1338,6 +1332,9 @@ bool llvm::inferNonMandatoryLibFuncAttrs(Function &F,
   case LibFunc_copysign:
   case LibFunc_copysignf:
   case LibFunc_copysignl:
+  case LibFunc_ceil:
+  case LibFunc_ceilf:
+  case LibFunc_ceill:
   case LibFunc_fabs:
   case LibFunc_fabsf:
   case LibFunc_fabsl:
@@ -1356,11 +1353,23 @@ bool llvm::inferNonMandatoryLibFuncAttrs(Function &F,
   case LibFunc_fmin:
   case LibFunc_fminf:
   case LibFunc_fminl:
+  case LibFunc_fmaximum_num:
+  case LibFunc_fmaximum_numf:
+  case LibFunc_fmaximum_numl:
+  case LibFunc_fminimum_num:
+  case LibFunc_fminimum_numf:
+  case LibFunc_fminimum_numl:
   case LibFunc_labs:
   case LibFunc_llabs:
   case LibFunc_nearbyint:
   case LibFunc_nearbyintf:
   case LibFunc_nearbyintl:
+  case LibFunc_round:
+  case LibFunc_roundf:
+  case LibFunc_roundl:
+  case LibFunc_roundeven:
+  case LibFunc_roundevenf:
+  case LibFunc_roundevenl:
   case LibFunc_toascii:
   case LibFunc_trunc:
   case LibFunc_truncf:
diff --git a/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp b/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp
index d1548694baa27..20d9b03bd87c8 100644
--- a/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp
@@ -2543,6 +2543,30 @@ Value *LibCallSimplifier::optimizeFMinFMax(CallInst *CI, IRBuilderBase &B) {
                                                 CI->getArgOperand(1), FMF));
 }
 
+Value *LibCallSimplifier::optimizeFMinimumnumFMaximumnum(CallInst *CI,
+                                                         IRBuilderBase &B) {
+  Module *M = CI->getModule();
+
+  // If we can shrink the call to a float function rather than a double
+  // function, do that first.
+  Function *Callee = CI->getCalledFunction();
+  StringRef Name = Callee->getName();
+  if ((Name == "fminimum_num" || Name == "fmaximum_num") &&
+      hasFloatVersion(M, Name))
+    if (Value *Ret = optimizeBinaryDoubleFP(CI, B, TLI))
+      return Ret;
+
+  // The new fminimum_num/fmaximum_num functions, unlike fmin/fmax, *are*
+  // sensitive to the sigh of zero, so we don't change the fast-math flags like
+  // we did for those.
+
+  Intrinsic::ID IID = Callee->getName().starts_with("fminimum_num")
+                          ? Intrinsic::minimumnum
+                          : Intrinsic::maximumnum;
+  return copyFlags(*CI, B.CreateBinaryIntrinsic(IID, CI->getArgOperand(0),
+                                                CI->getArgOperand(1), CI));
+}
+
 Value *LibCallSimplifier::optimizeLog(CallInst *Log, IRBuilderBase &B) {
   Function *LogFn = Log->getCalledFunction();
   StringRef LogNm = LogFn->getName();
@@ -4123,6 +4147,13 @@ Value *LibCallSimplifier::optimizeFloatingPointLibCall(CallInst *CI,
   case LibFunc_fmax:
   case LibFunc_fmaxl:
     return optimizeFMinFMax(CI, Builder);
+  case LibFunc_fminimum_numf:
+  case LibFunc_fminimum_num:
+  case LibFunc_fminimum_numl:
+  case LibFunc_fmaximum_numf:
+  case LibFunc_fmaximum_num:
+  case LibFunc_fmaximum_numl:
+    return optimizeFMinimumnumFMaximumnum(CI, Builder);
   case LibFunc_cabs:
   case LibFunc_cabsf:
   case LibFunc_cabsl:
diff --git a/llvm/test/Transforms/InferFunctionAttrs/annotate.ll b/llvm/test/Transforms/InferFunctionAttrs/annotate.ll
index 25a70a026a0b7..4a0a20b71dfde 100644
--- a/llvm/test/Transforms/InferFunctionAttrs/annotate.ll
+++ b/llvm/test/Transforms/InferFunctionAttrs/annotate.ll
@@ -304,13 +304,13 @@ declare float @cbrtf(float)
 ; CHECK: declare x86_fp80 @cbrtl(x86_fp80) [[MEMNONE_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
 declare x86_fp80 @cbrtl(x86_fp80)
 
-; CHECK: declare double @ceil(double) [[ERRNOMEMONLY_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
+; CHECK: declare double @ceil(double) [[MEMNONE_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
 declare double @ceil(double)
 
-; CHECK: declare float @ceilf(float) [[ERRNOMEMONLY_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
+; CHECK: declare float @ceilf(float) [[MEMNONE_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
 declare float @ceilf(float)
 
-; CHECK: declare x86_fp80 @ceill(x86_fp80) [[ERRNOMEMONLY_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
+; CHECK: declare x86_fp80 @ceill(x86_fp80) [[MEMNONE_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
 declare x86_fp80 @ceill(x86_fp80)
 
 ; The second argument of int chmod(FILE*, mode_t) is a 32-bit int on most
@@ -909,13 +909,13 @@ declare x86_fp80 @rintl(x86_fp80)
 ; CHECK: declare noundef i32 @rmdir(ptr noundef readonly captures(none)) [[NOFREE_NOUNWIND]]
 declare i32 @rmdir(ptr)
 
-; CHECK: declare double @round(double) [[ERRNOMEMONLY_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
+; CHECK: declare double @round(double) [[MEMNONE_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
 declare double @round(double)
 
-; CHECK: declare float @roundf(float) [[ERRNOMEMONLY_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
+; CHECK: declare float @roundf(float) [[MEMNONE_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
 declare float @roundf(float)
 
-; CHECK: declare x86_fp80 @roundl(x86_fp80) [[ERRNOMEMONLY_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
+; CHECK: declare x86_fp80 @roundl(x86_fp80) [[MEMNONE_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
 declare x86_fp80 @roundl(x86_fp80)
 
 ; CHECK: declare double @scalbln(double, i64) [[ERRNOMEMONLY_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
diff --git a/llvm/test/Transforms/InstCombine/float-shrink-compare.ll b/llvm/test/Transforms/InstCombine/float-shrink-compare.ll
index 77b6ed7c5abe8..6383feff3a6ee 100644
--- a/llvm/test/Transforms/InstCombine/float-shrink-compare.ll
+++ b/llvm/test/Transforms/InstCombine/float-shrink-compare.ll
@@ -463,6 +463,34 @@ define i1 @test18(float %x, float %y, float %z) {
   ret i1 %5
 }
 
+define i1 @test_fminimum_num(float %x, float %y, float %z) {
+; CHECK-LABEL: @test_fminimum_num(
+; CHECK-NEXT:    [[FMINIMUM_NUMF:%.*]] = call float @llvm.minimumnum.f32(float [[X:%.*]], float [[Y:%.*]])
+; CHECK-NEXT:    [[TMP5:%.*]] = fcmp oeq float [[FMINIMUM_NUMF]], [[Z:%.*]]
+; CHECK-NEXT:    ret i1 [[TMP5]]
+;
+  %1 = fpext float %x to double
+  %2 = fpext float %y to double
+  %3 = call double @fminimum_num(double %1, double %2) nounwind
+  %4 = fpext float %z to double
+  %5 = fcmp oeq double %3, %4
+  ret i1 %5
+}
+
+define i1 @test_fmaximum_num(float %x, float %y, float %z) {
+; CHECK-LABEL: @test_fmaximum_num(
+; CHECK-NEXT:    [[FMAXIMUM_NUMF:%.*]] = call float @llvm.maximumnum.f32(float [[X:%.*]], float [[Y:%.*]])
+; CHECK-NEXT:    [[TMP5:%.*]] = fcmp oeq float [[FMAXIMUM_NUMF]], [[Z:%.*]]
+; CHECK-NEXT:    ret i1 [[TMP5]]
+;
+  %1 = fpext float %x to double
+  %2 = fpext float %y to double
+  %3 = call double @fmaximum_num(double %1, double %2) nounwind
+  %4 = fpext float %z to double
+  %5 = fcmp oeq double %3, %4
+  ret i1 %5
+}
+
 define i1 @test19(float %x, float %y, float %z) {
 ; CHECK-LABEL: @test19(
 ; CHECK-NEXT:    [[COPYSIGNF:%.*]] = call float @copysignf(float [[X:%.*]], float [[Y:%.*]]) #[[ATTR0:[0-9]+]]
@@ -518,6 +546,8 @@ declare double @roundeven(double) nounwind readnone
 declare double @trunc(double) nounwind readnone
 declare double @fmin(double, double) nounwind readnone
 declare double @fmax(double, double) nounwind readnone
+declare double @fminimum_num(double, double) nounwind readnone
+declare double @fmaximum_num(double, double) nounwind readnone
 
 declare double @llvm.fabs.f64(double) nounwind readnone
 declare double @llvm.ceil.f64(double) nounwind readnone
diff --git a/llvm/test/Transforms/InstSimplify/ConstProp/roundeven.ll b/llvm/test/Transforms/InstSimplify/ConstProp/roundeven.ll
new file mode 100644
index 0000000000000..e6448847112fb
--- /dev/null
+++ b/llvm/test/Transforms/InstSimplify/ConstProp/roundeven.ll
@@ -0,0 +1,157 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -passes=early-cse -earlycse-debug-hash < %s | FileCheck %s
+
+declare float @roundevenf(float) #0
+declare float @llvm.roundeven.f32(float)
+declare double @roundeven(double) #0
+declare double @llvm.roundeven.f64(double)
+
+define float @constant_fold_roundeven_f32_01() #0 {
+; CHECK-LABEL: define float @constant_fold_roundeven_f32_01(
+; CHECK-SAME: ) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT:    ret float 1.000000e+00
+;
+  %x = call float @roundevenf(float 1.25) #0
+  ret float %x
+}
+
+define float @constant_fold_roundeven_f32_02() #0 {
+; CHECK-LABEL: define float @constant_fold_roundeven_f32_02(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret float -1.000000e+00
+;
+  %x = call float @llvm.roundeven.f32(float -1.25) #0
+  ret float %x
+}
+
+; roundeven rounds ties to even, so 1.5 -> 2.0 (nearest even)
+define float @constant_fold_roundeven_f32_03() #0 {
+; CHECK-LABEL: define float @constant_fold_roundeven_f32_03(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret float 2.000000e+00
+;
+  %x = call float @roundevenf(float 1.5) #0
+  ret float %x
+}
+
+; roundeven rounds ties to even, so -1.5 -> -2.0 (nearest even)
+define float @constant_fold_roundeven_f32_04() #0 {
+; CHECK-LABEL: define float @constant_fold_roundeven_f32_04(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret float -2.000000e+00
+;
+  %x = call float @llvm.roundeven.f32(float -1.5) #0
+  ret float %x
+}
+
+; roundeven rounds ties to even, so 2.5 -> 2.0 (nearest even)
+define float @constant_fold_roundeven_f32_05() #0 {
+; CHECK-LABEL: define float @constant_fold_roundeven_f32_05(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret float 2.000000e+00
+;
+  %x = call float @roundevenf(float 2.5) #0
+  ret float %x
+}
+
+; roundeven rounds ties to even, so -2.5 -> -2.0 (nearest even)
+define float @constant_fold_roundeven_f32_06() #0 {
+; CHECK-LABEL: define float @constant_fold_roundeven_f32_06(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret float -2.000000e+00
+;
+  %x = call float @llvm.roundeven.f32(float -2.5) #0
+  ret float %x
+}
+
+define float @constant_fold_roundeven_f32_07() #0 {
+; CHECK-LABEL: define float @constant_fold_roundeven_f32_07(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret float 3.000000e+00
+;
+  %x = call float @roundevenf(float 2.75) #0
+  ret float %x
+}
+
+define float @constant_fold_roundeven_f32_08() #0 {
+; CHECK-LABEL: define float @constant_fold_roundeven_f32_08(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret float -3.000000e+00
+;
+  %x = call float @llvm.roundeven.f32(float -2.75) #0
+  ret float %x
+}
+
+define double @constant_fold_roundeven_f64_01() #0 {
+; CHECK-LABEL: define double @constant_fold_roundeven_f64_01(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret double 1.000000e+00
+;
+  %x = call double @roundeven(double 1.3) #0
+  ret double %x
+}
+
+define double @constant_fold_roundeven_f64_02() #0 {
+; CHECK-LABEL: define double @constant_fold_roundeven_f64_02(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret double -1.000000e+00
+;
+  %x = call double @llvm.roundeven.f64(double -1.3) #0
+  ret double %x
+}
+
+define double @constant_fold_roundeven_f64_03() #0 {
+; CHECK-LABEL: define double @constant_fold_roundeven_f64_03(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret double 2.000000e+00
+;
+  %x = call double @roundeven(double 1.5) #0
+  ret double %x
+}
+
+define double @constant_fold_roundeven_f64_04() #0 {
+; CHECK-LABEL: define double @constant_fold_roundeven_f64_04(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret double -2.000000e+00
+;
+  %x = call double @llvm.roundeven.f64(double -1.5) #0
+  ret double %x
+}
+
+define double @constant_fold_roundeven_f64_05() #0 {
+; CHECK-LABEL: define double @constant_fold_roundeven_f64_05(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret double 2.000000e+00
+;
+  %x = call double @roundeven(double 2.5) #0
+  ret double %x
+}
+
+define double @constant_fold_roundeven_f64_06() #0 {
+; CHECK-LABEL: define double @constant_fold_roundeven_f64_06(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret double -2.000000e+00
+;
+  %x = call double @llvm.roundeven.f64(double -2.5) #0
+  ret double %x
+}
+
+define double @constant_fold_roundeven_f64_07() #0 {
+; CHECK-LABEL: define double @constant_fold_roundeven_f64_07(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret double 3.000000e+00
+;
+  %x = call double @roundeven(double 2.7) #0
+  ret double %x
+}
+
+define double @constant_fold_roundeven_f64_08() #0 {
+; CHECK-LABEL: define double @constant_fold_roundeven_f64_08(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    ret double -3.000000e+00
+;
+  %x = call double @llvm.roundeven.f64(double -2.7) #0
+  ...
[truncated]

declare x86_fp80 @cbrtl(x86_fp80)

; CHECK: declare double @ceil(double) [[ERRNOMEMONLY_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
; CHECK: declare double @ceil(double) [[MEMNONE_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is something to be fixed in a future PR and not here, but these CHECK lines don't appear to do anything. I'm not sure why, but this test always seems to pass regardless of what the actual function attributes are.

Name == "rint" || Name == "rintf" ||
Name == "round" || Name == "roundf";
Name == "round" || Name == "roundf" ||
Name == "roundeven" || Name == "roundevenf";
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's a bit sad that adding full support for a new libcall requires adding it to a manually-maintained list of functions starting with the letter R.

Copy link
Contributor

@nikic nikic left a comment

Choose a reason for hiding this comment

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

This needs to be split into multiple PRs, at least per component.

@valadaptive
Copy link
Contributor Author

What constitutes a "component" here? Moving the NumericalStabilitySanitizer stuff into a separate PR would be pretty easy, but anything more granular would result in a lot of stacked PRs.

The libcall versions will later be optimized and constant-folded.
This probably should've been done in
llvm#124742, but it wasn't. The
floor, ceil, round, and trunc family of functions all behave the same.
This is a prerequisite to being able to turn it into an intrinsic.
We can now lower it to its proper instruction.
It was previously missing entirely. It behaves like floor, ceil, round,
and trunc, having no side effects.
Basically identical to nearbyint and rint, which we already treat as
rounding to nearest with ties to even during constant folding.
Here, it's a simple matter of wiring them up.
Add tests corresponding to the existing fmin/fmax tests, as we'll later
begin optimizing these into intrinsics.
This is a prerequisite to lowering them to intrinsics later.
A straightforward mapping onto the corresponding intrinsic.
Unlike fmin and fmax, these are deterministic with regards to signed
zero.
A simple matter of adding them to the table.
@valadaptive
Copy link
Contributor Author

I've split things up into much more granular commits, but I don't want to spam this repo with 13 separate pull requests, especially since I can't use Graphite.

@nikic
Copy link
Contributor

nikic commented Dec 4, 2025

Nearly all changes here look orthogonal to me, there should be not need for stacked PRs.

Note that LLVM always uses squash merge, so you can't break things down by commit, it has to be by PRs.

@valadaptive
Copy link
Contributor Author

They're orthogonal except for the fact that whatever goes in last is what will ultimately require the regression tests to be updated.

@RKSimon RKSimon requested a review from arsenm December 4, 2025 16:38
@valadaptive
Copy link
Contributor Author

I've split up the PRs. There will be an unavoidable merge conflict between two of them.

@valadaptive valadaptive closed this Dec 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

compiler-rt:sanitizer llvm:analysis Includes value tracking, cost tables and constant folding llvm:instcombine Covers the InstCombine, InstSimplify and AggressiveInstCombine passes llvm:SelectionDAG SelectionDAGISel as well llvm:transforms

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants