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/Mangler.h"
19 #include "flang/Lower/StatementContext.h"
20 #include "flang/Lower/SymbolMap.h"
21 #include "flang/Lower/Todo.h"
22 #include "flang/Optimizer/Builder/Character.h"
23 #include "flang/Optimizer/Builder/Complex.h"
24 #include "flang/Optimizer/Builder/FIRBuilder.h"
25 #include "flang/Optimizer/Builder/MutableBox.h"
26 #include "flang/Optimizer/Builder/Runtime/RTBuilder.h"
27 #include "flang/Optimizer/Builder/Runtime/Reduction.h"
28 #include "flang/Optimizer/Support/FatalError.h"
29 #include "mlir/Dialect/LLVMIR/LLVMDialect.h"
30 #include "llvm/Support/CommandLine.h"
31 
32 #define DEBUG_TYPE "flang-lower-intrinsic"
33 
34 #define PGMATH_DECLARE
35 #include "flang/Evaluate/pgmath.h.inc"
36 
37 /// Enums used to templatize and share lowering of MIN and MAX.
38 enum class Extremum { Min, Max };
39 
40 // There are different ways to deal with NaNs in MIN and MAX.
41 // Known existing behaviors are listed below and can be selected for
42 // f18 MIN/MAX implementation.
43 enum class ExtremumBehavior {
44   // Note: the Signaling/quiet aspect of NaNs in the behaviors below are
45   // not described because there is no way to control/observe such aspect in
46   // MLIR/LLVM yet. The IEEE behaviors come with requirements regarding this
47   // aspect that are therefore currently not enforced. In the descriptions
48   // below, NaNs can be signaling or quite. Returned NaNs may be signaling
49   // if one of the input NaN was signaling but it cannot be guaranteed either.
50   // Existing compilers using an IEEE behavior (gfortran) also do not fulfill
51   // signaling/quiet requirements.
52   IeeeMinMaximumNumber,
53   // IEEE minimumNumber/maximumNumber behavior (754-2019, section 9.6):
54   // If one of the argument is and number and the other is NaN, return the
55   // number. If both arguements are NaN, return NaN.
56   // Compilers: gfortran.
57   IeeeMinMaximum,
58   // IEEE minimum/maximum behavior (754-2019, section 9.6):
59   // If one of the argument is NaN, return NaN.
60   MinMaxss,
61   // x86 minss/maxss behavior:
62   // If the second argument is a number and the other is NaN, return the number.
63   // In all other cases where at least one operand is NaN, return NaN.
64   // Compilers: xlf (only for MAX), ifort, pgfortran -nollvm, and nagfor.
65   PgfortranLlvm,
66   // "Opposite of" x86 minss/maxss behavior:
67   // If the first argument is a number and the other is NaN, return the
68   // number.
69   // In all other cases where at least one operand is NaN, return NaN.
70   // Compilers: xlf (only for MIN), and pgfortran (with llvm).
71   IeeeMinMaxNum
72   // IEEE minNum/maxNum behavior (754-2008, section 5.3.1):
73   // TODO: Not implemented.
74   // It is the only behavior where the signaling/quiet aspect of a NaN argument
75   // impacts if the result should be NaN or the argument that is a number.
76   // LLVM/MLIR do not provide ways to observe this aspect, so it is not
77   // possible to implement it without some target dependent runtime.
78 };
79 
80 /// This file implements lowering of Fortran intrinsic procedures.
81 /// Intrinsics are lowered to a mix of FIR and MLIR operations as
82 /// well as call to runtime functions or LLVM intrinsics.
83 
84 /// Lowering of intrinsic procedure calls is based on a map that associates
85 /// Fortran intrinsic generic names to FIR generator functions.
86 /// All generator functions are member functions of the IntrinsicLibrary class
87 /// and have the same interface.
88 /// If no generator is given for an intrinsic name, a math runtime library
89 /// is searched for an implementation and, if a runtime function is found,
90 /// a call is generated for it. LLVM intrinsics are handled as a math
91 /// runtime library here.
92 
93 fir::ExtendedValue Fortran::lower::getAbsentIntrinsicArgument() {
94   return fir::UnboxedValue{};
95 }
96 
97 /// Test if an ExtendedValue is absent.
98 static bool isAbsent(const fir::ExtendedValue &exv) {
99   return !fir::getBase(exv);
100 }
101 
102 /// Process calls to Maxval, Minval, Product, Sum intrinsic functions that
103 /// take a DIM argument.
104 template <typename FD>
105 static fir::ExtendedValue
106 genFuncDim(FD funcDim, mlir::Type resultType, fir::FirOpBuilder &builder,
107            mlir::Location loc, Fortran::lower::StatementContext *stmtCtx,
108            llvm::StringRef errMsg, mlir::Value array, fir::ExtendedValue dimArg,
109            mlir::Value mask, int rank) {
110 
111   // Create mutable fir.box to be passed to the runtime for the result.
112   mlir::Type resultArrayType = builder.getVarLenSeqTy(resultType, rank - 1);
113   fir::MutableBoxValue resultMutableBox =
114       fir::factory::createTempMutableBox(builder, loc, resultArrayType);
115   mlir::Value resultIrBox =
116       fir::factory::getMutableIRBox(builder, loc, resultMutableBox);
117 
118   mlir::Value dim =
119       isAbsent(dimArg)
120           ? builder.createIntegerConstant(loc, builder.getIndexType(), 0)
121           : fir::getBase(dimArg);
122   funcDim(builder, loc, resultIrBox, array, dim, mask);
123 
124   fir::ExtendedValue res =
125       fir::factory::genMutableBoxRead(builder, loc, resultMutableBox);
126   return res.match(
127       [&](const fir::ArrayBoxValue &box) -> fir::ExtendedValue {
128         // Add cleanup code
129         assert(stmtCtx);
130         fir::FirOpBuilder *bldr = &builder;
131         mlir::Value temp = box.getAddr();
132         stmtCtx->attachCleanup(
133             [=]() { bldr->create<fir::FreeMemOp>(loc, temp); });
134         return box;
135       },
136       [&](const fir::CharArrayBoxValue &box) -> fir::ExtendedValue {
137         // Add cleanup code
138         assert(stmtCtx);
139         fir::FirOpBuilder *bldr = &builder;
140         mlir::Value temp = box.getAddr();
141         stmtCtx->attachCleanup(
142             [=]() { bldr->create<fir::FreeMemOp>(loc, temp); });
143         return box;
144       },
145       [&](const auto &) -> fir::ExtendedValue {
146         fir::emitFatalError(loc, errMsg);
147       });
148 }
149 
150 /// Process calls to Product, Sum intrinsic functions
151 template <typename FN, typename FD>
152 static fir::ExtendedValue
153 genProdOrSum(FN func, FD funcDim, mlir::Type resultType,
154              fir::FirOpBuilder &builder, mlir::Location loc,
155              Fortran::lower::StatementContext *stmtCtx, llvm::StringRef errMsg,
156              llvm::ArrayRef<fir::ExtendedValue> args) {
157 
158   assert(args.size() == 3);
159 
160   // Handle required array argument
161   fir::BoxValue arryTmp = builder.createBox(loc, args[0]);
162   mlir::Value array = fir::getBase(arryTmp);
163   int rank = arryTmp.rank();
164   assert(rank >= 1);
165 
166   // Handle optional mask argument
167   auto mask = isAbsent(args[2])
168                   ? builder.create<fir::AbsentOp>(
169                         loc, fir::BoxType::get(builder.getI1Type()))
170                   : builder.createBox(loc, args[2]);
171 
172   bool absentDim = isAbsent(args[1]);
173 
174   // We call the type specific versions because the result is scalar
175   // in the case below.
176   if (absentDim || rank == 1) {
177     mlir::Type ty = array.getType();
178     mlir::Type arrTy = fir::dyn_cast_ptrOrBoxEleTy(ty);
179     auto eleTy = arrTy.cast<fir::SequenceType>().getEleTy();
180     if (fir::isa_complex(eleTy)) {
181       mlir::Value result = builder.createTemporary(loc, eleTy);
182       func(builder, loc, array, mask, result);
183       return builder.create<fir::LoadOp>(loc, result);
184     }
185     auto resultBox = builder.create<fir::AbsentOp>(
186         loc, fir::BoxType::get(builder.getI1Type()));
187     return func(builder, loc, array, mask, resultBox);
188   }
189   // Handle Product/Sum cases that have an array result.
190   return genFuncDim(funcDim, resultType, builder, loc, stmtCtx, errMsg, array,
191                     args[1], mask, rank);
192 }
193 
194 // TODO error handling -> return a code or directly emit messages ?
195 struct IntrinsicLibrary {
196 
197   // Constructors.
198   explicit IntrinsicLibrary(fir::FirOpBuilder &builder, mlir::Location loc,
199                             Fortran::lower::StatementContext *stmtCtx = nullptr)
200       : builder{builder}, loc{loc}, stmtCtx{stmtCtx} {}
201   IntrinsicLibrary() = delete;
202   IntrinsicLibrary(const IntrinsicLibrary &) = delete;
203 
204   /// Generate FIR for call to Fortran intrinsic \p name with arguments \p arg
205   /// and expected result type \p resultType.
206   fir::ExtendedValue genIntrinsicCall(llvm::StringRef name,
207                                       llvm::Optional<mlir::Type> resultType,
208                                       llvm::ArrayRef<fir::ExtendedValue> arg);
209 
210   /// Search a runtime function that is associated to the generic intrinsic name
211   /// and whose signature matches the intrinsic arguments and result types.
212   /// If no such runtime function is found but a runtime function associated
213   /// with the Fortran generic exists and has the same number of arguments,
214   /// conversions will be inserted before and/or after the call. This is to
215   /// mainly to allow 16 bits float support even-though little or no math
216   /// runtime is currently available for it.
217   mlir::Value genRuntimeCall(llvm::StringRef name, mlir::Type,
218                              llvm::ArrayRef<mlir::Value>);
219 
220   using RuntimeCallGenerator = std::function<mlir::Value(
221       fir::FirOpBuilder &, mlir::Location, llvm::ArrayRef<mlir::Value>)>;
222   RuntimeCallGenerator
223   getRuntimeCallGenerator(llvm::StringRef name,
224                           mlir::FunctionType soughtFuncType);
225 
226   /// Lowering for the ABS intrinsic. The ABS intrinsic expects one argument in
227   /// the llvm::ArrayRef. The ABS intrinsic is lowered into MLIR/FIR operation
228   /// if the argument is an integer, into llvm intrinsics if the argument is
229   /// real and to the `hypot` math routine if the argument is of complex type.
230   mlir::Value genAbs(mlir::Type, llvm::ArrayRef<mlir::Value>);
231   template <Extremum, ExtremumBehavior>
232   mlir::Value genExtremum(mlir::Type, llvm::ArrayRef<mlir::Value>);
233   /// Lowering for the IAND intrinsic. The IAND intrinsic expects two arguments
234   /// in the llvm::ArrayRef.
235   mlir::Value genIand(mlir::Type, llvm::ArrayRef<mlir::Value>);
236   fir::ExtendedValue genSum(mlir::Type, llvm::ArrayRef<fir::ExtendedValue>);
237   /// Define the different FIR generators that can be mapped to intrinsic to
238   /// generate the related code. The intrinsic is lowered into an MLIR
239   /// arith::AndIOp.
240   using ElementalGenerator = decltype(&IntrinsicLibrary::genAbs);
241   using ExtendedGenerator = decltype(&IntrinsicLibrary::genSum);
242   using Generator = std::variant<ElementalGenerator, ExtendedGenerator>;
243 
244   template <typename GeneratorType>
245   fir::ExtendedValue
246   outlineInExtendedWrapper(GeneratorType, llvm::StringRef name,
247                            llvm::Optional<mlir::Type> resultType,
248                            llvm::ArrayRef<fir::ExtendedValue> args);
249 
250   template <typename GeneratorType>
251   mlir::FuncOp getWrapper(GeneratorType, llvm::StringRef name,
252                           mlir::FunctionType, bool loadRefArguments = false);
253 
254   /// Generate calls to ElementalGenerator, handling the elemental aspects
255   template <typename GeneratorType>
256   fir::ExtendedValue
257   genElementalCall(GeneratorType, llvm::StringRef name, mlir::Type resultType,
258                    llvm::ArrayRef<fir::ExtendedValue> args, bool outline);
259 
260   /// Helper to invoke code generator for the intrinsics given arguments.
261   mlir::Value invokeGenerator(ElementalGenerator generator,
262                               mlir::Type resultType,
263                               llvm::ArrayRef<mlir::Value> args);
264   mlir::Value invokeGenerator(RuntimeCallGenerator generator,
265                               mlir::Type resultType,
266                               llvm::ArrayRef<mlir::Value> args);
267   mlir::Value invokeGenerator(ExtendedGenerator generator,
268                               mlir::Type resultType,
269                               llvm::ArrayRef<mlir::Value> args);
270 
271   fir::FirOpBuilder &builder;
272   mlir::Location loc;
273   Fortran::lower::StatementContext *stmtCtx;
274 };
275 
276 struct IntrinsicDummyArgument {
277   const char *name = nullptr;
278   Fortran::lower::LowerIntrinsicArgAs lowerAs =
279       Fortran::lower::LowerIntrinsicArgAs::Value;
280   bool handleDynamicOptional = false;
281 };
282 
283 struct Fortran::lower::IntrinsicArgumentLoweringRules {
284   /// There is no more than 7 non repeated arguments in Fortran intrinsics.
285   IntrinsicDummyArgument args[7];
286   constexpr bool hasDefaultRules() const { return args[0].name == nullptr; }
287 };
288 
289 /// Structure describing what needs to be done to lower intrinsic "name".
290 struct IntrinsicHandler {
291   const char *name;
292   IntrinsicLibrary::Generator generator;
293   // The following may be omitted in the table below.
294   Fortran::lower::IntrinsicArgumentLoweringRules argLoweringRules = {};
295   bool isElemental = true;
296 };
297 
298 constexpr auto asValue = Fortran::lower::LowerIntrinsicArgAs::Value;
299 constexpr auto asBox = Fortran::lower::LowerIntrinsicArgAs::Box;
300 using I = IntrinsicLibrary;
301 
302 /// Flag to indicate that an intrinsic argument has to be handled as
303 /// being dynamically optional (e.g. special handling when actual
304 /// argument is an optional variable in the current scope).
305 static constexpr bool handleDynamicOptional = true;
306 
307 /// Table that drives the fir generation depending on the intrinsic.
308 /// one to one mapping with Fortran arguments. If no mapping is
309 /// defined here for a generic intrinsic, genRuntimeCall will be called
310 /// to look for a match in the runtime a emit a call. Note that the argument
311 /// lowering rules for an intrinsic need to be provided only if at least one
312 /// argument must not be lowered by value. In which case, the lowering rules
313 /// should be provided for all the intrinsic arguments for completeness.
314 static constexpr IntrinsicHandler handlers[]{
315     {"abs", &I::genAbs},
316     {"iand", &I::genIand},
317     {"sum",
318      &I::genSum,
319      {{{"array", asBox},
320        {"dim", asValue},
321        {"mask", asBox, handleDynamicOptional}}},
322      /*isElemental=*/false},
323 };
324 
325 static const IntrinsicHandler *findIntrinsicHandler(llvm::StringRef name) {
326   auto compare = [](const IntrinsicHandler &handler, llvm::StringRef name) {
327     return name.compare(handler.name) > 0;
328   };
329   auto result =
330       std::lower_bound(std::begin(handlers), std::end(handlers), name, compare);
331   return result != std::end(handlers) && result->name == name ? result
332                                                               : nullptr;
333 }
334 
335 //===----------------------------------------------------------------------===//
336 // Math runtime description and matching utility
337 //===----------------------------------------------------------------------===//
338 
339 /// Command line option to modify math runtime version used to implement
340 /// intrinsics.
341 enum MathRuntimeVersion { fastVersion, llvmOnly };
342 llvm::cl::opt<MathRuntimeVersion> mathRuntimeVersion(
343     "math-runtime", llvm::cl::desc("Select math runtime version:"),
344     llvm::cl::values(
345         clEnumValN(fastVersion, "fast", "use pgmath fast runtime"),
346         clEnumValN(llvmOnly, "llvm",
347                    "only use LLVM intrinsics (may be incomplete)")),
348     llvm::cl::init(fastVersion));
349 
350 struct RuntimeFunction {
351   // llvm::StringRef comparison operator are not constexpr, so use string_view.
352   using Key = std::string_view;
353   // Needed for implicit compare with keys.
354   constexpr operator Key() const { return key; }
355   Key key; // intrinsic name
356   llvm::StringRef symbol;
357   fir::runtime::FuncTypeBuilderFunc typeGenerator;
358 };
359 
360 #define RUNTIME_STATIC_DESCRIPTION(name, func)                                 \
361   {#name, #func, fir::runtime::RuntimeTableKey<decltype(func)>::getTypeModel()},
362 static constexpr RuntimeFunction pgmathFast[] = {
363 #define PGMATH_FAST
364 #define PGMATH_USE_ALL_TYPES(name, func) RUNTIME_STATIC_DESCRIPTION(name, func)
365 #include "flang/Evaluate/pgmath.h.inc"
366 };
367 
368 static mlir::FunctionType genF32F32FuncType(mlir::MLIRContext *context) {
369   mlir::Type t = mlir::FloatType::getF32(context);
370   return mlir::FunctionType::get(context, {t}, {t});
371 }
372 
373 static mlir::FunctionType genF64F64FuncType(mlir::MLIRContext *context) {
374   mlir::Type t = mlir::FloatType::getF64(context);
375   return mlir::FunctionType::get(context, {t}, {t});
376 }
377 
378 static mlir::FunctionType genF32F32F32FuncType(mlir::MLIRContext *context) {
379   auto t = mlir::FloatType::getF32(context);
380   return mlir::FunctionType::get(context, {t, t}, {t});
381 }
382 
383 static mlir::FunctionType genF64F64F64FuncType(mlir::MLIRContext *context) {
384   auto t = mlir::FloatType::getF64(context);
385   return mlir::FunctionType::get(context, {t, t}, {t});
386 }
387 
388 // TODO : Fill-up this table with more intrinsic.
389 // Note: These are also defined as operations in LLVM dialect. See if this
390 // can be use and has advantages.
391 static constexpr RuntimeFunction llvmIntrinsics[] = {
392     {"abs", "llvm.fabs.f32", genF32F32FuncType},
393     {"abs", "llvm.fabs.f64", genF64F64FuncType},
394     {"pow", "llvm.pow.f32", genF32F32F32FuncType},
395     {"pow", "llvm.pow.f64", genF64F64F64FuncType},
396 };
397 
398 // This helper class computes a "distance" between two function types.
399 // The distance measures how many narrowing conversions of actual arguments
400 // and result of "from" must be made in order to use "to" instead of "from".
401 // For instance, the distance between ACOS(REAL(10)) and ACOS(REAL(8)) is
402 // greater than the one between ACOS(REAL(10)) and ACOS(REAL(16)). This means
403 // if no implementation of ACOS(REAL(10)) is available, it is better to use
404 // ACOS(REAL(16)) with casts rather than ACOS(REAL(8)).
405 // Note that this is not a symmetric distance and the order of "from" and "to"
406 // arguments matters, d(foo, bar) may not be the same as d(bar, foo) because it
407 // may be safe to replace foo by bar, but not the opposite.
408 class FunctionDistance {
409 public:
410   FunctionDistance() : infinite{true} {}
411 
412   FunctionDistance(mlir::FunctionType from, mlir::FunctionType to) {
413     unsigned nInputs = from.getNumInputs();
414     unsigned nResults = from.getNumResults();
415     if (nResults != to.getNumResults() || nInputs != to.getNumInputs()) {
416       infinite = true;
417     } else {
418       for (decltype(nInputs) i = 0; i < nInputs && !infinite; ++i)
419         addArgumentDistance(from.getInput(i), to.getInput(i));
420       for (decltype(nResults) i = 0; i < nResults && !infinite; ++i)
421         addResultDistance(to.getResult(i), from.getResult(i));
422     }
423   }
424 
425   /// Beware both d1.isSmallerThan(d2) *and* d2.isSmallerThan(d1) may be
426   /// false if both d1 and d2 are infinite. This implies that
427   ///  d1.isSmallerThan(d2) is not equivalent to !d2.isSmallerThan(d1)
428   bool isSmallerThan(const FunctionDistance &d) const {
429     return !infinite &&
430            (d.infinite || std::lexicographical_compare(
431                               conversions.begin(), conversions.end(),
432                               d.conversions.begin(), d.conversions.end()));
433   }
434 
435   bool isLosingPrecision() const {
436     return conversions[narrowingArg] != 0 || conversions[extendingResult] != 0;
437   }
438 
439   bool isInfinite() const { return infinite; }
440 
441 private:
442   enum class Conversion { Forbidden, None, Narrow, Extend };
443 
444   void addArgumentDistance(mlir::Type from, mlir::Type to) {
445     switch (conversionBetweenTypes(from, to)) {
446     case Conversion::Forbidden:
447       infinite = true;
448       break;
449     case Conversion::None:
450       break;
451     case Conversion::Narrow:
452       conversions[narrowingArg]++;
453       break;
454     case Conversion::Extend:
455       conversions[nonNarrowingArg]++;
456       break;
457     }
458   }
459 
460   void addResultDistance(mlir::Type from, mlir::Type to) {
461     switch (conversionBetweenTypes(from, to)) {
462     case Conversion::Forbidden:
463       infinite = true;
464       break;
465     case Conversion::None:
466       break;
467     case Conversion::Narrow:
468       conversions[nonExtendingResult]++;
469       break;
470     case Conversion::Extend:
471       conversions[extendingResult]++;
472       break;
473     }
474   }
475 
476   // Floating point can be mlir::FloatType or fir::real
477   static unsigned getFloatingPointWidth(mlir::Type t) {
478     if (auto f{t.dyn_cast<mlir::FloatType>()})
479       return f.getWidth();
480     // FIXME: Get width another way for fir.real/complex
481     // - use fir/KindMapping.h and llvm::Type
482     // - or use evaluate/type.h
483     if (auto r{t.dyn_cast<fir::RealType>()})
484       return r.getFKind() * 4;
485     if (auto cplx{t.dyn_cast<fir::ComplexType>()})
486       return cplx.getFKind() * 4;
487     llvm_unreachable("not a floating-point type");
488   }
489 
490   static Conversion conversionBetweenTypes(mlir::Type from, mlir::Type to) {
491     if (from == to)
492       return Conversion::None;
493 
494     if (auto fromIntTy{from.dyn_cast<mlir::IntegerType>()}) {
495       if (auto toIntTy{to.dyn_cast<mlir::IntegerType>()}) {
496         return fromIntTy.getWidth() > toIntTy.getWidth() ? Conversion::Narrow
497                                                          : Conversion::Extend;
498       }
499     }
500 
501     if (fir::isa_real(from) && fir::isa_real(to)) {
502       return getFloatingPointWidth(from) > getFloatingPointWidth(to)
503                  ? Conversion::Narrow
504                  : Conversion::Extend;
505     }
506 
507     if (auto fromCplxTy{from.dyn_cast<fir::ComplexType>()}) {
508       if (auto toCplxTy{to.dyn_cast<fir::ComplexType>()}) {
509         return getFloatingPointWidth(fromCplxTy) >
510                        getFloatingPointWidth(toCplxTy)
511                    ? Conversion::Narrow
512                    : Conversion::Extend;
513       }
514     }
515     // Notes:
516     // - No conversion between character types, specialization of runtime
517     // functions should be made instead.
518     // - It is not clear there is a use case for automatic conversions
519     // around Logical and it may damage hidden information in the physical
520     // storage so do not do it.
521     return Conversion::Forbidden;
522   }
523 
524   // Below are indexes to access data in conversions.
525   // The order in data does matter for lexicographical_compare
526   enum {
527     narrowingArg = 0,   // usually bad
528     extendingResult,    // usually bad
529     nonExtendingResult, // usually ok
530     nonNarrowingArg,    // usually ok
531     dataSize
532   };
533 
534   std::array<int, dataSize> conversions = {};
535   bool infinite = false; // When forbidden conversion or wrong argument number
536 };
537 
538 /// Build mlir::FuncOp from runtime symbol description and add
539 /// fir.runtime attribute.
540 static mlir::FuncOp getFuncOp(mlir::Location loc, fir::FirOpBuilder &builder,
541                               const RuntimeFunction &runtime) {
542   mlir::FuncOp function = builder.addNamedFunction(
543       loc, runtime.symbol, runtime.typeGenerator(builder.getContext()));
544   function->setAttr("fir.runtime", builder.getUnitAttr());
545   return function;
546 }
547 
548 /// Select runtime function that has the smallest distance to the intrinsic
549 /// function type and that will not imply narrowing arguments or extending the
550 /// result.
551 /// If nothing is found, the mlir::FuncOp will contain a nullptr.
552 mlir::FuncOp searchFunctionInLibrary(
553     mlir::Location loc, fir::FirOpBuilder &builder,
554     const Fortran::common::StaticMultimapView<RuntimeFunction> &lib,
555     llvm::StringRef name, mlir::FunctionType funcType,
556     const RuntimeFunction **bestNearMatch,
557     FunctionDistance &bestMatchDistance) {
558   std::pair<const RuntimeFunction *, const RuntimeFunction *> range =
559       lib.equal_range(name);
560   for (auto iter = range.first; iter != range.second && iter; ++iter) {
561     const RuntimeFunction &impl = *iter;
562     mlir::FunctionType implType = impl.typeGenerator(builder.getContext());
563     if (funcType == implType)
564       return getFuncOp(loc, builder, impl); // exact match
565 
566     FunctionDistance distance(funcType, implType);
567     if (distance.isSmallerThan(bestMatchDistance)) {
568       *bestNearMatch = &impl;
569       bestMatchDistance = std::move(distance);
570     }
571   }
572   return {};
573 }
574 
575 /// Search runtime for the best runtime function given an intrinsic name
576 /// and interface. The interface may not be a perfect match in which case
577 /// the caller is responsible to insert argument and return value conversions.
578 /// If nothing is found, the mlir::FuncOp will contain a nullptr.
579 static mlir::FuncOp getRuntimeFunction(mlir::Location loc,
580                                        fir::FirOpBuilder &builder,
581                                        llvm::StringRef name,
582                                        mlir::FunctionType funcType) {
583   const RuntimeFunction *bestNearMatch = nullptr;
584   FunctionDistance bestMatchDistance{};
585   mlir::FuncOp match;
586   using RtMap = Fortran::common::StaticMultimapView<RuntimeFunction>;
587   static constexpr RtMap pgmathF(pgmathFast);
588   static_assert(pgmathF.Verify() && "map must be sorted");
589   if (mathRuntimeVersion == fastVersion) {
590     match = searchFunctionInLibrary(loc, builder, pgmathF, name, funcType,
591                                     &bestNearMatch, bestMatchDistance);
592   } else {
593     assert(mathRuntimeVersion == llvmOnly && "unknown math runtime");
594   }
595   if (match)
596     return match;
597 
598   // Go through llvm intrinsics if not exact match in libpgmath or if
599   // mathRuntimeVersion == llvmOnly
600   static constexpr RtMap llvmIntr(llvmIntrinsics);
601   static_assert(llvmIntr.Verify() && "map must be sorted");
602   if (mlir::FuncOp exactMatch =
603           searchFunctionInLibrary(loc, builder, llvmIntr, name, funcType,
604                                   &bestNearMatch, bestMatchDistance))
605     return exactMatch;
606 
607   if (bestNearMatch != nullptr) {
608     if (bestMatchDistance.isLosingPrecision()) {
609       // Using this runtime version requires narrowing the arguments
610       // or extending the result. It is not numerically safe. There
611       // is currently no quad math library that was described in
612       // lowering and could be used here. Emit an error and continue
613       // generating the code with the narrowing cast so that the user
614       // can get a complete list of the problematic intrinsic calls.
615       std::string message("TODO: no math runtime available for '");
616       llvm::raw_string_ostream sstream(message);
617       if (name == "pow") {
618         assert(funcType.getNumInputs() == 2 &&
619                "power operator has two arguments");
620         sstream << funcType.getInput(0) << " ** " << funcType.getInput(1);
621       } else {
622         sstream << name << "(";
623         if (funcType.getNumInputs() > 0)
624           sstream << funcType.getInput(0);
625         for (mlir::Type argType : funcType.getInputs().drop_front())
626           sstream << ", " << argType;
627         sstream << ")";
628       }
629       sstream << "'";
630       mlir::emitError(loc, message);
631     }
632     return getFuncOp(loc, builder, *bestNearMatch);
633   }
634   return {};
635 }
636 
637 /// Helpers to get function type from arguments and result type.
638 static mlir::FunctionType getFunctionType(llvm::Optional<mlir::Type> resultType,
639                                           llvm::ArrayRef<mlir::Value> arguments,
640                                           fir::FirOpBuilder &builder) {
641   llvm::SmallVector<mlir::Type> argTypes;
642   for (mlir::Value arg : arguments)
643     argTypes.push_back(arg.getType());
644   llvm::SmallVector<mlir::Type> resTypes;
645   if (resultType)
646     resTypes.push_back(*resultType);
647   return mlir::FunctionType::get(builder.getModule().getContext(), argTypes,
648                                  resTypes);
649 }
650 
651 /// fir::ExtendedValue to mlir::Value translation layer
652 
653 fir::ExtendedValue toExtendedValue(mlir::Value val, fir::FirOpBuilder &builder,
654                                    mlir::Location loc) {
655   assert(val && "optional unhandled here");
656   mlir::Type type = val.getType();
657   mlir::Value base = val;
658   mlir::IndexType indexType = builder.getIndexType();
659   llvm::SmallVector<mlir::Value> extents;
660 
661   fir::factory::CharacterExprHelper charHelper{builder, loc};
662   // FIXME: we may want to allow non character scalar here.
663   if (charHelper.isCharacterScalar(type))
664     return charHelper.toExtendedValue(val);
665 
666   if (auto refType = type.dyn_cast<fir::ReferenceType>())
667     type = refType.getEleTy();
668 
669   if (auto arrayType = type.dyn_cast<fir::SequenceType>()) {
670     type = arrayType.getEleTy();
671     for (fir::SequenceType::Extent extent : arrayType.getShape()) {
672       if (extent == fir::SequenceType::getUnknownExtent())
673         break;
674       extents.emplace_back(
675           builder.createIntegerConstant(loc, indexType, extent));
676     }
677     // Last extent might be missing in case of assumed-size. If more extents
678     // could not be deduced from type, that's an error (a fir.box should
679     // have been used in the interface).
680     if (extents.size() + 1 < arrayType.getShape().size())
681       mlir::emitError(loc, "cannot retrieve array extents from type");
682   } else if (type.isa<fir::BoxType>() || type.isa<fir::RecordType>()) {
683     fir::emitFatalError(loc, "not yet implemented: descriptor or derived type");
684   }
685 
686   if (!extents.empty())
687     return fir::ArrayBoxValue{base, extents};
688   return base;
689 }
690 
691 mlir::Value toValue(const fir::ExtendedValue &val, fir::FirOpBuilder &builder,
692                     mlir::Location loc) {
693   if (const fir::CharBoxValue *charBox = val.getCharBox()) {
694     mlir::Value buffer = charBox->getBuffer();
695     if (buffer.getType().isa<fir::BoxCharType>())
696       return buffer;
697     return fir::factory::CharacterExprHelper{builder, loc}.createEmboxChar(
698         buffer, charBox->getLen());
699   }
700 
701   // FIXME: need to access other ExtendedValue variants and handle them
702   // properly.
703   return fir::getBase(val);
704 }
705 
706 //===----------------------------------------------------------------------===//
707 // IntrinsicLibrary
708 //===----------------------------------------------------------------------===//
709 
710 /// Emit a TODO error message for as yet unimplemented intrinsics.
711 static void crashOnMissingIntrinsic(mlir::Location loc, llvm::StringRef name) {
712   TODO(loc, "missing intrinsic lowering: " + llvm::Twine(name));
713 }
714 
715 template <typename GeneratorType>
716 fir::ExtendedValue IntrinsicLibrary::genElementalCall(
717     GeneratorType generator, llvm::StringRef name, mlir::Type resultType,
718     llvm::ArrayRef<fir::ExtendedValue> args, bool outline) {
719   llvm::SmallVector<mlir::Value> scalarArgs;
720   for (const fir::ExtendedValue &arg : args)
721     if (arg.getUnboxed() || arg.getCharBox())
722       scalarArgs.emplace_back(fir::getBase(arg));
723     else
724       fir::emitFatalError(loc, "nonscalar intrinsic argument");
725   return invokeGenerator(generator, resultType, scalarArgs);
726 }
727 
728 template <>
729 fir::ExtendedValue
730 IntrinsicLibrary::genElementalCall<IntrinsicLibrary::ExtendedGenerator>(
731     ExtendedGenerator generator, llvm::StringRef name, mlir::Type resultType,
732     llvm::ArrayRef<fir::ExtendedValue> args, bool outline) {
733   for (const fir::ExtendedValue &arg : args)
734     if (!arg.getUnboxed() && !arg.getCharBox())
735       fir::emitFatalError(loc, "nonscalar intrinsic argument");
736   if (outline)
737     return outlineInExtendedWrapper(generator, name, resultType, args);
738   return std::invoke(generator, *this, resultType, args);
739 }
740 
741 static fir::ExtendedValue
742 invokeHandler(IntrinsicLibrary::ElementalGenerator generator,
743               const IntrinsicHandler &handler,
744               llvm::Optional<mlir::Type> resultType,
745               llvm::ArrayRef<fir::ExtendedValue> args, bool outline,
746               IntrinsicLibrary &lib) {
747   assert(resultType && "expect elemental intrinsic to be functions");
748   return lib.genElementalCall(generator, handler.name, *resultType, args,
749                               outline);
750 }
751 
752 static fir::ExtendedValue
753 invokeHandler(IntrinsicLibrary::ExtendedGenerator generator,
754               const IntrinsicHandler &handler,
755               llvm::Optional<mlir::Type> resultType,
756               llvm::ArrayRef<fir::ExtendedValue> args, bool outline,
757               IntrinsicLibrary &lib) {
758   assert(resultType && "expect intrinsic function");
759   if (handler.isElemental)
760     return lib.genElementalCall(generator, handler.name, *resultType, args,
761                                 outline);
762   if (outline)
763     return lib.outlineInExtendedWrapper(generator, handler.name, *resultType,
764                                         args);
765   return std::invoke(generator, lib, *resultType, args);
766 }
767 
768 fir::ExtendedValue
769 IntrinsicLibrary::genIntrinsicCall(llvm::StringRef name,
770                                    llvm::Optional<mlir::Type> resultType,
771                                    llvm::ArrayRef<fir::ExtendedValue> args) {
772   if (const IntrinsicHandler *handler = findIntrinsicHandler(name)) {
773     bool outline = false;
774     return std::visit(
775         [&](auto &generator) -> fir::ExtendedValue {
776           return invokeHandler(generator, *handler, resultType, args, outline,
777                                *this);
778         },
779         handler->generator);
780   }
781 
782   if (!resultType)
783     // Subroutine should have a handler, they are likely missing for now.
784     crashOnMissingIntrinsic(loc, name);
785 
786   // Try the runtime if no special handler was defined for the
787   // intrinsic being called. Maths runtime only has numerical elemental.
788   // No optional arguments are expected at this point, the code will
789   // crash if it gets absent optional.
790 
791   // FIXME: using toValue to get the type won't work with array arguments.
792   llvm::SmallVector<mlir::Value> mlirArgs;
793   for (const fir::ExtendedValue &extendedVal : args) {
794     mlir::Value val = toValue(extendedVal, builder, loc);
795     if (!val)
796       // If an absent optional gets there, most likely its handler has just
797       // not yet been defined.
798       crashOnMissingIntrinsic(loc, name);
799     mlirArgs.emplace_back(val);
800   }
801   mlir::FunctionType soughtFuncType =
802       getFunctionType(*resultType, mlirArgs, builder);
803 
804   IntrinsicLibrary::RuntimeCallGenerator runtimeCallGenerator =
805       getRuntimeCallGenerator(name, soughtFuncType);
806   return genElementalCall(runtimeCallGenerator, name, *resultType, args,
807                           /* outline */ true);
808 }
809 
810 mlir::Value
811 IntrinsicLibrary::invokeGenerator(ElementalGenerator generator,
812                                   mlir::Type resultType,
813                                   llvm::ArrayRef<mlir::Value> args) {
814   return std::invoke(generator, *this, resultType, args);
815 }
816 
817 mlir::Value
818 IntrinsicLibrary::invokeGenerator(RuntimeCallGenerator generator,
819                                   mlir::Type resultType,
820                                   llvm::ArrayRef<mlir::Value> args) {
821   return generator(builder, loc, args);
822 }
823 
824 mlir::Value
825 IntrinsicLibrary::invokeGenerator(ExtendedGenerator generator,
826                                   mlir::Type resultType,
827                                   llvm::ArrayRef<mlir::Value> args) {
828   llvm::SmallVector<fir::ExtendedValue> extendedArgs;
829   for (mlir::Value arg : args)
830     extendedArgs.emplace_back(toExtendedValue(arg, builder, loc));
831   auto extendedResult = std::invoke(generator, *this, resultType, extendedArgs);
832   return toValue(extendedResult, builder, loc);
833 }
834 
835 template <typename GeneratorType>
836 mlir::FuncOp IntrinsicLibrary::getWrapper(GeneratorType generator,
837                                           llvm::StringRef name,
838                                           mlir::FunctionType funcType,
839                                           bool loadRefArguments) {
840   std::string wrapperName = fir::mangleIntrinsicProcedure(name, funcType);
841   mlir::FuncOp function = builder.getNamedFunction(wrapperName);
842   if (!function) {
843     // First time this wrapper is needed, build it.
844     function = builder.createFunction(loc, wrapperName, funcType);
845     function->setAttr("fir.intrinsic", builder.getUnitAttr());
846     auto internalLinkage = mlir::LLVM::linkage::Linkage::Internal;
847     auto linkage =
848         mlir::LLVM::LinkageAttr::get(builder.getContext(), internalLinkage);
849     function->setAttr("llvm.linkage", linkage);
850     function.addEntryBlock();
851 
852     // Create local context to emit code into the newly created function
853     // This new function is not linked to a source file location, only
854     // its calls will be.
855     auto localBuilder =
856         std::make_unique<fir::FirOpBuilder>(function, builder.getKindMap());
857     localBuilder->setInsertionPointToStart(&function.front());
858     // Location of code inside wrapper of the wrapper is independent from
859     // the location of the intrinsic call.
860     mlir::Location localLoc = localBuilder->getUnknownLoc();
861     llvm::SmallVector<mlir::Value> localArguments;
862     for (mlir::BlockArgument bArg : function.front().getArguments()) {
863       auto refType = bArg.getType().dyn_cast<fir::ReferenceType>();
864       if (loadRefArguments && refType) {
865         auto loaded = localBuilder->create<fir::LoadOp>(localLoc, bArg);
866         localArguments.push_back(loaded);
867       } else {
868         localArguments.push_back(bArg);
869       }
870     }
871 
872     IntrinsicLibrary localLib{*localBuilder, localLoc};
873 
874     assert(funcType.getNumResults() == 1 &&
875            "expect one result for intrinsic function wrapper type");
876     mlir::Type resultType = funcType.getResult(0);
877     auto result =
878         localLib.invokeGenerator(generator, resultType, localArguments);
879     localBuilder->create<mlir::func::ReturnOp>(localLoc, result);
880   } else {
881     // Wrapper was already built, ensure it has the sought type
882     assert(function.getType() == funcType &&
883            "conflict between intrinsic wrapper types");
884   }
885   return function;
886 }
887 
888 /// Helpers to detect absent optional (not yet supported in outlining).
889 bool static hasAbsentOptional(llvm::ArrayRef<fir::ExtendedValue> args) {
890   for (const fir::ExtendedValue &arg : args)
891     if (!fir::getBase(arg))
892       return true;
893   return false;
894 }
895 
896 template <typename GeneratorType>
897 fir::ExtendedValue IntrinsicLibrary::outlineInExtendedWrapper(
898     GeneratorType generator, llvm::StringRef name,
899     llvm::Optional<mlir::Type> resultType,
900     llvm::ArrayRef<fir::ExtendedValue> args) {
901   if (hasAbsentOptional(args))
902     TODO(loc, "cannot outline call to intrinsic " + llvm::Twine(name) +
903                   " with absent optional argument");
904   llvm::SmallVector<mlir::Value> mlirArgs;
905   for (const auto &extendedVal : args)
906     mlirArgs.emplace_back(toValue(extendedVal, builder, loc));
907   mlir::FunctionType funcType = getFunctionType(resultType, mlirArgs, builder);
908   mlir::FuncOp wrapper = getWrapper(generator, name, funcType);
909   auto call = builder.create<fir::CallOp>(loc, wrapper, mlirArgs);
910   if (resultType)
911     return toExtendedValue(call.getResult(0), builder, loc);
912   // Subroutine calls
913   return mlir::Value{};
914 }
915 
916 IntrinsicLibrary::RuntimeCallGenerator
917 IntrinsicLibrary::getRuntimeCallGenerator(llvm::StringRef name,
918                                           mlir::FunctionType soughtFuncType) {
919   mlir::FuncOp funcOp = getRuntimeFunction(loc, builder, name, soughtFuncType);
920   if (!funcOp) {
921     std::string buffer("not yet implemented: missing intrinsic lowering: ");
922     llvm::raw_string_ostream sstream(buffer);
923     sstream << name << "\nrequested type was: " << soughtFuncType << '\n';
924     fir::emitFatalError(loc, buffer);
925   }
926 
927   mlir::FunctionType actualFuncType = funcOp.getType();
928   assert(actualFuncType.getNumResults() == soughtFuncType.getNumResults() &&
929          actualFuncType.getNumInputs() == soughtFuncType.getNumInputs() &&
930          actualFuncType.getNumResults() == 1 && "Bad intrinsic match");
931 
932   return [funcOp, actualFuncType,
933           soughtFuncType](fir::FirOpBuilder &builder, mlir::Location loc,
934                           llvm::ArrayRef<mlir::Value> args) {
935     llvm::SmallVector<mlir::Value> convertedArguments;
936     for (auto [fst, snd] : llvm::zip(actualFuncType.getInputs(), args))
937       convertedArguments.push_back(builder.createConvert(loc, fst, snd));
938     auto call = builder.create<fir::CallOp>(loc, funcOp, convertedArguments);
939     mlir::Type soughtType = soughtFuncType.getResult(0);
940     return builder.createConvert(loc, soughtType, call.getResult(0));
941   };
942 }
943 //===----------------------------------------------------------------------===//
944 // Code generators for the intrinsic
945 //===----------------------------------------------------------------------===//
946 
947 mlir::Value IntrinsicLibrary::genRuntimeCall(llvm::StringRef name,
948                                              mlir::Type resultType,
949                                              llvm::ArrayRef<mlir::Value> args) {
950   mlir::FunctionType soughtFuncType =
951       getFunctionType(resultType, args, builder);
952   return getRuntimeCallGenerator(name, soughtFuncType)(builder, loc, args);
953 }
954 
955 // ABS
956 mlir::Value IntrinsicLibrary::genAbs(mlir::Type resultType,
957                                      llvm::ArrayRef<mlir::Value> args) {
958   assert(args.size() == 1);
959   mlir::Value arg = args[0];
960   mlir::Type type = arg.getType();
961   if (fir::isa_real(type)) {
962     // Runtime call to fp abs. An alternative would be to use mlir
963     // math::AbsFOp but it does not support all fir floating point types.
964     return genRuntimeCall("abs", resultType, args);
965   }
966   if (auto intType = type.dyn_cast<mlir::IntegerType>()) {
967     // At the time of this implementation there is no abs op in mlir.
968     // So, implement abs here without branching.
969     mlir::Value shift =
970         builder.createIntegerConstant(loc, intType, intType.getWidth() - 1);
971     auto mask = builder.create<mlir::arith::ShRSIOp>(loc, arg, shift);
972     auto xored = builder.create<mlir::arith::XOrIOp>(loc, arg, mask);
973     return builder.create<mlir::arith::SubIOp>(loc, xored, mask);
974   }
975   if (fir::isa_complex(type)) {
976     // Use HYPOT to fulfill the no underflow/overflow requirement.
977     auto parts = fir::factory::Complex{builder, loc}.extractParts(arg);
978     llvm::SmallVector<mlir::Value> args = {parts.first, parts.second};
979     return genRuntimeCall("hypot", resultType, args);
980   }
981   llvm_unreachable("unexpected type in ABS argument");
982 }
983 
984 // IAND
985 mlir::Value IntrinsicLibrary::genIand(mlir::Type resultType,
986                                       llvm::ArrayRef<mlir::Value> args) {
987   assert(args.size() == 2);
988   return builder.create<mlir::arith::AndIOp>(loc, args[0], args[1]);
989 }
990 
991 // Compare two FIR values and return boolean result as i1.
992 template <Extremum extremum, ExtremumBehavior behavior>
993 static mlir::Value createExtremumCompare(mlir::Location loc,
994                                          fir::FirOpBuilder &builder,
995                                          mlir::Value left, mlir::Value right) {
996   static constexpr mlir::arith::CmpIPredicate integerPredicate =
997       extremum == Extremum::Max ? mlir::arith::CmpIPredicate::sgt
998                                 : mlir::arith::CmpIPredicate::slt;
999   static constexpr mlir::arith::CmpFPredicate orderedCmp =
1000       extremum == Extremum::Max ? mlir::arith::CmpFPredicate::OGT
1001                                 : mlir::arith::CmpFPredicate::OLT;
1002   mlir::Type type = left.getType();
1003   mlir::Value result;
1004   if (fir::isa_real(type)) {
1005     // Note: the signaling/quit aspect of the result required by IEEE
1006     // cannot currently be obtained with LLVM without ad-hoc runtime.
1007     if constexpr (behavior == ExtremumBehavior::IeeeMinMaximumNumber) {
1008       // Return the number if one of the inputs is NaN and the other is
1009       // a number.
1010       auto leftIsResult =
1011           builder.create<mlir::arith::CmpFOp>(loc, orderedCmp, left, right);
1012       auto rightIsNan = builder.create<mlir::arith::CmpFOp>(
1013           loc, mlir::arith::CmpFPredicate::UNE, right, right);
1014       result =
1015           builder.create<mlir::arith::OrIOp>(loc, leftIsResult, rightIsNan);
1016     } else if constexpr (behavior == ExtremumBehavior::IeeeMinMaximum) {
1017       // Always return NaNs if one the input is NaNs
1018       auto leftIsResult =
1019           builder.create<mlir::arith::CmpFOp>(loc, orderedCmp, left, right);
1020       auto leftIsNan = builder.create<mlir::arith::CmpFOp>(
1021           loc, mlir::arith::CmpFPredicate::UNE, left, left);
1022       result = builder.create<mlir::arith::OrIOp>(loc, leftIsResult, leftIsNan);
1023     } else if constexpr (behavior == ExtremumBehavior::MinMaxss) {
1024       // If the left is a NaN, return the right whatever it is.
1025       result =
1026           builder.create<mlir::arith::CmpFOp>(loc, orderedCmp, left, right);
1027     } else if constexpr (behavior == ExtremumBehavior::PgfortranLlvm) {
1028       // If one of the operand is a NaN, return left whatever it is.
1029       static constexpr auto unorderedCmp =
1030           extremum == Extremum::Max ? mlir::arith::CmpFPredicate::UGT
1031                                     : mlir::arith::CmpFPredicate::ULT;
1032       result =
1033           builder.create<mlir::arith::CmpFOp>(loc, unorderedCmp, left, right);
1034     } else {
1035       // TODO: ieeeMinNum/ieeeMaxNum
1036       static_assert(behavior == ExtremumBehavior::IeeeMinMaxNum,
1037                     "ieeeMinNum/ieeeMaxNum behavior not implemented");
1038     }
1039   } else if (fir::isa_integer(type)) {
1040     result =
1041         builder.create<mlir::arith::CmpIOp>(loc, integerPredicate, left, right);
1042   } else if (fir::isa_char(type)) {
1043     // TODO: ! character min and max is tricky because the result
1044     // length is the length of the longest argument!
1045     // So we may need a temp.
1046     TODO(loc, "CHARACTER min and max");
1047   }
1048   assert(result && "result must be defined");
1049   return result;
1050 }
1051 
1052 // MIN and MAX
1053 template <Extremum extremum, ExtremumBehavior behavior>
1054 mlir::Value IntrinsicLibrary::genExtremum(mlir::Type,
1055                                           llvm::ArrayRef<mlir::Value> args) {
1056   assert(args.size() >= 1);
1057   mlir::Value result = args[0];
1058   for (auto arg : args.drop_front()) {
1059     mlir::Value mask =
1060         createExtremumCompare<extremum, behavior>(loc, builder, result, arg);
1061     result = builder.create<mlir::arith::SelectOp>(loc, mask, result, arg);
1062   }
1063   return result;
1064 }
1065 
1066 // SUM
1067 fir::ExtendedValue
1068 IntrinsicLibrary::genSum(mlir::Type resultType,
1069                          llvm::ArrayRef<fir::ExtendedValue> args) {
1070   return genProdOrSum(fir::runtime::genSum, fir::runtime::genSumDim, resultType,
1071                       builder, loc, stmtCtx, "unexpected result for Sum", args);
1072 }
1073 
1074 //===----------------------------------------------------------------------===//
1075 // Argument lowering rules interface
1076 //===----------------------------------------------------------------------===//
1077 
1078 const Fortran::lower::IntrinsicArgumentLoweringRules *
1079 Fortran::lower::getIntrinsicArgumentLowering(llvm::StringRef intrinsicName) {
1080   if (const IntrinsicHandler *handler = findIntrinsicHandler(intrinsicName))
1081     if (!handler->argLoweringRules.hasDefaultRules())
1082       return &handler->argLoweringRules;
1083   return nullptr;
1084 }
1085 
1086 /// Return how argument \p argName should be lowered given the rules for the
1087 /// intrinsic function.
1088 Fortran::lower::ArgLoweringRule Fortran::lower::lowerIntrinsicArgumentAs(
1089     mlir::Location loc, const IntrinsicArgumentLoweringRules &rules,
1090     llvm::StringRef argName) {
1091   for (const IntrinsicDummyArgument &arg : rules.args) {
1092     if (arg.name && arg.name == argName)
1093       return {arg.lowerAs, arg.handleDynamicOptional};
1094   }
1095   fir::emitFatalError(
1096       loc, "internal: unknown intrinsic argument name in lowering '" + argName +
1097                "'");
1098 }
1099 
1100 //===----------------------------------------------------------------------===//
1101 // Public intrinsic call helpers
1102 //===----------------------------------------------------------------------===//
1103 
1104 fir::ExtendedValue
1105 Fortran::lower::genIntrinsicCall(fir::FirOpBuilder &builder, mlir::Location loc,
1106                                  llvm::StringRef name,
1107                                  llvm::Optional<mlir::Type> resultType,
1108                                  llvm::ArrayRef<fir::ExtendedValue> args,
1109                                  Fortran::lower::StatementContext &stmtCtx) {
1110   return IntrinsicLibrary{builder, loc, &stmtCtx}.genIntrinsicCall(
1111       name, resultType, args);
1112 }
1113 
1114 mlir::Value Fortran::lower::genMax(fir::FirOpBuilder &builder,
1115                                    mlir::Location loc,
1116                                    llvm::ArrayRef<mlir::Value> args) {
1117   assert(args.size() > 0 && "max requires at least one argument");
1118   return IntrinsicLibrary{builder, loc}
1119       .genExtremum<Extremum::Max, ExtremumBehavior::MinMaxss>(args[0].getType(),
1120                                                               args);
1121 }
1122 
1123 mlir::Value Fortran::lower::genPow(fir::FirOpBuilder &builder,
1124                                    mlir::Location loc, mlir::Type type,
1125                                    mlir::Value x, mlir::Value y) {
1126   return IntrinsicLibrary{builder, loc}.genRuntimeCall("pow", type, {x, y});
1127 }
1128