1 //===-- runtime/numeric-templates.h -----------------------------*- C++ -*-===// 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 // Generic class and function templates used for implementing 10 // various numeric intrinsics (EXPONENT, FRACTION, etc.). 11 // 12 // This header file also defines generic templates for "basic" 13 // math operations like abs, isnan, etc. The Float128Math 14 // library provides specializations for these templates 15 // for the data type corresponding to CppTypeFor<TypeCategory::Real, 16> 16 // on the target. 17 18 #ifndef FORTRAN_RUNTIME_NUMERIC_TEMPLATES_H_ 19 #define FORTRAN_RUNTIME_NUMERIC_TEMPLATES_H_ 20 21 #include "terminator.h" 22 #include "tools.h" 23 #include "flang/Common/api-attrs.h" 24 #include "flang/Common/erfc-scaled.h" 25 #include "flang/Common/float128.h" 26 #include <cstdint> 27 #include <limits> 28 29 namespace Fortran::runtime { 30 31 // MAX/MIN/LOWEST values for different data types. 32 33 // MaxOrMinIdentity returns MAX or LOWEST value of the given type. 34 template <TypeCategory CAT, int KIND, bool IS_MAXVAL, typename Enable = void> 35 struct MaxOrMinIdentity { 36 using Type = CppTypeFor<CAT, KIND>; 37 static constexpr RT_API_ATTRS Type Value() { 38 return IS_MAXVAL ? std::numeric_limits<Type>::lowest() 39 : std::numeric_limits<Type>::max(); 40 } 41 }; 42 43 // std::numeric_limits<> may not know int128_t 44 template <bool IS_MAXVAL> 45 struct MaxOrMinIdentity<TypeCategory::Integer, 16, IS_MAXVAL> { 46 using Type = CppTypeFor<TypeCategory::Integer, 16>; 47 static constexpr RT_API_ATTRS Type Value() { 48 return IS_MAXVAL ? Type{1} << 127 : ~Type{0} >> 1; 49 } 50 }; 51 52 #if HAS_FLOAT128 53 // std::numeric_limits<> may not support __float128. 54 // 55 // Usage of GCC quadmath.h's FLT128_MAX is complicated by the fact that 56 // even GCC complains about 'Q' literal suffix under -Wpedantic. 57 // We just recreate FLT128_MAX ourselves. 58 // 59 // This specialization must engage only when 60 // CppTypeFor<TypeCategory::Real, 16> is __float128. 61 template <bool IS_MAXVAL> 62 struct MaxOrMinIdentity<TypeCategory::Real, 16, IS_MAXVAL, 63 typename std::enable_if_t< 64 std::is_same_v<CppTypeFor<TypeCategory::Real, 16>, __float128>>> { 65 using Type = __float128; 66 static RT_API_ATTRS Type Value() { 67 // Create a buffer to store binary representation of __float128 constant. 68 constexpr std::size_t alignment = 69 std::max(alignof(Type), alignof(std::uint64_t)); 70 alignas(alignment) char data[sizeof(Type)]; 71 72 // First, verify that our interpretation of __float128 format is correct, 73 // e.g. by checking at least one known constant. 74 *reinterpret_cast<Type *>(data) = Type(1.0); 75 if (*reinterpret_cast<std::uint64_t *>(data) != 0 || 76 *(reinterpret_cast<std::uint64_t *>(data) + 1) != 0x3FFF000000000000) { 77 Terminator terminator{__FILE__, __LINE__}; 78 terminator.Crash("not yet implemented: no full support for __float128"); 79 } 80 81 // Recreate FLT128_MAX. 82 *reinterpret_cast<std::uint64_t *>(data) = 0xFFFFFFFFFFFFFFFF; 83 *(reinterpret_cast<std::uint64_t *>(data) + 1) = 0x7FFEFFFFFFFFFFFF; 84 Type max = *reinterpret_cast<Type *>(data); 85 return IS_MAXVAL ? -max : max; 86 } 87 }; 88 #endif // HAS_FLOAT128 89 90 // Minimum finite representable value. 91 // For floating-point types, returns minimum positive normalized value. 92 template <int PREC, typename T> struct MinValue { 93 static RT_API_ATTRS T get() { return std::numeric_limits<T>::min(); } 94 }; 95 template <typename T> struct MinValue<11, T> { 96 // TINY(0._2) 97 static constexpr RT_API_ATTRS T get() { return 0.00006103515625E-04; } 98 }; 99 100 #if HAS_FLOAT128 101 template <> struct MinValue<113, CppTypeFor<TypeCategory::Real, 16>> { 102 using Type = CppTypeFor<TypeCategory::Real, 16>; 103 static RT_API_ATTRS Type get() { 104 // Create a buffer to store binary representation of __float128 constant. 105 constexpr std::size_t alignment = 106 std::max(alignof(Type), alignof(std::uint64_t)); 107 alignas(alignment) char data[sizeof(Type)]; 108 109 // First, verify that our interpretation of __float128 format is correct, 110 // e.g. by checking at least one known constant. 111 *reinterpret_cast<Type *>(data) = Type(1.0); 112 if (*reinterpret_cast<std::uint64_t *>(data) != 0 || 113 *(reinterpret_cast<std::uint64_t *>(data) + 1) != 0x3FFF000000000000) { 114 Terminator terminator{__FILE__, __LINE__}; 115 terminator.Crash("not yet implemented: no full support for __float128"); 116 } 117 118 // Recreate FLT128_MIN. 119 *reinterpret_cast<std::uint64_t *>(data) = 0; 120 *(reinterpret_cast<std::uint64_t *>(data) + 1) = 0x1000000000000; 121 return *reinterpret_cast<Type *>(data); 122 } 123 }; 124 #endif // HAS_FLOAT128 125 126 template <typename T> struct ABSTy { 127 static constexpr RT_API_ATTRS T compute(T x) { return std::abs(x); } 128 }; 129 130 // Suppress the warnings about calling __host__-only 131 // 'long double' std::frexp, from __device__ code. 132 RT_DIAG_PUSH 133 RT_DIAG_DISABLE_CALL_HOST_FROM_DEVICE_WARN 134 135 template <typename T> struct FREXPTy { 136 static constexpr RT_API_ATTRS T compute(T x, int *e) { 137 return std::frexp(x, e); 138 } 139 }; 140 141 RT_DIAG_POP 142 143 template <typename T> struct ILOGBTy { 144 static constexpr RT_API_ATTRS int compute(T x) { return std::ilogb(x); } 145 }; 146 147 template <typename T> struct ISINFTy { 148 static constexpr RT_API_ATTRS bool compute(T x) { return std::isinf(x); } 149 }; 150 151 template <typename T> struct ISNANTy { 152 static constexpr RT_API_ATTRS bool compute(T x) { return std::isnan(x); } 153 }; 154 155 template <typename T> struct LDEXPTy { 156 template <typename ET> static constexpr RT_API_ATTRS T compute(T x, ET e) { 157 return std::ldexp(x, e); 158 } 159 }; 160 161 template <typename T> struct MAXTy { 162 static constexpr RT_API_ATTRS T compute() { 163 return std::numeric_limits<T>::max(); 164 } 165 }; 166 167 #if HAS_LDBL128 || HAS_FLOAT128 168 template <> struct MAXTy<CppTypeFor<TypeCategory::Real, 16>> { 169 static CppTypeFor<TypeCategory::Real, 16> compute() { 170 return MaxOrMinIdentity<TypeCategory::Real, 16, true>::Value(); 171 } 172 }; 173 #endif 174 175 template <int PREC, typename T> struct MINTy { 176 static constexpr RT_API_ATTRS T compute() { return MinValue<PREC, T>::get(); } 177 }; 178 179 template <typename T> struct QNANTy { 180 static constexpr RT_API_ATTRS T compute() { 181 return std::numeric_limits<T>::quiet_NaN(); 182 } 183 }; 184 185 template <typename T> struct SQRTTy { 186 static constexpr RT_API_ATTRS T compute(T x) { return std::sqrt(x); } 187 }; 188 189 // EXPONENT (16.9.75) 190 template <typename RESULT, typename ARG> 191 inline RT_API_ATTRS RESULT Exponent(ARG x) { 192 if (ISINFTy<ARG>::compute(x) || ISNANTy<ARG>::compute(x)) { 193 return MAXTy<RESULT>::compute(); // +/-Inf, NaN -> HUGE(0) 194 } else if (x == 0) { 195 return 0; // 0 -> 0 196 } else { 197 return ILOGBTy<ARG>::compute(x) + 1; 198 } 199 } 200 201 // FRACTION (16.9.80) 202 template <typename T> inline RT_API_ATTRS T Fraction(T x) { 203 if (ISNANTy<T>::compute(x)) { 204 return x; // NaN -> same NaN 205 } else if (ISINFTy<T>::compute(x)) { 206 return QNANTy<T>::compute(); // +/-Inf -> NaN 207 } else if (x == 0) { 208 return x; // 0 -> same 0 209 } else { 210 int ignoredExp; 211 return FREXPTy<T>::compute(x, &ignoredExp); 212 } 213 } 214 215 // SET_EXPONENT (16.9.171) 216 template <typename T> inline RT_API_ATTRS T SetExponent(T x, std::int64_t p) { 217 if (ISNANTy<T>::compute(x)) { 218 return x; // NaN -> same NaN 219 } else if (ISINFTy<T>::compute(x)) { 220 return QNANTy<T>::compute(); // +/-Inf -> NaN 221 } else if (x == 0) { 222 return x; // return negative zero if x is negative zero 223 } else { 224 int expo{ILOGBTy<T>::compute(x) + 1}; 225 auto ip{static_cast<int>(p - expo)}; 226 if (ip != p - expo) { 227 ip = p < 0 ? std::numeric_limits<int>::min() 228 : std::numeric_limits<int>::max(); 229 } 230 return LDEXPTy<T>::compute(x, ip); // x*2**(p-e) 231 } 232 } 233 234 // MOD & MODULO (16.9.135, .136) 235 template <bool IS_MODULO, typename T> 236 inline RT_API_ATTRS T RealMod( 237 T a, T p, const char *sourceFile, int sourceLine) { 238 if (p == 0) { 239 Terminator{sourceFile, sourceLine}.Crash( 240 IS_MODULO ? "MODULO with P==0" : "MOD with P==0"); 241 } 242 if (ISNANTy<T>::compute(a) || ISNANTy<T>::compute(p) || 243 ISINFTy<T>::compute(a)) { 244 return QNANTy<T>::compute(); 245 } else if (IS_MODULO && ISINFTy<T>::compute(p)) { 246 // Other compilers behave consistently for MOD(x, +/-INF) 247 // and always return x. This is probably related to 248 // implementation of std::fmod(). Stick to this behavior 249 // for MOD, but return NaN for MODULO(x, +/-INF). 250 return QNANTy<T>::compute(); 251 } 252 T aAbs{ABSTy<T>::compute(a)}; 253 T pAbs{ABSTy<T>::compute(p)}; 254 if (aAbs <= static_cast<T>(std::numeric_limits<std::int64_t>::max()) && 255 pAbs <= static_cast<T>(std::numeric_limits<std::int64_t>::max())) { 256 if (auto aInt{static_cast<std::int64_t>(a)}; a == aInt) { 257 if (auto pInt{static_cast<std::int64_t>(p)}; p == pInt) { 258 // Fast exact case for integer operands 259 auto mod{aInt - (aInt / pInt) * pInt}; 260 if constexpr (IS_MODULO) { 261 if (mod == 0) { 262 // Return properly signed zero. 263 return pInt > 0 ? T{0} : -T{0}; 264 } 265 if ((aInt > 0) != (pInt > 0)) { 266 mod += pInt; 267 } 268 } else { 269 if (mod == 0) { 270 // Return properly signed zero. 271 return aInt > 0 ? T{0} : -T{0}; 272 } 273 } 274 return static_cast<T>(mod); 275 } 276 } 277 } 278 if constexpr (std::is_same_v<T, float> || std::is_same_v<T, double> || 279 std::is_same_v<T, long double>) { 280 // std::fmod() semantics on signed operands seems to match 281 // the requirements of MOD(). MODULO() needs adjustment. 282 T result{std::fmod(a, p)}; 283 if constexpr (IS_MODULO) { 284 if ((a < 0) != (p < 0)) { 285 if (result == 0.) { 286 result = -result; 287 } else { 288 result += p; 289 } 290 } 291 } 292 return result; 293 } else { 294 // The standard defines MOD(a,p)=a-AINT(a/p)*p and 295 // MODULO(a,p)=a-FLOOR(a/p)*p, but those definitions lose 296 // precision badly due to cancellation when ABS(a) is 297 // much larger than ABS(p). 298 // Insights: 299 // - MOD(a,p)=MOD(a-n*p,p) when a>0, p>0, integer n>0, and a>=n*p 300 // - when n is a power of two, n*p is exact 301 // - as a>=n*p, a-n*p does not round. 302 // So repeatedly reduce a by all n*p in decreasing order of n; 303 // what's left is the desired remainder. This is basically 304 // the same algorithm as arbitrary precision binary long division, 305 // discarding the quotient. 306 T tmp{aAbs}; 307 for (T adj{SetExponent(pAbs, Exponent<int>(aAbs))}; tmp >= pAbs; adj /= 2) { 308 if (tmp >= adj) { 309 tmp -= adj; 310 if (tmp == 0) { 311 break; 312 } 313 } 314 } 315 if (a < 0) { 316 tmp = -tmp; 317 } 318 if constexpr (IS_MODULO) { 319 if ((a < 0) != (p < 0)) { 320 if (tmp == 0.) { 321 tmp = -tmp; 322 } else { 323 tmp += p; 324 } 325 } 326 } 327 return tmp; 328 } 329 } 330 331 // RRSPACING (16.9.164) 332 template <int PREC, typename T> inline RT_API_ATTRS T RRSpacing(T x) { 333 if (ISNANTy<T>::compute(x)) { 334 return x; // NaN -> same NaN 335 } else if (ISINFTy<T>::compute(x)) { 336 return QNANTy<T>::compute(); // +/-Inf -> NaN 337 } else if (x == 0) { 338 return 0; // 0 -> 0 339 } else { 340 return LDEXPTy<T>::compute( 341 ABSTy<T>::compute(x), PREC - (ILOGBTy<T>::compute(x) + 1)); 342 } 343 } 344 345 // SPACING (16.9.180) 346 template <int PREC, typename T> inline RT_API_ATTRS T Spacing(T x) { 347 T tiny{MINTy<PREC, T>::compute()}; 348 if (ISNANTy<T>::compute(x)) { 349 return x; // NaN -> same NaN 350 } else if (ISINFTy<T>::compute(x)) { 351 return QNANTy<T>::compute(); // +/-Inf -> NaN 352 } else if (x == 0) { // 0 -> TINY(x) 353 return tiny; 354 } else { 355 T result{LDEXPTy<T>::compute( 356 static_cast<T>(1.0), ILOGBTy<T>::compute(x) + 1 - PREC)}; // 2**(e-p) 357 // All compilers return TINY(x) for |x| <= TINY(x), but differ over whether 358 // SPACING(x) can be < TINY(x) for |x| > TINY(x). The most common precedent 359 // is to never return a value < TINY(x). 360 return result <= tiny ? tiny : result; 361 } 362 } 363 364 // ERFC_SCALED (16.9.71) 365 template <typename T> inline RT_API_ATTRS T ErfcScaled(T arg) { 366 return common::ErfcScaled(arg); 367 } 368 369 } // namespace Fortran::runtime 370 371 #endif // FORTRAN_RUNTIME_NUMERIC_TEMPLATES_H_ 372