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