1 //===-- IntrinsicCall.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 // Helper routines for constructing the FIR dialect of MLIR. As FIR is a
10 // dialect of MLIR, it makes extensive use of MLIR interfaces and MLIR's coding
11 // style (https://mlir.llvm.org/getting_started/DeveloperGuide/) is used in this
12 // module.
13 //
14 //===----------------------------------------------------------------------===//
15 
16 #include "flang/Lower/IntrinsicCall.h"
17 #include "flang/Common/static-multimap-view.h"
18 #include "flang/Lower/SymbolMap.h"
19 #include "flang/Lower/Todo.h"
20 #include "flang/Optimizer/Builder/Complex.h"
21 #include "flang/Optimizer/Builder/FIRBuilder.h"
22 #include "flang/Optimizer/Builder/Runtime/RTBuilder.h"
23 #include "flang/Optimizer/Support/FatalError.h"
24 #include "llvm/Support/CommandLine.h"
25 
26 #define DEBUG_TYPE "flang-lower-intrinsic"
27 
28 #define PGMATH_DECLARE
29 #include "flang/Evaluate/pgmath.h.inc"
30 
31 /// This file implements lowering of Fortran intrinsic procedures.
32 /// Intrinsics are lowered to a mix of FIR and MLIR operations as
33 /// well as call to runtime functions or LLVM intrinsics.
34 
35 /// Lowering of intrinsic procedure calls is based on a map that associates
36 /// Fortran intrinsic generic names to FIR generator functions.
37 /// All generator functions are member functions of the IntrinsicLibrary class
38 /// and have the same interface.
39 /// If no generator is given for an intrinsic name, a math runtime library
40 /// is searched for an implementation and, if a runtime function is found,
41 /// a call is generated for it. LLVM intrinsics are handled as a math
42 /// runtime library here.
43 
44 fir::ExtendedValue Fortran::lower::getAbsentIntrinsicArgument() {
45   return fir::UnboxedValue{};
46 }
47 
48 // TODO error handling -> return a code or directly emit messages ?
49 struct IntrinsicLibrary {
50 
51   // Constructors.
52   explicit IntrinsicLibrary(fir::FirOpBuilder &builder, mlir::Location loc)
53       : builder{builder}, loc{loc} {}
54   IntrinsicLibrary() = delete;
55   IntrinsicLibrary(const IntrinsicLibrary &) = delete;
56 
57   /// Generate FIR for call to Fortran intrinsic \p name with arguments \p arg
58   /// and expected result type \p resultType.
59   fir::ExtendedValue genIntrinsicCall(llvm::StringRef name,
60                                       llvm::Optional<mlir::Type> resultType,
61                                       llvm::ArrayRef<fir::ExtendedValue> arg);
62 
63   /// Search a runtime function that is associated to the generic intrinsic name
64   /// and whose signature matches the intrinsic arguments and result types.
65   /// If no such runtime function is found but a runtime function associated
66   /// with the Fortran generic exists and has the same number of arguments,
67   /// conversions will be inserted before and/or after the call. This is to
68   /// mainly to allow 16 bits float support even-though little or no math
69   /// runtime is currently available for it.
70   mlir::Value genRuntimeCall(llvm::StringRef name, mlir::Type,
71                              llvm::ArrayRef<mlir::Value>);
72 
73   using RuntimeCallGenerator = std::function<mlir::Value(
74       fir::FirOpBuilder &, mlir::Location, llvm::ArrayRef<mlir::Value>)>;
75   RuntimeCallGenerator
76   getRuntimeCallGenerator(llvm::StringRef name,
77                           mlir::FunctionType soughtFuncType);
78 
79   /// Lowering for the ABS intrinsic. The ABS intrinsic expects one argument in
80   /// the llvm::ArrayRef. The ABS intrinsic is lowered into MLIR/FIR operation
81   /// if the argument is an integer, into llvm intrinsics if the argument is
82   /// real and to the `hypot` math routine if the argument is of complex type.
83   mlir::Value genAbs(mlir::Type, llvm::ArrayRef<mlir::Value>);
84   /// Lowering for the IAND intrinsic. The IAND intrinsic expects two arguments
85   /// in the llvm::ArrayRef.
86   mlir::Value genIand(mlir::Type, llvm::ArrayRef<mlir::Value>);
87   /// Define the different FIR generators that can be mapped to intrinsic to
88   /// generate the related code. The intrinsic is lowered into an MLIR
89   /// arith::AndIOp.
90   using ElementalGenerator = decltype(&IntrinsicLibrary::genAbs);
91   using Generator = std::variant<ElementalGenerator>;
92 
93   /// Generate calls to ElementalGenerator, handling the elemental aspects
94   template <typename GeneratorType>
95   fir::ExtendedValue
96   genElementalCall(GeneratorType, llvm::StringRef name, mlir::Type resultType,
97                    llvm::ArrayRef<fir::ExtendedValue> args, bool outline);
98 
99   /// Helper to invoke code generator for the intrinsics given arguments.
100   mlir::Value invokeGenerator(ElementalGenerator generator,
101                               mlir::Type resultType,
102                               llvm::ArrayRef<mlir::Value> args);
103   mlir::Value invokeGenerator(RuntimeCallGenerator generator,
104                               mlir::Type resultType,
105                               llvm::ArrayRef<mlir::Value> args);
106   fir::FirOpBuilder &builder;
107   mlir::Location loc;
108 };
109 
110 struct IntrinsicDummyArgument {
111   const char *name = nullptr;
112   Fortran::lower::LowerIntrinsicArgAs lowerAs =
113       Fortran::lower::LowerIntrinsicArgAs::Value;
114   bool handleDynamicOptional = false;
115 };
116 
117 struct Fortran::lower::IntrinsicArgumentLoweringRules {
118   /// There is no more than 7 non repeated arguments in Fortran intrinsics.
119   IntrinsicDummyArgument args[7];
120   constexpr bool hasDefaultRules() const { return args[0].name == nullptr; }
121 };
122 
123 /// Structure describing what needs to be done to lower intrinsic "name".
124 struct IntrinsicHandler {
125   const char *name;
126   IntrinsicLibrary::Generator generator;
127   Fortran::lower::IntrinsicArgumentLoweringRules argLoweringRules = {};
128 };
129 
130 using I = IntrinsicLibrary;
131 
132 /// Table that drives the fir generation depending on the intrinsic.
133 /// one to one mapping with Fortran arguments. If no mapping is
134 /// defined here for a generic intrinsic, genRuntimeCall will be called
135 /// to look for a match in the runtime a emit a call. Note that the argument
136 /// lowering rules for an intrinsic need to be provided only if at least one
137 /// argument must not be lowered by value. In which case, the lowering rules
138 /// should be provided for all the intrinsic arguments for completeness.
139 static constexpr IntrinsicHandler handlers[]{
140     {"abs", &I::genAbs},
141     {"iand", &I::genIand},
142 };
143 
144 static const IntrinsicHandler *findIntrinsicHandler(llvm::StringRef name) {
145   auto compare = [](const IntrinsicHandler &handler, llvm::StringRef name) {
146     return name.compare(handler.name) > 0;
147   };
148   auto result =
149       std::lower_bound(std::begin(handlers), std::end(handlers), name, compare);
150   return result != std::end(handlers) && result->name == name ? result
151                                                               : nullptr;
152 }
153 
154 //===----------------------------------------------------------------------===//
155 // Math runtime description and matching utility
156 //===----------------------------------------------------------------------===//
157 
158 /// Command line option to modify math runtime version used to implement
159 /// intrinsics.
160 enum MathRuntimeVersion { fastVersion, llvmOnly };
161 llvm::cl::opt<MathRuntimeVersion> mathRuntimeVersion(
162     "math-runtime", llvm::cl::desc("Select math runtime version:"),
163     llvm::cl::values(
164         clEnumValN(fastVersion, "fast", "use pgmath fast runtime"),
165         clEnumValN(llvmOnly, "llvm",
166                    "only use LLVM intrinsics (may be incomplete)")),
167     llvm::cl::init(fastVersion));
168 
169 struct RuntimeFunction {
170   // llvm::StringRef comparison operator are not constexpr, so use string_view.
171   using Key = std::string_view;
172   // Needed for implicit compare with keys.
173   constexpr operator Key() const { return key; }
174   Key key; // intrinsic name
175   llvm::StringRef symbol;
176   fir::runtime::FuncTypeBuilderFunc typeGenerator;
177 };
178 
179 #define RUNTIME_STATIC_DESCRIPTION(name, func)                                 \
180   {#name, #func, fir::runtime::RuntimeTableKey<decltype(func)>::getTypeModel()},
181 static constexpr RuntimeFunction pgmathFast[] = {
182 #define PGMATH_FAST
183 #define PGMATH_USE_ALL_TYPES(name, func) RUNTIME_STATIC_DESCRIPTION(name, func)
184 #include "flang/Evaluate/pgmath.h.inc"
185 };
186 
187 static mlir::FunctionType genF32F32FuncType(mlir::MLIRContext *context) {
188   mlir::Type t = mlir::FloatType::getF32(context);
189   return mlir::FunctionType::get(context, {t}, {t});
190 }
191 
192 static mlir::FunctionType genF64F64FuncType(mlir::MLIRContext *context) {
193   mlir::Type t = mlir::FloatType::getF64(context);
194   return mlir::FunctionType::get(context, {t}, {t});
195 }
196 
197 static mlir::FunctionType genF32F32F32FuncType(mlir::MLIRContext *context) {
198   auto t = mlir::FloatType::getF32(context);
199   return mlir::FunctionType::get(context, {t, t}, {t});
200 }
201 
202 static mlir::FunctionType genF64F64F64FuncType(mlir::MLIRContext *context) {
203   auto t = mlir::FloatType::getF64(context);
204   return mlir::FunctionType::get(context, {t, t}, {t});
205 }
206 
207 // TODO : Fill-up this table with more intrinsic.
208 // Note: These are also defined as operations in LLVM dialect. See if this
209 // can be use and has advantages.
210 static constexpr RuntimeFunction llvmIntrinsics[] = {
211     {"abs", "llvm.fabs.f32", genF32F32FuncType},
212     {"abs", "llvm.fabs.f64", genF64F64FuncType},
213     {"pow", "llvm.pow.f32", genF32F32F32FuncType},
214     {"pow", "llvm.pow.f64", genF64F64F64FuncType},
215 };
216 
217 // This helper class computes a "distance" between two function types.
218 // The distance measures how many narrowing conversions of actual arguments
219 // and result of "from" must be made in order to use "to" instead of "from".
220 // For instance, the distance between ACOS(REAL(10)) and ACOS(REAL(8)) is
221 // greater than the one between ACOS(REAL(10)) and ACOS(REAL(16)). This means
222 // if no implementation of ACOS(REAL(10)) is available, it is better to use
223 // ACOS(REAL(16)) with casts rather than ACOS(REAL(8)).
224 // Note that this is not a symmetric distance and the order of "from" and "to"
225 // arguments matters, d(foo, bar) may not be the same as d(bar, foo) because it
226 // may be safe to replace foo by bar, but not the opposite.
227 class FunctionDistance {
228 public:
229   FunctionDistance() : infinite{true} {}
230 
231   FunctionDistance(mlir::FunctionType from, mlir::FunctionType to) {
232     unsigned nInputs = from.getNumInputs();
233     unsigned nResults = from.getNumResults();
234     if (nResults != to.getNumResults() || nInputs != to.getNumInputs()) {
235       infinite = true;
236     } else {
237       for (decltype(nInputs) i = 0; i < nInputs && !infinite; ++i)
238         addArgumentDistance(from.getInput(i), to.getInput(i));
239       for (decltype(nResults) i = 0; i < nResults && !infinite; ++i)
240         addResultDistance(to.getResult(i), from.getResult(i));
241     }
242   }
243 
244   /// Beware both d1.isSmallerThan(d2) *and* d2.isSmallerThan(d1) may be
245   /// false if both d1 and d2 are infinite. This implies that
246   ///  d1.isSmallerThan(d2) is not equivalent to !d2.isSmallerThan(d1)
247   bool isSmallerThan(const FunctionDistance &d) const {
248     return !infinite &&
249            (d.infinite || std::lexicographical_compare(
250                               conversions.begin(), conversions.end(),
251                               d.conversions.begin(), d.conversions.end()));
252   }
253 
254   bool isLosingPrecision() const {
255     return conversions[narrowingArg] != 0 || conversions[extendingResult] != 0;
256   }
257 
258   bool isInfinite() const { return infinite; }
259 
260 private:
261   enum class Conversion { Forbidden, None, Narrow, Extend };
262 
263   void addArgumentDistance(mlir::Type from, mlir::Type to) {
264     switch (conversionBetweenTypes(from, to)) {
265     case Conversion::Forbidden:
266       infinite = true;
267       break;
268     case Conversion::None:
269       break;
270     case Conversion::Narrow:
271       conversions[narrowingArg]++;
272       break;
273     case Conversion::Extend:
274       conversions[nonNarrowingArg]++;
275       break;
276     }
277   }
278 
279   void addResultDistance(mlir::Type from, mlir::Type to) {
280     switch (conversionBetweenTypes(from, to)) {
281     case Conversion::Forbidden:
282       infinite = true;
283       break;
284     case Conversion::None:
285       break;
286     case Conversion::Narrow:
287       conversions[nonExtendingResult]++;
288       break;
289     case Conversion::Extend:
290       conversions[extendingResult]++;
291       break;
292     }
293   }
294 
295   // Floating point can be mlir::FloatType or fir::real
296   static unsigned getFloatingPointWidth(mlir::Type t) {
297     if (auto f{t.dyn_cast<mlir::FloatType>()})
298       return f.getWidth();
299     // FIXME: Get width another way for fir.real/complex
300     // - use fir/KindMapping.h and llvm::Type
301     // - or use evaluate/type.h
302     if (auto r{t.dyn_cast<fir::RealType>()})
303       return r.getFKind() * 4;
304     if (auto cplx{t.dyn_cast<fir::ComplexType>()})
305       return cplx.getFKind() * 4;
306     llvm_unreachable("not a floating-point type");
307   }
308 
309   static Conversion conversionBetweenTypes(mlir::Type from, mlir::Type to) {
310     if (from == to)
311       return Conversion::None;
312 
313     if (auto fromIntTy{from.dyn_cast<mlir::IntegerType>()}) {
314       if (auto toIntTy{to.dyn_cast<mlir::IntegerType>()}) {
315         return fromIntTy.getWidth() > toIntTy.getWidth() ? Conversion::Narrow
316                                                          : Conversion::Extend;
317       }
318     }
319 
320     if (fir::isa_real(from) && fir::isa_real(to)) {
321       return getFloatingPointWidth(from) > getFloatingPointWidth(to)
322                  ? Conversion::Narrow
323                  : Conversion::Extend;
324     }
325 
326     if (auto fromCplxTy{from.dyn_cast<fir::ComplexType>()}) {
327       if (auto toCplxTy{to.dyn_cast<fir::ComplexType>()}) {
328         return getFloatingPointWidth(fromCplxTy) >
329                        getFloatingPointWidth(toCplxTy)
330                    ? Conversion::Narrow
331                    : Conversion::Extend;
332       }
333     }
334     // Notes:
335     // - No conversion between character types, specialization of runtime
336     // functions should be made instead.
337     // - It is not clear there is a use case for automatic conversions
338     // around Logical and it may damage hidden information in the physical
339     // storage so do not do it.
340     return Conversion::Forbidden;
341   }
342 
343   // Below are indexes to access data in conversions.
344   // The order in data does matter for lexicographical_compare
345   enum {
346     narrowingArg = 0,   // usually bad
347     extendingResult,    // usually bad
348     nonExtendingResult, // usually ok
349     nonNarrowingArg,    // usually ok
350     dataSize
351   };
352 
353   std::array<int, dataSize> conversions = {};
354   bool infinite = false; // When forbidden conversion or wrong argument number
355 };
356 
357 /// Build mlir::FuncOp from runtime symbol description and add
358 /// fir.runtime attribute.
359 static mlir::FuncOp getFuncOp(mlir::Location loc, fir::FirOpBuilder &builder,
360                               const RuntimeFunction &runtime) {
361   mlir::FuncOp function = builder.addNamedFunction(
362       loc, runtime.symbol, runtime.typeGenerator(builder.getContext()));
363   function->setAttr("fir.runtime", builder.getUnitAttr());
364   return function;
365 }
366 
367 /// Select runtime function that has the smallest distance to the intrinsic
368 /// function type and that will not imply narrowing arguments or extending the
369 /// result.
370 /// If nothing is found, the mlir::FuncOp will contain a nullptr.
371 mlir::FuncOp searchFunctionInLibrary(
372     mlir::Location loc, fir::FirOpBuilder &builder,
373     const Fortran::common::StaticMultimapView<RuntimeFunction> &lib,
374     llvm::StringRef name, mlir::FunctionType funcType,
375     const RuntimeFunction **bestNearMatch,
376     FunctionDistance &bestMatchDistance) {
377   std::pair<const RuntimeFunction *, const RuntimeFunction *> range =
378       lib.equal_range(name);
379   for (auto iter = range.first; iter != range.second && iter; ++iter) {
380     const RuntimeFunction &impl = *iter;
381     mlir::FunctionType implType = impl.typeGenerator(builder.getContext());
382     if (funcType == implType)
383       return getFuncOp(loc, builder, impl); // exact match
384 
385     FunctionDistance distance(funcType, implType);
386     if (distance.isSmallerThan(bestMatchDistance)) {
387       *bestNearMatch = &impl;
388       bestMatchDistance = std::move(distance);
389     }
390   }
391   return {};
392 }
393 
394 /// Search runtime for the best runtime function given an intrinsic name
395 /// and interface. The interface may not be a perfect match in which case
396 /// the caller is responsible to insert argument and return value conversions.
397 /// If nothing is found, the mlir::FuncOp will contain a nullptr.
398 static mlir::FuncOp getRuntimeFunction(mlir::Location loc,
399                                        fir::FirOpBuilder &builder,
400                                        llvm::StringRef name,
401                                        mlir::FunctionType funcType) {
402   const RuntimeFunction *bestNearMatch = nullptr;
403   FunctionDistance bestMatchDistance{};
404   mlir::FuncOp match;
405   using RtMap = Fortran::common::StaticMultimapView<RuntimeFunction>;
406   static constexpr RtMap pgmathF(pgmathFast);
407   static_assert(pgmathF.Verify() && "map must be sorted");
408   if (mathRuntimeVersion == fastVersion) {
409     match = searchFunctionInLibrary(loc, builder, pgmathF, name, funcType,
410                                     &bestNearMatch, bestMatchDistance);
411   } else {
412     assert(mathRuntimeVersion == llvmOnly && "unknown math runtime");
413   }
414   if (match)
415     return match;
416 
417   // Go through llvm intrinsics if not exact match in libpgmath or if
418   // mathRuntimeVersion == llvmOnly
419   static constexpr RtMap llvmIntr(llvmIntrinsics);
420   static_assert(llvmIntr.Verify() && "map must be sorted");
421   if (mlir::FuncOp exactMatch =
422           searchFunctionInLibrary(loc, builder, llvmIntr, name, funcType,
423                                   &bestNearMatch, bestMatchDistance))
424     return exactMatch;
425 
426   if (bestNearMatch != nullptr) {
427     if (bestMatchDistance.isLosingPrecision()) {
428       // Using this runtime version requires narrowing the arguments
429       // or extending the result. It is not numerically safe. There
430       // is currently no quad math library that was described in
431       // lowering and could be used here. Emit an error and continue
432       // generating the code with the narrowing cast so that the user
433       // can get a complete list of the problematic intrinsic calls.
434       std::string message("TODO: no math runtime available for '");
435       llvm::raw_string_ostream sstream(message);
436       if (name == "pow") {
437         assert(funcType.getNumInputs() == 2 &&
438                "power operator has two arguments");
439         sstream << funcType.getInput(0) << " ** " << funcType.getInput(1);
440       } else {
441         sstream << name << "(";
442         if (funcType.getNumInputs() > 0)
443           sstream << funcType.getInput(0);
444         for (mlir::Type argType : funcType.getInputs().drop_front())
445           sstream << ", " << argType;
446         sstream << ")";
447       }
448       sstream << "'";
449       mlir::emitError(loc, message);
450     }
451     return getFuncOp(loc, builder, *bestNearMatch);
452   }
453   return {};
454 }
455 
456 /// Helpers to get function type from arguments and result type.
457 static mlir::FunctionType getFunctionType(llvm::Optional<mlir::Type> resultType,
458                                           llvm::ArrayRef<mlir::Value> arguments,
459                                           fir::FirOpBuilder &builder) {
460   llvm::SmallVector<mlir::Type> argTypes;
461   for (mlir::Value arg : arguments)
462     argTypes.push_back(arg.getType());
463   llvm::SmallVector<mlir::Type> resTypes;
464   if (resultType)
465     resTypes.push_back(*resultType);
466   return mlir::FunctionType::get(builder.getModule().getContext(), argTypes,
467                                  resTypes);
468 }
469 //===----------------------------------------------------------------------===//
470 // IntrinsicLibrary
471 //===----------------------------------------------------------------------===//
472 
473 template <typename GeneratorType>
474 fir::ExtendedValue IntrinsicLibrary::genElementalCall(
475     GeneratorType generator, llvm::StringRef name, mlir::Type resultType,
476     llvm::ArrayRef<fir::ExtendedValue> args, bool outline) {
477   llvm::SmallVector<mlir::Value> scalarArgs;
478   for (const fir::ExtendedValue &arg : args)
479     if (arg.getUnboxed() || arg.getCharBox())
480       scalarArgs.emplace_back(fir::getBase(arg));
481     else
482       fir::emitFatalError(loc, "nonscalar intrinsic argument");
483   return invokeGenerator(generator, resultType, scalarArgs);
484 }
485 
486 static fir::ExtendedValue
487 invokeHandler(IntrinsicLibrary::ElementalGenerator generator,
488               const IntrinsicHandler &handler,
489               llvm::Optional<mlir::Type> resultType,
490               llvm::ArrayRef<fir::ExtendedValue> args, bool outline,
491               IntrinsicLibrary &lib) {
492   assert(resultType && "expect elemental intrinsic to be functions");
493   return lib.genElementalCall(generator, handler.name, *resultType, args,
494                               outline);
495 }
496 
497 fir::ExtendedValue
498 IntrinsicLibrary::genIntrinsicCall(llvm::StringRef name,
499                                    llvm::Optional<mlir::Type> resultType,
500                                    llvm::ArrayRef<fir::ExtendedValue> args) {
501   if (const IntrinsicHandler *handler = findIntrinsicHandler(name)) {
502     bool outline = false;
503     return std::visit(
504         [&](auto &generator) -> fir::ExtendedValue {
505           return invokeHandler(generator, *handler, resultType, args, outline,
506                                *this);
507         },
508         handler->generator);
509   }
510 
511   TODO(loc, "genIntrinsicCall runtime");
512   return {};
513 }
514 
515 mlir::Value
516 IntrinsicLibrary::invokeGenerator(ElementalGenerator generator,
517                                   mlir::Type resultType,
518                                   llvm::ArrayRef<mlir::Value> args) {
519   return std::invoke(generator, *this, resultType, args);
520 }
521 
522 mlir::Value
523 IntrinsicLibrary::invokeGenerator(RuntimeCallGenerator generator,
524                                   mlir::Type resultType,
525                                   llvm::ArrayRef<mlir::Value> args) {
526   return generator(builder, loc, args);
527 }
528 IntrinsicLibrary::RuntimeCallGenerator
529 IntrinsicLibrary::getRuntimeCallGenerator(llvm::StringRef name,
530                                           mlir::FunctionType soughtFuncType) {
531   mlir::FuncOp funcOp = getRuntimeFunction(loc, builder, name, soughtFuncType);
532   if (!funcOp) {
533     mlir::emitError(loc,
534                     "TODO: missing intrinsic lowering: " + llvm::Twine(name));
535     llvm::errs() << "requested type was: " << soughtFuncType << "\n";
536     exit(1);
537   }
538 
539   mlir::FunctionType actualFuncType = funcOp.getType();
540   assert(actualFuncType.getNumResults() == soughtFuncType.getNumResults() &&
541          actualFuncType.getNumInputs() == soughtFuncType.getNumInputs() &&
542          actualFuncType.getNumResults() == 1 && "Bad intrinsic match");
543 
544   return [funcOp, actualFuncType,
545           soughtFuncType](fir::FirOpBuilder &builder, mlir::Location loc,
546                           llvm::ArrayRef<mlir::Value> args) {
547     llvm::SmallVector<mlir::Value> convertedArguments;
548     for (auto [fst, snd] : llvm::zip(actualFuncType.getInputs(), args))
549       convertedArguments.push_back(builder.createConvert(loc, fst, snd));
550     auto call = builder.create<fir::CallOp>(loc, funcOp, convertedArguments);
551     mlir::Type soughtType = soughtFuncType.getResult(0);
552     return builder.createConvert(loc, soughtType, call.getResult(0));
553   };
554 }
555 //===----------------------------------------------------------------------===//
556 // Code generators for the intrinsic
557 //===----------------------------------------------------------------------===//
558 
559 mlir::Value IntrinsicLibrary::genRuntimeCall(llvm::StringRef name,
560                                              mlir::Type resultType,
561                                              llvm::ArrayRef<mlir::Value> args) {
562   mlir::FunctionType soughtFuncType =
563       getFunctionType(resultType, args, builder);
564   return getRuntimeCallGenerator(name, soughtFuncType)(builder, loc, args);
565 }
566 
567 // ABS
568 mlir::Value IntrinsicLibrary::genAbs(mlir::Type resultType,
569                                      llvm::ArrayRef<mlir::Value> args) {
570   assert(args.size() == 1);
571   mlir::Value arg = args[0];
572   mlir::Type type = arg.getType();
573   if (fir::isa_real(type)) {
574     // Runtime call to fp abs. An alternative would be to use mlir
575     // math::AbsFOp but it does not support all fir floating point types.
576     return genRuntimeCall("abs", resultType, args);
577   }
578   if (auto intType = type.dyn_cast<mlir::IntegerType>()) {
579     // At the time of this implementation there is no abs op in mlir.
580     // So, implement abs here without branching.
581     mlir::Value shift =
582         builder.createIntegerConstant(loc, intType, intType.getWidth() - 1);
583     auto mask = builder.create<mlir::arith::ShRSIOp>(loc, arg, shift);
584     auto xored = builder.create<mlir::arith::XOrIOp>(loc, arg, mask);
585     return builder.create<mlir::arith::SubIOp>(loc, xored, mask);
586   }
587   if (fir::isa_complex(type)) {
588     // Use HYPOT to fulfill the no underflow/overflow requirement.
589     auto parts = fir::factory::Complex{builder, loc}.extractParts(arg);
590     llvm::SmallVector<mlir::Value> args = {parts.first, parts.second};
591     return genRuntimeCall("hypot", resultType, args);
592   }
593   llvm_unreachable("unexpected type in ABS argument");
594 }
595 
596 // IAND
597 mlir::Value IntrinsicLibrary::genIand(mlir::Type resultType,
598                                       llvm::ArrayRef<mlir::Value> args) {
599   assert(args.size() == 2);
600   return builder.create<mlir::arith::AndIOp>(loc, args[0], args[1]);
601 }
602 
603 //===----------------------------------------------------------------------===//
604 // Argument lowering rules interface
605 //===----------------------------------------------------------------------===//
606 
607 const Fortran::lower::IntrinsicArgumentLoweringRules *
608 Fortran::lower::getIntrinsicArgumentLowering(llvm::StringRef intrinsicName) {
609   if (const IntrinsicHandler *handler = findIntrinsicHandler(intrinsicName))
610     if (!handler->argLoweringRules.hasDefaultRules())
611       return &handler->argLoweringRules;
612   return nullptr;
613 }
614 
615 /// Return how argument \p argName should be lowered given the rules for the
616 /// intrinsic function.
617 Fortran::lower::ArgLoweringRule Fortran::lower::lowerIntrinsicArgumentAs(
618     mlir::Location loc, const IntrinsicArgumentLoweringRules &rules,
619     llvm::StringRef argName) {
620   for (const IntrinsicDummyArgument &arg : rules.args) {
621     if (arg.name && arg.name == argName)
622       return {arg.lowerAs, arg.handleDynamicOptional};
623   }
624   fir::emitFatalError(
625       loc, "internal: unknown intrinsic argument name in lowering '" + argName +
626                "'");
627 }
628 
629 //===----------------------------------------------------------------------===//
630 // Public intrinsic call helpers
631 //===----------------------------------------------------------------------===//
632 
633 fir::ExtendedValue
634 Fortran::lower::genIntrinsicCall(fir::FirOpBuilder &builder, mlir::Location loc,
635                                  llvm::StringRef name,
636                                  llvm::Optional<mlir::Type> resultType,
637                                  llvm::ArrayRef<fir::ExtendedValue> args) {
638   return IntrinsicLibrary{builder, loc}.genIntrinsicCall(name, resultType,
639                                                          args);
640 }
641 
642 mlir::Value Fortran::lower::genPow(fir::FirOpBuilder &builder,
643                                    mlir::Location loc, mlir::Type type,
644                                    mlir::Value x, mlir::Value y) {
645   return IntrinsicLibrary{builder, loc}.genRuntimeCall("pow", type, {x, y});
646 }
647