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/static-multimap-view.h"
18 #include "flang/Evaluate/expression.h"
19 #include <cmath>
20 #include <complex>
21 #include <functional>
22 #include <type_traits>
23 
24 namespace Fortran::evaluate {
25 
26 // Define a vector like class that can hold an arbitrary number of
27 // Dynamic type and be built at compile time. This is like a
28 // std::vector<DynamicType>, but constexpr only.
29 template <typename... FortranType> struct TypeVectorStorage {
30   static constexpr DynamicType values[]{FortranType{}.GetType()...};
31   static constexpr const DynamicType *start{&values[0]};
32   static constexpr const DynamicType *end{start + sizeof...(FortranType)};
33 };
34 template <> struct TypeVectorStorage<> {
35   static constexpr const DynamicType *start{nullptr}, *end{nullptr};
36 };
37 struct TypeVector {
CreateFortran::evaluate::TypeVector38   template <typename... FortranType> static constexpr TypeVector Create() {
39     using storage = TypeVectorStorage<FortranType...>;
40     return TypeVector{storage::start, storage::end, sizeof...(FortranType)};
41   }
sizeFortran::evaluate::TypeVector42   constexpr size_t size() const { return size_; };
43   using const_iterator = const DynamicType *;
beginFortran::evaluate::TypeVector44   constexpr const_iterator begin() const { return startPtr; }
endFortran::evaluate::TypeVector45   constexpr const_iterator end() const { return endPtr; }
operator []Fortran::evaluate::TypeVector46   const DynamicType &operator[](size_t i) const { return *(startPtr + i); }
47 
48   const DynamicType *startPtr{nullptr};
49   const DynamicType *endPtr{nullptr};
50   const size_t size_;
51 };
operator ==(const TypeVector & lhs,const std::vector<DynamicType> & rhs)52 inline bool operator==(
53     const TypeVector &lhs, const std::vector<DynamicType> &rhs) {
54   if (lhs.size() != rhs.size()) {
55     return false;
56   }
57   for (size_t i{0}; i < lhs.size(); ++i) {
58     if (lhs[i] != rhs[i]) {
59       return false;
60     }
61   }
62   return true;
63 }
64 
65 // HostRuntimeFunction holds a pointer to a Folder function that can fold
66 // a Fortran scalar intrinsic using host runtime functions (e.g libm).
67 // The folder take care of all conversions between Fortran types and the related
68 // host types as well as setting and cleaning-up the floating point environment.
69 // HostRuntimeFunction are intended to be built at compile time (members are all
70 // constexpr constructible) so that they can be stored in a compile time static
71 // map.
72 struct HostRuntimeFunction {
73   using Folder = Expr<SomeType> (*)(
74       FoldingContext &, std::vector<Expr<SomeType>> &&);
75   using Key = std::string_view;
76   // Needed for implicit compare with keys.
operator KeyFortran::evaluate::HostRuntimeFunction77   constexpr operator Key() const { return key; }
78   // Name of the related Fortran intrinsic.
79   Key key;
80   // DynamicType of the Expr<SomeType> returns by folder.
81   DynamicType resultType;
82   // DynamicTypes expected for the Expr<SomeType> arguments of the folder.
83   // The folder will crash if provided arguments of different types.
84   TypeVector argumentTypes;
85   // Folder to be called to fold the intrinsic with host runtime. The provided
86   // Expr<SomeType> arguments must wrap scalar constants of the type described
87   // in argumentTypes, otherwise folder will crash. Any floating point issue
88   // raised while executing the host runtime will be reported in FoldingContext
89   // messages.
90   Folder folder;
91 };
92 
93 // Translate a host function type signature (template arguments) into a
94 // constexpr data representation based on Fortran DynamicType that can be
95 // stored.
96 template <typename TR, typename... TA> using FuncPointer = TR (*)(TA...);
97 template <typename T> struct FuncTypeAnalyzer {};
98 template <typename HostTR, typename... HostTA>
99 struct FuncTypeAnalyzer<FuncPointer<HostTR, HostTA...>> {
100   static constexpr DynamicType result{host::FortranType<HostTR>{}.GetType()};
101   static constexpr TypeVector arguments{
102       TypeVector::Create<host::FortranType<HostTA>...>()};
103 };
104 
105 // Define helpers to deal with host floating environment.
106 template <typename TR>
CheckFloatingPointIssues(host::HostFloatingPointEnvironment & hostFPE,const Scalar<TR> & x)107 static void CheckFloatingPointIssues(
108     host::HostFloatingPointEnvironment &hostFPE, const Scalar<TR> &x) {
109   if constexpr (TR::category == TypeCategory::Complex ||
110       TR::category == TypeCategory::Real) {
111     if (x.IsNotANumber()) {
112       hostFPE.SetFlag(RealFlag::InvalidArgument);
113     } else if (x.IsInfinite()) {
114       hostFPE.SetFlag(RealFlag::Overflow);
115     }
116   }
117 }
118 // Software Subnormal Flushing helper.
119 // Only flush floating-points. Forward other scalars untouched.
120 // Software flushing is only performed if hardware flushing is not available
121 // because it may not result in the same behavior as hardware flushing.
122 // Some runtime implementations are "working around" subnormal flushing to
123 // return results that they deem better than returning the result they would
124 // with a null argument. An example is logf that should return -inf if arguments
125 // are flushed to zero, but some implementations return -1.03972076416015625e2_4
126 // for all subnormal values instead. It is impossible to reproduce this with the
127 // simple software flushing below.
128 template <typename T>
FlushSubnormals(Scalar<T> && x)129 static constexpr inline const Scalar<T> FlushSubnormals(Scalar<T> &&x) {
130   if constexpr (T::category == TypeCategory::Real ||
131       T::category == TypeCategory::Complex) {
132     return x.FlushSubnormalToZero();
133   }
134   return x;
135 }
136 
137 // This is the kernel called by all HostRuntimeFunction folders, it convert the
138 // Fortran Expr<SomeType> to the host runtime function argument types, calls
139 // the runtime function, and wrap back the result into an Expr<SomeType>.
140 // It deals with host floating point environment set-up and clean-up.
141 template <typename FuncType, typename TR, typename... TA, size_t... I>
ApplyHostFunctionHelper(FuncType func,FoldingContext & context,std::vector<Expr<SomeType>> && args,std::index_sequence<I...>)142 static Expr<SomeType> ApplyHostFunctionHelper(FuncType func,
143     FoldingContext &context, std::vector<Expr<SomeType>> &&args,
144     std::index_sequence<I...>) {
145   host::HostFloatingPointEnvironment hostFPE;
146   hostFPE.SetUpHostFloatingPointEnvironment(context);
147   host::HostType<TR> hostResult{};
148   Scalar<TR> result{};
149   std::tuple<Scalar<TA>...> scalarArgs{
150       GetScalarConstantValue<TA>(args[I]).value()...};
151   if (context.flushSubnormalsToZero() &&
152       !hostFPE.hasSubnormalFlushingHardwareControl()) {
153     hostResult = func(host::CastFortranToHost<TA>(
154         FlushSubnormals<TA>(std::move(std::get<I>(scalarArgs))))...);
155     result = FlushSubnormals<TR>(host::CastHostToFortran<TR>(hostResult));
156   } else {
157     hostResult = func(host::CastFortranToHost<TA>(std::get<I>(scalarArgs))...);
158     result = host::CastHostToFortran<TR>(hostResult);
159   }
160   if (!hostFPE.hardwareFlagsAreReliable()) {
161     CheckFloatingPointIssues<TR>(hostFPE, result);
162   }
163   hostFPE.CheckAndRestoreFloatingPointEnvironment(context);
164   return AsGenericExpr(Constant<TR>(std::move(result)));
165 }
166 template <typename HostTR, typename... HostTA>
ApplyHostFunction(FuncPointer<HostTR,HostTA...> func,FoldingContext & context,std::vector<Expr<SomeType>> && args)167 Expr<SomeType> ApplyHostFunction(FuncPointer<HostTR, HostTA...> func,
168     FoldingContext &context, std::vector<Expr<SomeType>> &&args) {
169   return ApplyHostFunctionHelper<decltype(func), host::FortranType<HostTR>,
170       host::FortranType<HostTA>...>(
171       func, context, std::move(args), std::index_sequence_for<HostTA...>{});
172 }
173 
174 // FolderFactory builds a HostRuntimeFunction for the host runtime function
175 // passed as a template argument.
176 // Its static member function "fold" is the resulting folder. It captures the
177 // host runtime function pointer and pass it to the host runtime function folder
178 // kernel.
179 template <typename HostFuncType, HostFuncType func> class FolderFactory {
180 public:
Create(const std::string_view & name)181   static constexpr HostRuntimeFunction Create(const std::string_view &name) {
182     return HostRuntimeFunction{name, FuncTypeAnalyzer<HostFuncType>::result,
183         FuncTypeAnalyzer<HostFuncType>::arguments, &Fold};
184   }
185 
186 private:
Fold(FoldingContext & context,std::vector<Expr<SomeType>> && args)187   static Expr<SomeType> Fold(
188       FoldingContext &context, std::vector<Expr<SomeType>> &&args) {
189     return ApplyHostFunction(func, context, std::move(args));
190   }
191 };
192 
193 // Define host runtime libraries that can be used for folding and
194 // fill their description if they are available.
195 enum class LibraryVersion { Libm, PgmathFast, PgmathRelaxed, PgmathPrecise };
196 template <typename HostT, LibraryVersion> struct HostRuntimeLibrary {
197   // When specialized, this class holds a static constexpr table containing
198   // all the HostRuntimeLibrary for functions of library LibraryVersion
199   // that returns a value of type HostT.
200 };
201 
202 using HostRuntimeMap = common::StaticMultimapView<HostRuntimeFunction>;
203 
204 // Map numerical intrinsic to  <cmath>/<complex> functions
205 template <typename HostT>
206 struct HostRuntimeLibrary<HostT, LibraryVersion::Libm> {
207   using F = FuncPointer<HostT, HostT>;
208   using F2 = FuncPointer<HostT, HostT, HostT>;
209   using ComplexToRealF = FuncPointer<HostT, const std::complex<HostT> &>;
210   static constexpr HostRuntimeFunction table[]{
211       FolderFactory<ComplexToRealF, ComplexToRealF{std::abs}>::Create("abs"),
212       FolderFactory<F, F{std::acos}>::Create("acos"),
213       FolderFactory<F, F{std::acosh}>::Create("acosh"),
214       FolderFactory<F, F{std::asin}>::Create("asin"),
215       FolderFactory<F, F{std::asinh}>::Create("asinh"),
216       FolderFactory<F, F{std::atan}>::Create("atan"),
217       FolderFactory<F2, F2{std::atan2}>::Create("atan2"),
218       FolderFactory<F, F{std::atanh}>::Create("atanh"),
219       FolderFactory<F, F{std::cos}>::Create("cos"),
220       FolderFactory<F, F{std::cosh}>::Create("cosh"),
221       FolderFactory<F, F{std::erf}>::Create("erf"),
222       FolderFactory<F, F{std::erfc}>::Create("erfc"),
223       FolderFactory<F, F{std::exp}>::Create("exp"),
224       FolderFactory<F, F{std::tgamma}>::Create("gamma"),
225       FolderFactory<F, F{std::log}>::Create("log"),
226       FolderFactory<F, F{std::log10}>::Create("log10"),
227       FolderFactory<F, F{std::lgamma}>::Create("log_gamma"),
228       FolderFactory<F2, F2{std::fmod}>::Create("mod"),
229       FolderFactory<F2, F2{std::pow}>::Create("pow"),
230       FolderFactory<F, F{std::sin}>::Create("sin"),
231       FolderFactory<F, F{std::sinh}>::Create("sinh"),
232       FolderFactory<F, F{std::tan}>::Create("tan"),
233       FolderFactory<F, F{std::tanh}>::Create("tanh"),
234   };
235   // Note: cmath does not have modulo and erfc_scaled equivalent
236 
237   // Note regarding  lack of bessel function support:
238   // C++17 defined standard Bessel math functions std::cyl_bessel_j
239   // and std::cyl_neumann that can be used for Fortran j and y
240   // bessel functions. However, they are not yet implemented in
241   // clang libc++ (ok in GNU libstdc++). C maths functions j0...
242   // are not C standard but a GNU extension so they are not used
243   // to avoid introducing incompatibilities.
244   // Use libpgmath to get bessel function folding support.
245   // TODO:  Add Bessel functions when possible.
246   static constexpr HostRuntimeMap map{table};
247   static_assert(map.Verify(), "map must be sorted");
248 };
249 template <typename HostT>
250 struct HostRuntimeLibrary<std::complex<HostT>, LibraryVersion::Libm> {
251   using F = FuncPointer<std::complex<HostT>, const std::complex<HostT> &>;
252   using F2 = FuncPointer<std::complex<HostT>, const std::complex<HostT> &,
253       const std::complex<HostT> &>;
254   using F2A = FuncPointer<std::complex<HostT>, const HostT &,
255       const std::complex<HostT> &>;
256   using F2B = FuncPointer<std::complex<HostT>, const std::complex<HostT> &,
257       const HostT &>;
258   static constexpr HostRuntimeFunction table[]{
259       FolderFactory<F, F{std::acos}>::Create("acos"),
260       FolderFactory<F, F{std::acosh}>::Create("acosh"),
261       FolderFactory<F, F{std::asin}>::Create("asin"),
262       FolderFactory<F, F{std::asinh}>::Create("asinh"),
263       FolderFactory<F, F{std::atan}>::Create("atan"),
264       FolderFactory<F, F{std::atanh}>::Create("atanh"),
265       FolderFactory<F, F{std::cos}>::Create("cos"),
266       FolderFactory<F, F{std::cosh}>::Create("cosh"),
267       FolderFactory<F, F{std::exp}>::Create("exp"),
268       FolderFactory<F, F{std::log}>::Create("log"),
269       FolderFactory<F2, F2{std::pow}>::Create("pow"),
270       FolderFactory<F2A, F2A{std::pow}>::Create("pow"),
271       FolderFactory<F2B, F2B{std::pow}>::Create("pow"),
272       FolderFactory<F, F{std::sin}>::Create("sin"),
273       FolderFactory<F, F{std::sinh}>::Create("sinh"),
274       FolderFactory<F, F{std::sqrt}>::Create("sqrt"),
275       FolderFactory<F, F{std::tan}>::Create("tan"),
276       FolderFactory<F, F{std::tanh}>::Create("tanh"),
277   };
278   static constexpr HostRuntimeMap map{table};
279   static_assert(map.Verify(), "map must be sorted");
280 };
281 
282 /// Define pgmath description
283 #if LINK_WITH_LIBPGMATH
284 // Only use libpgmath for folding if it is available.
285 // First declare all libpgmaths functions
286 #define PGMATH_LINKING
287 #define PGMATH_DECLARE
288 #include "flang/Evaluate/pgmath.h.inc"
289 
290 #define REAL_FOLDER(name, func) \
291   FolderFactory<decltype(&func), &func>::Create(#name)
292 template <> struct HostRuntimeLibrary<float, LibraryVersion::PgmathFast> {
293   static constexpr HostRuntimeFunction table[]{
294 #define PGMATH_FAST
295 #define PGMATH_USE_S(name, func) REAL_FOLDER(name, func),
296 #include "flang/Evaluate/pgmath.h.inc"
297   };
298   static constexpr HostRuntimeMap map{table};
299   static_assert(map.Verify(), "map must be sorted");
300 };
301 template <> struct HostRuntimeLibrary<double, LibraryVersion::PgmathFast> {
302   static constexpr HostRuntimeFunction table[]{
303 #define PGMATH_FAST
304 #define PGMATH_USE_D(name, func) REAL_FOLDER(name, func),
305 #include "flang/Evaluate/pgmath.h.inc"
306   };
307   static constexpr HostRuntimeMap map{table};
308   static_assert(map.Verify(), "map must be sorted");
309 };
310 template <> struct HostRuntimeLibrary<float, LibraryVersion::PgmathRelaxed> {
311   static constexpr HostRuntimeFunction table[]{
312 #define PGMATH_RELAXED
313 #define PGMATH_USE_S(name, func) REAL_FOLDER(name, func),
314 #include "flang/Evaluate/pgmath.h.inc"
315   };
316   static constexpr HostRuntimeMap map{table};
317   static_assert(map.Verify(), "map must be sorted");
318 };
319 template <> struct HostRuntimeLibrary<double, LibraryVersion::PgmathRelaxed> {
320   static constexpr HostRuntimeFunction table[]{
321 #define PGMATH_RELAXED
322 #define PGMATH_USE_D(name, func) REAL_FOLDER(name, func),
323 #include "flang/Evaluate/pgmath.h.inc"
324   };
325   static constexpr HostRuntimeMap map{table};
326   static_assert(map.Verify(), "map must be sorted");
327 };
328 template <> struct HostRuntimeLibrary<float, LibraryVersion::PgmathPrecise> {
329   static constexpr HostRuntimeFunction table[]{
330 #define PGMATH_PRECISE
331 #define PGMATH_USE_S(name, func) REAL_FOLDER(name, func),
332 #include "flang/Evaluate/pgmath.h.inc"
333   };
334   static constexpr HostRuntimeMap map{table};
335   static_assert(map.Verify(), "map must be sorted");
336 };
337 template <> struct HostRuntimeLibrary<double, LibraryVersion::PgmathPrecise> {
338   static constexpr HostRuntimeFunction table[]{
339 #define PGMATH_PRECISE
340 #define PGMATH_USE_D(name, func) REAL_FOLDER(name, func),
341 #include "flang/Evaluate/pgmath.h.inc"
342   };
343   static constexpr HostRuntimeMap map{table};
344   static_assert(map.Verify(), "map must be sorted");
345 };
346 
347 // TODO: double _Complex/float _Complex have been removed from llvm flang
348 // pgmath.h.inc because they caused warnings, they need to be added back
349 // so that the complex pgmath versions can be used when requested.
350 
351 #endif /* LINK_WITH_LIBPGMATH */
352 
353 // Helper to check if a HostRuntimeLibrary specialization exists
354 template <typename T, typename = void> struct IsAvailable : std::false_type {};
355 template <typename T>
356 struct IsAvailable<T, decltype((void)T::table, void())> : std::true_type {};
357 // Define helpers to find host runtime library map according to desired version
358 // and type.
359 template <typename HostT, LibraryVersion version>
GetHostRuntimeMapHelper(DynamicType resultType)360 static const HostRuntimeMap *GetHostRuntimeMapHelper(
361     [[maybe_unused]] DynamicType resultType) {
362   // A library must only be instantiated if LibraryVersion is
363   // available on the host and if HostT maps to a Fortran type.
364   // For instance, whenever long double and double are both 64-bits, double
365   // is mapped to Fortran 64bits real type, and long double will be left
366   // unmapped.
367   if constexpr (host::FortranTypeExists<HostT>()) {
368     using Lib = HostRuntimeLibrary<HostT, version>;
369     if constexpr (IsAvailable<Lib>::value) {
370       if (host::FortranType<HostT>{}.GetType() == resultType) {
371         return &Lib::map;
372       }
373     }
374   }
375   return nullptr;
376 }
377 template <LibraryVersion version>
GetHostRuntimeMapVersion(DynamicType resultType)378 static const HostRuntimeMap *GetHostRuntimeMapVersion(DynamicType resultType) {
379   if (resultType.category() == TypeCategory::Real) {
380     if (const auto *map{GetHostRuntimeMapHelper<float, version>(resultType)}) {
381       return map;
382     }
383     if (const auto *map{GetHostRuntimeMapHelper<double, version>(resultType)}) {
384       return map;
385     }
386     if (const auto *map{
387             GetHostRuntimeMapHelper<long double, version>(resultType)}) {
388       return map;
389     }
390   }
391   if (resultType.category() == TypeCategory::Complex) {
392     if (const auto *map{GetHostRuntimeMapHelper<std::complex<float>, version>(
393             resultType)}) {
394       return map;
395     }
396     if (const auto *map{GetHostRuntimeMapHelper<std::complex<double>, version>(
397             resultType)}) {
398       return map;
399     }
400     if (const auto *map{
401             GetHostRuntimeMapHelper<std::complex<long double>, version>(
402                 resultType)}) {
403       return map;
404     }
405   }
406   return nullptr;
407 }
GetHostRuntimeMap(LibraryVersion version,DynamicType resultType)408 static const HostRuntimeMap *GetHostRuntimeMap(
409     LibraryVersion version, DynamicType resultType) {
410   switch (version) {
411   case LibraryVersion::Libm:
412     return GetHostRuntimeMapVersion<LibraryVersion::Libm>(resultType);
413   case LibraryVersion::PgmathPrecise:
414     return GetHostRuntimeMapVersion<LibraryVersion::PgmathPrecise>(resultType);
415   case LibraryVersion::PgmathRelaxed:
416     return GetHostRuntimeMapVersion<LibraryVersion::PgmathRelaxed>(resultType);
417   case LibraryVersion::PgmathFast:
418     return GetHostRuntimeMapVersion<LibraryVersion::PgmathFast>(resultType);
419   }
420   return nullptr;
421 }
422 
SearchInHostRuntimeMap(const HostRuntimeMap & map,const std::string & name,DynamicType resultType,const std::vector<DynamicType> & argTypes)423 static const HostRuntimeFunction *SearchInHostRuntimeMap(
424     const HostRuntimeMap &map, const std::string &name, DynamicType resultType,
425     const std::vector<DynamicType> &argTypes) {
426   auto sameNameRange{map.equal_range(name)};
427   for (const auto *iter{sameNameRange.first}; iter != sameNameRange.second;
428        ++iter) {
429     if (iter->resultType == resultType && iter->argumentTypes == argTypes) {
430       return &*iter;
431     }
432   }
433   return nullptr;
434 }
435 
436 // Search host runtime libraries for an exact type match.
SearchHostRuntime(const std::string & name,DynamicType resultType,const std::vector<DynamicType> & argTypes)437 static const HostRuntimeFunction *SearchHostRuntime(const std::string &name,
438     DynamicType resultType, const std::vector<DynamicType> &argTypes) {
439   // TODO: When command line options regarding targeted numerical library is
440   // available, this needs to be revisited to take it into account. So far,
441   // default to libpgmath if F18 is built with it.
442 #if LINK_WITH_LIBPGMATH
443   if (const auto *map{
444           GetHostRuntimeMap(LibraryVersion::PgmathPrecise, resultType)}) {
445     if (const auto *hostFunction{
446             SearchInHostRuntimeMap(*map, name, resultType, argTypes)}) {
447       return hostFunction;
448     }
449   }
450   // Default to libm if functions or types are not available in pgmath.
451 #endif
452   if (const auto *map{GetHostRuntimeMap(LibraryVersion::Libm, resultType)}) {
453     if (const auto *hostFunction{
454             SearchInHostRuntimeMap(*map, name, resultType, argTypes)}) {
455       return hostFunction;
456     }
457   }
458   return nullptr;
459 }
460 
461 // Return a DynamicType that can hold all values of a given type.
462 // This is used to allow 16bit float to be folded with 32bits and
463 // x87 float to be folded with IEEE 128bits.
BiggerType(DynamicType type)464 static DynamicType BiggerType(DynamicType type) {
465   if (type.category() == TypeCategory::Real ||
466       type.category() == TypeCategory::Complex) {
467     // 16 bits floats to IEEE 32 bits float
468     if (type.kind() == common::RealKindForPrecision(11) ||
469         type.kind() == common::RealKindForPrecision(8)) {
470       return {type.category(), common::RealKindForPrecision(24)};
471     }
472     // x87 float to IEEE 128 bits float
473     if (type.kind() == common::RealKindForPrecision(64)) {
474       return {type.category(), common::RealKindForPrecision(113)};
475     }
476   }
477   return type;
478 }
479 
GetHostRuntimeWrapper(const std::string & name,DynamicType resultType,const std::vector<DynamicType> & argTypes)480 std::optional<HostRuntimeWrapper> GetHostRuntimeWrapper(const std::string &name,
481     DynamicType resultType, const std::vector<DynamicType> &argTypes) {
482   if (const auto *hostFunction{SearchHostRuntime(name, resultType, argTypes)}) {
483     return hostFunction->folder;
484   }
485   // If no exact match, search with "bigger" types and insert type
486   // conversions around the folder.
487   std::vector<evaluate::DynamicType> biggerArgTypes;
488   evaluate::DynamicType biggerResultType{BiggerType(resultType)};
489   for (auto type : argTypes) {
490     biggerArgTypes.emplace_back(BiggerType(type));
491   }
492   if (const auto *hostFunction{
493           SearchHostRuntime(name, biggerResultType, biggerArgTypes)}) {
494     return [hostFunction, resultType](
495                FoldingContext &context, std::vector<Expr<SomeType>> &&args) {
496       auto nArgs{args.size()};
497       for (size_t i{0}; i < nArgs; ++i) {
498         args[i] = Fold(context,
499             ConvertToType(hostFunction->argumentTypes[i], std::move(args[i]))
500                 .value());
501       }
502       return Fold(context,
503           ConvertToType(
504               resultType, hostFunction->folder(context, std::move(args)))
505               .value());
506     };
507   }
508   return std::nullopt;
509 }
510 } // namespace Fortran::evaluate
511