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