Skip to content

Commit e619c07

Browse files
committed
[flang] Fold NEAREST() and its relatives
Implement constant folding for the intrinsic function NEAREST() and the related functions IEEE_NEXT_AFTER(), IEEE_NEXT_UP(), and IEEE_NEXT_DOWN(). Differential Revision: https://reviews.llvm.org/D122510
1 parent fceea4e commit e619c07

File tree

4 files changed

+205
-2
lines changed

4 files changed

+205
-2
lines changed

flang/include/flang/Evaluate/real.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ class Real : public common::RealDetails<PREC> {
120120

121121
ValueWithRealFlags<Real> SQRT(Rounding rounding = defaultRounding) const;
122122

123+
// NEAREST(), IEEE_NEXT_AFTER(), IEEE_NEXT_UP(), and IEEE_NEXT_DOWN()
124+
ValueWithRealFlags<Real> NEAREST(bool upward) const;
125+
123126
// HYPOT(x,y)=SQRT(x**2 + y**2) computed so as to avoid spurious
124127
// intermediate overflows.
125128
ValueWithRealFlags<Real> HYPOT(

flang/lib/Evaluate/fold-real.cpp

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,31 @@ Expr<Type<TypeCategory::Real, KIND>> FoldIntrinsicFunction(
119119
RelationalOperator::GT, T::Scalar::HUGE().Negate());
120120
} else if (name == "merge") {
121121
return FoldMerge<T>(context, std::move(funcRef));
122+
} else if (name == "nearest") {
123+
if (const auto *sExpr{UnwrapExpr<Expr<SomeReal>>(args[1])}) {
124+
return std::visit(
125+
[&](const auto &sVal) {
126+
using TS = ResultType<decltype(sVal)>;
127+
return FoldElementalIntrinsic<T, T, TS>(context, std::move(funcRef),
128+
ScalarFunc<T, T, TS>([&](const Scalar<T> &x,
129+
const Scalar<TS> &s) -> Scalar<T> {
130+
if (s.IsZero()) {
131+
context.messages().Say(
132+
"NEAREST: S argument is zero"_warn_en_US);
133+
}
134+
auto result{x.NEAREST(!s.IsNegative())};
135+
if (result.flags.test(RealFlag::Overflow)) {
136+
context.messages().Say(
137+
"NEAREST intrinsic folding overflow"_warn_en_US);
138+
} else if (result.flags.test(RealFlag::InvalidArgument)) {
139+
context.messages().Say(
140+
"NEAREST intrinsic folding: bad argument"_warn_en_US);
141+
}
142+
return result.value;
143+
}));
144+
},
145+
sExpr->u);
146+
}
122147
} else if (name == "min") {
123148
return FoldMINorMAX(context, std::move(funcRef), Ordering::Less);
124149
} else if (name == "minval") {
@@ -167,10 +192,58 @@ Expr<Type<TypeCategory::Real, KIND>> FoldIntrinsicFunction(
167192
return FoldSum<T>(context, std::move(funcRef));
168193
} else if (name == "tiny") {
169194
return Expr<T>{Scalar<T>::TINY()};
195+
} else if (name == "__builtin_ieee_next_after") {
196+
if (const auto *yExpr{UnwrapExpr<Expr<SomeReal>>(args[1])}) {
197+
return std::visit(
198+
[&](const auto &yVal) {
199+
using TY = ResultType<decltype(yVal)>;
200+
return FoldElementalIntrinsic<T, T, TY>(context, std::move(funcRef),
201+
ScalarFunc<T, T, TY>([&](const Scalar<T> &x,
202+
const Scalar<TY> &y) -> Scalar<T> {
203+
bool upward{true};
204+
switch (x.Compare(Scalar<T>::Convert(y).value)) {
205+
case Relation::Unordered:
206+
context.messages().Say(
207+
"IEEE_NEXT_AFTER intrinsic folding: bad argument"_warn_en_US);
208+
return x;
209+
case Relation::Equal:
210+
return x;
211+
case Relation::Less:
212+
upward = true;
213+
break;
214+
case Relation::Greater:
215+
upward = false;
216+
break;
217+
}
218+
auto result{x.NEAREST(upward)};
219+
if (result.flags.test(RealFlag::Overflow)) {
220+
context.messages().Say(
221+
"IEEE_NEXT_AFTER intrinsic folding overflow"_warn_en_US);
222+
}
223+
return result.value;
224+
}));
225+
},
226+
yExpr->u);
227+
}
228+
} else if (name == "__builtin_ieee_next_up" ||
229+
name == "__builtin_ieee_next_down") {
230+
bool upward{name == "__builtin_ieee_next_up"};
231+
const char *iName{upward ? "IEEE_NEXT_UP" : "IEEE_NEXT_DOWN"};
232+
return FoldElementalIntrinsic<T, T>(context, std::move(funcRef),
233+
ScalarFunc<T, T>([&](const Scalar<T> &x) -> Scalar<T> {
234+
auto result{x.NEAREST(upward)};
235+
if (result.flags.test(RealFlag::Overflow)) {
236+
context.messages().Say(
237+
"%s intrinsic folding overflow"_warn_en_US, iName);
238+
} else if (result.flags.test(RealFlag::InvalidArgument)) {
239+
context.messages().Say(
240+
"%s intrinsic folding: bad argument"_warn_en_US, iName);
241+
}
242+
return result.value;
243+
}));
170244
}
171245
// TODO: dim, dot_product, fraction, matmul,
172-
// modulo, nearest, norm2, rrspacing,
173-
// __builtin_next_after/down/up,
246+
// modulo, norm2, rrspacing,
174247
// set_exponent, spacing, transfer,
175248
// bessel_jn (transformational) and bessel_yn (transformational)
176249
return Expr<T>{std::move(funcRef)};

flang/lib/Evaluate/real.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,45 @@ ValueWithRealFlags<Real<W, P>> Real<W, P>::SQRT(Rounding rounding) const {
346346
return result;
347347
}
348348

349+
template <typename W, int P>
350+
ValueWithRealFlags<Real<W, P>> Real<W, P>::NEAREST(bool upward) const {
351+
ValueWithRealFlags<Real> result;
352+
if (IsFinite()) {
353+
Fraction fraction{GetFraction()};
354+
int expo{Exponent()};
355+
Fraction one{1};
356+
Fraction nearest;
357+
bool isNegative{IsNegative()};
358+
if (upward != isNegative) { // upward in magnitude
359+
auto next{fraction.AddUnsigned(one)};
360+
if (next.carry) {
361+
++expo;
362+
nearest = Fraction::Least(); // MSB only
363+
} else {
364+
nearest = next.value;
365+
}
366+
} else { // downward in magnitude
367+
if (IsZero()) {
368+
nearest = 1; // smallest magnitude negative subnormal
369+
isNegative = !isNegative;
370+
} else {
371+
auto sub1{fraction.SubtractSigned(one)};
372+
if (sub1.overflow) {
373+
nearest = Fraction{0}.NOT();
374+
--expo;
375+
} else {
376+
nearest = sub1.value;
377+
}
378+
}
379+
}
380+
result.flags = result.value.Normalize(isNegative, expo, nearest);
381+
} else {
382+
result.flags.set(RealFlag::InvalidArgument);
383+
result.value = *this;
384+
}
385+
return result;
386+
}
387+
349388
// HYPOT(x,y) = SQRT(x**2 + y**2) by definition, but those squared intermediate
350389
// values are susceptible to over/underflow when computed naively.
351390
// Assuming that x>=y, calculate instead:

flang/test/Evaluate/fold-nearest.f90

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
! RUN: %python %S/test_folding.py %s %flang_fc1
2+
! Tests folding of NEAREST() and its relatives
3+
module m1
4+
real, parameter :: minSubnormal = 1.e-45
5+
logical, parameter :: test_1 = nearest(0., 1.) == minSubnormal
6+
logical, parameter :: test_2 = nearest(minSubnormal, -1.) == 0
7+
logical, parameter :: test_3 = nearest(1., 1.) == 1.0000001
8+
logical, parameter :: test_4 = nearest(1.0000001, -1.) == 1
9+
!WARN: warning: NEAREST intrinsic folding overflow
10+
real, parameter :: inf = nearest(huge(1.), 1.)
11+
!WARN: warning: NEAREST intrinsic folding: bad argument
12+
logical, parameter :: test_5 = nearest(inf, 1.) == inf
13+
!WARN: warning: NEAREST intrinsic folding: bad argument
14+
logical, parameter :: test_6 = nearest(-inf, -1.) == -inf
15+
logical, parameter :: test_7 = nearest(1.9999999, 1.) == 2.
16+
logical, parameter :: test_8 = nearest(2., -1.) == 1.9999999
17+
logical, parameter :: test_9 = nearest(1.9999999999999999999_10, 1.) == 2._10
18+
logical, parameter :: test_10 = nearest(-1., 1.) == -.99999994
19+
logical, parameter :: test_11 = nearest(-1., -2.) == -1.0000001
20+
real, parameter :: negZero = sign(0., -1.)
21+
logical, parameter :: test_12 = nearest(negZero, 1.) == minSubnormal
22+
logical, parameter :: test_13 = nearest(negZero, -1.) == -minSubnormal
23+
!WARN: warning: NEAREST: S argument is zero
24+
logical, parameter :: test_14 = nearest(0., negZero) == -minSubnormal
25+
!WARN: warning: NEAREST: S argument is zero
26+
logical, parameter :: test_15 = nearest(negZero, 0.) == minSubnormal
27+
end module
28+
29+
module m2
30+
use ieee_arithmetic, only: ieee_next_after
31+
real, parameter :: minSubnormal = 1.e-45
32+
logical, parameter :: test_0 = ieee_next_after(0., 0.) == 0.
33+
logical, parameter :: test_1 = ieee_next_after(0., 1.) == minSubnormal
34+
logical, parameter :: test_2 = ieee_next_after(minSubnormal, -1.) == 0
35+
logical, parameter :: test_3 = ieee_next_after(1., 2.) == 1.0000001
36+
logical, parameter :: test_4 = ieee_next_after(1.0000001, -1.) == 1
37+
!WARN: warning: division by zero
38+
real, parameter :: inf = 1. / 0.
39+
logical, parameter :: test_5 = ieee_next_after(inf, inf) == inf
40+
logical, parameter :: test_6 = ieee_next_after(inf, -inf) == inf
41+
logical, parameter :: test_7 = ieee_next_after(-inf, inf) == -inf
42+
logical, parameter :: test_8 = ieee_next_after(-inf, -1.) == -inf
43+
logical, parameter :: test_9 = ieee_next_after(1.9999999, 3.) == 2.
44+
logical, parameter :: test_10 = ieee_next_after(2., 1.) == 1.9999999
45+
logical, parameter :: test_11 = ieee_next_after(1.9999999999999999999_10, 3.) == 2._10
46+
logical, parameter :: test_12 = ieee_next_after(1., 1.) == 1.
47+
!WARN: warning: invalid argument on division
48+
real, parameter :: nan = 0. / 0.
49+
!WARN: warning: IEEE_NEXT_AFTER intrinsic folding: bad argument
50+
real, parameter :: x13 = ieee_next_after(nan, nan)
51+
logical, parameter :: test_13 = .not. (x13 == x13)
52+
!WARN: warning: IEEE_NEXT_AFTER intrinsic folding: bad argument
53+
real, parameter :: x14 = ieee_next_after(nan, 0.)
54+
logical, parameter :: test_14 = .not. (x14 == x14)
55+
end module
56+
57+
module m3
58+
use ieee_arithmetic, only: ieee_next_up, ieee_next_down
59+
real(kind(0.d0)), parameter :: minSubnormal = 5.d-324
60+
logical, parameter :: test_1 = ieee_next_up(0.d0) == minSubnormal
61+
logical, parameter :: test_2 = ieee_next_down(0.d0) == -minSubnormal
62+
logical, parameter :: test_3 = ieee_next_up(1.d0) == 1.0000000000000002d0
63+
logical, parameter :: test_4 = ieee_next_down(1.0000000000000002d0) == 1.d0
64+
!WARN: warning: division by zero
65+
real(kind(0.d0)), parameter :: inf = 1.d0 / 0.d0
66+
!WARN: warning: IEEE_NEXT_UP intrinsic folding overflow
67+
logical, parameter :: test_5 = ieee_next_up(huge(0.d0)) == inf
68+
!WARN: warning: IEEE_NEXT_DOWN intrinsic folding overflow
69+
logical, parameter :: test_6 = ieee_next_down(-huge(0.d0)) == -inf
70+
!WARN: warning: IEEE_NEXT_UP intrinsic folding: bad argument
71+
logical, parameter :: test_7 = ieee_next_up(inf) == inf
72+
!WARN: warning: IEEE_NEXT_DOWN intrinsic folding: bad argument
73+
logical, parameter :: test_8 = ieee_next_down(inf) == inf
74+
!WARN: warning: IEEE_NEXT_UP intrinsic folding: bad argument
75+
logical, parameter :: test_9 = ieee_next_up(-inf) == -inf
76+
!WARN: warning: IEEE_NEXT_DOWN intrinsic folding: bad argument
77+
logical, parameter :: test_10 = ieee_next_down(-inf) == -inf
78+
logical, parameter :: test_11 = ieee_next_up(1.9999999999999997d0) == 2.d0
79+
logical, parameter :: test_12 = ieee_next_down(2.d0) == 1.9999999999999997d0
80+
!WARN: warning: invalid argument on division
81+
real(kind(0.d0)), parameter :: nan = 0.d0 / 0.d0
82+
!WARN: warning: IEEE_NEXT_UP intrinsic folding: bad argument
83+
real(kind(0.d0)), parameter :: x13 = ieee_next_up(nan)
84+
logical, parameter :: test_13 = .not. (x13 == x13)
85+
!WARN: warning: IEEE_NEXT_DOWN intrinsic folding: bad argument
86+
real(kind(0.d0)), parameter :: x14 = ieee_next_down(nan)
87+
logical, parameter :: test_14 = .not. (x14 == x14)
88+
end module

0 commit comments

Comments
 (0)