Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions llvm/include/llvm/ADT/APFloat.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
54 changes: 53 additions & 1 deletion llvm/lib/Analysis/ConstantFolding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Comment on lines +1999 to +2001
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

case 'p':
return Name == "pow" || Name == "powf";
case 'r':
Expand Down Expand Up @@ -3221,6 +3223,37 @@ 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)) {
// Make sure to propagate NaN payloads.
if (Op1V.isNaN()) {
return ConstantFP::get(Ty->getContext(), Op1V);
}
if (Op2V.isNaN()) {
// Payload propagation might not make sense if the second argument's
// type is wider than the return value. We'll give up in the latter
// case.
bool SemEqual = &Op2V.getSemantics() == &Ty->getFltSemantics();
APFloat Ret = SemEqual ? Op2V : APFloat::getNaN(Ty->getFltSemantics());
return ConstantFP::get(Ty->getContext(), Ret);
}

// The two arguments of nexttoward can have differing semantics.
// We need to convert both arguments to the same semantics so
// we can do comparisons.
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.

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;
Expand Down Expand Up @@ -4655,6 +4688,25 @@ 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: {
// The two arguments of nexttoward can have differing semantics.
// We need to convert both arguments to the same semantics so
// we can do comparisons.
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.

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;
}
Expand Down
193 changes: 193 additions & 0 deletions llvm/test/Transforms/InstCombine/constant-fold-nextafter.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
; 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_constant_fold_propagates_nan_payload() {
; CHECK-LABEL: define double @nextafter_constant_fold_propagates_nan_payload() {
; CHECK-NEXT: ret double 0x7FF8000000000001
;
%nan = load double, double* @dbl_nan
%tmp1 = bitcast double %nan to i64
%tmp2 = or i64 %tmp1, 1
%nan_with_payload = bitcast i64 %tmp2 to double
%next = call double @nextafter(double %nan_with_payload, double 1.0)
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_constant_fold_propagates_nan_payload() {
; CHECK-LABEL: define float @nextafterf_constant_fold_propagates_nan_payload() {
; CHECK-NEXT: ret float 0x7FF8000020000000
;
%nan = load float, float* @flt_nan
%tmp1 = bitcast float %nan to i32
%tmp2 = or i32 %tmp1, 1
%nan_with_payload = bitcast i32 %tmp2 to float
%next = call float @nextafterf(float %nan_with_payload, float 1.0)
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
}
Loading
Loading