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