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