From 2cc5630e08a3b5f9708f1dc8703cd626c197c94a Mon Sep 17 00:00:00 2001 From: valadaptive Date: Thu, 4 Dec 2025 10:37:27 -0500 Subject: [PATCH 01/13] [InstSimplify] Add roundeven constant-propagation tests The libcall versions will later be optimized and constant-folded. --- .../InstSimplify/ConstProp/roundeven.ll | 165 ++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 llvm/test/Transforms/InstSimplify/ConstProp/roundeven.ll diff --git a/llvm/test/Transforms/InstSimplify/ConstProp/roundeven.ll b/llvm/test/Transforms/InstSimplify/ConstProp/roundeven.ll new file mode 100644 index 0000000000000..68938b3c6244c --- /dev/null +++ b/llvm/test/Transforms/InstSimplify/ConstProp/roundeven.ll @@ -0,0 +1,165 @@ +; 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: [[X:%.*]] = call float @roundevenf(float 1.250000e+00) #[[ATTR0]] +; CHECK-NEXT: ret float [[X]] +; + %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: [[X:%.*]] = call float @roundevenf(float 1.500000e+00) #[[ATTR0]] +; CHECK-NEXT: ret float [[X]] +; + %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: [[X:%.*]] = call float @roundevenf(float 2.500000e+00) #[[ATTR0]] +; CHECK-NEXT: ret float [[X]] +; + %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: [[X:%.*]] = call float @roundevenf(float 2.750000e+00) #[[ATTR0]] +; CHECK-NEXT: ret float [[X]] +; + %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: [[X:%.*]] = call double @roundeven(double 1.300000e+00) #[[ATTR0]] +; CHECK-NEXT: ret double [[X]] +; + %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: [[X:%.*]] = call double @roundeven(double 1.500000e+00) #[[ATTR0]] +; CHECK-NEXT: ret double [[X]] +; + %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: [[X:%.*]] = call double @roundeven(double 2.500000e+00) #[[ATTR0]] +; CHECK-NEXT: ret double [[X]] +; + %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: [[X:%.*]] = call double @roundeven(double 2.700000e+00) #[[ATTR0]] +; CHECK-NEXT: ret double [[X]] +; + %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 + ret double %x +} + +attributes #0 = { nounwind readnone willreturn } From 416811a24f06c8cae99116d9cd9509b58e079afe Mon Sep 17 00:00:00 2001 From: valadaptive Date: Thu, 4 Dec 2025 07:48:07 -0500 Subject: [PATCH 02/13] [InferAttrs] Mark ceil and round as memory(none) This probably should've been done in https://github.com/llvm/llvm-project/pull/124742, but it wasn't. The floor, ceil, round, and trunc family of functions all behave the same. --- llvm/lib/Transforms/Utils/BuildLibCalls.cpp | 12 ++++++------ llvm/test/Transforms/InferFunctionAttrs/annotate.ll | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/llvm/lib/Transforms/Utils/BuildLibCalls.cpp b/llvm/lib/Transforms/Utils/BuildLibCalls.cpp index 02b73e85d783f..213869fb993d7 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: @@ -1361,6 +1358,9 @@ bool llvm::inferNonMandatoryLibFuncAttrs(Function &F, case LibFunc_nearbyint: case LibFunc_nearbyintf: case LibFunc_nearbyintl: + case LibFunc_round: + case LibFunc_roundf: + case LibFunc_roundl: case LibFunc_toascii: case LibFunc_trunc: case LibFunc_truncf: 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]+]] From a50096bf4fbd31dd847aabbf1df3e5f1042c3f92 Mon Sep 17 00:00:00 2001 From: valadaptive Date: Thu, 4 Dec 2025 10:39:28 -0500 Subject: [PATCH 03/13] [TLI] Handle roundeven libcall in hasOptimizedCodegen This is a prerequisite to being able to turn it into an intrinsic. --- llvm/include/llvm/Analysis/TargetLibraryInfo.h | 1 + 1 file changed, 1 insertion(+) diff --git a/llvm/include/llvm/Analysis/TargetLibraryInfo.h b/llvm/include/llvm/Analysis/TargetLibraryInfo.h index 0f98af69f12c6..a88497beb20ae 100644 --- a/llvm/include/llvm/Analysis/TargetLibraryInfo.h +++ b/llvm/include/llvm/Analysis/TargetLibraryInfo.h @@ -417,6 +417,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: From 28589a3a2f223c05b1138b6f86107aa9b203998d Mon Sep 17 00:00:00 2001 From: valadaptive Date: Thu, 4 Dec 2025 10:40:01 -0500 Subject: [PATCH 04/13] [SelectionDAG] Handle roundeven libcall in visitCall We can now lower it to its proper instruction. --- llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp | 6 ++++++ 1 file changed, 6 insertions(+) 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: From 2536928fab13c63602620f93be673c545934b1fa Mon Sep 17 00:00:00 2001 From: valadaptive Date: Thu, 4 Dec 2025 10:40:55 -0500 Subject: [PATCH 05/13] [InferAttrs] Handle roundeven like other libm functions It was previously missing entirely. It behaves like floor, ceil, round, and trunc, having no side effects. --- llvm/lib/Transforms/Utils/BuildLibCalls.cpp | 3 +++ llvm/test/Transforms/InferFunctionAttrs/annotate.ll | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/llvm/lib/Transforms/Utils/BuildLibCalls.cpp b/llvm/lib/Transforms/Utils/BuildLibCalls.cpp index 213869fb993d7..05c882da38d61 100644 --- a/llvm/lib/Transforms/Utils/BuildLibCalls.cpp +++ b/llvm/lib/Transforms/Utils/BuildLibCalls.cpp @@ -1361,6 +1361,9 @@ bool llvm::inferNonMandatoryLibFuncAttrs(Function &F, 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/test/Transforms/InferFunctionAttrs/annotate.ll b/llvm/test/Transforms/InferFunctionAttrs/annotate.ll index 4a0a20b71dfde..f5020a93250ea 100644 --- a/llvm/test/Transforms/InferFunctionAttrs/annotate.ll +++ b/llvm/test/Transforms/InferFunctionAttrs/annotate.ll @@ -918,6 +918,15 @@ declare float @roundf(float) ; CHECK: declare x86_fp80 @roundl(x86_fp80) [[MEMNONE_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]] declare x86_fp80 @roundl(x86_fp80) +; CHECK: declare double @roundeven(double) [[MEMNONE_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]] +declare double @roundeven(double) + +; CHECK: declare float @roundevenf(float) [[MEMNONE_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]] +declare float @roundevenf(float) + +; CHECK: declare x86_fp80 @roundevenl(x86_fp80) [[MEMNONE_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]] +declare x86_fp80 @roundevenl(x86_fp80) + ; CHECK: declare double @scalbln(double, i64) [[ERRNOMEMONLY_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]] declare double @scalbln(double, i64) From d0d46899144e3b4b9384b257ce93fa7c5b838b1d Mon Sep 17 00:00:00 2001 From: valadaptive Date: Thu, 4 Dec 2025 10:42:03 -0500 Subject: [PATCH 06/13] [ConstantFolding] Handle roundeven libcalls Basically identical to nearbyint and rint, which we already treat as rounding to nearest with ties to even during constant folding. --- llvm/lib/Analysis/ConstantFolding.cpp | 10 ++++++-- .../InstSimplify/ConstProp/roundeven.ll | 24 +++++++------------ 2 files changed, 16 insertions(+), 18 deletions(-) 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/test/Transforms/InstSimplify/ConstProp/roundeven.ll b/llvm/test/Transforms/InstSimplify/ConstProp/roundeven.ll index 68938b3c6244c..e6448847112fb 100644 --- a/llvm/test/Transforms/InstSimplify/ConstProp/roundeven.ll +++ b/llvm/test/Transforms/InstSimplify/ConstProp/roundeven.ll @@ -9,8 +9,7 @@ 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: [[X:%.*]] = call float @roundevenf(float 1.250000e+00) #[[ATTR0]] -; CHECK-NEXT: ret float [[X]] +; CHECK-NEXT: ret float 1.000000e+00 ; %x = call float @roundevenf(float 1.25) #0 ret float %x @@ -29,8 +28,7 @@ define float @constant_fold_roundeven_f32_02() #0 { define float @constant_fold_roundeven_f32_03() #0 { ; CHECK-LABEL: define float @constant_fold_roundeven_f32_03( ; CHECK-SAME: ) #[[ATTR0]] { -; CHECK-NEXT: [[X:%.*]] = call float @roundevenf(float 1.500000e+00) #[[ATTR0]] -; CHECK-NEXT: ret float [[X]] +; CHECK-NEXT: ret float 2.000000e+00 ; %x = call float @roundevenf(float 1.5) #0 ret float %x @@ -50,8 +48,7 @@ define float @constant_fold_roundeven_f32_04() #0 { define float @constant_fold_roundeven_f32_05() #0 { ; CHECK-LABEL: define float @constant_fold_roundeven_f32_05( ; CHECK-SAME: ) #[[ATTR0]] { -; CHECK-NEXT: [[X:%.*]] = call float @roundevenf(float 2.500000e+00) #[[ATTR0]] -; CHECK-NEXT: ret float [[X]] +; CHECK-NEXT: ret float 2.000000e+00 ; %x = call float @roundevenf(float 2.5) #0 ret float %x @@ -70,8 +67,7 @@ define float @constant_fold_roundeven_f32_06() #0 { define float @constant_fold_roundeven_f32_07() #0 { ; CHECK-LABEL: define float @constant_fold_roundeven_f32_07( ; CHECK-SAME: ) #[[ATTR0]] { -; CHECK-NEXT: [[X:%.*]] = call float @roundevenf(float 2.750000e+00) #[[ATTR0]] -; CHECK-NEXT: ret float [[X]] +; CHECK-NEXT: ret float 3.000000e+00 ; %x = call float @roundevenf(float 2.75) #0 ret float %x @@ -89,8 +85,7 @@ define float @constant_fold_roundeven_f32_08() #0 { define double @constant_fold_roundeven_f64_01() #0 { ; CHECK-LABEL: define double @constant_fold_roundeven_f64_01( ; CHECK-SAME: ) #[[ATTR0]] { -; CHECK-NEXT: [[X:%.*]] = call double @roundeven(double 1.300000e+00) #[[ATTR0]] -; CHECK-NEXT: ret double [[X]] +; CHECK-NEXT: ret double 1.000000e+00 ; %x = call double @roundeven(double 1.3) #0 ret double %x @@ -108,8 +103,7 @@ define double @constant_fold_roundeven_f64_02() #0 { define double @constant_fold_roundeven_f64_03() #0 { ; CHECK-LABEL: define double @constant_fold_roundeven_f64_03( ; CHECK-SAME: ) #[[ATTR0]] { -; CHECK-NEXT: [[X:%.*]] = call double @roundeven(double 1.500000e+00) #[[ATTR0]] -; CHECK-NEXT: ret double [[X]] +; CHECK-NEXT: ret double 2.000000e+00 ; %x = call double @roundeven(double 1.5) #0 ret double %x @@ -127,8 +121,7 @@ define double @constant_fold_roundeven_f64_04() #0 { define double @constant_fold_roundeven_f64_05() #0 { ; CHECK-LABEL: define double @constant_fold_roundeven_f64_05( ; CHECK-SAME: ) #[[ATTR0]] { -; CHECK-NEXT: [[X:%.*]] = call double @roundeven(double 2.500000e+00) #[[ATTR0]] -; CHECK-NEXT: ret double [[X]] +; CHECK-NEXT: ret double 2.000000e+00 ; %x = call double @roundeven(double 2.5) #0 ret double %x @@ -146,8 +139,7 @@ define double @constant_fold_roundeven_f64_06() #0 { define double @constant_fold_roundeven_f64_07() #0 { ; CHECK-LABEL: define double @constant_fold_roundeven_f64_07( ; CHECK-SAME: ) #[[ATTR0]] { -; CHECK-NEXT: [[X:%.*]] = call double @roundeven(double 2.700000e+00) #[[ATTR0]] -; CHECK-NEXT: ret double [[X]] +; CHECK-NEXT: ret double 3.000000e+00 ; %x = call double @roundeven(double 2.7) #0 ret double %x From 09cae4b5053c58ac1dba8072016841df51e10962 Mon Sep 17 00:00:00 2001 From: valadaptive Date: Thu, 4 Dec 2025 10:42:19 -0500 Subject: [PATCH 07/13] [NumericalStabilitySanitizer] Handle roundeven libcalls Here, it's a simple matter of wiring them up. --- .../Transforms/Instrumentation/NumericalStabilitySanitizer.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/llvm/lib/Transforms/Instrumentation/NumericalStabilitySanitizer.cpp b/llvm/lib/Transforms/Instrumentation/NumericalStabilitySanitizer.cpp index 66d570b3f831e..3bbf049a1d8b9 100644 --- a/llvm/lib/Transforms/Instrumentation/NumericalStabilitySanitizer.cpp +++ b/llvm/lib/Transforms/Instrumentation/NumericalStabilitySanitizer.cpp @@ -1514,6 +1514,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) { From 3fb29b79894cee481142d49cf59d9ed368629fd1 Mon Sep 17 00:00:00 2001 From: valadaptive Date: Thu, 4 Dec 2025 10:37:16 -0500 Subject: [PATCH 08/13] [InstCombine] Add regression tests for f[min][max]imum_num libcalls Add tests corresponding to the existing fmin/fmax tests, as we'll later begin optimizing these into intrinsics. --- .../InstCombine/float-shrink-compare.ll | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/llvm/test/Transforms/InstCombine/float-shrink-compare.ll b/llvm/test/Transforms/InstCombine/float-shrink-compare.ll index 77b6ed7c5abe8..46341cf3d346f 100644 --- a/llvm/test/Transforms/InstCombine/float-shrink-compare.ll +++ b/llvm/test/Transforms/InstCombine/float-shrink-compare.ll @@ -463,6 +463,40 @@ 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: [[TMP1:%.*]] = fpext float [[X:%.*]] to double +; CHECK-NEXT: [[TMP2:%.*]] = fpext float [[Y:%.*]] to double +; CHECK-NEXT: [[TMP3:%.*]] = call double @fminimum_num(double [[TMP1]], double [[TMP2]]) #[[ATTR3:[0-9]+]] +; CHECK-NEXT: [[TMP4:%.*]] = fpext float [[Z:%.*]] to double +; CHECK-NEXT: [[TMP5:%.*]] = fcmp oeq double [[TMP3]], [[TMP4]] +; 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: [[TMP1:%.*]] = fpext float [[X:%.*]] to double +; CHECK-NEXT: [[TMP2:%.*]] = fpext float [[Y:%.*]] to double +; CHECK-NEXT: [[TMP3:%.*]] = call double @fmaximum_num(double [[TMP1]], double [[TMP2]]) #[[ATTR3]] +; CHECK-NEXT: [[TMP4:%.*]] = fpext float [[Z:%.*]] to double +; CHECK-NEXT: [[TMP5:%.*]] = fcmp oeq double [[TMP3]], [[TMP4]] +; 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 +552,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 From 95677da47cf749cff3c4f32740da2a1e228e87b3 Mon Sep 17 00:00:00 2001 From: valadaptive Date: Thu, 4 Dec 2025 10:42:54 -0500 Subject: [PATCH 09/13] [TLI] Handle f[min/max]imum_num in hasOptimizedCodegen This is a prerequisite to lowering them to intrinsics later. --- llvm/include/llvm/Analysis/TargetLibraryInfo.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/llvm/include/llvm/Analysis/TargetLibraryInfo.h b/llvm/include/llvm/Analysis/TargetLibraryInfo.h index a88497beb20ae..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: From e0a160bed23ba133a70aea408c621fe6dbe1e051 Mon Sep 17 00:00:00 2001 From: valadaptive Date: Thu, 4 Dec 2025 10:43:22 -0500 Subject: [PATCH 10/13] [InferAttrs] Handle f[min/max]imum_num like other libm functions These behave like fmin and fmax. --- llvm/lib/Transforms/Utils/BuildLibCalls.cpp | 6 ++++++ .../Transforms/InferFunctionAttrs/annotate.ll | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/llvm/lib/Transforms/Utils/BuildLibCalls.cpp b/llvm/lib/Transforms/Utils/BuildLibCalls.cpp index 05c882da38d61..a245b9405cfb7 100644 --- a/llvm/lib/Transforms/Utils/BuildLibCalls.cpp +++ b/llvm/lib/Transforms/Utils/BuildLibCalls.cpp @@ -1353,6 +1353,12 @@ 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: diff --git a/llvm/test/Transforms/InferFunctionAttrs/annotate.ll b/llvm/test/Transforms/InferFunctionAttrs/annotate.ll index f5020a93250ea..27ae0d53350f4 100644 --- a/llvm/test/Transforms/InferFunctionAttrs/annotate.ll +++ b/llvm/test/Transforms/InferFunctionAttrs/annotate.ll @@ -473,6 +473,24 @@ declare float @fminf(float, float) ; CHECK: declare x86_fp80 @fminl(x86_fp80, x86_fp80) [[MEMNONE_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]] declare x86_fp80 @fminl(x86_fp80, x86_fp80) +; CHECK: declare double @fmaximum_num(double, double) [[MEMNONE_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]] +declare double @fmaximum_num(double, double) + +; CHECK: declare float @fmaximum_numf(float, float) [[MEMNONE_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]] +declare float @fmaximum_numf(float, float) + +; CHECK: declare x86_fp80 @fmaximum_numl(x86_fp80, x86_fp80) [[MEMNONE_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]] +declare x86_fp80 @fmaximum_numl(x86_fp80, x86_fp80) + +; CHECK: declare double @fminimum_num(double, double) [[MEMNONE_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]] +declare double @fminimum_num(double, double) + +; CHECK: declare float @fminimum_numf(float, float) [[MEMNONE_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]] +declare float @fminimum_numf(float, float) + +; CHECK: declare x86_fp80 @fminimum_numl(x86_fp80, x86_fp80) [[MEMNONE_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]] +declare x86_fp80 @fminimum_numl(x86_fp80, x86_fp80) + ; CHECK: declare double @fmod(double, double) [[ERRNOMEMONLY_NOFREE_NOUNWIND_WILLRETURN:#[0-9]+]] declare double @fmod(double, double) From 3696d6254fedd41f6184adb012ef63b2a0a7b77b Mon Sep 17 00:00:00 2001 From: valadaptive Date: Thu, 4 Dec 2025 10:44:16 -0500 Subject: [PATCH 11/13] [ValueTracking] Handle f[min/max]imum_num in getIntrinsicForCallSite A straightforward mapping onto the corresponding intrinsic. --- llvm/lib/Analysis/ValueTracking.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) 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: From 11e899cfa8970d8e93db9a011904b59e986faf32 Mon Sep 17 00:00:00 2001 From: valadaptive Date: Thu, 4 Dec 2025 10:44:47 -0500 Subject: [PATCH 12/13] [SimplifyLibCalls] Recognize and simplify f[min/max]imumnum Unlike fmin and fmax, these are deterministic with regards to signed zero. --- .../llvm/Transforms/Utils/SimplifyLibCalls.h | 1 + .../lib/Transforms/Utils/SimplifyLibCalls.cpp | 31 +++++++++++++++++++ .../InstCombine/float-shrink-compare.ll | 14 +++------ 3 files changed, 36 insertions(+), 10 deletions(-) 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/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/InstCombine/float-shrink-compare.ll b/llvm/test/Transforms/InstCombine/float-shrink-compare.ll index 46341cf3d346f..6383feff3a6ee 100644 --- a/llvm/test/Transforms/InstCombine/float-shrink-compare.ll +++ b/llvm/test/Transforms/InstCombine/float-shrink-compare.ll @@ -465,11 +465,8 @@ define i1 @test18(float %x, float %y, float %z) { define i1 @test_fminimum_num(float %x, float %y, float %z) { ; CHECK-LABEL: @test_fminimum_num( -; CHECK-NEXT: [[TMP1:%.*]] = fpext float [[X:%.*]] to double -; CHECK-NEXT: [[TMP2:%.*]] = fpext float [[Y:%.*]] to double -; CHECK-NEXT: [[TMP3:%.*]] = call double @fminimum_num(double [[TMP1]], double [[TMP2]]) #[[ATTR3:[0-9]+]] -; CHECK-NEXT: [[TMP4:%.*]] = fpext float [[Z:%.*]] to double -; CHECK-NEXT: [[TMP5:%.*]] = fcmp oeq double [[TMP3]], [[TMP4]] +; 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 @@ -482,11 +479,8 @@ define i1 @test_fminimum_num(float %x, float %y, float %z) { define i1 @test_fmaximum_num(float %x, float %y, float %z) { ; CHECK-LABEL: @test_fmaximum_num( -; CHECK-NEXT: [[TMP1:%.*]] = fpext float [[X:%.*]] to double -; CHECK-NEXT: [[TMP2:%.*]] = fpext float [[Y:%.*]] to double -; CHECK-NEXT: [[TMP3:%.*]] = call double @fmaximum_num(double [[TMP1]], double [[TMP2]]) #[[ATTR3]] -; CHECK-NEXT: [[TMP4:%.*]] = fpext float [[Z:%.*]] to double -; CHECK-NEXT: [[TMP5:%.*]] = fcmp oeq double [[TMP3]], [[TMP4]] +; 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 From f055c83345390fcdb6aadc7d2fd3d0d95e1435e4 Mon Sep 17 00:00:00 2001 From: valadaptive Date: Thu, 4 Dec 2025 10:45:13 -0500 Subject: [PATCH 13/13] [NumericalStabilitySanitizer] Handle f[min/max]imum_num libcalls A simple matter of adding them to the table. --- .../Instrumentation/NumericalStabilitySanitizer.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/llvm/lib/Transforms/Instrumentation/NumericalStabilitySanitizer.cpp b/llvm/lib/Transforms/Instrumentation/NumericalStabilitySanitizer.cpp index 3bbf049a1d8b9..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"},