xref: /llvm-project/flang/lib/Evaluate/fold-real.cpp (revision 3f594741cf8e1537fb25f84ef3cf2245b08d8089)
1 //===-- lib/Evaluate/fold-real.cpp ----------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "fold-implementation.h"
10 #include "fold-matmul.h"
11 #include "fold-reduction.h"
12 
13 namespace Fortran::evaluate {
14 
15 template <typename T>
16 static Expr<T> FoldTransformationalBessel(
17     FunctionRef<T> &&funcRef, FoldingContext &context) {
18   CHECK(funcRef.arguments().size() == 3);
19   /// Bessel runtime functions use `int` integer arguments. Convert integer
20   /// arguments to Int4, any overflow error will be reported during the
21   /// conversion folding.
22   using Int4 = Type<TypeCategory::Integer, 4>;
23   if (auto args{GetConstantArguments<Int4, Int4, T>(
24           context, funcRef.arguments(), /*hasOptionalArgument=*/false)}) {
25     const std::string &name{std::get<SpecificIntrinsic>(funcRef.proc().u).name};
26     if (auto elementalBessel{GetHostRuntimeWrapper<T, Int4, T>(name)}) {
27       std::vector<Scalar<T>> results;
28       int n1{static_cast<int>(
29           std::get<0>(*args)->GetScalarValue().value().ToInt64())};
30       int n2{static_cast<int>(
31           std::get<1>(*args)->GetScalarValue().value().ToInt64())};
32       Scalar<T> x{std::get<2>(*args)->GetScalarValue().value()};
33       for (int i{n1}; i <= n2; ++i) {
34         results.emplace_back((*elementalBessel)(context, Scalar<Int4>{i}, x));
35       }
36       return Expr<T>{Constant<T>{
37           std::move(results), ConstantSubscripts{std::max(n2 - n1 + 1, 0)}}};
38     } else if (context.languageFeatures().ShouldWarn(
39                    common::UsageWarning::FoldingFailure)) {
40       context.messages().Say(common::UsageWarning::FoldingFailure,
41           "%s(integer(kind=4), real(kind=%d)) cannot be folded on host"_warn_en_US,
42           name, T::kind);
43     }
44   }
45   return Expr<T>{std::move(funcRef)};
46 }
47 
48 // NORM2
49 template <int KIND> class Norm2Accumulator {
50   using T = Type<TypeCategory::Real, KIND>;
51 
52 public:
53   Norm2Accumulator(
54       const Constant<T> &array, const Constant<T> &maxAbs, Rounding rounding)
55       : array_{array}, maxAbs_{maxAbs}, rounding_{rounding} {};
56   void operator()(
57       Scalar<T> &element, const ConstantSubscripts &at, bool /*first*/) {
58     // Summation of scaled elements:
59     // Naively,
60     //   NORM2(A(:)) = SQRT(SUM(A(:)**2))
61     // For any T > 0, we have mathematically
62     //   SQRT(SUM(A(:)**2))
63     //     = SQRT(T**2 * (SUM(A(:)**2) / T**2))
64     //     = SQRT(T**2 * SUM(A(:)**2 / T**2))
65     //     = SQRT(T**2 * SUM((A(:)/T)**2))
66     //     = SQRT(T**2) * SQRT(SUM((A(:)/T)**2))
67     //     = T * SQRT(SUM((A(:)/T)**2))
68     // By letting T = MAXVAL(ABS(A)), we ensure that
69     // ALL(ABS(A(:)/T) <= 1), so ALL((A(:)/T)**2 <= 1), and the SUM will
70     // not overflow unless absolutely necessary.
71     auto scale{maxAbs_.At(maxAbsAt_)};
72     if (scale.IsZero()) {
73       // Maximum value is zero, and so will the result be.
74       // Avoid division by zero below.
75       element = scale;
76     } else {
77       auto item{array_.At(at)};
78       auto scaled{item.Divide(scale).value};
79       auto square{scaled.Multiply(scaled).value};
80       if constexpr (useKahanSummation) {
81         auto next{square.Subtract(correction_, rounding_)};
82         overflow_ |= next.flags.test(RealFlag::Overflow);
83         auto sum{element.Add(next.value, rounding_)};
84         overflow_ |= sum.flags.test(RealFlag::Overflow);
85         correction_ = sum.value.Subtract(element, rounding_)
86                           .value.Subtract(next.value, rounding_)
87                           .value;
88         element = sum.value;
89       } else {
90         auto sum{element.Add(square, rounding_)};
91         overflow_ |= sum.flags.test(RealFlag::Overflow);
92         element = sum.value;
93       }
94     }
95   }
96   bool overflow() const { return overflow_; }
97   void Done(Scalar<T> &result) {
98     // incoming result = SUM((data(:)/maxAbs)**2)
99     // outgoing result = maxAbs * SQRT(result)
100     auto root{result.SQRT().value};
101     auto product{root.Multiply(maxAbs_.At(maxAbsAt_))};
102     maxAbs_.IncrementSubscripts(maxAbsAt_);
103     overflow_ |= product.flags.test(RealFlag::Overflow);
104     result = product.value;
105   }
106 
107 private:
108   const Constant<T> &array_;
109   const Constant<T> &maxAbs_;
110   const Rounding rounding_;
111   bool overflow_{false};
112   Scalar<T> correction_{};
113   ConstantSubscripts maxAbsAt_{maxAbs_.lbounds()};
114 };
115 
116 template <int KIND>
117 static Expr<Type<TypeCategory::Real, KIND>> FoldNorm2(FoldingContext &context,
118     FunctionRef<Type<TypeCategory::Real, KIND>> &&funcRef) {
119   using T = Type<TypeCategory::Real, KIND>;
120   using Element = typename Constant<T>::Element;
121   std::optional<int> dim;
122   if (std::optional<ArrayAndMask<T>> arrayAndMask{
123           ProcessReductionArgs<T>(context, funcRef.arguments(), dim,
124               /*X=*/0, /*DIM=*/1)}) {
125     MaxvalMinvalAccumulator<T, /*ABS=*/true> maxAbsAccumulator{
126         RelationalOperator::GT, context, arrayAndMask->array};
127     const Element identity{};
128     Constant<T> maxAbs{DoReduction<T>(arrayAndMask->array, arrayAndMask->mask,
129         dim, identity, maxAbsAccumulator)};
130     Norm2Accumulator norm2Accumulator{arrayAndMask->array, maxAbs,
131         context.targetCharacteristics().roundingMode()};
132     Constant<T> result{DoReduction<T>(arrayAndMask->array, arrayAndMask->mask,
133         dim, identity, norm2Accumulator)};
134     if (norm2Accumulator.overflow() &&
135         context.languageFeatures().ShouldWarn(
136             common::UsageWarning::FoldingException)) {
137       context.messages().Say(common::UsageWarning::FoldingException,
138           "NORM2() of REAL(%d) data overflowed"_warn_en_US, KIND);
139     }
140     return Expr<T>{std::move(result)};
141   }
142   return Expr<T>{std::move(funcRef)};
143 }
144 
145 template <int KIND>
146 Expr<Type<TypeCategory::Real, KIND>> FoldIntrinsicFunction(
147     FoldingContext &context,
148     FunctionRef<Type<TypeCategory::Real, KIND>> &&funcRef) {
149   using T = Type<TypeCategory::Real, KIND>;
150   using ComplexT = Type<TypeCategory::Complex, KIND>;
151   using Int4 = Type<TypeCategory::Integer, 4>;
152   ActualArguments &args{funcRef.arguments()};
153   auto *intrinsic{std::get_if<SpecificIntrinsic>(&funcRef.proc().u)};
154   CHECK(intrinsic);
155   std::string name{intrinsic->name};
156   if (name == "acos" || name == "acosh" || name == "asin" || name == "asinh" ||
157       (name == "atan" && args.size() == 1) || name == "atanh" ||
158       name == "bessel_j0" || name == "bessel_j1" || name == "bessel_y0" ||
159       name == "bessel_y1" || name == "cos" || name == "cosh" || name == "erf" ||
160       name == "erfc" || name == "erfc_scaled" || name == "exp" ||
161       name == "gamma" || name == "log" || name == "log10" ||
162       name == "log_gamma" || name == "sin" || name == "sinh" || name == "tan" ||
163       name == "tanh") {
164     CHECK(args.size() == 1);
165     if (auto callable{GetHostRuntimeWrapper<T, T>(name)}) {
166       return FoldElementalIntrinsic<T, T>(
167           context, std::move(funcRef), *callable);
168     } else if (context.languageFeatures().ShouldWarn(
169                    common::UsageWarning::FoldingFailure)) {
170       context.messages().Say(common::UsageWarning::FoldingFailure,
171           "%s(real(kind=%d)) cannot be folded on host"_warn_en_US, name, KIND);
172     }
173   } else if (name == "amax0" || name == "amin0" || name == "amin1" ||
174       name == "amax1" || name == "dmin1" || name == "dmax1") {
175     return RewriteSpecificMINorMAX(context, std::move(funcRef));
176   } else if (name == "atan" || name == "atan2") {
177     std::string localName{name == "atan" ? "atan2" : name};
178     CHECK(args.size() == 2);
179     if (auto callable{GetHostRuntimeWrapper<T, T, T>(localName)}) {
180       return FoldElementalIntrinsic<T, T, T>(
181           context, std::move(funcRef), *callable);
182     } else if (context.languageFeatures().ShouldWarn(
183                    common::UsageWarning::FoldingFailure)) {
184       context.messages().Say(common::UsageWarning::FoldingFailure,
185           "%s(real(kind=%d), real(kind%d)) cannot be folded on host"_warn_en_US,
186           name, KIND, KIND);
187     }
188   } else if (name == "bessel_jn" || name == "bessel_yn") {
189     if (args.size() == 2) { // elemental
190       // runtime functions use int arg
191       if (auto callable{GetHostRuntimeWrapper<T, Int4, T>(name)}) {
192         return FoldElementalIntrinsic<T, Int4, T>(
193             context, std::move(funcRef), *callable);
194       } else if (context.languageFeatures().ShouldWarn(
195                      common::UsageWarning::FoldingFailure)) {
196         context.messages().Say(common::UsageWarning::FoldingFailure,
197             "%s(integer(kind=4), real(kind=%d)) cannot be folded on host"_warn_en_US,
198             name, KIND);
199       }
200     } else {
201       return FoldTransformationalBessel<T>(std::move(funcRef), context);
202     }
203   } else if (name == "abs") { // incl. zabs & cdabs
204     // Argument can be complex or real
205     if (UnwrapExpr<Expr<SomeReal>>(args[0])) {
206       return FoldElementalIntrinsic<T, T>(
207           context, std::move(funcRef), &Scalar<T>::ABS);
208     } else if (UnwrapExpr<Expr<SomeComplex>>(args[0])) {
209       return FoldElementalIntrinsic<T, ComplexT>(context, std::move(funcRef),
210           ScalarFunc<T, ComplexT>([&name, &context](
211                                       const Scalar<ComplexT> &z) -> Scalar<T> {
212             ValueWithRealFlags<Scalar<T>> y{z.ABS()};
213             if (y.flags.test(RealFlag::Overflow) &&
214                 context.languageFeatures().ShouldWarn(
215                     common::UsageWarning::FoldingException)) {
216               context.messages().Say(common::UsageWarning::FoldingException,
217                   "complex ABS intrinsic folding overflow"_warn_en_US, name);
218             }
219             return y.value;
220           }));
221     } else {
222       common::die(" unexpected argument type inside abs");
223     }
224   } else if (name == "aimag") {
225     if (auto *zExpr{UnwrapExpr<Expr<ComplexT>>(args[0])}) {
226       return Fold(context, Expr<T>{ComplexComponent{true, std::move(*zExpr)}});
227     }
228   } else if (name == "aint" || name == "anint") {
229     // ANINT rounds ties away from zero, not to even
230     common::RoundingMode mode{name == "aint"
231             ? common::RoundingMode::ToZero
232             : common::RoundingMode::TiesAwayFromZero};
233     return FoldElementalIntrinsic<T, T>(context, std::move(funcRef),
234         ScalarFunc<T, T>(
235             [&name, &context, mode](const Scalar<T> &x) -> Scalar<T> {
236               ValueWithRealFlags<Scalar<T>> y{x.ToWholeNumber(mode)};
237               if (y.flags.test(RealFlag::Overflow) &&
238                   context.languageFeatures().ShouldWarn(
239                       common::UsageWarning::FoldingException)) {
240                 context.messages().Say(common::UsageWarning::FoldingException,
241                     "%s intrinsic folding overflow"_warn_en_US, name);
242               }
243               return y.value;
244             }));
245   } else if (name == "dim") {
246     return FoldElementalIntrinsic<T, T, T>(context, std::move(funcRef),
247         ScalarFunc<T, T, T>([&context](const Scalar<T> &x,
248                                 const Scalar<T> &y) -> Scalar<T> {
249           ValueWithRealFlags<Scalar<T>> result{x.DIM(y)};
250           if (result.flags.test(RealFlag::Overflow) &&
251               context.languageFeatures().ShouldWarn(
252                   common::UsageWarning::FoldingException)) {
253             context.messages().Say(common::UsageWarning::FoldingException,
254                 "DIM intrinsic folding overflow"_warn_en_US);
255           }
256           return result.value;
257         }));
258   } else if (name == "dot_product") {
259     return FoldDotProduct<T>(context, std::move(funcRef));
260   } else if (name == "dprod") {
261     // Rewrite DPROD(x,y) -> DBLE(x)*DBLE(y)
262     if (args.at(0) && args.at(1)) {
263       const auto *xExpr{args[0]->UnwrapExpr()};
264       const auto *yExpr{args[1]->UnwrapExpr()};
265       if (xExpr && yExpr) {
266         return Fold(context,
267             ToReal<T::kind>(context, common::Clone(*xExpr)) *
268                 ToReal<T::kind>(context, common::Clone(*yExpr)));
269       }
270     }
271   } else if (name == "epsilon") {
272     return Expr<T>{Scalar<T>::EPSILON()};
273   } else if (name == "fraction") {
274     return FoldElementalIntrinsic<T, T>(context, std::move(funcRef),
275         ScalarFunc<T, T>(
276             [](const Scalar<T> &x) -> Scalar<T> { return x.FRACTION(); }));
277   } else if (name == "huge") {
278     return Expr<T>{Scalar<T>::HUGE()};
279   } else if (name == "hypot") {
280     CHECK(args.size() == 2);
281     return FoldElementalIntrinsic<T, T, T>(context, std::move(funcRef),
282         ScalarFunc<T, T, T>(
283             [&](const Scalar<T> &x, const Scalar<T> &y) -> Scalar<T> {
284               ValueWithRealFlags<Scalar<T>> result{x.HYPOT(y)};
285               if (result.flags.test(RealFlag::Overflow) &&
286                   context.languageFeatures().ShouldWarn(
287                       common::UsageWarning::FoldingException)) {
288                 context.messages().Say(common::UsageWarning::FoldingException,
289                     "HYPOT intrinsic folding overflow"_warn_en_US);
290               }
291               return result.value;
292             }));
293   } else if (name == "matmul") {
294     return FoldMatmul(context, std::move(funcRef));
295   } else if (name == "max") {
296     return FoldMINorMAX(context, std::move(funcRef), Ordering::Greater);
297   } else if (name == "maxval") {
298     return FoldMaxvalMinval<T>(context, std::move(funcRef),
299         RelationalOperator::GT, T::Scalar::HUGE().Negate());
300   } else if (name == "min") {
301     return FoldMINorMAX(context, std::move(funcRef), Ordering::Less);
302   } else if (name == "minval") {
303     return FoldMaxvalMinval<T>(
304         context, std::move(funcRef), RelationalOperator::LT, T::Scalar::HUGE());
305   } else if (name == "mod") {
306     CHECK(args.size() == 2);
307     bool badPConst{false};
308     if (auto *pExpr{UnwrapExpr<Expr<T>>(args[1])}) {
309       *pExpr = Fold(context, std::move(*pExpr));
310       if (auto pConst{GetScalarConstantValue<T>(*pExpr)}; pConst &&
311           pConst->IsZero() &&
312           context.languageFeatures().ShouldWarn(
313               common::UsageWarning::FoldingAvoidsRuntimeCrash)) {
314         context.messages().Say(common::UsageWarning::FoldingAvoidsRuntimeCrash,
315             "MOD: P argument is zero"_warn_en_US);
316         badPConst = true;
317       }
318     }
319     return FoldElementalIntrinsic<T, T, T>(context, std::move(funcRef),
320         ScalarFunc<T, T, T>([&context, badPConst](const Scalar<T> &x,
321                                 const Scalar<T> &y) -> Scalar<T> {
322           auto result{x.MOD(y)};
323           if (!badPConst && result.flags.test(RealFlag::DivideByZero) &&
324               context.languageFeatures().ShouldWarn(
325                   common::UsageWarning::FoldingAvoidsRuntimeCrash)) {
326             context.messages().Say(
327                 common::UsageWarning::FoldingAvoidsRuntimeCrash,
328                 "second argument to MOD must not be zero"_warn_en_US);
329           }
330           return result.value;
331         }));
332   } else if (name == "modulo") {
333     CHECK(args.size() == 2);
334     bool badPConst{false};
335     if (auto *pExpr{UnwrapExpr<Expr<T>>(args[1])}) {
336       *pExpr = Fold(context, std::move(*pExpr));
337       if (auto pConst{GetScalarConstantValue<T>(*pExpr)}; pConst &&
338           pConst->IsZero() &&
339           context.languageFeatures().ShouldWarn(
340               common::UsageWarning::FoldingAvoidsRuntimeCrash)) {
341         context.messages().Say(common::UsageWarning::FoldingAvoidsRuntimeCrash,
342             "MODULO: P argument is zero"_warn_en_US);
343         badPConst = true;
344       }
345     }
346     return FoldElementalIntrinsic<T, T, T>(context, std::move(funcRef),
347         ScalarFunc<T, T, T>([&context, badPConst](const Scalar<T> &x,
348                                 const Scalar<T> &y) -> Scalar<T> {
349           auto result{x.MODULO(y)};
350           if (!badPConst && result.flags.test(RealFlag::DivideByZero) &&
351               context.languageFeatures().ShouldWarn(
352                   common::UsageWarning::FoldingAvoidsRuntimeCrash)) {
353             context.messages().Say(
354                 common::UsageWarning::FoldingAvoidsRuntimeCrash,
355                 "second argument to MODULO must not be zero"_warn_en_US);
356           }
357           return result.value;
358         }));
359   } else if (name == "nearest") {
360     if (auto *sExpr{UnwrapExpr<Expr<SomeReal>>(args[1])}) {
361       *sExpr = Fold(context, std::move(*sExpr));
362       return common::visit(
363           [&](const auto &sVal) {
364             using TS = ResultType<decltype(sVal)>;
365             bool badSConst{false};
366             if (auto sConst{GetScalarConstantValue<TS>(sVal)}; sConst &&
367                 (sConst->IsZero() || sConst->IsNotANumber()) &&
368                 context.languageFeatures().ShouldWarn(
369                     common::UsageWarning::FoldingValueChecks)) {
370               context.messages().Say(common::UsageWarning::FoldingValueChecks,
371                   "NEAREST: S argument is %s"_warn_en_US,
372                   sConst->IsZero() ? "zero" : "NaN");
373               badSConst = true;
374             }
375             return FoldElementalIntrinsic<T, T, TS>(context, std::move(funcRef),
376                 ScalarFunc<T, T, TS>([&](const Scalar<T> &x,
377                                          const Scalar<TS> &s) -> Scalar<T> {
378                   if (!badSConst && (s.IsZero() || s.IsNotANumber()) &&
379                       context.languageFeatures().ShouldWarn(
380                           common::UsageWarning::FoldingValueChecks)) {
381                     context.messages().Say(
382                         common::UsageWarning::FoldingValueChecks,
383                         "NEAREST: S argument is %s"_warn_en_US,
384                         s.IsZero() ? "zero" : "NaN");
385                   }
386                   auto result{x.NEAREST(!s.IsNegative())};
387                   if (context.languageFeatures().ShouldWarn(
388                           common::UsageWarning::FoldingException)) {
389                     if (result.flags.test(RealFlag::InvalidArgument)) {
390                       context.messages().Say(
391                           common::UsageWarning::FoldingException,
392                           "NEAREST intrinsic folding: bad argument"_warn_en_US);
393                     }
394                   }
395                   return result.value;
396                 }));
397           },
398           sExpr->u);
399     }
400   } else if (name == "norm2") {
401     return FoldNorm2<T::kind>(context, std::move(funcRef));
402   } else if (name == "product") {
403     auto one{Scalar<T>::FromInteger(value::Integer<8>{1}).value};
404     return FoldProduct<T>(context, std::move(funcRef), one);
405   } else if (name == "real" || name == "dble") {
406     if (auto *expr{args[0].value().UnwrapExpr()}) {
407       return ToReal<KIND>(context, std::move(*expr));
408     }
409   } else if (name == "rrspacing") {
410     return FoldElementalIntrinsic<T, T>(context, std::move(funcRef),
411         ScalarFunc<T, T>(
412             [](const Scalar<T> &x) -> Scalar<T> { return x.RRSPACING(); }));
413   } else if (name == "scale") {
414     if (const auto *byExpr{UnwrapExpr<Expr<SomeInteger>>(args[1])}) {
415       return common::visit(
416           [&](const auto &byVal) {
417             using TBY = ResultType<decltype(byVal)>;
418             return FoldElementalIntrinsic<T, T, TBY>(context,
419                 std::move(funcRef),
420                 ScalarFunc<T, T, TBY>(
421                     [&](const Scalar<T> &x, const Scalar<TBY> &y) -> Scalar<T> {
422                       ValueWithRealFlags<Scalar<T>> result{
423                           x.
424 // MSVC chokes on the keyword "template" here in a call to a
425 // member function template.
426 #ifndef _MSC_VER
427                           template
428 #endif
429                           SCALE<Scalar<TBY>>(y)};
430                       if (result.flags.test(RealFlag::Overflow) &&
431                           context.languageFeatures().ShouldWarn(
432                               common::UsageWarning::FoldingException)) {
433                         context.messages().Say(
434                             common::UsageWarning::FoldingException,
435                             "SCALE/IEEE_SCALB intrinsic folding overflow"_warn_en_US);
436                       }
437                       return result.value;
438                     }));
439           },
440           byExpr->u);
441     }
442   } else if (name == "set_exponent") {
443     if (const auto *iExpr{UnwrapExpr<Expr<SomeInteger>>(args[1])}) {
444       return common::visit(
445           [&](const auto &iVal) {
446             using TY = ResultType<decltype(iVal)>;
447             return FoldElementalIntrinsic<T, T, TY>(context, std::move(funcRef),
448                 ScalarFunc<T, T, TY>(
449                     [&](const Scalar<T> &x, const Scalar<TY> &i) -> Scalar<T> {
450                       return x.SET_EXPONENT(i.ToInt64());
451                     }));
452           },
453           iExpr->u);
454     }
455   } else if (name == "sign") {
456     return FoldElementalIntrinsic<T, T, T>(
457         context, std::move(funcRef), &Scalar<T>::SIGN);
458   } else if (name == "spacing") {
459     return FoldElementalIntrinsic<T, T>(context, std::move(funcRef),
460         ScalarFunc<T, T>(
461             [](const Scalar<T> &x) -> Scalar<T> { return x.SPACING(); }));
462   } else if (name == "sqrt") {
463     return FoldElementalIntrinsic<T, T>(context, std::move(funcRef),
464         ScalarFunc<T, T>(
465             [](const Scalar<T> &x) -> Scalar<T> { return x.SQRT().value; }));
466   } else if (name == "sum") {
467     return FoldSum<T>(context, std::move(funcRef));
468   } else if (name == "tiny") {
469     return Expr<T>{Scalar<T>::TINY()};
470   } else if (name == "__builtin_fma") {
471     CHECK(args.size() == 3);
472   } else if (name == "__builtin_ieee_next_after") {
473     if (const auto *yExpr{UnwrapExpr<Expr<SomeReal>>(args[1])}) {
474       return common::visit(
475           [&](const auto &yVal) {
476             using TY = ResultType<decltype(yVal)>;
477             return FoldElementalIntrinsic<T, T, TY>(context, std::move(funcRef),
478                 ScalarFunc<T, T, TY>([&](const Scalar<T> &x,
479                                          const Scalar<TY> &y) -> Scalar<T> {
480                   auto xBig{Scalar<LargestReal>::Convert(x).value};
481                   auto yBig{Scalar<LargestReal>::Convert(y).value};
482                   switch (xBig.Compare(yBig)) {
483                   case Relation::Unordered:
484                     if (context.languageFeatures().ShouldWarn(
485                             common::UsageWarning::FoldingValueChecks)) {
486                       context.messages().Say(
487                           common::UsageWarning::FoldingValueChecks,
488                           "IEEE_NEXT_AFTER intrinsic folding: arguments are unordered"_warn_en_US);
489                     }
490                     return x.NotANumber();
491                   case Relation::Equal:
492                     break;
493                   case Relation::Less:
494                     return x.NEAREST(true).value;
495                   case Relation::Greater:
496                     return x.NEAREST(false).value;
497                   }
498                   return x; // dodge bogus "missing return" GCC warning
499                 }));
500           },
501           yExpr->u);
502     }
503   } else if (name == "__builtin_ieee_next_up" ||
504       name == "__builtin_ieee_next_down") {
505     bool upward{name == "__builtin_ieee_next_up"};
506     const char *iName{upward ? "IEEE_NEXT_UP" : "IEEE_NEXT_DOWN"};
507     return FoldElementalIntrinsic<T, T>(context, std::move(funcRef),
508         ScalarFunc<T, T>([&](const Scalar<T> &x) -> Scalar<T> {
509           auto result{x.NEAREST(upward)};
510           if (context.languageFeatures().ShouldWarn(
511                   common::UsageWarning::FoldingException)) {
512             if (result.flags.test(RealFlag::InvalidArgument)) {
513               context.messages().Say(common::UsageWarning::FoldingException,
514                   "%s intrinsic folding: argument is NaN"_warn_en_US, iName);
515             }
516           }
517           return result.value;
518         }));
519   }
520   return Expr<T>{std::move(funcRef)};
521 }
522 
523 #ifdef _MSC_VER // disable bogus warning about missing definitions
524 #pragma warning(disable : 4661)
525 #endif
526 FOR_EACH_REAL_KIND(template class ExpressionBase, )
527 template class ExpressionBase<SomeReal>;
528 } // namespace Fortran::evaluate
529