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 HostIntrinsicProceduresLibrary is built with <cmath> and
12 // <complex> functions that are guaranteed to exist from the C++ standard.
13 
14 #include "intrinsics-library-templates.h"
15 #include <cmath>
16 #include <complex>
17 
18 namespace Fortran::evaluate {
19 
20 // Note: argument passing is ignored in equivalence
HasEquivalentProcedure(const IntrinsicProcedureRuntimeDescription & sym) const21 bool HostIntrinsicProceduresLibrary::HasEquivalentProcedure(
22     const IntrinsicProcedureRuntimeDescription &sym) const {
23   const auto rteProcRange{procedures_.equal_range(sym.name)};
24   const size_t nargs{sym.argumentsType.size()};
25   for (auto iter{rteProcRange.first}; iter != rteProcRange.second; ++iter) {
26     if (nargs == iter->second.argumentsType.size() &&
27         sym.returnType == iter->second.returnType &&
28         (sym.isElemental || iter->second.isElemental)) {
29       bool match{true};
30       int pos{0};
31       for (const auto &type : sym.argumentsType) {
32         if (type != iter->second.argumentsType[pos++]) {
33           match = false;
34           break;
35         }
36       }
37       if (match) {
38         return true;
39       }
40     }
41   }
42   return false;
43 }
44 
45 // Map numerical intrinsic to  <cmath>/<complex> functions
46 
47 // Define which host runtime functions will be used for folding
48 
49 template <typename HostT>
AddLibmRealHostProcedures(HostIntrinsicProceduresLibrary & hostIntrinsicLibrary)50 static void AddLibmRealHostProcedures(
51     HostIntrinsicProceduresLibrary &hostIntrinsicLibrary) {
52   using F = FuncPointer<HostT, HostT>;
53   using F2 = FuncPointer<HostT, HostT, HostT>;
54   HostRuntimeIntrinsicProcedure libmSymbols[]{
55       {"acos", F{std::acos}, true},
56       {"acosh", F{std::acosh}, true},
57       {"asin", F{std::asin}, true},
58       {"asinh", F{std::asinh}, true},
59       {"atan", F{std::atan}, true},
60       {"atan2", F2{std::atan2}, true},
61       {"atanh", F{std::atanh}, true},
62       {"cos", F{std::cos}, true},
63       {"cosh", F{std::cosh}, true},
64       {"erf", F{std::erf}, true},
65       {"erfc", F{std::erfc}, true},
66       {"exp", F{std::exp}, true},
67       {"gamma", F{std::tgamma}, true},
68       {"hypot", F2{std::hypot}, true},
69       {"log", F{std::log}, true},
70       {"log10", F{std::log10}, true},
71       {"log_gamma", F{std::lgamma}, true},
72       {"mod", F2{std::fmod}, true},
73       {"pow", F2{std::pow}, true},
74       {"sin", F{std::sin}, true},
75       {"sinh", F{std::sinh}, true},
76       {"sqrt", F{std::sqrt}, true},
77       {"tan", F{std::tan}, true},
78       {"tanh", F{std::tanh}, true},
79   };
80   // Note: cmath does not have modulo and erfc_scaled equivalent
81 
82   // Note regarding  lack of bessel function support:
83   // C++17 defined standard Bessel math functions std::cyl_bessel_j
84   // and std::cyl_neumann that can be used for Fortran j and y
85   // bessel functions. However, they are not yet implemented in
86   // clang libc++ (ok in GNU libstdc++). C maths functions j0...
87   // are not C standard but a GNU extension so they are not used
88   // to avoid introducing incompatibilities.
89   // Use libpgmath to get bessel function folding support.
90   // TODO:  Add Bessel functions when possible.
91 
92   for (auto sym : libmSymbols) {
93     if (!hostIntrinsicLibrary.HasEquivalentProcedure(sym)) {
94       hostIntrinsicLibrary.AddProcedure(std::move(sym));
95     }
96   }
97 }
98 
99 template <typename HostT>
AddLibmComplexHostProcedures(HostIntrinsicProceduresLibrary & hostIntrinsicLibrary)100 static void AddLibmComplexHostProcedures(
101     HostIntrinsicProceduresLibrary &hostIntrinsicLibrary) {
102   using F = FuncPointer<std::complex<HostT>, const std::complex<HostT> &>;
103   using F2 = FuncPointer<std::complex<HostT>, const std::complex<HostT> &,
104       const std::complex<HostT> &>;
105   using F2a = FuncPointer<std::complex<HostT>, const HostT &,
106       const std::complex<HostT> &>;
107   using F2b = FuncPointer<std::complex<HostT>, const std::complex<HostT> &,
108       const HostT &>;
109   HostRuntimeIntrinsicProcedure libmSymbols[]{
110       {"abs", FuncPointer<HostT, const std::complex<HostT> &>{std::abs}, true},
111       {"acos", F{std::acos}, true},
112       {"acosh", F{std::acosh}, true},
113       {"asin", F{std::asin}, true},
114       {"asinh", F{std::asinh}, true},
115       {"atan", F{std::atan}, true},
116       {"atanh", F{std::atanh}, true},
117       {"cos", F{std::cos}, true},
118       {"cosh", F{std::cosh}, true},
119       {"exp", F{std::exp}, true},
120       {"log", F{std::log}, true},
121       {"pow", F2{std::pow}, true},
122       {"pow", F2a{std::pow}, true},
123       {"pow", F2b{std::pow}, true},
124       {"sin", F{std::sin}, true},
125       {"sinh", F{std::sinh}, true},
126       {"sqrt", F{std::sqrt}, true},
127       {"tan", F{std::tan}, true},
128       {"tanh", F{std::tanh}, true},
129   };
130 
131   for (auto sym : libmSymbols) {
132     if (!hostIntrinsicLibrary.HasEquivalentProcedure(sym)) {
133       hostIntrinsicLibrary.AddProcedure(std::move(sym));
134     }
135   }
136 }
137 
InitHostIntrinsicLibraryWithLibm(HostIntrinsicProceduresLibrary & lib)138 [[maybe_unused]] static void InitHostIntrinsicLibraryWithLibm(
139     HostIntrinsicProceduresLibrary &lib) {
140   if constexpr (host::FortranTypeExists<float>()) {
141     AddLibmRealHostProcedures<float>(lib);
142   }
143   if constexpr (host::FortranTypeExists<double>()) {
144     AddLibmRealHostProcedures<double>(lib);
145   }
146   if constexpr (host::FortranTypeExists<long double>()) {
147     AddLibmRealHostProcedures<long double>(lib);
148   }
149 
150   if constexpr (host::FortranTypeExists<std::complex<float>>()) {
151     AddLibmComplexHostProcedures<float>(lib);
152   }
153   if constexpr (host::FortranTypeExists<std::complex<double>>()) {
154     AddLibmComplexHostProcedures<double>(lib);
155   }
156   if constexpr (host::FortranTypeExists<std::complex<long double>>()) {
157     AddLibmComplexHostProcedures<long double>(lib);
158   }
159 }
160 
161 #if LINK_WITH_LIBPGMATH
162 // Only use libpgmath for folding if it is available.
163 // First declare all libpgmaths functions
164 #define PGMATH_DECLARE
165 #include "../runtime/pgmath.h.inc"
166 
167 // Library versions: P for Precise, R for Relaxed, F for Fast
168 enum class L { F, R, P };
169 
170 // Fill the function map used for folding with libpgmath symbols
171 template <L Lib>
AddLibpgmathFloatHostProcedures(HostIntrinsicProceduresLibrary & hostIntrinsicLibrary)172 static void AddLibpgmathFloatHostProcedures(
173     HostIntrinsicProceduresLibrary &hostIntrinsicLibrary) {
174   if constexpr (Lib == L::F) {
175     HostRuntimeIntrinsicProcedure pgmathSymbols[]{
176 #define PGMATH_FAST
177 #define PGMATH_USE_S(name, function) {#name, function, true},
178 #include "../runtime/pgmath.h.inc"
179     };
180     for (auto sym : pgmathSymbols) {
181       hostIntrinsicLibrary.AddProcedure(std::move(sym));
182     }
183   } else if constexpr (Lib == L::R) {
184     HostRuntimeIntrinsicProcedure pgmathSymbols[]{
185 #define PGMATH_RELAXED
186 #define PGMATH_USE_S(name, function) {#name, function, true},
187 #include "../runtime/pgmath.h.inc"
188     };
189     for (auto sym : pgmathSymbols) {
190       hostIntrinsicLibrary.AddProcedure(std::move(sym));
191     }
192   } else {
193     static_assert(Lib == L::P && "unexpected libpgmath version");
194     HostRuntimeIntrinsicProcedure pgmathSymbols[]{
195 #define PGMATH_PRECISE
196 #define PGMATH_USE_S(name, function) {#name, function, true},
197 #include "../runtime/pgmath.h.inc"
198     };
199     for (auto sym : pgmathSymbols) {
200       hostIntrinsicLibrary.AddProcedure(std::move(sym));
201     }
202   }
203 }
204 
205 template <L Lib>
AddLibpgmathDoubleHostProcedures(HostIntrinsicProceduresLibrary & hostIntrinsicLibrary)206 static void AddLibpgmathDoubleHostProcedures(
207     HostIntrinsicProceduresLibrary &hostIntrinsicLibrary) {
208   if constexpr (Lib == L::F) {
209     HostRuntimeIntrinsicProcedure pgmathSymbols[]{
210 #define PGMATH_FAST
211 #define PGMATH_USE_D(name, function) {#name, function, true},
212 #include "../runtime/pgmath.h.inc"
213     };
214     for (auto sym : pgmathSymbols) {
215       hostIntrinsicLibrary.AddProcedure(std::move(sym));
216     }
217   } else if constexpr (Lib == L::R) {
218     HostRuntimeIntrinsicProcedure pgmathSymbols[]{
219 #define PGMATH_RELAXED
220 #define PGMATH_USE_D(name, function) {#name, function, true},
221 #include "../runtime/pgmath.h.inc"
222     };
223     for (auto sym : pgmathSymbols) {
224       hostIntrinsicLibrary.AddProcedure(std::move(sym));
225     }
226   } else {
227     static_assert(Lib == L::P && "unexpected libpgmath version");
228     HostRuntimeIntrinsicProcedure pgmathSymbols[]{
229 #define PGMATH_PRECISE
230 #define PGMATH_USE_D(name, function) {#name, function, true},
231 #include "../runtime/pgmath.h.inc"
232     };
233     for (auto sym : pgmathSymbols) {
234       hostIntrinsicLibrary.AddProcedure(std::move(sym));
235     }
236   }
237 }
238 
239 // Note: Lipgmath uses _Complex but the front-end use std::complex for folding.
240 // std::complex and _Complex are layout compatible but are not guaranteed
241 // to be linkage compatible. For instance, on i386, float _Complex is returned
242 // by a pair of register but std::complex<float> is returned by structure
243 // address. To fix the issue, wrapper around C _Complex functions are defined
244 // below.
245 
246 template <typename T> struct ToStdComplex {
247   using Type = T;
248   using AType = Type;
249 };
250 
251 template <typename F, F func> struct CComplexFunc {};
252 template <typename R, typename... A, FuncPointer<R, A...> func>
253 struct CComplexFunc<FuncPointer<R, A...>, func> {
wrapperFortran::evaluate::CComplexFunc254   static typename ToStdComplex<R>::Type wrapper(
255       typename ToStdComplex<A>::AType... args) {
256     R res{func(*reinterpret_cast<A *>(&args)...)};
257     return *reinterpret_cast<typename ToStdComplex<R>::Type *>(&res);
258   }
259 };
260 
261 template <L Lib>
AddLibpgmathComplexHostProcedures(HostIntrinsicProceduresLibrary & hostIntrinsicLibrary)262 static void AddLibpgmathComplexHostProcedures(
263     HostIntrinsicProceduresLibrary &hostIntrinsicLibrary) {
264   if constexpr (Lib == L::F) {
265     HostRuntimeIntrinsicProcedure pgmathSymbols[]{
266 #define PGMATH_FAST
267 #define PGMATH_USE_C(name, function) \
268   {#name, CComplexFunc<decltype(&function), &function>::wrapper, true},
269 #include "../runtime/pgmath.h.inc"
270     };
271     for (auto sym : pgmathSymbols) {
272       hostIntrinsicLibrary.AddProcedure(std::move(sym));
273     }
274   } else if constexpr (Lib == L::R) {
275     HostRuntimeIntrinsicProcedure pgmathSymbols[]{
276 #define PGMATH_RELAXED
277 #define PGMATH_USE_C(name, function) \
278   {#name, CComplexFunc<decltype(&function), &function>::wrapper, true},
279 #include "../runtime/pgmath.h.inc"
280     };
281     for (auto sym : pgmathSymbols) {
282       hostIntrinsicLibrary.AddProcedure(std::move(sym));
283     }
284   } else {
285     static_assert(Lib == L::P && "unexpected libpgmath version");
286     HostRuntimeIntrinsicProcedure pgmathSymbols[]{
287 #define PGMATH_PRECISE
288 #define PGMATH_USE_C(name, function) \
289   {#name, CComplexFunc<decltype(&function), &function>::wrapper, true},
290 #include "../runtime/pgmath.h.inc"
291     };
292     for (auto sym : pgmathSymbols) {
293       hostIntrinsicLibrary.AddProcedure(std::move(sym));
294     }
295   }
296 
297   // cmath is used to complement pgmath when symbols are not available
298   using HostT = float;
299   using CHostT = std::complex<HostT>;
300   using CmathF = FuncPointer<CHostT, const CHostT &>;
301   hostIntrinsicLibrary.AddProcedure(
302       {"abs", FuncPointer<HostT, const CHostT &>{std::abs}, true});
303   hostIntrinsicLibrary.AddProcedure({"acosh", CmathF{std::acosh}, true});
304   hostIntrinsicLibrary.AddProcedure({"asinh", CmathF{std::asinh}, true});
305   hostIntrinsicLibrary.AddProcedure({"atanh", CmathF{std::atanh}, true});
306 }
307 
308 template <L Lib>
AddLibpgmathDoubleComplexHostProcedures(HostIntrinsicProceduresLibrary & hostIntrinsicLibrary)309 static void AddLibpgmathDoubleComplexHostProcedures(
310     HostIntrinsicProceduresLibrary &hostIntrinsicLibrary) {
311   if constexpr (Lib == L::F) {
312     HostRuntimeIntrinsicProcedure pgmathSymbols[]{
313 #define PGMATH_FAST
314 #define PGMATH_USE_Z(name, function) \
315   {#name, CComplexFunc<decltype(&function), &function>::wrapper, true},
316 #include "../runtime/pgmath.h.inc"
317     };
318     for (auto sym : pgmathSymbols) {
319       hostIntrinsicLibrary.AddProcedure(std::move(sym));
320     }
321   } else if constexpr (Lib == L::R) {
322     HostRuntimeIntrinsicProcedure pgmathSymbols[]{
323 #define PGMATH_RELAXED
324 #define PGMATH_USE_Z(name, function) \
325   {#name, CComplexFunc<decltype(&function), &function>::wrapper, true},
326 #include "../runtime/pgmath.h.inc"
327     };
328     for (auto sym : pgmathSymbols) {
329       hostIntrinsicLibrary.AddProcedure(std::move(sym));
330     }
331   } else {
332     static_assert(Lib == L::P && "unexpected libpgmath version");
333     HostRuntimeIntrinsicProcedure pgmathSymbols[]{
334 #define PGMATH_PRECISE
335 #define PGMATH_USE_Z(name, function) \
336   {#name, CComplexFunc<decltype(&function), &function>::wrapper, true},
337 #include "../runtime/pgmath.h.inc"
338     };
339     for (auto sym : pgmathSymbols) {
340       hostIntrinsicLibrary.AddProcedure(std::move(sym));
341     }
342   }
343 
344   // cmath is used to complement pgmath when symbols are not available
345   using HostT = double;
346   using CHostT = std::complex<HostT>;
347   using CmathF = FuncPointer<CHostT, const CHostT &>;
348   hostIntrinsicLibrary.AddProcedure(
349       {"abs", FuncPointer<HostT, const CHostT &>{std::abs}, true});
350   hostIntrinsicLibrary.AddProcedure({"acosh", CmathF{std::acosh}, true});
351   hostIntrinsicLibrary.AddProcedure({"asinh", CmathF{std::asinh}, true});
352   hostIntrinsicLibrary.AddProcedure({"atanh", CmathF{std::atanh}, true});
353 }
354 
355 template <L Lib>
InitHostIntrinsicLibraryWithLibpgmath(HostIntrinsicProceduresLibrary & lib)356 static void InitHostIntrinsicLibraryWithLibpgmath(
357     HostIntrinsicProceduresLibrary &lib) {
358   if constexpr (host::FortranTypeExists<float>()) {
359     AddLibpgmathFloatHostProcedures<Lib>(lib);
360   }
361   if constexpr (host::FortranTypeExists<double>()) {
362     AddLibpgmathDoubleHostProcedures<Lib>(lib);
363   }
364   if constexpr (host::FortranTypeExists<std::complex<float>>()) {
365     AddLibpgmathComplexHostProcedures<Lib>(lib);
366   }
367   if constexpr (host::FortranTypeExists<std::complex<double>>()) {
368     AddLibpgmathDoubleComplexHostProcedures<Lib>(lib);
369   }
370   // No long double functions in libpgmath
371   if constexpr (host::FortranTypeExists<long double>()) {
372     AddLibmRealHostProcedures<long double>(lib);
373   }
374   if constexpr (host::FortranTypeExists<std::complex<long double>>()) {
375     AddLibmComplexHostProcedures<long double>(lib);
376   }
377 }
378 #endif // LINK_WITH_LIBPGMATH
379 
380 // Define which host runtime functions will be used for folding
HostIntrinsicProceduresLibrary()381 HostIntrinsicProceduresLibrary::HostIntrinsicProceduresLibrary() {
382   // TODO: When command line options regarding targeted numerical library is
383   // available, this needs to be revisited to take it into account. So far,
384   // default to libpgmath if F18 is built with it.
385 #if LINK_WITH_LIBPGMATH
386   // This looks and is stupid for now (until TODO above), but it is needed
387   // to silence clang warnings on unused symbols if all declared pgmath
388   // symbols are not used somewhere.
389   if (true) {
390     InitHostIntrinsicLibraryWithLibpgmath<L::P>(*this);
391   } else if (false) {
392     InitHostIntrinsicLibraryWithLibpgmath<L::F>(*this);
393   } else {
394     InitHostIntrinsicLibraryWithLibpgmath<L::R>(*this);
395   }
396 #else
397   InitHostIntrinsicLibraryWithLibm(*this);
398 #endif
399 }
400 } // namespace Fortran::evaluate
401