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