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_wrapper.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 | #define COMPLEX_SIGNATURES(HOST_T) \ |
264 | using F = FuncPointer<std::complex<HOST_T>, const std::complex<HOST_T> &>; \ |
265 | using F2 = FuncPointer<std::complex<HOST_T>, const std::complex<HOST_T> &, \ |
266 | const std::complex<HOST_T> &>; \ |
267 | using F2A = FuncPointer<std::complex<HOST_T>, const HOST_T &, \ |
268 | const std::complex<HOST_T> &>; \ |
269 | using F2B = FuncPointer<std::complex<HOST_T>, const std::complex<HOST_T> &, \ |
270 | const HOST_T &>; |
271 | |
272 | #ifndef _AIX |
273 | // Helpers to map complex std::pow whose resolution in F2{std::pow} is |
274 | // ambiguous as of clang++ 20. |
275 | template <typename HostT> |
276 | static std::complex<HostT> StdPowF2( |
277 | const std::complex<HostT> &x, const std::complex<HostT> &y) { |
278 | return std::pow(x, y); |
279 | } |
280 | |
281 | template <typename HostT> |
282 | static std::complex<HostT> StdPowF2A( |
283 | const HostT &x, const std::complex<HostT> &y) { |
284 | return std::pow(x, y); |
285 | } |
286 | |
287 | template <typename HostT> |
288 | static std::complex<HostT> StdPowF2B( |
289 | const std::complex<HostT> &x, const HostT &y) { |
290 | return std::pow(x, y); |
291 | } |
292 | |
293 | template <typename HostT> |
294 | struct HostRuntimeLibrary<std::complex<HostT>, LibraryVersion::Libm> { |
295 | COMPLEX_SIGNATURES(HostT) |
296 | static constexpr HostRuntimeFunction table[]{ |
297 | FolderFactory<F, F{std::acos}>::Create("acos" ), |
298 | FolderFactory<F, F{std::acosh}>::Create("acosh" ), |
299 | FolderFactory<F, F{std::asin}>::Create("asin" ), |
300 | FolderFactory<F, F{std::asinh}>::Create("asinh" ), |
301 | FolderFactory<F, F{std::atan}>::Create("atan" ), |
302 | FolderFactory<F, F{std::atanh}>::Create("atanh" ), |
303 | FolderFactory<F, F{std::cos}>::Create("cos" ), |
304 | FolderFactory<F, F{std::cosh}>::Create("cosh" ), |
305 | FolderFactory<F, F{std::exp}>::Create("exp" ), |
306 | FolderFactory<F, F{std::log}>::Create("log" ), |
307 | FolderFactory<F2, F2{StdPowF2}>::Create("pow" ), |
308 | FolderFactory<F2A, F2A{StdPowF2A}>::Create("pow" ), |
309 | FolderFactory<F2B, F2B{StdPowF2B}>::Create("pow" ), |
310 | FolderFactory<F, F{std::sin}>::Create("sin" ), |
311 | FolderFactory<F, F{std::sinh}>::Create("sinh" ), |
312 | FolderFactory<F, F{std::sqrt}>::Create("sqrt" ), |
313 | FolderFactory<F, F{std::tan}>::Create("tan" ), |
314 | FolderFactory<F, F{std::tanh}>::Create("tanh" ), |
315 | }; |
316 | static constexpr HostRuntimeMap map{table}; |
317 | static_assert(map.Verify(), "map must be sorted" ); |
318 | }; |
319 | #else |
320 | // On AIX, call libm routines to preserve consistent value between |
321 | // runtime and compile time evaluation. |
322 | #ifdef __clang_major__ |
323 | #pragma clang diagnostic ignored "-Wc99-extensions" |
324 | #endif |
325 | |
326 | extern "C" { |
327 | float _Complex cacosf(float _Complex); |
328 | double _Complex cacos(double _Complex); |
329 | float _Complex cacoshf(float _Complex); |
330 | double _Complex cacosh(double _Complex); |
331 | float _Complex casinf(float _Complex); |
332 | double _Complex casin(double _Complex); |
333 | float _Complex casinhf(float _Complex); |
334 | double _Complex casinh(double _Complex); |
335 | float _Complex catanf(float _Complex); |
336 | double _Complex catan(double _Complex); |
337 | float _Complex catanhf(float _Complex); |
338 | double _Complex catanh(double _Complex); |
339 | float _Complex ccosf(float _Complex); |
340 | double _Complex ccos(double _Complex); |
341 | float _Complex ccoshf(float _Complex); |
342 | double _Complex ccosh(double _Complex); |
343 | float _Complex cexpf(float _Complex); |
344 | double _Complex cexp(double _Complex); |
345 | float _Complex clogf(float _Complex); |
346 | double _Complex __clog(double _Complex); |
347 | float _Complex cpowf(float _Complex, float _Complex); |
348 | double _Complex cpow(double _Complex, double _Complex); |
349 | float _Complex csinf(float _Complex); |
350 | double _Complex csin(double _Complex); |
351 | float _Complex csinhf(float _Complex); |
352 | double _Complex csinh(double _Complex); |
353 | float _Complex csqrtf(float _Complex); |
354 | double _Complex csqrt(double _Complex); |
355 | float _Complex ctanf(float _Complex); |
356 | double _Complex ctan(double _Complex); |
357 | float _Complex ctanhf(float _Complex); |
358 | double _Complex ctanh(double _Complex); |
359 | } |
360 | |
361 | template <typename T> struct ToStdComplex { |
362 | using Type = T; |
363 | using AType = Type; |
364 | }; |
365 | template <> struct ToStdComplex<float _Complex> { |
366 | using Type = std::complex<float>; |
367 | using AType = const Type &; |
368 | }; |
369 | template <> struct ToStdComplex<double _Complex> { |
370 | using Type = std::complex<double>; |
371 | using AType = const Type &; |
372 | }; |
373 | |
374 | template <typename F, F func> struct CComplexFunc {}; |
375 | template <typename R, typename... A, FuncPointer<R, A...> func> |
376 | struct CComplexFunc<FuncPointer<R, A...>, func> { |
377 | static typename ToStdComplex<R>::Type wrapper( |
378 | typename ToStdComplex<A>::AType... args) { |
379 | R res{func(*reinterpret_cast<const A *>(&args)...)}; |
380 | return *reinterpret_cast<typename ToStdComplex<R>::Type *>(&res); |
381 | } |
382 | }; |
383 | #define C_COMPLEX_FUNC(func) CComplexFunc<decltype(&func), &func>::wrapper |
384 | |
385 | template <> |
386 | struct HostRuntimeLibrary<std::complex<float>, LibraryVersion::Libm> { |
387 | COMPLEX_SIGNATURES(float) |
388 | static constexpr HostRuntimeFunction table[]{ |
389 | FolderFactory<F, C_COMPLEX_FUNC(cacosf)>::Create("acos" ), |
390 | FolderFactory<F, C_COMPLEX_FUNC(cacoshf)>::Create("acosh" ), |
391 | FolderFactory<F, C_COMPLEX_FUNC(casinf)>::Create("asin" ), |
392 | FolderFactory<F, C_COMPLEX_FUNC(casinhf)>::Create("asinh" ), |
393 | FolderFactory<F, C_COMPLEX_FUNC(catanf)>::Create("atan" ), |
394 | FolderFactory<F, C_COMPLEX_FUNC(catanhf)>::Create("atanh" ), |
395 | FolderFactory<F, C_COMPLEX_FUNC(ccosf)>::Create("cos" ), |
396 | FolderFactory<F, C_COMPLEX_FUNC(ccoshf)>::Create("cosh" ), |
397 | FolderFactory<F, C_COMPLEX_FUNC(cexpf)>::Create("exp" ), |
398 | FolderFactory<F, C_COMPLEX_FUNC(clogf)>::Create("log" ), |
399 | FolderFactory<F2, C_COMPLEX_FUNC(cpowf)>::Create("pow" ), |
400 | FolderFactory<F, C_COMPLEX_FUNC(csinf)>::Create("sin" ), |
401 | FolderFactory<F, C_COMPLEX_FUNC(csinhf)>::Create("sinh" ), |
402 | FolderFactory<F, C_COMPLEX_FUNC(csqrtf)>::Create("sqrt" ), |
403 | FolderFactory<F, C_COMPLEX_FUNC(ctanf)>::Create("tan" ), |
404 | FolderFactory<F, C_COMPLEX_FUNC(ctanhf)>::Create("tanh" ), |
405 | }; |
406 | static constexpr HostRuntimeMap map{table}; |
407 | static_assert(map.Verify(), "map must be sorted" ); |
408 | }; |
409 | template <> |
410 | struct HostRuntimeLibrary<std::complex<double>, LibraryVersion::Libm> { |
411 | COMPLEX_SIGNATURES(double) |
412 | static constexpr HostRuntimeFunction table[]{ |
413 | FolderFactory<F, C_COMPLEX_FUNC(cacos)>::Create("acos" ), |
414 | FolderFactory<F, C_COMPLEX_FUNC(cacosh)>::Create("acosh" ), |
415 | FolderFactory<F, C_COMPLEX_FUNC(casin)>::Create("asin" ), |
416 | FolderFactory<F, C_COMPLEX_FUNC(casinh)>::Create("asinh" ), |
417 | FolderFactory<F, C_COMPLEX_FUNC(catan)>::Create("atan" ), |
418 | FolderFactory<F, C_COMPLEX_FUNC(catanh)>::Create("atanh" ), |
419 | FolderFactory<F, C_COMPLEX_FUNC(ccos)>::Create("cos" ), |
420 | FolderFactory<F, C_COMPLEX_FUNC(ccosh)>::Create("cosh" ), |
421 | FolderFactory<F, C_COMPLEX_FUNC(cexp)>::Create("exp" ), |
422 | FolderFactory<F, C_COMPLEX_FUNC(__clog)>::Create("log" ), |
423 | FolderFactory<F2, C_COMPLEX_FUNC(cpow)>::Create("pow" ), |
424 | FolderFactory<F, C_COMPLEX_FUNC(csin)>::Create("sin" ), |
425 | FolderFactory<F, C_COMPLEX_FUNC(csinh)>::Create("sinh" ), |
426 | FolderFactory<F, C_COMPLEX_FUNC(csqrt)>::Create("sqrt" ), |
427 | FolderFactory<F, C_COMPLEX_FUNC(ctan)>::Create("tan" ), |
428 | FolderFactory<F, C_COMPLEX_FUNC(ctanh)>::Create("tanh" ), |
429 | }; |
430 | static constexpr HostRuntimeMap map{table}; |
431 | static_assert(map.Verify(), "map must be sorted" ); |
432 | }; |
433 | #endif // _AIX |
434 | |
435 | // Note regarding cmath: |
436 | // - cmath does not have modulo and erfc_scaled equivalent |
437 | // - C++17 defined standard Bessel math functions std::cyl_bessel_j |
438 | // and std::cyl_neumann that can be used for Fortran j and y |
439 | // bessel functions. However, they are not yet implemented in |
440 | // clang libc++ (ok in GNU libstdc++). Instead, the Posix libm |
441 | // extensions are used when available below. |
442 | |
443 | #if _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600 |
444 | /// Define libm extensions |
445 | /// Bessel functions are defined in POSIX.1-2001. |
446 | |
447 | // Remove float bessel functions for AIX and Darwin as they are not supported |
448 | #if !defined(_AIX) && !defined(__APPLE__) |
449 | template <> struct HostRuntimeLibrary<float, LibraryVersion::LibmExtensions> { |
450 | using F = FuncPointer<float, float>; |
451 | using FN = FuncPointer<float, int, float>; |
452 | static constexpr HostRuntimeFunction table[]{ |
453 | FolderFactory<F, F{::j0f}>::Create("bessel_j0" ), |
454 | FolderFactory<F, F{::j1f}>::Create("bessel_j1" ), |
455 | FolderFactory<FN, FN{::jnf}>::Create("bessel_jn" ), |
456 | FolderFactory<F, F{::y0f}>::Create("bessel_y0" ), |
457 | FolderFactory<F, F{::y1f}>::Create("bessel_y1" ), |
458 | FolderFactory<FN, FN{::ynf}>::Create("bessel_yn" ), |
459 | }; |
460 | static constexpr HostRuntimeMap map{table}; |
461 | static_assert(map.Verify(), "map must be sorted" ); |
462 | }; |
463 | #endif |
464 | |
465 | #if HAS_QUADMATHLIB |
466 | template <> struct HostRuntimeLibrary<__float128, LibraryVersion::Libm> { |
467 | using F = FuncPointer<__float128, __float128>; |
468 | using F2 = FuncPointer<__float128, __float128, __float128>; |
469 | using FN = FuncPointer<__float128, int, __float128>; |
470 | static constexpr HostRuntimeFunction table[]{ |
471 | FolderFactory<F, F{::acosq}>::Create("acos" ), |
472 | FolderFactory<F, F{::acoshq}>::Create("acosh" ), |
473 | FolderFactory<F, F{::asinq}>::Create("asin" ), |
474 | FolderFactory<F, F{::asinhq}>::Create("asinh" ), |
475 | FolderFactory<F, F{::atanq}>::Create("atan" ), |
476 | FolderFactory<F2, F2{::atan2q}>::Create("atan2" ), |
477 | FolderFactory<F, F{::atanhq}>::Create("atanh" ), |
478 | FolderFactory<F, F{::j0q}>::Create("bessel_j0" ), |
479 | FolderFactory<F, F{::j1q}>::Create("bessel_j1" ), |
480 | FolderFactory<FN, FN{::jnq}>::Create("bessel_jn" ), |
481 | FolderFactory<F, F{::y0q}>::Create("bessel_y0" ), |
482 | FolderFactory<F, F{::y1q}>::Create("bessel_y1" ), |
483 | FolderFactory<FN, FN{::ynq}>::Create("bessel_yn" ), |
484 | FolderFactory<F, F{::cosq}>::Create("cos" ), |
485 | FolderFactory<F, F{::coshq}>::Create("cosh" ), |
486 | FolderFactory<F, F{::erfq}>::Create("erf" ), |
487 | FolderFactory<F, F{::erfcq}>::Create("erfc" ), |
488 | FolderFactory<F, F{::expq}>::Create("exp" ), |
489 | FolderFactory<F, F{::tgammaq}>::Create("gamma" ), |
490 | FolderFactory<F, F{::logq}>::Create("log" ), |
491 | FolderFactory<F, F{::log10q}>::Create("log10" ), |
492 | FolderFactory<F, F{::lgammaq}>::Create("log_gamma" ), |
493 | FolderFactory<F2, F2{::powq}>::Create("pow" ), |
494 | FolderFactory<F, F{::sinq}>::Create("sin" ), |
495 | FolderFactory<F, F{::sinhq}>::Create("sinh" ), |
496 | FolderFactory<F, F{::tanq}>::Create("tan" ), |
497 | FolderFactory<F, F{::tanhq}>::Create("tanh" ), |
498 | }; |
499 | static constexpr HostRuntimeMap map{table}; |
500 | static_assert(map.Verify(), "map must be sorted" ); |
501 | }; |
502 | template <> struct HostRuntimeLibrary<__complex128, LibraryVersion::Libm> { |
503 | using F = FuncPointer<__complex128, __complex128>; |
504 | using F2 = FuncPointer<__complex128, __complex128, __complex128>; |
505 | static constexpr HostRuntimeFunction table[]{ |
506 | FolderFactory<F, F{::cacosq}>::Create("acos" ), |
507 | FolderFactory<F, F{::cacoshq}>::Create("acosh" ), |
508 | FolderFactory<F, F{::casinq}>::Create("asin" ), |
509 | FolderFactory<F, F{::casinhq}>::Create("asinh" ), |
510 | FolderFactory<F, F{::catanq}>::Create("atan" ), |
511 | FolderFactory<F, F{::catanhq}>::Create("atanh" ), |
512 | FolderFactory<F, F{::ccosq}>::Create("cos" ), |
513 | FolderFactory<F, F{::ccoshq}>::Create("cosh" ), |
514 | FolderFactory<F, F{::cexpq}>::Create("exp" ), |
515 | FolderFactory<F, F{::clogq}>::Create("log" ), |
516 | FolderFactory<F2, F2{::cpowq}>::Create("pow" ), |
517 | FolderFactory<F, F{::csinq}>::Create("sin" ), |
518 | FolderFactory<F, F{::csinhq}>::Create("sinh" ), |
519 | FolderFactory<F, F{::csqrtq}>::Create("sqrt" ), |
520 | FolderFactory<F, F{::ctanq}>::Create("tan" ), |
521 | FolderFactory<F, F{::ctanhq}>::Create("tanh" ), |
522 | }; |
523 | static constexpr HostRuntimeMap map{table}; |
524 | static_assert(map.Verify(), "map must be sorted" ); |
525 | }; |
526 | #endif |
527 | |
528 | template <> struct HostRuntimeLibrary<double, LibraryVersion::LibmExtensions> { |
529 | using F = FuncPointer<double, double>; |
530 | using FN = FuncPointer<double, int, double>; |
531 | static constexpr HostRuntimeFunction table[]{ |
532 | FolderFactory<F, F{::j0}>::Create("bessel_j0" ), |
533 | FolderFactory<F, F{::j1}>::Create("bessel_j1" ), |
534 | FolderFactory<FN, FN{::jn}>::Create("bessel_jn" ), |
535 | FolderFactory<F, F{::y0}>::Create("bessel_y0" ), |
536 | FolderFactory<F, F{::y1}>::Create("bessel_y1" ), |
537 | FolderFactory<FN, FN{::yn}>::Create("bessel_yn" ), |
538 | }; |
539 | static constexpr HostRuntimeMap map{table}; |
540 | static_assert(map.Verify(), "map must be sorted" ); |
541 | }; |
542 | |
543 | #if defined(__GLIBC__) && (HAS_FLOAT80 || HAS_LDBL128) |
544 | template <> |
545 | struct HostRuntimeLibrary<long double, LibraryVersion::LibmExtensions> { |
546 | using F = FuncPointer<long double, long double>; |
547 | using FN = FuncPointer<long double, int, long double>; |
548 | static constexpr HostRuntimeFunction table[]{ |
549 | FolderFactory<F, F{::j0l}>::Create("bessel_j0" ), |
550 | FolderFactory<F, F{::j1l}>::Create("bessel_j1" ), |
551 | FolderFactory<FN, FN{::jnl}>::Create("bessel_jn" ), |
552 | FolderFactory<F, F{::y0l}>::Create("bessel_y0" ), |
553 | FolderFactory<F, F{::y1l}>::Create("bessel_y1" ), |
554 | FolderFactory<FN, FN{::ynl}>::Create("bessel_yn" ), |
555 | }; |
556 | static constexpr HostRuntimeMap map{table}; |
557 | static_assert(map.Verify(), "map must be sorted" ); |
558 | }; |
559 | #endif // HAS_FLOAT80 || HAS_LDBL128 |
560 | #endif //_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600 |
561 | |
562 | #ifdef _WIN32 |
563 | template <> struct HostRuntimeLibrary<double, LibraryVersion::LibmExtensions> { |
564 | using F = FuncPointer<double, double>; |
565 | using FN = FuncPointer<double, int, double>; |
566 | static constexpr HostRuntimeFunction table[]{ |
567 | FolderFactory<F, F{::_j0}>::Create("bessel_j0" ), |
568 | FolderFactory<F, F{::_j1}>::Create("bessel_j1" ), |
569 | FolderFactory<FN, FN{::_jn}>::Create("bessel_jn" ), |
570 | FolderFactory<F, F{::_y0}>::Create("bessel_y0" ), |
571 | FolderFactory<F, F{::_y1}>::Create("bessel_y1" ), |
572 | FolderFactory<FN, FN{::_yn}>::Create("bessel_yn" ), |
573 | }; |
574 | static constexpr HostRuntimeMap map{table}; |
575 | static_assert(map.Verify(), "map must be sorted" ); |
576 | }; |
577 | #endif |
578 | |
579 | /// Define pgmath description |
580 | #if LINK_WITH_LIBPGMATH |
581 | // Only use libpgmath for folding if it is available. |
582 | // First declare all libpgmaths functions |
583 | #define PGMATH_LINKING |
584 | #define PGMATH_DECLARE |
585 | #include "flang/Evaluate/pgmath.h.inc" |
586 | |
587 | #define REAL_FOLDER(name, func) \ |
588 | FolderFactory<decltype(&func), &func>::Create(#name) |
589 | template <> struct HostRuntimeLibrary<float, LibraryVersion::PgmathFast> { |
590 | static constexpr HostRuntimeFunction table[]{ |
591 | #define PGMATH_FAST |
592 | #define PGMATH_USE_S(name, func) REAL_FOLDER(name, func), |
593 | #include "flang/Evaluate/pgmath.h.inc" |
594 | }; |
595 | static constexpr HostRuntimeMap map{table}; |
596 | static_assert(map.Verify(), "map must be sorted" ); |
597 | }; |
598 | template <> struct HostRuntimeLibrary<double, LibraryVersion::PgmathFast> { |
599 | static constexpr HostRuntimeFunction table[]{ |
600 | #define PGMATH_FAST |
601 | #define PGMATH_USE_D(name, func) REAL_FOLDER(name, func), |
602 | #include "flang/Evaluate/pgmath.h.inc" |
603 | }; |
604 | static constexpr HostRuntimeMap map{table}; |
605 | static_assert(map.Verify(), "map must be sorted" ); |
606 | }; |
607 | template <> struct HostRuntimeLibrary<float, LibraryVersion::PgmathRelaxed> { |
608 | static constexpr HostRuntimeFunction table[]{ |
609 | #define PGMATH_RELAXED |
610 | #define PGMATH_USE_S(name, func) REAL_FOLDER(name, func), |
611 | #include "flang/Evaluate/pgmath.h.inc" |
612 | }; |
613 | static constexpr HostRuntimeMap map{table}; |
614 | static_assert(map.Verify(), "map must be sorted" ); |
615 | }; |
616 | template <> struct HostRuntimeLibrary<double, LibraryVersion::PgmathRelaxed> { |
617 | static constexpr HostRuntimeFunction table[]{ |
618 | #define PGMATH_RELAXED |
619 | #define PGMATH_USE_D(name, func) REAL_FOLDER(name, func), |
620 | #include "flang/Evaluate/pgmath.h.inc" |
621 | }; |
622 | static constexpr HostRuntimeMap map{table}; |
623 | static_assert(map.Verify(), "map must be sorted" ); |
624 | }; |
625 | template <> struct HostRuntimeLibrary<float, LibraryVersion::PgmathPrecise> { |
626 | static constexpr HostRuntimeFunction table[]{ |
627 | #define PGMATH_PRECISE |
628 | #define PGMATH_USE_S(name, func) REAL_FOLDER(name, func), |
629 | #include "flang/Evaluate/pgmath.h.inc" |
630 | }; |
631 | static constexpr HostRuntimeMap map{table}; |
632 | static_assert(map.Verify(), "map must be sorted" ); |
633 | }; |
634 | template <> struct HostRuntimeLibrary<double, LibraryVersion::PgmathPrecise> { |
635 | static constexpr HostRuntimeFunction table[]{ |
636 | #define PGMATH_PRECISE |
637 | #define PGMATH_USE_D(name, func) REAL_FOLDER(name, func), |
638 | #include "flang/Evaluate/pgmath.h.inc" |
639 | }; |
640 | static constexpr HostRuntimeMap map{table}; |
641 | static_assert(map.Verify(), "map must be sorted" ); |
642 | }; |
643 | |
644 | // TODO: double _Complex/float _Complex have been removed from llvm flang |
645 | // pgmath.h.inc because they caused warnings, they need to be added back |
646 | // so that the complex pgmath versions can be used when requested. |
647 | |
648 | #endif /* LINK_WITH_LIBPGMATH */ |
649 | |
650 | // Helper to check if a HostRuntimeLibrary specialization exists |
651 | template <typename T, typename = void> struct IsAvailable : std::false_type {}; |
652 | template <typename T> |
653 | struct IsAvailable<T, decltype((void)T::table, void())> : std::true_type {}; |
654 | // Define helpers to find host runtime library map according to desired version |
655 | // and type. |
656 | template <typename HostT, LibraryVersion version> |
657 | static const HostRuntimeMap *GetHostRuntimeMapHelper( |
658 | [[maybe_unused]] DynamicType resultType) { |
659 | // A library must only be instantiated if LibraryVersion is |
660 | // available on the host and if HostT maps to a Fortran type. |
661 | // For instance, whenever long double and double are both 64-bits, double |
662 | // is mapped to Fortran 64bits real type, and long double will be left |
663 | // unmapped. |
664 | if constexpr (host::FortranTypeExists<HostT>()) { |
665 | using Lib = HostRuntimeLibrary<HostT, version>; |
666 | if constexpr (IsAvailable<Lib>::value) { |
667 | if (host::FortranType<HostT>{}.GetType() == resultType) { |
668 | return &Lib::map; |
669 | } |
670 | } |
671 | } |
672 | return nullptr; |
673 | } |
674 | template <LibraryVersion version> |
675 | static const HostRuntimeMap *GetHostRuntimeMapVersion(DynamicType resultType) { |
676 | if (resultType.category() == TypeCategory::Real) { |
677 | if (const auto *map{GetHostRuntimeMapHelper<float, version>(resultType)}) { |
678 | return map; |
679 | } |
680 | if (const auto *map{GetHostRuntimeMapHelper<double, version>(resultType)}) { |
681 | return map; |
682 | } |
683 | if (const auto *map{ |
684 | GetHostRuntimeMapHelper<long double, version>(resultType)}) { |
685 | return map; |
686 | } |
687 | #if HAS_QUADMATHLIB |
688 | if (const auto *map{ |
689 | GetHostRuntimeMapHelper<__float128, version>(resultType)}) { |
690 | return map; |
691 | } |
692 | #endif |
693 | } |
694 | if (resultType.category() == TypeCategory::Complex) { |
695 | if (const auto *map{GetHostRuntimeMapHelper<std::complex<float>, version>( |
696 | resultType)}) { |
697 | return map; |
698 | } |
699 | if (const auto *map{GetHostRuntimeMapHelper<std::complex<double>, version>( |
700 | resultType)}) { |
701 | return map; |
702 | } |
703 | if (const auto *map{ |
704 | GetHostRuntimeMapHelper<std::complex<long double>, version>( |
705 | resultType)}) { |
706 | return map; |
707 | } |
708 | #if HAS_QUADMATHLIB |
709 | if (const auto *map{ |
710 | GetHostRuntimeMapHelper<__complex128, version>(resultType)}) { |
711 | return map; |
712 | } |
713 | #endif |
714 | } |
715 | return nullptr; |
716 | } |
717 | static const HostRuntimeMap *GetHostRuntimeMap( |
718 | LibraryVersion version, DynamicType resultType) { |
719 | switch (version) { |
720 | case LibraryVersion::Libm: |
721 | return GetHostRuntimeMapVersion<LibraryVersion::Libm>(resultType); |
722 | case LibraryVersion::LibmExtensions: |
723 | return GetHostRuntimeMapVersion<LibraryVersion::LibmExtensions>(resultType); |
724 | case LibraryVersion::PgmathPrecise: |
725 | return GetHostRuntimeMapVersion<LibraryVersion::PgmathPrecise>(resultType); |
726 | case LibraryVersion::PgmathRelaxed: |
727 | return GetHostRuntimeMapVersion<LibraryVersion::PgmathRelaxed>(resultType); |
728 | case LibraryVersion::PgmathFast: |
729 | return GetHostRuntimeMapVersion<LibraryVersion::PgmathFast>(resultType); |
730 | } |
731 | return nullptr; |
732 | } |
733 | |
734 | static const HostRuntimeFunction *SearchInHostRuntimeMap( |
735 | const HostRuntimeMap &map, const std::string &name, DynamicType resultType, |
736 | const std::vector<DynamicType> &argTypes) { |
737 | auto sameNameRange{map.equal_range(name)}; |
738 | for (const auto *iter{sameNameRange.first}; iter != sameNameRange.second; |
739 | ++iter) { |
740 | if (iter->resultType == resultType && iter->argumentTypes == argTypes) { |
741 | return &*iter; |
742 | } |
743 | } |
744 | return nullptr; |
745 | } |
746 | |
747 | // Search host runtime libraries for an exact type match. |
748 | static const HostRuntimeFunction *SearchHostRuntime(const std::string &name, |
749 | DynamicType resultType, const std::vector<DynamicType> &argTypes) { |
750 | // TODO: When command line options regarding targeted numerical library is |
751 | // available, this needs to be revisited to take it into account. So far, |
752 | // default to libpgmath if F18 is built with it. |
753 | #if LINK_WITH_LIBPGMATH |
754 | if (const auto *map{ |
755 | GetHostRuntimeMap(LibraryVersion::PgmathPrecise, resultType)}) { |
756 | if (const auto *hostFunction{ |
757 | SearchInHostRuntimeMap(*map, name, resultType, argTypes)}) { |
758 | return hostFunction; |
759 | } |
760 | } |
761 | // Default to libm if functions or types are not available in pgmath. |
762 | #endif |
763 | if (const auto *map{GetHostRuntimeMap(LibraryVersion::Libm, resultType)}) { |
764 | if (const auto *hostFunction{ |
765 | SearchInHostRuntimeMap(*map, name, resultType, argTypes)}) { |
766 | return hostFunction; |
767 | } |
768 | } |
769 | if (const auto *map{ |
770 | GetHostRuntimeMap(LibraryVersion::LibmExtensions, resultType)}) { |
771 | if (const auto *hostFunction{ |
772 | SearchInHostRuntimeMap(*map, name, resultType, argTypes)}) { |
773 | return hostFunction; |
774 | } |
775 | } |
776 | return nullptr; |
777 | } |
778 | |
779 | // Return a DynamicType that can hold all values of a given type. |
780 | // This is used to allow 16bit float to be folded with 32bits and |
781 | // x87 float to be folded with IEEE 128bits. |
782 | static DynamicType BiggerType(DynamicType type) { |
783 | if (type.category() == TypeCategory::Real || |
784 | type.category() == TypeCategory::Complex) { |
785 | // 16 bits floats to IEEE 32 bits float |
786 | if (type.kind() == common::RealKindForPrecision(11) || |
787 | type.kind() == common::RealKindForPrecision(8)) { |
788 | return {type.category(), common::RealKindForPrecision(24)}; |
789 | } |
790 | // x87 float to IEEE 128 bits float |
791 | if (type.kind() == common::RealKindForPrecision(64)) { |
792 | return {type.category(), common::RealKindForPrecision(113)}; |
793 | } |
794 | } |
795 | return type; |
796 | } |
797 | |
798 | /// Structure to register intrinsic argument checks that must be performed. |
799 | using ArgumentVerifierFunc = bool (*)( |
800 | const std::vector<Expr<SomeType>> &, FoldingContext &); |
801 | struct ArgumentVerifier { |
802 | using Key = std::string_view; |
803 | // Needed for implicit compare with keys. |
804 | constexpr operator Key() const { return key; } |
805 | Key key; |
806 | ArgumentVerifierFunc verifier; |
807 | }; |
808 | |
809 | static constexpr int lastArg{-1}; |
810 | static constexpr int firstArg{0}; |
811 | |
812 | static const Expr<SomeType> &GetArg( |
813 | int position, const std::vector<Expr<SomeType>> &args) { |
814 | if (position == lastArg) { |
815 | CHECK(!args.empty()); |
816 | return args.back(); |
817 | } |
818 | CHECK(position >= 0 && static_cast<std::size_t>(position) < args.size()); |
819 | return args[position]; |
820 | } |
821 | |
822 | template <typename T> |
823 | static bool IsInRange(const Expr<T> &expr, int lb, int ub) { |
824 | if (auto scalar{GetScalarConstantValue<T>(expr)}) { |
825 | auto lbValue{Scalar<T>::FromInteger(value::Integer<8>{lb}).value}; |
826 | auto ubValue{Scalar<T>::FromInteger(value::Integer<8>{ub}).value}; |
827 | return Satisfies(RelationalOperator::LE, lbValue.Compare(*scalar)) && |
828 | Satisfies(RelationalOperator::LE, scalar->Compare(ubValue)); |
829 | } |
830 | return true; |
831 | } |
832 | |
833 | /// Verify that the argument in an intrinsic call belongs to [lb, ub] if is |
834 | /// real. |
835 | template <int lb, int ub> |
836 | static bool VerifyInRangeIfReal( |
837 | const std::vector<Expr<SomeType>> &args, FoldingContext &context) { |
838 | if (const auto *someReal{ |
839 | std::get_if<Expr<SomeReal>>(&GetArg(firstArg, args).u)}) { |
840 | bool isInRange{ |
841 | std::visit([&](const auto &x) -> bool { return IsInRange(x, lb, ub); }, |
842 | someReal->u)}; |
843 | if (!isInRange) { |
844 | context.messages().Say( |
845 | "argument is out of range [%d., %d.]"_warn_en_US , lb, ub); |
846 | } |
847 | return isInRange; |
848 | } |
849 | return true; |
850 | } |
851 | |
852 | template <int argPosition, const char *argName> |
853 | static bool VerifyStrictlyPositiveIfReal( |
854 | const std::vector<Expr<SomeType>> &args, FoldingContext &context) { |
855 | if (const auto *someReal = |
856 | std::get_if<Expr<SomeReal>>(&GetArg(argPosition, args).u)) { |
857 | const bool isStrictlyPositive{std::visit( |
858 | [&](const auto &x) -> bool { |
859 | using T = typename std::decay_t<decltype(x)>::Result; |
860 | auto scalar{GetScalarConstantValue<T>(x)}; |
861 | return Satisfies( |
862 | RelationalOperator::LT, Scalar<T>{}.Compare(*scalar)); |
863 | }, |
864 | someReal->u)}; |
865 | if (!isStrictlyPositive) { |
866 | context.messages().Say( |
867 | "argument '%s' must be strictly positive"_warn_en_US , argName); |
868 | } |
869 | return isStrictlyPositive; |
870 | } |
871 | return true; |
872 | } |
873 | |
874 | /// Verify that an intrinsic call argument is not zero if it is real. |
875 | template <int argPosition, const char *argName> |
876 | static bool VerifyNotZeroIfReal( |
877 | const std::vector<Expr<SomeType>> &args, FoldingContext &context) { |
878 | if (const auto *someReal = |
879 | std::get_if<Expr<SomeReal>>(&GetArg(argPosition, args).u)) { |
880 | const bool isNotZero{std::visit( |
881 | [&](const auto &x) -> bool { |
882 | using T = typename std::decay_t<decltype(x)>::Result; |
883 | auto scalar{GetScalarConstantValue<T>(x)}; |
884 | return !scalar || !scalar->IsZero(); |
885 | }, |
886 | someReal->u)}; |
887 | if (!isNotZero) { |
888 | context.messages().Say( |
889 | "argument '%s' must be different from zero"_warn_en_US , argName); |
890 | } |
891 | return isNotZero; |
892 | } |
893 | return true; |
894 | } |
895 | |
896 | /// Verify that the argument in an intrinsic call is not zero if is complex. |
897 | static bool VerifyNotZeroIfComplex( |
898 | const std::vector<Expr<SomeType>> &args, FoldingContext &context) { |
899 | if (const auto *someComplex = |
900 | std::get_if<Expr<SomeComplex>>(&GetArg(firstArg, args).u)) { |
901 | const bool isNotZero{std::visit( |
902 | [&](const auto &z) -> bool { |
903 | using T = typename std::decay_t<decltype(z)>::Result; |
904 | auto scalar{GetScalarConstantValue<T>(z)}; |
905 | return !scalar || !scalar->IsZero(); |
906 | }, |
907 | someComplex->u)}; |
908 | if (!isNotZero) { |
909 | context.messages().Say( |
910 | "complex argument must be different from zero"_warn_en_US ); |
911 | } |
912 | return isNotZero; |
913 | } |
914 | return true; |
915 | } |
916 | |
917 | // Verify that the argument in an intrinsic call is not zero and not a negative |
918 | // integer. |
919 | static bool VerifyGammaLikeArgument( |
920 | const std::vector<Expr<SomeType>> &args, FoldingContext &context) { |
921 | if (const auto *someReal = |
922 | std::get_if<Expr<SomeReal>>(&GetArg(firstArg, args).u)) { |
923 | const bool isValid{std::visit( |
924 | [&](const auto &x) -> bool { |
925 | using T = typename std::decay_t<decltype(x)>::Result; |
926 | auto scalar{GetScalarConstantValue<T>(x)}; |
927 | if (scalar) { |
928 | return !scalar->IsZero() && |
929 | !(scalar->IsNegative() && |
930 | scalar->ToWholeNumber().value == scalar); |
931 | } |
932 | return true; |
933 | }, |
934 | someReal->u)}; |
935 | if (!isValid) { |
936 | context.messages().Say( |
937 | "argument must not be a negative integer or zero"_warn_en_US ); |
938 | } |
939 | return isValid; |
940 | } |
941 | return true; |
942 | } |
943 | |
944 | // Verify that two real arguments are not both zero. |
945 | static bool VerifyAtan2LikeArguments( |
946 | const std::vector<Expr<SomeType>> &args, FoldingContext &context) { |
947 | if (const auto *someReal = |
948 | std::get_if<Expr<SomeReal>>(&GetArg(firstArg, args).u)) { |
949 | const bool isValid{std::visit( |
950 | [&](const auto &typedExpr) -> bool { |
951 | using T = typename std::decay_t<decltype(typedExpr)>::Result; |
952 | auto x{GetScalarConstantValue<T>(typedExpr)}; |
953 | auto y{GetScalarConstantValue<T>(GetArg(lastArg, args))}; |
954 | if (x && y) { |
955 | return !(x->IsZero() && y->IsZero()); |
956 | } |
957 | return true; |
958 | }, |
959 | someReal->u)}; |
960 | if (!isValid) { |
961 | context.messages().Say( |
962 | "'x' and 'y' arguments must not be both zero"_warn_en_US ); |
963 | } |
964 | return isValid; |
965 | } |
966 | return true; |
967 | } |
968 | |
969 | template <ArgumentVerifierFunc... F> |
970 | static bool CombineVerifiers( |
971 | const std::vector<Expr<SomeType>> &args, FoldingContext &context) { |
972 | return (... && F(args, context)); |
973 | } |
974 | |
975 | /// Define argument names to be used error messages when the intrinsic have |
976 | /// several arguments. |
977 | static constexpr char xName[]{"x" }; |
978 | static constexpr char pName[]{"p" }; |
979 | |
980 | /// Register argument verifiers for all intrinsics folded with runtime. |
981 | static constexpr ArgumentVerifier intrinsicArgumentVerifiers[]{ |
982 | {"acos" , VerifyInRangeIfReal<-1, 1>}, |
983 | {"asin" , VerifyInRangeIfReal<-1, 1>}, |
984 | {"atan2" , VerifyAtan2LikeArguments}, |
985 | {"bessel_y0" , VerifyStrictlyPositiveIfReal<firstArg, xName>}, |
986 | {"bessel_y1" , VerifyStrictlyPositiveIfReal<firstArg, xName>}, |
987 | {"bessel_yn" , VerifyStrictlyPositiveIfReal<lastArg, xName>}, |
988 | {"gamma" , VerifyGammaLikeArgument}, |
989 | {"log" , |
990 | CombineVerifiers<VerifyStrictlyPositiveIfReal<firstArg, xName>, |
991 | VerifyNotZeroIfComplex>}, |
992 | {"log10" , VerifyStrictlyPositiveIfReal<firstArg, xName>}, |
993 | {"log_gamma" , VerifyGammaLikeArgument}, |
994 | {"mod" , VerifyNotZeroIfReal<lastArg, pName>}, |
995 | }; |
996 | |
997 | const ArgumentVerifierFunc *findVerifier(const std::string &intrinsicName) { |
998 | static constexpr Fortran::common::StaticMultimapView<ArgumentVerifier> |
999 | verifiers(intrinsicArgumentVerifiers); |
1000 | static_assert(verifiers.Verify(), "map must be sorted" ); |
1001 | auto range{verifiers.equal_range(intrinsicName)}; |
1002 | if (range.first != range.second) { |
1003 | return &range.first->verifier; |
1004 | } |
1005 | return nullptr; |
1006 | } |
1007 | |
1008 | /// Ensure argument verifiers, if any, are run before calling the runtime |
1009 | /// wrapper to fold an intrinsic. |
1010 | static HostRuntimeWrapper AddArgumentVerifierIfAny( |
1011 | const std::string &intrinsicName, const HostRuntimeFunction &hostFunction) { |
1012 | if (const auto *verifier{findVerifier(intrinsicName)}) { |
1013 | const HostRuntimeFunction *hostFunctionPtr = &hostFunction; |
1014 | return [hostFunctionPtr, verifier]( |
1015 | FoldingContext &context, std::vector<Expr<SomeType>> &&args) { |
1016 | const bool validArguments{(*verifier)(args, context)}; |
1017 | if (!validArguments) { |
1018 | // Silence fp signal warnings since a more detailed warning about |
1019 | // invalid arguments was already emitted. |
1020 | parser::Messages localBuffer; |
1021 | parser::ContextualMessages localMessages{&localBuffer}; |
1022 | FoldingContext localContext{context, localMessages}; |
1023 | return hostFunctionPtr->folder(localContext, std::move(args)); |
1024 | } |
1025 | return hostFunctionPtr->folder(context, std::move(args)); |
1026 | }; |
1027 | } |
1028 | return hostFunction.folder; |
1029 | } |
1030 | |
1031 | std::optional<HostRuntimeWrapper> GetHostRuntimeWrapper(const std::string &name, |
1032 | DynamicType resultType, const std::vector<DynamicType> &argTypes) { |
1033 | if (const auto *hostFunction{SearchHostRuntime(name, resultType, argTypes)}) { |
1034 | return AddArgumentVerifierIfAny(name, *hostFunction); |
1035 | } |
1036 | // If no exact match, search with "bigger" types and insert type |
1037 | // conversions around the folder. |
1038 | std::vector<evaluate::DynamicType> biggerArgTypes; |
1039 | evaluate::DynamicType biggerResultType{BiggerType(resultType)}; |
1040 | for (auto type : argTypes) { |
1041 | biggerArgTypes.emplace_back(BiggerType(type)); |
1042 | } |
1043 | if (const auto *hostFunction{ |
1044 | SearchHostRuntime(name, biggerResultType, biggerArgTypes)}) { |
1045 | auto hostFolderWithChecks{AddArgumentVerifierIfAny(name, *hostFunction)}; |
1046 | return [hostFunction, resultType, hostFolderWithChecks]( |
1047 | FoldingContext &context, std::vector<Expr<SomeType>> &&args) { |
1048 | auto nArgs{args.size()}; |
1049 | for (size_t i{0}; i < nArgs; ++i) { |
1050 | args[i] = Fold(context, |
1051 | ConvertToType(hostFunction->argumentTypes[i], std::move(args[i])) |
1052 | .value()); |
1053 | } |
1054 | return Fold(context, |
1055 | ConvertToType( |
1056 | resultType, hostFolderWithChecks(context, std::move(args))) |
1057 | .value()); |
1058 | }; |
1059 | } |
1060 | return std::nullopt; |
1061 | } |
1062 | } // namespace Fortran::evaluate |
1063 | |