1 //===-- lib/Evaluate/intrinsics-library.cpp -------------------------------===//
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
7 //===----------------------------------------------------------------------===//
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.
14 #include "flang/Evaluate/intrinsics-library.h"
15 #include "fold-implementation.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"
28 #include "flang/Common/float128.h"
29 #include "flang/Common/float80.h"
30 #include <type_traits>
32 namespace Fortran::evaluate
{
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
)};
42 template <> struct TypeVectorStorage
<> {
43 static constexpr const DynamicType
*start
{nullptr}, *end
{nullptr};
46 template <typename
... FortranType
> static constexpr TypeVector
Create() {
47 using storage
= TypeVectorStorage
<FortranType
...>;
48 return TypeVector
{storage::start
, storage::end
, sizeof...(FortranType
)};
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
); }
56 const DynamicType
*startPtr
{nullptr};
57 const DynamicType
*endPtr
{nullptr};
60 inline bool operator==(
61 const TypeVector
&lhs
, const std::vector
<DynamicType
> &rhs
) {
62 if (lhs
.size() != rhs
.size()) {
65 for (size_t i
{0}; i
< lhs
.size(); ++i
) {
66 if (lhs
[i
] != rhs
[i
]) {
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
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.
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
101 // Translate a host function type signature (template arguments) into a
102 // constexpr data representation based on Fortran DynamicType that can be
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
>...>()};
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
);
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();
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
{};
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
));
165 hostResult
= func(host::CastFortranToHost
<TA
>(std::get
<I
>(scalarArgs
))...);
166 result
= host::CastHostToFortran
<TR
>(hostResult
);
168 if (!hostFPE
.hardwareFlagsAreReliable()) {
169 CheckFloatingPointIssues
<TR
>(hostFPE
, result
);
171 hostFPE
.CheckAndRestoreFloatingPointEnvironment(context
);
172 return AsGenericExpr(Constant
<TR
>(std::move(result
)));
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
...>{});
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
187 template <typename HostFuncType
, HostFuncType func
> class FolderFactory
{
189 static constexpr HostRuntimeFunction
Create(const std::string_view
&name
) {
190 return HostRuntimeFunction
{name
, FuncTypeAnalyzer
<HostFuncType
>::result
,
191 FuncTypeAnalyzer
<HostFuncType
>::arguments
, &Fold
};
195 static Expr
<SomeType
> Fold(
196 FoldingContext
&context
, std::vector
<Expr
<SomeType
>> &&args
) {
197 return ApplyHostFunction(func
, context
, std::move(args
));
201 // Define host runtime libraries that can be used for folding and
202 // fill their description if they are available.
203 enum class LibraryVersion
{
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.
216 using HostRuntimeMap
= common::StaticMultimapView
<HostRuntimeFunction
>;
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"),
248 // Note: cmath does not have modulo and erfc_scaled equivalent
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");
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
);
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
);
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
);
282 #ifdef __clang_major__
283 #pragma clang diagnostic ignored "-Wc99-extensions"
287 float _Complex
cacosf(float _Complex
);
288 double _Complex
cacos(double _Complex
);
289 float _Complex
csqrtf(float _Complex
);
290 double _Complex
csqrt(double _Complex
);
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
];
297 template <typename TR
, typename T
> static TR
CppToC(const std::complex<T
> &x
) {
299 reIm
<T
, TR
>(r
, CRI::Real
) = x
.real();
300 reIm
<T
, TR
>(r
, CRI::Imag
) = x
.imag();
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
));
309 template <typename HostT
>
310 static std::complex<HostT
> CSqrt(const std::complex<HostT
> &x
) {
311 std::complex<HostT
> res
;
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
);
322 DIE("bad complex component type");
330 template <typename HostT
>
331 static std::complex<HostT
> CAcos(const std::complex<HostT
> &x
) {
332 std::complex<HostT
> res
;
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
);
343 DIE("bad complex component type");
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
> &,
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"),
380 static constexpr HostRuntimeMap map
{table
};
381 static_assert(map
.Verify(), "map must be sorted");
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.
391 #if _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600
392 /// Define libm extensions
393 /// Bessel functions are defined in POSIX.1-2001.
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"),
408 static constexpr HostRuntimeMap map
{table
};
409 static_assert(map
.Verify(), "map must be sorted");
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"),
447 static constexpr HostRuntimeMap map
{table
};
448 static_assert(map
.Verify(), "map must be sorted");
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"),
471 static constexpr HostRuntimeMap map
{table
};
472 static_assert(map
.Verify(), "map must be sorted");
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"),
487 static constexpr HostRuntimeMap map
{table
};
488 static_assert(map
.Verify(), "map must be sorted");
491 #if defined(__GLIBC__) && (HAS_FLOAT80 || HAS_LDBL128)
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"),
504 static constexpr HostRuntimeMap map
{table
};
505 static_assert(map
.Verify(), "map must be sorted");
507 #endif // HAS_FLOAT80 || HAS_LDBL128
508 #endif //_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600
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"
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
[]{
523 #define PGMATH_USE_S(name, func) REAL_FOLDER(name, func),
524 #include "flang/Evaluate/pgmath.h.inc"
526 static constexpr HostRuntimeMap map
{table
};
527 static_assert(map
.Verify(), "map must be sorted");
529 template <> struct HostRuntimeLibrary
<double, LibraryVersion::PgmathFast
> {
530 static constexpr HostRuntimeFunction table
[]{
532 #define PGMATH_USE_D(name, func) REAL_FOLDER(name, func),
533 #include "flang/Evaluate/pgmath.h.inc"
535 static constexpr HostRuntimeMap map
{table
};
536 static_assert(map
.Verify(), "map must be sorted");
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"
544 static constexpr HostRuntimeMap map
{table
};
545 static_assert(map
.Verify(), "map must be sorted");
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"
553 static constexpr HostRuntimeMap map
{table
};
554 static_assert(map
.Verify(), "map must be sorted");
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"
562 static constexpr HostRuntimeMap map
{table
};
563 static_assert(map
.Verify(), "map must be sorted");
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"
571 static constexpr HostRuntimeMap map
{table
};
572 static_assert(map
.Verify(), "map must be sorted");
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.
579 #endif /* LINK_WITH_LIBPGMATH */
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
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
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
) {
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
)}) {
611 if (const auto *map
{GetHostRuntimeMapHelper
<double, version
>(resultType
)}) {
615 GetHostRuntimeMapHelper
<long double, version
>(resultType
)}) {
620 GetHostRuntimeMapHelper
<__float128
, version
>(resultType
)}) {
625 if (resultType
.category() == TypeCategory::Complex
) {
626 if (const auto *map
{GetHostRuntimeMapHelper
<std::complex<float>, version
>(
630 if (const auto *map
{GetHostRuntimeMapHelper
<std::complex<double>, version
>(
635 GetHostRuntimeMapHelper
<std::complex<long double>, version
>(
641 GetHostRuntimeMapHelper
<__complex128
, version
>(resultType
)}) {
648 static const HostRuntimeMap
*GetHostRuntimeMap(
649 LibraryVersion version
, DynamicType resultType
) {
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
);
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
;
671 if (iter
->resultType
== resultType
&& iter
->argumentTypes
== argTypes
) {
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
686 GetHostRuntimeMap(LibraryVersion::PgmathPrecise
, resultType
)}) {
687 if (const auto *hostFunction
{
688 SearchInHostRuntimeMap(*map
, name
, resultType
, argTypes
)}) {
692 // Default to libm if functions or types are not available in pgmath.
694 if (const auto *map
{GetHostRuntimeMap(LibraryVersion::Libm
, resultType
)}) {
695 if (const auto *hostFunction
{
696 SearchInHostRuntimeMap(*map
, name
, resultType
, argTypes
)}) {
701 GetHostRuntimeMap(LibraryVersion::LibmExtensions
, resultType
)}) {
702 if (const auto *hostFunction
{
703 SearchInHostRuntimeMap(*map
, name
, resultType
, argTypes
)}) {
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)};
721 // x87 float to IEEE 128 bits float
722 if (type
.kind() == common::RealKindForPrecision(64)) {
723 return {type
.category(), common::RealKindForPrecision(113)};
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
; }
737 ArgumentVerifierFunc verifier
;
740 static constexpr int lastArg
{-1};
741 static constexpr int firstArg
{0};
743 static const Expr
<SomeType
> &GetArg(
744 int position
, const std::vector
<Expr
<SomeType
>> &args
) {
745 if (position
== lastArg
) {
746 CHECK(!args
.empty());
749 CHECK(position
>= 0 && static_cast<std::size_t>(position
) < args
.size());
750 return args
[position
];
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
));
764 /// Verify that the argument in an intrinsic call belongs to [lb, ub] if is
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
)}) {
772 std::visit([&](const auto &x
) -> bool { return IsInRange(x
, lb
, ub
); },
775 context
.messages().Say(
776 "argument is out of range [%d., %d.]"_warn_en_US
, lb
, ub
);
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
)};
793 RelationalOperator::LT
, Scalar
<T
>{}.Compare(*scalar
));
796 if (!isStrictlyPositive
) {
797 context
.messages().Say(
798 "argument '%s' must be strictly positive"_warn_en_US
, argName
);
800 return isStrictlyPositive
;
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();
819 context
.messages().Say(
820 "argument '%s' must be different from zero"_warn_en_US
, argName
);
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();
840 context
.messages().Say(
841 "complex argument must be different from zero"_warn_en_US
);
848 // Verify that the argument in an intrinsic call is not zero and not a negative
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
)};
859 return !scalar
->IsZero() &&
860 !(scalar
->IsNegative() &&
861 scalar
->ToWholeNumber().value
== scalar
);
867 context
.messages().Say(
868 "argument must not be a negative integer or zero"_warn_en_US
);
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
))};
886 return !(x
->IsZero() && y
->IsZero());
892 context
.messages().Say(
893 "'x' and 'y' arguments must not be both zero"_warn_en_US
);
900 template <ArgumentVerifierFunc
... F
>
901 static bool CombineVerifiers(
902 const std::vector
<Expr
<SomeType
>> &args
, FoldingContext
&context
) {
903 return (... && F(args
, context
));
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"};
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
},
921 CombineVerifiers
<VerifyStrictlyPositiveIfReal
<firstArg
, xName
>,
922 VerifyNotZeroIfComplex
>},
923 {"log10", VerifyStrictlyPositiveIfReal
<firstArg
, xName
>},
924 {"log_gamma", VerifyGammaLikeArgument
},
925 {"mod", VerifyNotZeroIfReal
<lastArg
, pName
>},
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
;
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
));
956 return hostFunctionPtr
->folder(context
, std::move(args
));
959 return hostFunction
.folder
;
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
);
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
));
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
]))
987 resultType
, hostFolderWithChecks(context
, std::move(args
)))
993 } // namespace Fortran::evaluate