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