xref: /llvm-project/flang/lib/Evaluate/intrinsics-library.cpp (revision 71d4f343f52756ca086d02151662e68633a0db52)
1 //===-- lib/Evaluate/intrinsics-library.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 // This file defines host runtime functions that can be used for folding
10 // intrinsic functions.
11 // The default host runtime folders are built with <cmath> and
12 // <complex> functions that are guaranteed to exist from the C++ standard.
13 
14 #include "flang/Evaluate/intrinsics-library.h"
15 #include "fold-implementation.h"
16 #include "host.h"
17 #include "flang/Common/erfc-scaled.h"
18 #include "flang/Common/idioms.h"
19 #include "flang/Common/static-multimap-view.h"
20 #include "flang/Evaluate/expression.h"
21 #include <cfloat>
22 #include <cmath>
23 #include <complex>
24 #include <functional>
25 #if HAS_QUADMATHLIB
26 #include "quadmath.h"
27 #endif
28 #include "flang/Common/float128.h"
29 #include "flang/Common/float80.h"
30 #include <type_traits>
31 
32 namespace Fortran::evaluate {
33 
34 // Define a vector like class that can hold an arbitrary number of
35 // Dynamic type and be built at compile time. This is like a
36 // std::vector<DynamicType>, but constexpr only.
37 template <typename... FortranType> struct TypeVectorStorage {
38   static constexpr DynamicType values[]{FortranType{}.GetType()...};
39   static constexpr const DynamicType *start{&values[0]};
40   static constexpr const DynamicType *end{start + sizeof...(FortranType)};
41 };
42 template <> struct TypeVectorStorage<> {
43   static constexpr const DynamicType *start{nullptr}, *end{nullptr};
44 };
45 struct TypeVector {
46   template <typename... FortranType> static constexpr TypeVector Create() {
47     using storage = TypeVectorStorage<FortranType...>;
48     return TypeVector{storage::start, storage::end, sizeof...(FortranType)};
49   }
50   constexpr size_t size() const { return size_; };
51   using const_iterator = const DynamicType *;
52   constexpr const_iterator begin() const { return startPtr; }
53   constexpr const_iterator end() const { return endPtr; }
54   const DynamicType &operator[](size_t i) const { return *(startPtr + i); }
55 
56   const DynamicType *startPtr{nullptr};
57   const DynamicType *endPtr{nullptr};
58   const size_t size_;
59 };
60 inline bool operator==(
61     const TypeVector &lhs, const std::vector<DynamicType> &rhs) {
62   if (lhs.size() != rhs.size()) {
63     return false;
64   }
65   for (size_t i{0}; i < lhs.size(); ++i) {
66     if (lhs[i] != rhs[i]) {
67       return false;
68     }
69   }
70   return true;
71 }
72 
73 // HostRuntimeFunction holds a pointer to a Folder function that can fold
74 // a Fortran scalar intrinsic using host runtime functions (e.g libm).
75 // The folder take care of all conversions between Fortran types and the related
76 // host types as well as setting and cleaning-up the floating point environment.
77 // HostRuntimeFunction are intended to be built at compile time (members are all
78 // constexpr constructible) so that they can be stored in a compile time static
79 // map.
80 struct HostRuntimeFunction {
81   using Folder = Expr<SomeType> (*)(
82       FoldingContext &, std::vector<Expr<SomeType>> &&);
83   using Key = std::string_view;
84   // Needed for implicit compare with keys.
85   constexpr operator Key() const { return key; }
86   // Name of the related Fortran intrinsic.
87   Key key;
88   // DynamicType of the Expr<SomeType> returns by folder.
89   DynamicType resultType;
90   // DynamicTypes expected for the Expr<SomeType> arguments of the folder.
91   // The folder will crash if provided arguments of different types.
92   TypeVector argumentTypes;
93   // Folder to be called to fold the intrinsic with host runtime. The provided
94   // Expr<SomeType> arguments must wrap scalar constants of the type described
95   // in argumentTypes, otherwise folder will crash. Any floating point issue
96   // raised while executing the host runtime will be reported in FoldingContext
97   // messages.
98   Folder folder;
99 };
100 
101 // Translate a host function type signature (template arguments) into a
102 // constexpr data representation based on Fortran DynamicType that can be
103 // stored.
104 template <typename TR, typename... TA> using FuncPointer = TR (*)(TA...);
105 template <typename T> struct FuncTypeAnalyzer {};
106 template <typename HostTR, typename... HostTA>
107 struct FuncTypeAnalyzer<FuncPointer<HostTR, HostTA...>> {
108   static constexpr DynamicType result{host::FortranType<HostTR>{}.GetType()};
109   static constexpr TypeVector arguments{
110       TypeVector::Create<host::FortranType<HostTA>...>()};
111 };
112 
113 // Define helpers to deal with host floating environment.
114 template <typename TR>
115 static void CheckFloatingPointIssues(
116     host::HostFloatingPointEnvironment &hostFPE, const Scalar<TR> &x) {
117   if constexpr (TR::category == TypeCategory::Complex ||
118       TR::category == TypeCategory::Real) {
119     if (x.IsNotANumber()) {
120       hostFPE.SetFlag(RealFlag::InvalidArgument);
121     } else if (x.IsInfinite()) {
122       hostFPE.SetFlag(RealFlag::Overflow);
123     }
124   }
125 }
126 // Software Subnormal Flushing helper.
127 // Only flush floating-points. Forward other scalars untouched.
128 // Software flushing is only performed if hardware flushing is not available
129 // because it may not result in the same behavior as hardware flushing.
130 // Some runtime implementations are "working around" subnormal flushing to
131 // return results that they deem better than returning the result they would
132 // with a null argument. An example is logf that should return -inf if arguments
133 // are flushed to zero, but some implementations return -1.03972076416015625e2_4
134 // for all subnormal values instead. It is impossible to reproduce this with the
135 // simple software flushing below.
136 template <typename T>
137 static constexpr inline const Scalar<T> FlushSubnormals(Scalar<T> &&x) {
138   if constexpr (T::category == TypeCategory::Real ||
139       T::category == TypeCategory::Complex) {
140     return x.FlushSubnormalToZero();
141   }
142   return x;
143 }
144 
145 // This is the kernel called by all HostRuntimeFunction folders, it convert the
146 // Fortran Expr<SomeType> to the host runtime function argument types, calls
147 // the runtime function, and wrap back the result into an Expr<SomeType>.
148 // It deals with host floating point environment set-up and clean-up.
149 template <typename FuncType, typename TR, typename... TA, size_t... I>
150 static Expr<SomeType> ApplyHostFunctionHelper(FuncType func,
151     FoldingContext &context, std::vector<Expr<SomeType>> &&args,
152     std::index_sequence<I...>) {
153   host::HostFloatingPointEnvironment hostFPE;
154   hostFPE.SetUpHostFloatingPointEnvironment(context);
155   host::HostType<TR> hostResult{};
156   Scalar<TR> result{};
157   std::tuple<Scalar<TA>...> scalarArgs{
158       GetScalarConstantValue<TA>(args[I]).value()...};
159   if (context.targetCharacteristics().areSubnormalsFlushedToZero() &&
160       !hostFPE.hasSubnormalFlushingHardwareControl()) {
161     hostResult = func(host::CastFortranToHost<TA>(
162         FlushSubnormals<TA>(std::move(std::get<I>(scalarArgs))))...);
163     result = FlushSubnormals<TR>(host::CastHostToFortran<TR>(hostResult));
164   } else {
165     hostResult = func(host::CastFortranToHost<TA>(std::get<I>(scalarArgs))...);
166     result = host::CastHostToFortran<TR>(hostResult);
167   }
168   if (!hostFPE.hardwareFlagsAreReliable()) {
169     CheckFloatingPointIssues<TR>(hostFPE, result);
170   }
171   hostFPE.CheckAndRestoreFloatingPointEnvironment(context);
172   return AsGenericExpr(Constant<TR>(std::move(result)));
173 }
174 template <typename HostTR, typename... HostTA>
175 Expr<SomeType> ApplyHostFunction(FuncPointer<HostTR, HostTA...> func,
176     FoldingContext &context, std::vector<Expr<SomeType>> &&args) {
177   return ApplyHostFunctionHelper<decltype(func), host::FortranType<HostTR>,
178       host::FortranType<HostTA>...>(
179       func, context, std::move(args), std::index_sequence_for<HostTA...>{});
180 }
181 
182 // FolderFactory builds a HostRuntimeFunction for the host runtime function
183 // passed as a template argument.
184 // Its static member function "fold" is the resulting folder. It captures the
185 // host runtime function pointer and pass it to the host runtime function folder
186 // kernel.
187 template <typename HostFuncType, HostFuncType func> class FolderFactory {
188 public:
189   static constexpr HostRuntimeFunction Create(const std::string_view &name) {
190     return HostRuntimeFunction{name, FuncTypeAnalyzer<HostFuncType>::result,
191         FuncTypeAnalyzer<HostFuncType>::arguments, &Fold};
192   }
193 
194 private:
195   static Expr<SomeType> Fold(
196       FoldingContext &context, std::vector<Expr<SomeType>> &&args) {
197     return ApplyHostFunction(func, context, std::move(args));
198   }
199 };
200 
201 // Define host runtime libraries that can be used for folding and
202 // fill their description if they are available.
203 enum class LibraryVersion {
204   Libm,
205   LibmExtensions,
206   PgmathFast,
207   PgmathRelaxed,
208   PgmathPrecise
209 };
210 template <typename HostT, LibraryVersion> struct HostRuntimeLibrary {
211   // When specialized, this class holds a static constexpr table containing
212   // all the HostRuntimeLibrary for functions of library LibraryVersion
213   // that returns a value of type HostT.
214 };
215 
216 using HostRuntimeMap = common::StaticMultimapView<HostRuntimeFunction>;
217 
218 // Map numerical intrinsic to  <cmath>/<complex> functions
219 // (Note: ABS() is folded in fold-real.cpp.)
220 template <typename HostT>
221 struct HostRuntimeLibrary<HostT, LibraryVersion::Libm> {
222   using F = FuncPointer<HostT, HostT>;
223   using F2 = FuncPointer<HostT, HostT, HostT>;
224   static constexpr HostRuntimeFunction table[]{
225       FolderFactory<F, F{std::acos}>::Create("acos"),
226       FolderFactory<F, F{std::acosh}>::Create("acosh"),
227       FolderFactory<F, F{std::asin}>::Create("asin"),
228       FolderFactory<F, F{std::asinh}>::Create("asinh"),
229       FolderFactory<F, F{std::atan}>::Create("atan"),
230       FolderFactory<F2, F2{std::atan2}>::Create("atan2"),
231       FolderFactory<F, F{std::atanh}>::Create("atanh"),
232       FolderFactory<F, F{std::cos}>::Create("cos"),
233       FolderFactory<F, F{std::cosh}>::Create("cosh"),
234       FolderFactory<F, F{std::erf}>::Create("erf"),
235       FolderFactory<F, F{std::erfc}>::Create("erfc"),
236       FolderFactory<F, F{common::ErfcScaled}>::Create("erfc_scaled"),
237       FolderFactory<F, F{std::exp}>::Create("exp"),
238       FolderFactory<F, F{std::tgamma}>::Create("gamma"),
239       FolderFactory<F, F{std::log}>::Create("log"),
240       FolderFactory<F, F{std::log10}>::Create("log10"),
241       FolderFactory<F, F{std::lgamma}>::Create("log_gamma"),
242       FolderFactory<F2, F2{std::pow}>::Create("pow"),
243       FolderFactory<F, F{std::sin}>::Create("sin"),
244       FolderFactory<F, F{std::sinh}>::Create("sinh"),
245       FolderFactory<F, F{std::tan}>::Create("tan"),
246       FolderFactory<F, F{std::tanh}>::Create("tanh"),
247   };
248   // Note: cmath does not have modulo and erfc_scaled equivalent
249 
250   // Note regarding  lack of bessel function support:
251   // C++17 defined standard Bessel math functions std::cyl_bessel_j
252   // and std::cyl_neumann that can be used for Fortran j and y
253   // bessel functions. However, they are not yet implemented in
254   // clang libc++ (ok in GNU libstdc++). C maths functions j0...
255   // are not C standard but a GNU extension so they are not used
256   // to avoid introducing incompatibilities.
257   // Use libpgmath to get bessel function folding support.
258   // TODO:  Add Bessel functions when possible.
259   static constexpr HostRuntimeMap map{table};
260   static_assert(map.Verify(), "map must be sorted");
261 };
262 
263 // Helpers to map complex std::pow whose resolution in F2{std::pow} is
264 // ambiguous as of clang++ 20.
265 template <typename HostT>
266 static std::complex<HostT> StdPowF2(
267     const std::complex<HostT> &x, const std::complex<HostT> &y) {
268   return std::pow(x, y);
269 }
270 template <typename HostT>
271 static std::complex<HostT> StdPowF2A(
272     const HostT &x, const std::complex<HostT> &y) {
273   return std::pow(x, y);
274 }
275 template <typename HostT>
276 static std::complex<HostT> StdPowF2B(
277     const std::complex<HostT> &x, const HostT &y) {
278   return std::pow(x, y);
279 }
280 
281 #ifdef _AIX
282 #ifdef __clang_major__
283 #pragma clang diagnostic ignored "-Wc99-extensions"
284 #endif
285 
286 extern "C" {
287 float _Complex cacosf(float _Complex);
288 double _Complex cacos(double _Complex);
289 float _Complex csqrtf(float _Complex);
290 double _Complex csqrt(double _Complex);
291 }
292 
293 enum CRI { Real, Imag };
294 template <typename TR, typename TA> static TR &reIm(TA &x, CRI n) {
295   return reinterpret_cast<TR(&)[2]>(x)[n];
296 }
297 template <typename TR, typename T> static TR CppToC(const std::complex<T> &x) {
298   TR r;
299   reIm<T, TR>(r, CRI::Real) = x.real();
300   reIm<T, TR>(r, CRI::Imag) = x.imag();
301   return r;
302 }
303 template <typename T, typename TA> static std::complex<T> CToCpp(const TA &x) {
304   TA &z{const_cast<TA &>(x)};
305   return std::complex<T>(reIm<T, TA>(z, CRI::Real), reIm<T, TA>(z, CRI::Imag));
306 }
307 #endif
308 
309 template <typename HostT>
310 static std::complex<HostT> CSqrt(const std::complex<HostT> &x) {
311   std::complex<HostT> res;
312 #ifdef _AIX
313   // On AIX, the implementation of csqrt[f] and std::sqrt is different,
314   // use csqrt[f] in folding.
315   if constexpr (std::is_same_v<HostT, float>) {
316     float _Complex r{csqrtf(CppToC<float _Complex, float>(x))};
317     res = CToCpp<float, float _Complex>(r);
318   } else if constexpr (std::is_same_v<HostT, double>) {
319     double _Complex r{csqrt(CppToC<double _Complex, double>(x))};
320     res = CToCpp<double, double _Complex>(r);
321   } else {
322     DIE("bad complex component type");
323   }
324 #else
325   res = std::sqrt(x);
326 #endif
327   return res;
328 }
329 
330 template <typename HostT>
331 static std::complex<HostT> CAcos(const std::complex<HostT> &x) {
332   std::complex<HostT> res;
333 #ifdef _AIX
334   // On AIX, the implementation of cacos[f] and std::acos is different,
335   // use cacos[f] in folding.
336   if constexpr (std::is_same_v<HostT, float>) {
337     float _Complex r{cacosf(CppToC<float _Complex, float>(x))};
338     res = CToCpp<float, float _Complex>(r);
339   } else if constexpr (std::is_same_v<HostT, double>) {
340     double _Complex r{cacos(CppToC<double _Complex, double>(x))};
341     res = CToCpp<double, double _Complex>(r);
342   } else {
343     DIE("bad complex component type");
344   }
345 #else
346   res = std::acos(x);
347 #endif
348   return res;
349 }
350 
351 template <typename HostT>
352 struct HostRuntimeLibrary<std::complex<HostT>, LibraryVersion::Libm> {
353   using F = FuncPointer<std::complex<HostT>, const std::complex<HostT> &>;
354   using F2 = FuncPointer<std::complex<HostT>, const std::complex<HostT> &,
355       const std::complex<HostT> &>;
356   using F2A = FuncPointer<std::complex<HostT>, const HostT &,
357       const std::complex<HostT> &>;
358   using F2B = FuncPointer<std::complex<HostT>, const std::complex<HostT> &,
359       const HostT &>;
360   static constexpr HostRuntimeFunction table[]{
361       FolderFactory<F, F{CAcos}>::Create("acos"),
362       FolderFactory<F, F{std::acosh}>::Create("acosh"),
363       FolderFactory<F, F{std::asin}>::Create("asin"),
364       FolderFactory<F, F{std::asinh}>::Create("asinh"),
365       FolderFactory<F, F{std::atan}>::Create("atan"),
366       FolderFactory<F, F{std::atanh}>::Create("atanh"),
367       FolderFactory<F, F{std::cos}>::Create("cos"),
368       FolderFactory<F, F{std::cosh}>::Create("cosh"),
369       FolderFactory<F, F{std::exp}>::Create("exp"),
370       FolderFactory<F, F{std::log}>::Create("log"),
371       FolderFactory<F2, F2{StdPowF2}>::Create("pow"),
372       FolderFactory<F2A, F2A{StdPowF2A}>::Create("pow"),
373       FolderFactory<F2B, F2B{StdPowF2B}>::Create("pow"),
374       FolderFactory<F, F{std::sin}>::Create("sin"),
375       FolderFactory<F, F{std::sinh}>::Create("sinh"),
376       FolderFactory<F, F{CSqrt}>::Create("sqrt"),
377       FolderFactory<F, F{std::tan}>::Create("tan"),
378       FolderFactory<F, F{std::tanh}>::Create("tanh"),
379   };
380   static constexpr HostRuntimeMap map{table};
381   static_assert(map.Verify(), "map must be sorted");
382 };
383 // Note regarding cmath:
384 //  - cmath does not have modulo and erfc_scaled equivalent
385 //  - C++17 defined standard Bessel math functions std::cyl_bessel_j
386 //    and std::cyl_neumann that can be used for Fortran j and y
387 //    bessel functions. However, they are not yet implemented in
388 //    clang libc++ (ok in GNU libstdc++). Instead, the Posix libm
389 //    extensions are used when available below.
390 
391 #if _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600
392 /// Define libm extensions
393 /// Bessel functions are defined in POSIX.1-2001.
394 
395 // Remove float bessel functions for AIX and Darwin as they are not supported
396 #if !defined(_AIX) && !defined(__APPLE__)
397 template <> struct HostRuntimeLibrary<float, LibraryVersion::LibmExtensions> {
398   using F = FuncPointer<float, float>;
399   using FN = FuncPointer<float, int, float>;
400   static constexpr HostRuntimeFunction table[]{
401       FolderFactory<F, F{::j0f}>::Create("bessel_j0"),
402       FolderFactory<F, F{::j1f}>::Create("bessel_j1"),
403       FolderFactory<FN, FN{::jnf}>::Create("bessel_jn"),
404       FolderFactory<F, F{::y0f}>::Create("bessel_y0"),
405       FolderFactory<F, F{::y1f}>::Create("bessel_y1"),
406       FolderFactory<FN, FN{::ynf}>::Create("bessel_yn"),
407   };
408   static constexpr HostRuntimeMap map{table};
409   static_assert(map.Verify(), "map must be sorted");
410 };
411 #endif
412 
413 #if HAS_QUADMATHLIB
414 template <> struct HostRuntimeLibrary<__float128, LibraryVersion::Libm> {
415   using F = FuncPointer<__float128, __float128>;
416   using F2 = FuncPointer<__float128, __float128, __float128>;
417   using FN = FuncPointer<__float128, int, __float128>;
418   static constexpr HostRuntimeFunction table[]{
419       FolderFactory<F, F{::acosq}>::Create("acos"),
420       FolderFactory<F, F{::acoshq}>::Create("acosh"),
421       FolderFactory<F, F{::asinq}>::Create("asin"),
422       FolderFactory<F, F{::asinhq}>::Create("asinh"),
423       FolderFactory<F, F{::atanq}>::Create("atan"),
424       FolderFactory<F2, F2{::atan2q}>::Create("atan2"),
425       FolderFactory<F, F{::atanhq}>::Create("atanh"),
426       FolderFactory<F, F{::j0q}>::Create("bessel_j0"),
427       FolderFactory<F, F{::j1q}>::Create("bessel_j1"),
428       FolderFactory<FN, FN{::jnq}>::Create("bessel_jn"),
429       FolderFactory<F, F{::y0q}>::Create("bessel_y0"),
430       FolderFactory<F, F{::y1q}>::Create("bessel_y1"),
431       FolderFactory<FN, FN{::ynq}>::Create("bessel_yn"),
432       FolderFactory<F, F{::cosq}>::Create("cos"),
433       FolderFactory<F, F{::coshq}>::Create("cosh"),
434       FolderFactory<F, F{::erfq}>::Create("erf"),
435       FolderFactory<F, F{::erfcq}>::Create("erfc"),
436       FolderFactory<F, F{::expq}>::Create("exp"),
437       FolderFactory<F, F{::tgammaq}>::Create("gamma"),
438       FolderFactory<F, F{::logq}>::Create("log"),
439       FolderFactory<F, F{::log10q}>::Create("log10"),
440       FolderFactory<F, F{::lgammaq}>::Create("log_gamma"),
441       FolderFactory<F2, F2{::powq}>::Create("pow"),
442       FolderFactory<F, F{::sinq}>::Create("sin"),
443       FolderFactory<F, F{::sinhq}>::Create("sinh"),
444       FolderFactory<F, F{::tanq}>::Create("tan"),
445       FolderFactory<F, F{::tanhq}>::Create("tanh"),
446   };
447   static constexpr HostRuntimeMap map{table};
448   static_assert(map.Verify(), "map must be sorted");
449 };
450 template <> struct HostRuntimeLibrary<__complex128, LibraryVersion::Libm> {
451   using F = FuncPointer<__complex128, __complex128>;
452   using F2 = FuncPointer<__complex128, __complex128, __complex128>;
453   static constexpr HostRuntimeFunction table[]{
454       FolderFactory<F, F{::cacosq}>::Create("acos"),
455       FolderFactory<F, F{::cacoshq}>::Create("acosh"),
456       FolderFactory<F, F{::casinq}>::Create("asin"),
457       FolderFactory<F, F{::casinhq}>::Create("asinh"),
458       FolderFactory<F, F{::catanq}>::Create("atan"),
459       FolderFactory<F, F{::catanhq}>::Create("atanh"),
460       FolderFactory<F, F{::ccosq}>::Create("cos"),
461       FolderFactory<F, F{::ccoshq}>::Create("cosh"),
462       FolderFactory<F, F{::cexpq}>::Create("exp"),
463       FolderFactory<F, F{::clogq}>::Create("log"),
464       FolderFactory<F2, F2{::cpowq}>::Create("pow"),
465       FolderFactory<F, F{::csinq}>::Create("sin"),
466       FolderFactory<F, F{::csinhq}>::Create("sinh"),
467       FolderFactory<F, F{::csqrtq}>::Create("sqrt"),
468       FolderFactory<F, F{::ctanq}>::Create("tan"),
469       FolderFactory<F, F{::ctanhq}>::Create("tanh"),
470   };
471   static constexpr HostRuntimeMap map{table};
472   static_assert(map.Verify(), "map must be sorted");
473 };
474 #endif
475 
476 template <> struct HostRuntimeLibrary<double, LibraryVersion::LibmExtensions> {
477   using F = FuncPointer<double, double>;
478   using FN = FuncPointer<double, int, double>;
479   static constexpr HostRuntimeFunction table[]{
480       FolderFactory<F, F{::j0}>::Create("bessel_j0"),
481       FolderFactory<F, F{::j1}>::Create("bessel_j1"),
482       FolderFactory<FN, FN{::jn}>::Create("bessel_jn"),
483       FolderFactory<F, F{::y0}>::Create("bessel_y0"),
484       FolderFactory<F, F{::y1}>::Create("bessel_y1"),
485       FolderFactory<FN, FN{::yn}>::Create("bessel_yn"),
486   };
487   static constexpr HostRuntimeMap map{table};
488   static_assert(map.Verify(), "map must be sorted");
489 };
490 
491 #if defined(__GLIBC__) && (HAS_FLOAT80 || HAS_LDBL128)
492 template <>
493 struct HostRuntimeLibrary<long double, LibraryVersion::LibmExtensions> {
494   using F = FuncPointer<long double, long double>;
495   using FN = FuncPointer<long double, int, long double>;
496   static constexpr HostRuntimeFunction table[]{
497       FolderFactory<F, F{::j0l}>::Create("bessel_j0"),
498       FolderFactory<F, F{::j1l}>::Create("bessel_j1"),
499       FolderFactory<FN, FN{::jnl}>::Create("bessel_jn"),
500       FolderFactory<F, F{::y0l}>::Create("bessel_y0"),
501       FolderFactory<F, F{::y1l}>::Create("bessel_y1"),
502       FolderFactory<FN, FN{::ynl}>::Create("bessel_yn"),
503   };
504   static constexpr HostRuntimeMap map{table};
505   static_assert(map.Verify(), "map must be sorted");
506 };
507 #endif // HAS_FLOAT80 || HAS_LDBL128
508 #endif //_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600
509 
510 /// Define pgmath description
511 #if LINK_WITH_LIBPGMATH
512 // Only use libpgmath for folding if it is available.
513 // First declare all libpgmaths functions
514 #define PGMATH_LINKING
515 #define PGMATH_DECLARE
516 #include "flang/Evaluate/pgmath.h.inc"
517 
518 #define REAL_FOLDER(name, func) \
519   FolderFactory<decltype(&func), &func>::Create(#name)
520 template <> struct HostRuntimeLibrary<float, LibraryVersion::PgmathFast> {
521   static constexpr HostRuntimeFunction table[]{
522 #define PGMATH_FAST
523 #define PGMATH_USE_S(name, func) REAL_FOLDER(name, func),
524 #include "flang/Evaluate/pgmath.h.inc"
525   };
526   static constexpr HostRuntimeMap map{table};
527   static_assert(map.Verify(), "map must be sorted");
528 };
529 template <> struct HostRuntimeLibrary<double, LibraryVersion::PgmathFast> {
530   static constexpr HostRuntimeFunction table[]{
531 #define PGMATH_FAST
532 #define PGMATH_USE_D(name, func) REAL_FOLDER(name, func),
533 #include "flang/Evaluate/pgmath.h.inc"
534   };
535   static constexpr HostRuntimeMap map{table};
536   static_assert(map.Verify(), "map must be sorted");
537 };
538 template <> struct HostRuntimeLibrary<float, LibraryVersion::PgmathRelaxed> {
539   static constexpr HostRuntimeFunction table[]{
540 #define PGMATH_RELAXED
541 #define PGMATH_USE_S(name, func) REAL_FOLDER(name, func),
542 #include "flang/Evaluate/pgmath.h.inc"
543   };
544   static constexpr HostRuntimeMap map{table};
545   static_assert(map.Verify(), "map must be sorted");
546 };
547 template <> struct HostRuntimeLibrary<double, LibraryVersion::PgmathRelaxed> {
548   static constexpr HostRuntimeFunction table[]{
549 #define PGMATH_RELAXED
550 #define PGMATH_USE_D(name, func) REAL_FOLDER(name, func),
551 #include "flang/Evaluate/pgmath.h.inc"
552   };
553   static constexpr HostRuntimeMap map{table};
554   static_assert(map.Verify(), "map must be sorted");
555 };
556 template <> struct HostRuntimeLibrary<float, LibraryVersion::PgmathPrecise> {
557   static constexpr HostRuntimeFunction table[]{
558 #define PGMATH_PRECISE
559 #define PGMATH_USE_S(name, func) REAL_FOLDER(name, func),
560 #include "flang/Evaluate/pgmath.h.inc"
561   };
562   static constexpr HostRuntimeMap map{table};
563   static_assert(map.Verify(), "map must be sorted");
564 };
565 template <> struct HostRuntimeLibrary<double, LibraryVersion::PgmathPrecise> {
566   static constexpr HostRuntimeFunction table[]{
567 #define PGMATH_PRECISE
568 #define PGMATH_USE_D(name, func) REAL_FOLDER(name, func),
569 #include "flang/Evaluate/pgmath.h.inc"
570   };
571   static constexpr HostRuntimeMap map{table};
572   static_assert(map.Verify(), "map must be sorted");
573 };
574 
575 // TODO: double _Complex/float _Complex have been removed from llvm flang
576 // pgmath.h.inc because they caused warnings, they need to be added back
577 // so that the complex pgmath versions can be used when requested.
578 
579 #endif /* LINK_WITH_LIBPGMATH */
580 
581 // Helper to check if a HostRuntimeLibrary specialization exists
582 template <typename T, typename = void> struct IsAvailable : std::false_type {};
583 template <typename T>
584 struct IsAvailable<T, decltype((void)T::table, void())> : std::true_type {};
585 // Define helpers to find host runtime library map according to desired version
586 // and type.
587 template <typename HostT, LibraryVersion version>
588 static const HostRuntimeMap *GetHostRuntimeMapHelper(
589     [[maybe_unused]] DynamicType resultType) {
590   // A library must only be instantiated if LibraryVersion is
591   // available on the host and if HostT maps to a Fortran type.
592   // For instance, whenever long double and double are both 64-bits, double
593   // is mapped to Fortran 64bits real type, and long double will be left
594   // unmapped.
595   if constexpr (host::FortranTypeExists<HostT>()) {
596     using Lib = HostRuntimeLibrary<HostT, version>;
597     if constexpr (IsAvailable<Lib>::value) {
598       if (host::FortranType<HostT>{}.GetType() == resultType) {
599         return &Lib::map;
600       }
601     }
602   }
603   return nullptr;
604 }
605 template <LibraryVersion version>
606 static const HostRuntimeMap *GetHostRuntimeMapVersion(DynamicType resultType) {
607   if (resultType.category() == TypeCategory::Real) {
608     if (const auto *map{GetHostRuntimeMapHelper<float, version>(resultType)}) {
609       return map;
610     }
611     if (const auto *map{GetHostRuntimeMapHelper<double, version>(resultType)}) {
612       return map;
613     }
614     if (const auto *map{
615             GetHostRuntimeMapHelper<long double, version>(resultType)}) {
616       return map;
617     }
618 #if HAS_QUADMATHLIB
619     if (const auto *map{
620             GetHostRuntimeMapHelper<__float128, version>(resultType)}) {
621       return map;
622     }
623 #endif
624   }
625   if (resultType.category() == TypeCategory::Complex) {
626     if (const auto *map{GetHostRuntimeMapHelper<std::complex<float>, version>(
627             resultType)}) {
628       return map;
629     }
630     if (const auto *map{GetHostRuntimeMapHelper<std::complex<double>, version>(
631             resultType)}) {
632       return map;
633     }
634     if (const auto *map{
635             GetHostRuntimeMapHelper<std::complex<long double>, version>(
636                 resultType)}) {
637       return map;
638     }
639 #if HAS_QUADMATHLIB
640     if (const auto *map{
641             GetHostRuntimeMapHelper<__complex128, version>(resultType)}) {
642       return map;
643     }
644 #endif
645   }
646   return nullptr;
647 }
648 static const HostRuntimeMap *GetHostRuntimeMap(
649     LibraryVersion version, DynamicType resultType) {
650   switch (version) {
651   case LibraryVersion::Libm:
652     return GetHostRuntimeMapVersion<LibraryVersion::Libm>(resultType);
653   case LibraryVersion::LibmExtensions:
654     return GetHostRuntimeMapVersion<LibraryVersion::LibmExtensions>(resultType);
655   case LibraryVersion::PgmathPrecise:
656     return GetHostRuntimeMapVersion<LibraryVersion::PgmathPrecise>(resultType);
657   case LibraryVersion::PgmathRelaxed:
658     return GetHostRuntimeMapVersion<LibraryVersion::PgmathRelaxed>(resultType);
659   case LibraryVersion::PgmathFast:
660     return GetHostRuntimeMapVersion<LibraryVersion::PgmathFast>(resultType);
661   }
662   return nullptr;
663 }
664 
665 static const HostRuntimeFunction *SearchInHostRuntimeMap(
666     const HostRuntimeMap &map, const std::string &name, DynamicType resultType,
667     const std::vector<DynamicType> &argTypes) {
668   auto sameNameRange{map.equal_range(name)};
669   for (const auto *iter{sameNameRange.first}; iter != sameNameRange.second;
670        ++iter) {
671     if (iter->resultType == resultType && iter->argumentTypes == argTypes) {
672       return &*iter;
673     }
674   }
675   return nullptr;
676 }
677 
678 // Search host runtime libraries for an exact type match.
679 static const HostRuntimeFunction *SearchHostRuntime(const std::string &name,
680     DynamicType resultType, const std::vector<DynamicType> &argTypes) {
681   // TODO: When command line options regarding targeted numerical library is
682   // available, this needs to be revisited to take it into account. So far,
683   // default to libpgmath if F18 is built with it.
684 #if LINK_WITH_LIBPGMATH
685   if (const auto *map{
686           GetHostRuntimeMap(LibraryVersion::PgmathPrecise, resultType)}) {
687     if (const auto *hostFunction{
688             SearchInHostRuntimeMap(*map, name, resultType, argTypes)}) {
689       return hostFunction;
690     }
691   }
692   // Default to libm if functions or types are not available in pgmath.
693 #endif
694   if (const auto *map{GetHostRuntimeMap(LibraryVersion::Libm, resultType)}) {
695     if (const auto *hostFunction{
696             SearchInHostRuntimeMap(*map, name, resultType, argTypes)}) {
697       return hostFunction;
698     }
699   }
700   if (const auto *map{
701           GetHostRuntimeMap(LibraryVersion::LibmExtensions, resultType)}) {
702     if (const auto *hostFunction{
703             SearchInHostRuntimeMap(*map, name, resultType, argTypes)}) {
704       return hostFunction;
705     }
706   }
707   return nullptr;
708 }
709 
710 // Return a DynamicType that can hold all values of a given type.
711 // This is used to allow 16bit float to be folded with 32bits and
712 // x87 float to be folded with IEEE 128bits.
713 static DynamicType BiggerType(DynamicType type) {
714   if (type.category() == TypeCategory::Real ||
715       type.category() == TypeCategory::Complex) {
716     // 16 bits floats to IEEE 32 bits float
717     if (type.kind() == common::RealKindForPrecision(11) ||
718         type.kind() == common::RealKindForPrecision(8)) {
719       return {type.category(), common::RealKindForPrecision(24)};
720     }
721     // x87 float to IEEE 128 bits float
722     if (type.kind() == common::RealKindForPrecision(64)) {
723       return {type.category(), common::RealKindForPrecision(113)};
724     }
725   }
726   return type;
727 }
728 
729 /// Structure to register intrinsic argument checks that must be performed.
730 using ArgumentVerifierFunc = bool (*)(
731     const std::vector<Expr<SomeType>> &, FoldingContext &);
732 struct ArgumentVerifier {
733   using Key = std::string_view;
734   // Needed for implicit compare with keys.
735   constexpr operator Key() const { return key; }
736   Key key;
737   ArgumentVerifierFunc verifier;
738 };
739 
740 static constexpr int lastArg{-1};
741 static constexpr int firstArg{0};
742 
743 static const Expr<SomeType> &GetArg(
744     int position, const std::vector<Expr<SomeType>> &args) {
745   if (position == lastArg) {
746     CHECK(!args.empty());
747     return args.back();
748   }
749   CHECK(position >= 0 && static_cast<std::size_t>(position) < args.size());
750   return args[position];
751 }
752 
753 template <typename T>
754 static bool IsInRange(const Expr<T> &expr, int lb, int ub) {
755   if (auto scalar{GetScalarConstantValue<T>(expr)}) {
756     auto lbValue{Scalar<T>::FromInteger(value::Integer<8>{lb}).value};
757     auto ubValue{Scalar<T>::FromInteger(value::Integer<8>{ub}).value};
758     return Satisfies(RelationalOperator::LE, lbValue.Compare(*scalar)) &&
759         Satisfies(RelationalOperator::LE, scalar->Compare(ubValue));
760   }
761   return true;
762 }
763 
764 /// Verify that the argument in an intrinsic call belongs to [lb, ub] if is
765 /// real.
766 template <int lb, int ub>
767 static bool VerifyInRangeIfReal(
768     const std::vector<Expr<SomeType>> &args, FoldingContext &context) {
769   if (const auto *someReal{
770           std::get_if<Expr<SomeReal>>(&GetArg(firstArg, args).u)}) {
771     bool isInRange{
772         std::visit([&](const auto &x) -> bool { return IsInRange(x, lb, ub); },
773             someReal->u)};
774     if (!isInRange) {
775       context.messages().Say(
776           "argument is out of range [%d., %d.]"_warn_en_US, lb, ub);
777     }
778     return isInRange;
779   }
780   return true;
781 }
782 
783 template <int argPosition, const char *argName>
784 static bool VerifyStrictlyPositiveIfReal(
785     const std::vector<Expr<SomeType>> &args, FoldingContext &context) {
786   if (const auto *someReal =
787           std::get_if<Expr<SomeReal>>(&GetArg(argPosition, args).u)) {
788     const bool isStrictlyPositive{std::visit(
789         [&](const auto &x) -> bool {
790           using T = typename std::decay_t<decltype(x)>::Result;
791           auto scalar{GetScalarConstantValue<T>(x)};
792           return Satisfies(
793               RelationalOperator::LT, Scalar<T>{}.Compare(*scalar));
794         },
795         someReal->u)};
796     if (!isStrictlyPositive) {
797       context.messages().Say(
798           "argument '%s' must be strictly positive"_warn_en_US, argName);
799     }
800     return isStrictlyPositive;
801   }
802   return true;
803 }
804 
805 /// Verify that an intrinsic call argument is not zero if it is real.
806 template <int argPosition, const char *argName>
807 static bool VerifyNotZeroIfReal(
808     const std::vector<Expr<SomeType>> &args, FoldingContext &context) {
809   if (const auto *someReal =
810           std::get_if<Expr<SomeReal>>(&GetArg(argPosition, args).u)) {
811     const bool isNotZero{std::visit(
812         [&](const auto &x) -> bool {
813           using T = typename std::decay_t<decltype(x)>::Result;
814           auto scalar{GetScalarConstantValue<T>(x)};
815           return !scalar || !scalar->IsZero();
816         },
817         someReal->u)};
818     if (!isNotZero) {
819       context.messages().Say(
820           "argument '%s' must be different from zero"_warn_en_US, argName);
821     }
822     return isNotZero;
823   }
824   return true;
825 }
826 
827 /// Verify that the argument in an intrinsic call is not zero if is complex.
828 static bool VerifyNotZeroIfComplex(
829     const std::vector<Expr<SomeType>> &args, FoldingContext &context) {
830   if (const auto *someComplex =
831           std::get_if<Expr<SomeComplex>>(&GetArg(firstArg, args).u)) {
832     const bool isNotZero{std::visit(
833         [&](const auto &z) -> bool {
834           using T = typename std::decay_t<decltype(z)>::Result;
835           auto scalar{GetScalarConstantValue<T>(z)};
836           return !scalar || !scalar->IsZero();
837         },
838         someComplex->u)};
839     if (!isNotZero) {
840       context.messages().Say(
841           "complex argument must be different from zero"_warn_en_US);
842     }
843     return isNotZero;
844   }
845   return true;
846 }
847 
848 // Verify that the argument in an intrinsic call is not zero and not a negative
849 // integer.
850 static bool VerifyGammaLikeArgument(
851     const std::vector<Expr<SomeType>> &args, FoldingContext &context) {
852   if (const auto *someReal =
853           std::get_if<Expr<SomeReal>>(&GetArg(firstArg, args).u)) {
854     const bool isValid{std::visit(
855         [&](const auto &x) -> bool {
856           using T = typename std::decay_t<decltype(x)>::Result;
857           auto scalar{GetScalarConstantValue<T>(x)};
858           if (scalar) {
859             return !scalar->IsZero() &&
860                 !(scalar->IsNegative() &&
861                     scalar->ToWholeNumber().value == scalar);
862           }
863           return true;
864         },
865         someReal->u)};
866     if (!isValid) {
867       context.messages().Say(
868           "argument must not be a negative integer or zero"_warn_en_US);
869     }
870     return isValid;
871   }
872   return true;
873 }
874 
875 // Verify that two real arguments are not both zero.
876 static bool VerifyAtan2LikeArguments(
877     const std::vector<Expr<SomeType>> &args, FoldingContext &context) {
878   if (const auto *someReal =
879           std::get_if<Expr<SomeReal>>(&GetArg(firstArg, args).u)) {
880     const bool isValid{std::visit(
881         [&](const auto &typedExpr) -> bool {
882           using T = typename std::decay_t<decltype(typedExpr)>::Result;
883           auto x{GetScalarConstantValue<T>(typedExpr)};
884           auto y{GetScalarConstantValue<T>(GetArg(lastArg, args))};
885           if (x && y) {
886             return !(x->IsZero() && y->IsZero());
887           }
888           return true;
889         },
890         someReal->u)};
891     if (!isValid) {
892       context.messages().Say(
893           "'x' and 'y' arguments must not be both zero"_warn_en_US);
894     }
895     return isValid;
896   }
897   return true;
898 }
899 
900 template <ArgumentVerifierFunc... F>
901 static bool CombineVerifiers(
902     const std::vector<Expr<SomeType>> &args, FoldingContext &context) {
903   return (... && F(args, context));
904 }
905 
906 /// Define argument names to be used error messages when the intrinsic have
907 /// several arguments.
908 static constexpr char xName[]{"x"};
909 static constexpr char pName[]{"p"};
910 
911 /// Register argument verifiers for all intrinsics folded with runtime.
912 static constexpr ArgumentVerifier intrinsicArgumentVerifiers[]{
913     {"acos", VerifyInRangeIfReal<-1, 1>},
914     {"asin", VerifyInRangeIfReal<-1, 1>},
915     {"atan2", VerifyAtan2LikeArguments},
916     {"bessel_y0", VerifyStrictlyPositiveIfReal<firstArg, xName>},
917     {"bessel_y1", VerifyStrictlyPositiveIfReal<firstArg, xName>},
918     {"bessel_yn", VerifyStrictlyPositiveIfReal<lastArg, xName>},
919     {"gamma", VerifyGammaLikeArgument},
920     {"log",
921         CombineVerifiers<VerifyStrictlyPositiveIfReal<firstArg, xName>,
922             VerifyNotZeroIfComplex>},
923     {"log10", VerifyStrictlyPositiveIfReal<firstArg, xName>},
924     {"log_gamma", VerifyGammaLikeArgument},
925     {"mod", VerifyNotZeroIfReal<lastArg, pName>},
926 };
927 
928 const ArgumentVerifierFunc *findVerifier(const std::string &intrinsicName) {
929   static constexpr Fortran::common::StaticMultimapView<ArgumentVerifier>
930       verifiers(intrinsicArgumentVerifiers);
931   static_assert(verifiers.Verify(), "map must be sorted");
932   auto range{verifiers.equal_range(intrinsicName)};
933   if (range.first != range.second) {
934     return &range.first->verifier;
935   }
936   return nullptr;
937 }
938 
939 /// Ensure argument verifiers, if any, are run before calling the runtime
940 /// wrapper to fold an intrinsic.
941 static HostRuntimeWrapper AddArgumentVerifierIfAny(
942     const std::string &intrinsicName, const HostRuntimeFunction &hostFunction) {
943   if (const auto *verifier{findVerifier(intrinsicName)}) {
944     const HostRuntimeFunction *hostFunctionPtr = &hostFunction;
945     return [hostFunctionPtr, verifier](
946                FoldingContext &context, std::vector<Expr<SomeType>> &&args) {
947       const bool validArguments{(*verifier)(args, context)};
948       if (!validArguments) {
949         // Silence fp signal warnings since a more detailed warning about
950         // invalid arguments was already emitted.
951         parser::Messages localBuffer;
952         parser::ContextualMessages localMessages{&localBuffer};
953         FoldingContext localContext{context, localMessages};
954         return hostFunctionPtr->folder(localContext, std::move(args));
955       }
956       return hostFunctionPtr->folder(context, std::move(args));
957     };
958   }
959   return hostFunction.folder;
960 }
961 
962 std::optional<HostRuntimeWrapper> GetHostRuntimeWrapper(const std::string &name,
963     DynamicType resultType, const std::vector<DynamicType> &argTypes) {
964   if (const auto *hostFunction{SearchHostRuntime(name, resultType, argTypes)}) {
965     return AddArgumentVerifierIfAny(name, *hostFunction);
966   }
967   // If no exact match, search with "bigger" types and insert type
968   // conversions around the folder.
969   std::vector<evaluate::DynamicType> biggerArgTypes;
970   evaluate::DynamicType biggerResultType{BiggerType(resultType)};
971   for (auto type : argTypes) {
972     biggerArgTypes.emplace_back(BiggerType(type));
973   }
974   if (const auto *hostFunction{
975           SearchHostRuntime(name, biggerResultType, biggerArgTypes)}) {
976     auto hostFolderWithChecks{AddArgumentVerifierIfAny(name, *hostFunction)};
977     return [hostFunction, resultType, hostFolderWithChecks](
978                FoldingContext &context, std::vector<Expr<SomeType>> &&args) {
979       auto nArgs{args.size()};
980       for (size_t i{0}; i < nArgs; ++i) {
981         args[i] = Fold(context,
982             ConvertToType(hostFunction->argumentTypes[i], std::move(args[i]))
983                 .value());
984       }
985       return Fold(context,
986           ConvertToType(
987               resultType, hostFolderWithChecks(context, std::move(args)))
988               .value());
989     };
990   }
991   return std::nullopt;
992 }
993 } // namespace Fortran::evaluate
994