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 "RTBuilder.h" 18 #include "flang/Lower/CharacterExpr.h" 19 #include "flang/Lower/ComplexExpr.h" 20 #include "flang/Lower/ConvertType.h" 21 #include "flang/Lower/FIRBuilder.h" 22 #include "flang/Lower/Mangler.h" 23 #include "flang/Lower/Runtime.h" 24 #include "llvm/Support/CommandLine.h" 25 #include "llvm/Support/ErrorHandling.h" 26 #include <algorithm> 27 #include <utility> 28 29 #define PGMATH_DECLARE 30 #include "../runtime/pgmath.h.inc" 31 32 /// This file implements lowering of Fortran intrinsic procedures. 33 /// Intrinsics are lowered to a mix of FIR and MLIR operations as 34 /// well as call to runtime functions or LLVM intrinsics. 35 36 /// Lowering of intrinsic procedure calls is based on a map that associates 37 /// Fortran intrinsic generic names to FIR generator functions. 38 /// All generator functions are member functions of the IntrinsicLibrary class 39 /// and have the same interface. 40 /// If no generator is given for an intrinsic name, a math runtime library 41 /// is searched for an implementation and, if a runtime function is found, 42 /// a call is generated for it. LLVM intrinsics are handled as a math 43 /// runtime library here. 44 45 /// Enums used to templatize and share lowering of MIN and MAX. 46 enum class Extremum { Min, Max }; 47 48 // There are different ways to deal with NaNs in MIN and MAX. 49 // Known existing behaviors are listed below and can be selected for 50 // f18 MIN/MAX implementation. 51 enum class ExtremumBehavior { 52 // Note: the Signaling/quiet aspect of NaNs in the behaviors below are 53 // not described because there is no way to control/observe such aspect in 54 // MLIR/LLVM yet. The IEEE behaviors come with requirements regarding this 55 // aspect that are therefore currently not enforced. In the descriptions 56 // below, NaNs can be signaling or quite. Returned NaNs may be signaling 57 // if one of the input NaN was signaling but it cannot be guaranteed either. 58 // Existing compilers using an IEEE behavior (gfortran) also do not fulfill 59 // signaling/quiet requirements. 60 IeeeMinMaximumNumber, 61 // IEEE minimumNumber/maximumNumber behavior (754-2019, section 9.6): 62 // If one of the argument is and number and the other is NaN, return the 63 // number. If both arguements are NaN, return NaN. 64 // Compilers: gfortran. 65 IeeeMinMaximum, 66 // IEEE minimum/maximum behavior (754-2019, section 9.6): 67 // If one of the argument is NaN, return NaN. 68 MinMaxss, 69 // x86 minss/maxss behavior: 70 // If the second argument is a number and the other is NaN, return the number. 71 // In all other cases where at least one operand is NaN, return NaN. 72 // Compilers: xlf (only for MAX), ifort, pgfortran -nollvm, and nagfor. 73 PgfortranLlvm, 74 // "Opposite of" x86 minss/maxss behavior: 75 // If the first argument is a number and the other is NaN, return the 76 // number. 77 // In all other cases where at least one operand is NaN, return NaN. 78 // Compilers: xlf (only for MIN), and pgfortran (with llvm). 79 IeeeMinMaxNum 80 // IEEE minNum/maxNum behavior (754-2008, section 5.3.1): 81 // TODO: Not implemented. 82 // It is the only behavior where the signaling/quiet aspect of a NaN argument 83 // impacts if the result should be NaN or the argument that is a number. 84 // LLVM/MLIR do not provide ways to observe this aspect, so it is not 85 // possible to implement it without some target dependent runtime. 86 }; 87 88 namespace { 89 /// StaticMultimapView is a constexpr friendly multimap 90 /// implementation over sorted constexpr arrays. 91 /// As the View name suggests, it does not duplicate the 92 /// sorted array but only brings range and search concepts 93 /// over it. It provides compile time search and can also 94 /// provide dynamic search (currently linear, can be improved to 95 /// log(n) due to the sorted array property). 96 97 // TODO: Find a better place for this if this is retained. 98 // This is currently here because this was designed to provide 99 // maps over runtime description without the burden of having to 100 // instantiate these maps dynamically and to keep them somewhere. 101 template <typename V> 102 class StaticMultimapView { 103 public: 104 using Key = typename V::Key; 105 struct Range { 106 using const_iterator = const V *; 107 constexpr const_iterator begin() const { return startPtr; } 108 constexpr const_iterator end() const { return endPtr; } 109 constexpr bool empty() const { 110 return startPtr == nullptr || endPtr == nullptr || endPtr <= startPtr; 111 } 112 constexpr std::size_t size() const { 113 return empty() ? 0 : static_cast<std::size_t>(endPtr - startPtr); 114 } 115 const V *startPtr{nullptr}; 116 const V *endPtr{nullptr}; 117 }; 118 using const_iterator = typename Range::const_iterator; 119 120 template <std::size_t N> 121 constexpr StaticMultimapView(const V (&array)[N]) 122 : range{&array[0], &array[0] + N} {} 123 template <typename Key> 124 constexpr bool verify() { 125 // TODO: sorted 126 // non empty increasing pointer direction 127 return !range.empty(); 128 } 129 constexpr const_iterator begin() const { return range.begin(); } 130 constexpr const_iterator end() const { return range.end(); } 131 132 // Assume array is sorted. 133 // TODO make it a log(n) search based on sorted property 134 // std::equal_range will be constexpr in C++20 only. 135 constexpr Range getRange(const Key &key) const { 136 bool matched{false}; 137 const V *start{nullptr}, *end{nullptr}; 138 for (const auto &desc : range) { 139 if (desc.key == key) { 140 if (!matched) { 141 start = &desc; 142 matched = true; 143 } 144 } else if (matched) { 145 end = &desc; 146 matched = false; 147 } 148 } 149 if (matched) { 150 end = range.end(); 151 } 152 return Range{start, end}; 153 } 154 155 constexpr std::pair<const_iterator, const_iterator> 156 equal_range(const Key &key) const { 157 Range range{getRange(key)}; 158 return {range.begin(), range.end()}; 159 } 160 161 constexpr typename Range::const_iterator find(Key key) const { 162 const Range subRange{getRange(key)}; 163 return subRange.size() == 1 ? subRange.begin() : end(); 164 } 165 166 private: 167 Range range{nullptr, nullptr}; 168 }; 169 } // namespace 170 171 // TODO error handling -> return a code or directly emit messages ? 172 struct IntrinsicLibrary { 173 174 // Constructors. 175 explicit IntrinsicLibrary(Fortran::lower::FirOpBuilder &builder, 176 mlir::Location loc) 177 : builder{builder}, loc{loc} {} 178 IntrinsicLibrary() = delete; 179 IntrinsicLibrary(const IntrinsicLibrary &) = delete; 180 181 /// Generate FIR for call to Fortran intrinsic \p name with arguments \p arg 182 /// and expected result type \p resultType. 183 fir::ExtendedValue genIntrinsicCall(llvm::StringRef name, 184 mlir::Type resultType, 185 llvm::ArrayRef<fir::ExtendedValue> arg); 186 187 /// Search a runtime function that is associated to the generic intrinsic name 188 /// and whose signature matches the intrinsic arguments and result types. 189 /// If no such runtime function is found but a runtime function associated 190 /// with the Fortran generic exists and has the same number of arguments, 191 /// conversions will be inserted before and/or after the call. This is to 192 /// mainly to allow 16 bits float support even-though little or no math 193 /// runtime is currently available for it. 194 mlir::Value genRuntimeCall(llvm::StringRef name, mlir::Type, 195 llvm::ArrayRef<mlir::Value>); 196 197 using RuntimeCallGenerator = 198 std::function<mlir::Value(Fortran::lower::FirOpBuilder &, mlir::Location, 199 llvm::ArrayRef<mlir::Value>)>; 200 RuntimeCallGenerator 201 getRuntimeCallGenerator(llvm::StringRef name, 202 mlir::FunctionType soughtFuncType); 203 204 mlir::Value genAbs(mlir::Type, llvm::ArrayRef<mlir::Value>); 205 mlir::Value genAimag(mlir::Type, llvm::ArrayRef<mlir::Value>); 206 mlir::Value genAint(mlir::Type, llvm::ArrayRef<mlir::Value>); 207 mlir::Value genAnint(mlir::Type, llvm::ArrayRef<mlir::Value>); 208 mlir::Value genCeiling(mlir::Type, llvm::ArrayRef<mlir::Value>); 209 mlir::Value genConjg(mlir::Type, llvm::ArrayRef<mlir::Value>); 210 mlir::Value genDim(mlir::Type, llvm::ArrayRef<mlir::Value>); 211 mlir::Value genDprod(mlir::Type, llvm::ArrayRef<mlir::Value>); 212 template <Extremum, ExtremumBehavior> 213 mlir::Value genExtremum(mlir::Type, llvm::ArrayRef<mlir::Value>); 214 mlir::Value genFloor(mlir::Type, llvm::ArrayRef<mlir::Value>); 215 mlir::Value genIAnd(mlir::Type, llvm::ArrayRef<mlir::Value>); 216 mlir::Value genIchar(mlir::Type, llvm::ArrayRef<mlir::Value>); 217 mlir::Value genIEOr(mlir::Type, llvm::ArrayRef<mlir::Value>); 218 mlir::Value genIOr(mlir::Type, llvm::ArrayRef<mlir::Value>); 219 fir::ExtendedValue genLen(mlir::Type, llvm::ArrayRef<fir::ExtendedValue>); 220 fir::ExtendedValue genLenTrim(mlir::Type, llvm::ArrayRef<fir::ExtendedValue>); 221 mlir::Value genMerge(mlir::Type, llvm::ArrayRef<mlir::Value>); 222 mlir::Value genMod(mlir::Type, llvm::ArrayRef<mlir::Value>); 223 mlir::Value genNint(mlir::Type, llvm::ArrayRef<mlir::Value>); 224 mlir::Value genSign(mlir::Type, llvm::ArrayRef<mlir::Value>); 225 /// Implement all conversion functions like DBLE, the first argument is 226 /// the value to convert. There may be an additional KIND arguments that 227 /// is ignored because this is already reflected in the result type. 228 mlir::Value genConversion(mlir::Type, llvm::ArrayRef<mlir::Value>); 229 230 /// Define the different FIR generators that can be mapped to intrinsic to 231 /// generate the related code. 232 using ElementalGenerator = decltype(&IntrinsicLibrary::genAbs); 233 using ExtendedGenerator = decltype(&IntrinsicLibrary::genLenTrim); 234 using Generator = std::variant<ElementalGenerator, ExtendedGenerator>; 235 236 /// All generators can be outlined. This will build a function named 237 /// "fir."+ <generic name> + "." + <result type code> and generate the 238 /// intrinsic implementation inside instead of at the intrinsic call sites. 239 /// This can be used to keep the FIR more readable. Only one function will 240 /// be generated for all the similar calls in a program. 241 /// If the Generator is nullptr, the wrapper uses genRuntimeCall. 242 template <typename GeneratorType> 243 mlir::Value outlineInWrapper(GeneratorType, llvm::StringRef name, 244 mlir::Type resultType, 245 llvm::ArrayRef<mlir::Value> args); 246 fir::ExtendedValue outlineInWrapper(ExtendedGenerator, llvm::StringRef name, 247 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 /// Get pointer to unrestricted intrinsic. Generate the related unrestricted 272 /// intrinsic if it is not defined yet. 273 mlir::SymbolRefAttr 274 getUnrestrictedIntrinsicSymbolRefAttr(llvm::StringRef name, 275 mlir::FunctionType signature); 276 277 Fortran::lower::FirOpBuilder &builder; 278 mlir::Location loc; 279 }; 280 281 /// Table that drives the fir generation depending on the intrinsic. 282 /// one to one mapping with Fortran arguments. If no mapping is 283 /// defined here for a generic intrinsic, genRuntimeCall will be called 284 /// to look for a match in the runtime a emit a call. 285 struct IntrinsicHandler { 286 const char *name; 287 IntrinsicLibrary::Generator generator; 288 bool isElemental = true; 289 /// Code heavy intrinsic can be outlined to make FIR 290 /// more readable. 291 bool outline = false; 292 }; 293 using I = IntrinsicLibrary; 294 static constexpr IntrinsicHandler handlers[]{ 295 {"abs", &I::genAbs}, 296 {"achar", &I::genConversion}, 297 {"aimag", &I::genAimag}, 298 {"aint", &I::genAint}, 299 {"anint", &I::genAnint}, 300 {"ceiling", &I::genCeiling}, 301 {"char", &I::genConversion}, 302 {"conjg", &I::genConjg}, 303 {"dim", &I::genDim}, 304 {"dble", &I::genConversion}, 305 {"dprod", &I::genDprod}, 306 {"floor", &I::genFloor}, 307 {"iand", &I::genIAnd}, 308 {"ichar", &I::genIchar}, 309 {"ieor", &I::genIEOr}, 310 {"ior", &I::genIOr}, 311 {"len", &I::genLen}, 312 {"len_trim", &I::genLenTrim}, 313 {"max", &I::genExtremum<Extremum::Max, ExtremumBehavior::MinMaxss>}, 314 {"min", &I::genExtremum<Extremum::Min, ExtremumBehavior::MinMaxss>}, 315 {"merge", &I::genMerge}, 316 {"mod", &I::genMod}, 317 {"nint", &I::genNint}, 318 {"sign", &I::genSign}, 319 }; 320 321 /// To make fir output more readable for debug, one can outline all intrinsic 322 /// implementation in wrappers (overrides the IntrinsicHandler::outline flag). 323 static llvm::cl::opt<bool> outlineAllIntrinsics( 324 "outline-intrinsics", 325 llvm::cl::desc( 326 "Lower all intrinsic procedure implementation in their own functions"), 327 llvm::cl::init(false)); 328 329 //===----------------------------------------------------------------------===// 330 // Math runtime description and matching utility 331 //===----------------------------------------------------------------------===// 332 333 /// Command line option to modify math runtime version used to implement 334 /// intrinsics. 335 enum MathRuntimeVersion { 336 fastVersion, 337 relaxedVersion, 338 preciseVersion, 339 llvmOnly 340 }; 341 llvm::cl::opt<MathRuntimeVersion> mathRuntimeVersion( 342 "math-runtime", llvm::cl::desc("Select math runtime version:"), 343 llvm::cl::values( 344 clEnumValN(fastVersion, "fast", "use pgmath fast runtime"), 345 clEnumValN(relaxedVersion, "relaxed", "use pgmath relaxed runtime"), 346 clEnumValN(preciseVersion, "precise", "use pgmath precise runtime"), 347 clEnumValN(llvmOnly, "llvm", 348 "only use LLVM intrinsics (may be incomplete)")), 349 llvm::cl::init(fastVersion)); 350 351 struct RuntimeFunction { 352 using Key = llvm::StringRef; 353 Key key; 354 llvm::StringRef symbol; 355 Fortran::lower::FuncTypeBuilderFunc typeGenerator; 356 }; 357 358 #define RUNTIME_STATIC_DESCRIPTION(name, func) \ 359 {#name, #func, \ 360 Fortran::lower::RuntimeTableKey<decltype(func)>::getTypeModel()}, 361 static constexpr RuntimeFunction pgmathFast[] = { 362 #define PGMATH_FAST 363 #define PGMATH_USE_ALL_TYPES(name, func) RUNTIME_STATIC_DESCRIPTION(name, func) 364 #include "../runtime/pgmath.h.inc" 365 }; 366 static constexpr RuntimeFunction pgmathRelaxed[] = { 367 #define PGMATH_RELAXED 368 #define PGMATH_USE_ALL_TYPES(name, func) RUNTIME_STATIC_DESCRIPTION(name, func) 369 #include "../runtime/pgmath.h.inc" 370 }; 371 static constexpr RuntimeFunction pgmathPrecise[] = { 372 #define PGMATH_PRECISE 373 #define PGMATH_USE_ALL_TYPES(name, func) RUNTIME_STATIC_DESCRIPTION(name, func) 374 #include "../runtime/pgmath.h.inc" 375 }; 376 377 static mlir::FunctionType genF32F32FuncType(mlir::MLIRContext *context) { 378 auto t = mlir::FloatType::getF32(context); 379 return mlir::FunctionType::get({t}, {t}, context); 380 } 381 382 static mlir::FunctionType genF64F64FuncType(mlir::MLIRContext *context) { 383 auto t = mlir::FloatType::getF64(context); 384 return mlir::FunctionType::get({t}, {t}, context); 385 } 386 387 template <int Bits> 388 static mlir::FunctionType genIntF64FuncType(mlir::MLIRContext *context) { 389 auto t = mlir::FloatType::getF64(context); 390 auto r = mlir::IntegerType::get(Bits, context); 391 return mlir::FunctionType::get({t}, {r}, context); 392 } 393 394 template <int Bits> 395 static mlir::FunctionType genIntF32FuncType(mlir::MLIRContext *context) { 396 auto t = mlir::FloatType::getF32(context); 397 auto r = mlir::IntegerType::get(Bits, context); 398 return mlir::FunctionType::get({t}, {r}, context); 399 } 400 401 // TODO : Fill-up this table with more intrinsic. 402 // Note: These are also defined as operations in LLVM dialect. See if this 403 // can be use and has advantages. 404 static constexpr RuntimeFunction llvmIntrinsics[] = { 405 {"abs", "llvm.fabs.f32", genF32F32FuncType}, 406 {"abs", "llvm.fabs.f64", genF64F64FuncType}, 407 {"aint", "llvm.trunc.f32", genF32F32FuncType}, 408 {"aint", "llvm.trunc.f64", genF64F64FuncType}, 409 {"anint", "llvm.round.f32", genF32F32FuncType}, 410 {"anint", "llvm.round.f64", genF64F64FuncType}, 411 // ceil is used for CEILING but is different, it returns a real. 412 {"ceil", "llvm.ceil.f32", genF32F32FuncType}, 413 {"ceil", "llvm.ceil.f64", genF64F64FuncType}, 414 {"cos", "llvm.cos.f32", genF32F32FuncType}, 415 {"cos", "llvm.cos.f64", genF64F64FuncType}, 416 // llvm.floor is used for FLOOR, but returns real. 417 {"floor", "llvm.floor.f32", genF32F32FuncType}, 418 {"floor", "llvm.floor.f64", genF64F64FuncType}, 419 {"log", "llvm.log.f32", genF32F32FuncType}, 420 {"log", "llvm.log.f64", genF64F64FuncType}, 421 {"log10", "llvm.log10.f32", genF32F32FuncType}, 422 {"log10", "llvm.log10.f64", genF64F64FuncType}, 423 {"nint", "llvm.lround.i64.f64", genIntF64FuncType<64>}, 424 {"nint", "llvm.lround.i64.f32", genIntF32FuncType<64>}, 425 {"nint", "llvm.lround.i32.f64", genIntF64FuncType<32>}, 426 {"nint", "llvm.lround.i32.f32", genIntF32FuncType<32>}, 427 {"sin", "llvm.sin.f32", genF32F32FuncType}, 428 {"sin", "llvm.sin.f64", genF64F64FuncType}, 429 {"sqrt", "llvm.sqrt.f32", genF32F32FuncType}, 430 {"sqrt", "llvm.sqrt.f64", genF64F64FuncType}, 431 }; 432 433 // This helper class computes a "distance" between two function types. 434 // The distance measures how many narrowing conversions of actual arguments 435 // and result of "from" must be made in order to use "to" instead of "from". 436 // For instance, the distance between ACOS(REAL(10)) and ACOS(REAL(8)) is 437 // greater than the one between ACOS(REAL(10)) and ACOS(REAL(16)). This means 438 // if no implementation of ACOS(REAL(10)) is available, it is better to use 439 // ACOS(REAL(16)) with casts rather than ACOS(REAL(8)). 440 // Note that this is not a symmetric distance and the order of "from" and "to" 441 // arguments matters, d(foo, bar) may not be the same as d(bar, foo) because it 442 // may be safe to replace foo by bar, but not the opposite. 443 class FunctionDistance { 444 public: 445 FunctionDistance() : infinite{true} {} 446 447 FunctionDistance(mlir::FunctionType from, mlir::FunctionType to) { 448 auto nInputs = from.getNumInputs(); 449 auto nResults = from.getNumResults(); 450 if (nResults != to.getNumResults() || nInputs != to.getNumInputs()) { 451 infinite = true; 452 } else { 453 for (decltype(nInputs) i{0}; i < nInputs && !infinite; ++i) 454 addArgumentDistance(from.getInput(i), to.getInput(i)); 455 for (decltype(nResults) i{0}; i < nResults && !infinite; ++i) 456 addResultDistance(to.getResult(i), from.getResult(i)); 457 } 458 } 459 460 /// Beware both d1.isSmallerThan(d2) *and* d2.isSmallerThan(d1) may be 461 /// false if both d1 and d2 are infinite. This implies that 462 /// d1.isSmallerThan(d2) is not equivalent to !d2.isSmallerThan(d1) 463 bool isSmallerThan(const FunctionDistance &d) const { 464 return !infinite && 465 (d.infinite || std::lexicographical_compare( 466 conversions.begin(), conversions.end(), 467 d.conversions.begin(), d.conversions.end())); 468 } 469 470 bool isLosingPrecision() const { 471 return conversions[narrowingArg] != 0 || conversions[extendingResult] != 0; 472 } 473 474 bool isInfinite() const { return infinite; } 475 476 private: 477 enum class Conversion { Forbidden, None, Narrow, Extend }; 478 479 void addArgumentDistance(mlir::Type from, mlir::Type to) { 480 switch (conversionBetweenTypes(from, to)) { 481 case Conversion::Forbidden: 482 infinite = true; 483 break; 484 case Conversion::None: 485 break; 486 case Conversion::Narrow: 487 conversions[narrowingArg]++; 488 break; 489 case Conversion::Extend: 490 conversions[nonNarrowingArg]++; 491 break; 492 } 493 } 494 495 void addResultDistance(mlir::Type from, mlir::Type to) { 496 switch (conversionBetweenTypes(from, to)) { 497 case Conversion::Forbidden: 498 infinite = true; 499 break; 500 case Conversion::None: 501 break; 502 case Conversion::Narrow: 503 conversions[nonExtendingResult]++; 504 break; 505 case Conversion::Extend: 506 conversions[extendingResult]++; 507 break; 508 } 509 } 510 511 // Floating point can be mlir::FloatType or fir::real 512 static unsigned getFloatingPointWidth(mlir::Type t) { 513 if (auto f{t.dyn_cast<mlir::FloatType>()}) 514 return f.getWidth(); 515 // FIXME: Get width another way for fir.real/complex 516 // - use fir/KindMapping.h and llvm::Type 517 // - or use evaluate/type.h 518 if (auto r{t.dyn_cast<fir::RealType>()}) 519 return r.getFKind() * 4; 520 if (auto cplx{t.dyn_cast<fir::CplxType>()}) 521 return cplx.getFKind() * 4; 522 llvm_unreachable("not a floating-point type"); 523 } 524 525 static Conversion conversionBetweenTypes(mlir::Type from, mlir::Type to) { 526 if (from == to) { 527 return Conversion::None; 528 } 529 if (auto fromIntTy{from.dyn_cast<mlir::IntegerType>()}) { 530 if (auto toIntTy{to.dyn_cast<mlir::IntegerType>()}) { 531 return fromIntTy.getWidth() > toIntTy.getWidth() ? Conversion::Narrow 532 : Conversion::Extend; 533 } 534 } 535 if (fir::isa_real(from) && fir::isa_real(to)) { 536 return getFloatingPointWidth(from) > getFloatingPointWidth(to) 537 ? Conversion::Narrow 538 : Conversion::Extend; 539 } 540 if (auto fromCplxTy{from.dyn_cast<fir::CplxType>()}) { 541 if (auto toCplxTy{to.dyn_cast<fir::CplxType>()}) { 542 return getFloatingPointWidth(fromCplxTy) > 543 getFloatingPointWidth(toCplxTy) 544 ? Conversion::Narrow 545 : Conversion::Extend; 546 } 547 } 548 // Notes: 549 // - No conversion between character types, specialization of runtime 550 // functions should be made instead. 551 // - It is not clear there is a use case for automatic conversions 552 // around Logical and it may damage hidden information in the physical 553 // storage so do not do it. 554 return Conversion::Forbidden; 555 } 556 557 // Below are indexes to access data in conversions. 558 // The order in data does matter for lexicographical_compare 559 enum { 560 narrowingArg = 0, // usually bad 561 extendingResult, // usually bad 562 nonExtendingResult, // usually ok 563 nonNarrowingArg, // usually ok 564 dataSize 565 }; 566 567 std::array<int, dataSize> conversions{/* zero init*/}; 568 bool infinite{false}; // When forbidden conversion or wrong argument number 569 }; 570 571 /// Build mlir::FuncOp from runtime symbol description and add 572 /// fir.runtime attribute. 573 static mlir::FuncOp getFuncOp(mlir::Location loc, 574 Fortran::lower::FirOpBuilder &builder, 575 const RuntimeFunction &runtime) { 576 auto function = builder.addNamedFunction( 577 loc, runtime.symbol, runtime.typeGenerator(builder.getContext())); 578 function.setAttr("fir.runtime", builder.getUnitAttr()); 579 return function; 580 } 581 582 /// Select runtime function that has the smallest distance to the intrinsic 583 /// function type and that will not imply narrowing arguments or extending the 584 /// result. 585 /// If nothing is found, the mlir::FuncOp will contain a nullptr. 586 template <std::size_t N> 587 mlir::FuncOp searchFunctionInLibrary(mlir::Location loc, 588 Fortran::lower::FirOpBuilder &builder, 589 const RuntimeFunction (&lib)[N], 590 llvm::StringRef name, 591 mlir::FunctionType funcType, 592 const RuntimeFunction **bestNearMatch, 593 FunctionDistance &bestMatchDistance) { 594 auto map = StaticMultimapView(lib); 595 auto range = map.equal_range(name); 596 for (auto iter{range.first}; iter != range.second && iter; ++iter) { 597 const auto &impl = *iter; 598 auto implType = impl.typeGenerator(builder.getContext()); 599 if (funcType == implType) { 600 return getFuncOp(loc, builder, impl); // exact match 601 } else { 602 FunctionDistance distance(funcType, implType); 603 if (distance.isSmallerThan(bestMatchDistance)) { 604 *bestNearMatch = &impl; 605 bestMatchDistance = std::move(distance); 606 } 607 } 608 } 609 return {}; 610 } 611 612 /// Search runtime for the best runtime function given an intrinsic name 613 /// and interface. The interface may not be a perfect match in which case 614 /// the caller is responsible to insert argument and return value conversions. 615 /// If nothing is found, the mlir::FuncOp will contain a nullptr. 616 static mlir::FuncOp getRuntimeFunction(mlir::Location loc, 617 Fortran::lower::FirOpBuilder &builder, 618 llvm::StringRef name, 619 mlir::FunctionType funcType) { 620 const RuntimeFunction *bestNearMatch = nullptr; 621 FunctionDistance bestMatchDistance{}; 622 mlir::FuncOp match; 623 if (mathRuntimeVersion == fastVersion) { 624 match = searchFunctionInLibrary(loc, builder, pgmathFast, name, funcType, 625 &bestNearMatch, bestMatchDistance); 626 } else if (mathRuntimeVersion == relaxedVersion) { 627 match = searchFunctionInLibrary(loc, builder, pgmathRelaxed, name, funcType, 628 &bestNearMatch, bestMatchDistance); 629 } else if (mathRuntimeVersion == preciseVersion) { 630 match = searchFunctionInLibrary(loc, builder, pgmathPrecise, name, funcType, 631 &bestNearMatch, bestMatchDistance); 632 } else { 633 assert(mathRuntimeVersion == llvmOnly && "unknown math runtime"); 634 } 635 if (match) 636 return match; 637 638 // Go through llvm intrinsics if not exact match in libpgmath or if 639 // mathRuntimeVersion == llvmOnly 640 if (auto exactMatch = 641 searchFunctionInLibrary(loc, builder, llvmIntrinsics, name, funcType, 642 &bestNearMatch, bestMatchDistance)) 643 return exactMatch; 644 645 if (bestNearMatch != nullptr) { 646 assert(!bestMatchDistance.isLosingPrecision() && 647 "runtime selection loses precision"); 648 return getFuncOp(loc, builder, *bestNearMatch); 649 } 650 return {}; 651 } 652 653 /// Helpers to get function type from arguments and result type. 654 static mlir::FunctionType 655 getFunctionType(mlir::Type resultType, llvm::ArrayRef<mlir::Value> arguments, 656 Fortran::lower::FirOpBuilder &builder) { 657 llvm::SmallVector<mlir::Type, 2> argumentTypes; 658 for (auto &arg : arguments) 659 argumentTypes.push_back(arg.getType()); 660 return mlir::FunctionType::get(argumentTypes, resultType, 661 builder.getModule().getContext()); 662 } 663 664 /// fir::ExtendedValue to mlir::Value translation layer 665 666 fir::ExtendedValue toExtendedValue(mlir::Value val, 667 Fortran::lower::FirOpBuilder &builder, 668 mlir::Location loc) { 669 assert(val && "optional unhandled here"); 670 auto type = val.getType(); 671 auto base = val; 672 auto indexType = builder.getIndexType(); 673 llvm::SmallVector<mlir::Value, 2> extents; 674 675 Fortran::lower::CharacterExprHelper charHelper{builder, loc}; 676 if (charHelper.isCharacter(type)) 677 return charHelper.toExtendedValue(val); 678 679 if (auto refType = type.dyn_cast<fir::ReferenceType>()) 680 type = refType.getEleTy(); 681 682 if (auto arrayType = type.dyn_cast<fir::SequenceType>()) { 683 type = arrayType.getEleTy(); 684 for (auto extent : arrayType.getShape()) { 685 if (extent == fir::SequenceType::getUnknownExtent()) 686 break; 687 extents.emplace_back( 688 builder.createIntegerConstant(loc, indexType, extent)); 689 } 690 // Last extent might be missing in case of assumed-size. If more extents 691 // could not be deduced from type, that's an error (a fir.box should 692 // have been used in the interface). 693 if (extents.size() + 1 < arrayType.getShape().size()) 694 mlir::emitError(loc, "cannot retrieve array extents from type"); 695 } else if (type.isa<fir::BoxType>() || type.isa<fir::RecordType>()) { 696 mlir::emitError(loc, "descriptor or derived type not yet handled"); 697 } 698 699 if (!extents.empty()) 700 return fir::ArrayBoxValue{base, extents}; 701 return base; 702 } 703 704 mlir::Value toValue(const fir::ExtendedValue &val, 705 Fortran::lower::FirOpBuilder &builder, mlir::Location loc) { 706 if (auto charBox = val.getCharBox()) { 707 auto buffer = charBox->getBuffer(); 708 if (buffer.getType().isa<fir::BoxCharType>()) 709 return buffer; 710 return Fortran::lower::CharacterExprHelper{builder, loc}.createEmboxChar( 711 buffer, charBox->getLen()); 712 } 713 714 // FIXME: need to access other ExtendedValue variants and handle them 715 // properly. 716 return fir::getBase(val); 717 } 718 719 //===----------------------------------------------------------------------===// 720 // IntrinsicLibrary 721 //===----------------------------------------------------------------------===// 722 723 template <typename GeneratorType> 724 fir::ExtendedValue IntrinsicLibrary::genElementalCall( 725 GeneratorType generator, llvm::StringRef name, mlir::Type resultType, 726 llvm::ArrayRef<fir::ExtendedValue> args, bool outline) { 727 llvm::SmallVector<mlir::Value, 2> scalarArgs; 728 for (const auto &arg : args) { 729 if (arg.getUnboxed() || arg.getCharBox()) { 730 scalarArgs.emplace_back(fir::getBase(arg)); 731 } else { 732 // TODO: get the result shape and create the loop... 733 mlir::emitError(loc, "array or descriptor not yet handled in elemental " 734 "intrinsic lowering"); 735 exit(1); 736 } 737 } 738 if (outline) 739 return outlineInWrapper(generator, name, resultType, scalarArgs); 740 return invokeGenerator(generator, resultType, scalarArgs); 741 } 742 743 /// Some ExtendedGenerator operating on characters are also elemental 744 /// (e.g LEN_TRIM). 745 template <> 746 fir::ExtendedValue 747 IntrinsicLibrary::genElementalCall<IntrinsicLibrary::ExtendedGenerator>( 748 ExtendedGenerator generator, llvm::StringRef name, mlir::Type resultType, 749 llvm::ArrayRef<fir::ExtendedValue> args, bool outline) { 750 for (const auto &arg : args) 751 if (!arg.getUnboxed() && !arg.getCharBox()) { 752 // TODO: get the result shape and create the loop... 753 mlir::emitError(loc, "array or descriptor not yet handled in elemental " 754 "intrinsic lowering"); 755 exit(1); 756 } 757 if (outline) 758 return outlineInWrapper(generator, name, resultType, args); 759 return std::invoke(generator, *this, resultType, args); 760 } 761 762 fir::ExtendedValue 763 IntrinsicLibrary::genIntrinsicCall(llvm::StringRef name, mlir::Type resultType, 764 llvm::ArrayRef<fir::ExtendedValue> args) { 765 for (auto &handler : handlers) 766 if (name == handler.name) { 767 bool outline = handler.outline || outlineAllIntrinsics; 768 if (const auto *elementalGenerator = 769 std::get_if<ElementalGenerator>(&handler.generator)) 770 return genElementalCall(*elementalGenerator, name, resultType, args, 771 outline); 772 const auto &generator = std::get<ExtendedGenerator>(handler.generator); 773 if (handler.isElemental) 774 return genElementalCall(generator, name, resultType, args, outline); 775 if (outline) 776 return outlineInWrapper(generator, name, resultType, args); 777 return std::invoke(generator, *this, resultType, args); 778 } 779 780 // Try the runtime if no special handler was defined for the 781 // intrinsic being called. Maths runtime only has numerical elemental. 782 // No optional arguments are expected at this point, the code will 783 // crash if it gets absent optional. 784 785 // FIXME: using toValue to get the type won't work with array arguments. 786 llvm::SmallVector<mlir::Value, 2> mlirArgs; 787 for (const auto &extendedVal : args) { 788 auto val = toValue(extendedVal, builder, loc); 789 if (!val) { 790 // If an absent optional gets there, most likely its handler has just 791 // not yet been defined. 792 mlir::emitError(loc, 793 "TODO: missing intrinsic lowering: " + llvm::Twine(name)); 794 exit(1); 795 } 796 mlirArgs.emplace_back(val); 797 } 798 mlir::FunctionType soughtFuncType = 799 getFunctionType(resultType, mlirArgs, builder); 800 801 auto runtimeCallGenerator = getRuntimeCallGenerator(name, soughtFuncType); 802 return genElementalCall(runtimeCallGenerator, name, resultType, args, 803 /* outline */ true); 804 } 805 806 mlir::Value 807 IntrinsicLibrary::invokeGenerator(ElementalGenerator generator, 808 mlir::Type resultType, 809 llvm::ArrayRef<mlir::Value> args) { 810 return std::invoke(generator, *this, resultType, args); 811 } 812 813 mlir::Value 814 IntrinsicLibrary::invokeGenerator(RuntimeCallGenerator generator, 815 mlir::Type resultType, 816 llvm::ArrayRef<mlir::Value> args) { 817 return generator(builder, loc, args); 818 } 819 820 mlir::Value 821 IntrinsicLibrary::invokeGenerator(ExtendedGenerator generator, 822 mlir::Type resultType, 823 llvm::ArrayRef<mlir::Value> args) { 824 llvm::SmallVector<fir::ExtendedValue, 2> extendedArgs; 825 for (auto arg : args) 826 extendedArgs.emplace_back(toExtendedValue(arg, builder, loc)); 827 auto extendedResult = std::invoke(generator, *this, resultType, extendedArgs); 828 return toValue(extendedResult, builder, loc); 829 } 830 831 template <typename GeneratorType> 832 mlir::FuncOp IntrinsicLibrary::getWrapper(GeneratorType generator, 833 llvm::StringRef name, 834 mlir::FunctionType funcType, 835 bool loadRefArguments) { 836 assert(funcType.getNumResults() == 1 && 837 "expect one result for intrinsic functions"); 838 auto resultType = funcType.getResult(0); 839 std::string wrapperName = fir::mangleIntrinsicProcedure(name, funcType); 840 auto function = builder.getNamedFunction(wrapperName); 841 if (!function) { 842 // First time this wrapper is needed, build it. 843 function = builder.createFunction(loc, wrapperName, funcType); 844 function.setAttr("fir.intrinsic", builder.getUnitAttr()); 845 function.addEntryBlock(); 846 847 // Create local context to emit code into the newly created function 848 // This new function is not linked to a source file location, only 849 // its calls will be. 850 auto localBuilder = std::make_unique<Fortran::lower::FirOpBuilder>( 851 function, builder.getKindMap()); 852 localBuilder->setInsertionPointToStart(&function.front()); 853 // Location of code inside wrapper of the wrapper is independent from 854 // the location of the intrinsic call. 855 auto localLoc = localBuilder->getUnknownLoc(); 856 llvm::SmallVector<mlir::Value, 2> localArguments; 857 for (mlir::BlockArgument bArg : function.front().getArguments()) { 858 auto refType = bArg.getType().dyn_cast<fir::ReferenceType>(); 859 if (loadRefArguments && refType) { 860 auto loaded = localBuilder->create<fir::LoadOp>(localLoc, bArg); 861 localArguments.push_back(loaded); 862 } else { 863 localArguments.push_back(bArg); 864 } 865 } 866 867 IntrinsicLibrary localLib{*localBuilder, localLoc}; 868 auto result = 869 localLib.invokeGenerator(generator, resultType, localArguments); 870 localBuilder->create<mlir::ReturnOp>(localLoc, result); 871 } else { 872 // Wrapper was already built, ensure it has the sought type 873 assert(function.getType() == funcType && 874 "conflict between intrinsic wrapper types"); 875 } 876 return function; 877 } 878 879 /// Helpers to detect absent optional (not yet supported in outlining). 880 bool static hasAbsentOptional(llvm::ArrayRef<mlir::Value> args) { 881 for (const auto &arg : args) 882 if (!arg) 883 return true; 884 return false; 885 } 886 bool static hasAbsentOptional(llvm::ArrayRef<fir::ExtendedValue> args) { 887 for (const auto &arg : args) 888 if (!fir::getBase(arg)) 889 return true; 890 return false; 891 } 892 893 template <typename GeneratorType> 894 mlir::Value 895 IntrinsicLibrary::outlineInWrapper(GeneratorType generator, 896 llvm::StringRef name, mlir::Type resultType, 897 llvm::ArrayRef<mlir::Value> args) { 898 if (hasAbsentOptional(args)) { 899 // TODO: absent optional in outlining is an issue: we cannot just ignore 900 // them. Needs a better interface here. The issue is that we cannot easily 901 // tell that a value is optional or not here if it is presents. And if it is 902 // absent, we cannot tell what it type should be. 903 mlir::emitError(loc, "todo: cannot outline call to intrinsic " + 904 llvm::Twine(name) + 905 " with absent optional argument"); 906 exit(1); 907 } 908 909 auto funcType = getFunctionType(resultType, args, builder); 910 auto wrapper = getWrapper(generator, name, funcType); 911 return builder.create<mlir::CallOp>(loc, wrapper, args).getResult(0); 912 } 913 914 fir::ExtendedValue 915 IntrinsicLibrary::outlineInWrapper(ExtendedGenerator generator, 916 llvm::StringRef name, mlir::Type resultType, 917 llvm::ArrayRef<fir::ExtendedValue> args) { 918 if (hasAbsentOptional(args)) { 919 // TODO 920 mlir::emitError(loc, "todo: cannot outline call to intrinsic " + 921 llvm::Twine(name) + 922 " with absent optional argument"); 923 exit(1); 924 } 925 llvm::SmallVector<mlir::Value, 2> mlirArgs; 926 for (const auto &extendedVal : args) 927 mlirArgs.emplace_back(toValue(extendedVal, builder, loc)); 928 auto funcType = getFunctionType(resultType, mlirArgs, builder); 929 auto wrapper = getWrapper(generator, name, funcType); 930 auto mlirResult = 931 builder.create<mlir::CallOp>(loc, wrapper, mlirArgs).getResult(0); 932 return toExtendedValue(mlirResult, builder, loc); 933 } 934 935 IntrinsicLibrary::RuntimeCallGenerator 936 IntrinsicLibrary::getRuntimeCallGenerator(llvm::StringRef name, 937 mlir::FunctionType soughtFuncType) { 938 auto funcOp = getRuntimeFunction(loc, builder, name, soughtFuncType); 939 if (!funcOp) { 940 mlir::emitError(loc, 941 "TODO: missing intrinsic lowering: " + llvm::Twine(name)); 942 llvm::errs() << "requested type was: " << soughtFuncType << "\n"; 943 exit(1); 944 } 945 946 mlir::FunctionType actualFuncType = funcOp.getType(); 947 assert(actualFuncType.getNumResults() == soughtFuncType.getNumResults() && 948 actualFuncType.getNumInputs() == soughtFuncType.getNumInputs() && 949 actualFuncType.getNumResults() == 1 && "Bad intrinsic match"); 950 951 return [funcOp, actualFuncType, soughtFuncType]( 952 Fortran::lower::FirOpBuilder &builder, mlir::Location loc, 953 llvm::ArrayRef<mlir::Value> args) { 954 llvm::SmallVector<mlir::Value, 2> convertedArguments; 955 for (const auto &pair : llvm::zip(actualFuncType.getInputs(), args)) 956 convertedArguments.push_back( 957 builder.createConvert(loc, std::get<0>(pair), std::get<1>(pair))); 958 auto call = builder.create<mlir::CallOp>(loc, funcOp, convertedArguments); 959 mlir::Type soughtType = soughtFuncType.getResult(0); 960 return builder.createConvert(loc, soughtType, call.getResult(0)); 961 }; 962 } 963 964 mlir::SymbolRefAttr IntrinsicLibrary::getUnrestrictedIntrinsicSymbolRefAttr( 965 llvm::StringRef name, mlir::FunctionType signature) { 966 // Unrestricted intrinsics signature follows implicit rules: argument 967 // are passed by references. But the runtime versions expect values. 968 // So instead of duplicating the runtime, just have the wrappers loading 969 // this before calling the code generators. 970 bool loadRefArguments = true; 971 mlir::FuncOp funcOp; 972 for (auto &handler : handlers) 973 if (name == handler.name) 974 funcOp = std::visit( 975 [&](auto generator) { 976 return getWrapper(generator, name, signature, loadRefArguments); 977 }, 978 handler.generator); 979 980 if (!funcOp) { 981 llvm::SmallVector<mlir::Type, 2> argTypes; 982 for (auto type : signature.getInputs()) { 983 if (auto refType = type.dyn_cast<fir::ReferenceType>()) 984 argTypes.push_back(refType.getEleTy()); 985 else 986 argTypes.push_back(type); 987 } 988 auto soughtFuncType = 989 builder.getFunctionType(signature.getResults(), argTypes); 990 auto rtCallGenerator = getRuntimeCallGenerator(name, soughtFuncType); 991 funcOp = getWrapper(rtCallGenerator, name, signature, loadRefArguments); 992 } 993 994 return builder.getSymbolRefAttr(funcOp.getName()); 995 } 996 997 //===----------------------------------------------------------------------===// 998 // Code generators for the intrinsic 999 //===----------------------------------------------------------------------===// 1000 1001 mlir::Value IntrinsicLibrary::genRuntimeCall(llvm::StringRef name, 1002 mlir::Type resultType, 1003 llvm::ArrayRef<mlir::Value> args) { 1004 mlir::FunctionType soughtFuncType = 1005 getFunctionType(resultType, args, builder); 1006 return getRuntimeCallGenerator(name, soughtFuncType)(builder, loc, args); 1007 } 1008 1009 mlir::Value IntrinsicLibrary::genConversion(mlir::Type resultType, 1010 llvm::ArrayRef<mlir::Value> args) { 1011 // There can be an optional kind in second argument. 1012 assert(args.size() >= 1); 1013 return builder.convertWithSemantics(loc, resultType, args[0]); 1014 } 1015 1016 // ABS 1017 mlir::Value IntrinsicLibrary::genAbs(mlir::Type resultType, 1018 llvm::ArrayRef<mlir::Value> args) { 1019 assert(args.size() == 1); 1020 auto arg = args[0]; 1021 auto type = arg.getType(); 1022 if (fir::isa_real(type)) { 1023 // Runtime call to fp abs. An alternative would be to use mlir AbsFOp 1024 // but it does not support all fir floating point types. 1025 return genRuntimeCall("abs", resultType, args); 1026 } 1027 if (auto intType = type.dyn_cast<mlir::IntegerType>()) { 1028 // At the time of this implementation there is no abs op in mlir. 1029 // So, implement abs here without branching. 1030 auto shift = 1031 builder.createIntegerConstant(loc, intType, intType.getWidth() - 1); 1032 auto mask = builder.create<mlir::SignedShiftRightOp>(loc, arg, shift); 1033 auto xored = builder.create<mlir::XOrOp>(loc, arg, mask); 1034 return builder.create<mlir::SubIOp>(loc, xored, mask); 1035 } 1036 if (fir::isa_complex(type)) { 1037 // Use HYPOT to fulfill the no underflow/overflow requirement. 1038 auto parts = 1039 Fortran::lower::ComplexExprHelper{builder, loc}.extractParts(arg); 1040 llvm::SmallVector<mlir::Value, 2> args = {parts.first, parts.second}; 1041 return genRuntimeCall("hypot", resultType, args); 1042 } 1043 llvm_unreachable("unexpected type in ABS argument"); 1044 } 1045 1046 // AIMAG 1047 mlir::Value IntrinsicLibrary::genAimag(mlir::Type resultType, 1048 llvm::ArrayRef<mlir::Value> args) { 1049 assert(args.size() == 1); 1050 return Fortran::lower::ComplexExprHelper{builder, loc}.extractComplexPart( 1051 args[0], true /* isImagPart */); 1052 } 1053 1054 // ANINT 1055 mlir::Value IntrinsicLibrary::genAnint(mlir::Type resultType, 1056 llvm::ArrayRef<mlir::Value> args) { 1057 assert(args.size() >= 1); 1058 // Skip optional kind argument to search the runtime; it is already reflected 1059 // in result type. 1060 return genRuntimeCall("anint", resultType, {args[0]}); 1061 } 1062 1063 // AINT 1064 mlir::Value IntrinsicLibrary::genAint(mlir::Type resultType, 1065 llvm::ArrayRef<mlir::Value> args) { 1066 assert(args.size() >= 1); 1067 // Skip optional kind argument to search the runtime; it is already reflected 1068 // in result type. 1069 return genRuntimeCall("aint", resultType, {args[0]}); 1070 } 1071 1072 // CEILING 1073 mlir::Value IntrinsicLibrary::genCeiling(mlir::Type resultType, 1074 llvm::ArrayRef<mlir::Value> args) { 1075 // Optional KIND argument. 1076 assert(args.size() >= 1); 1077 auto arg = args[0]; 1078 // Use ceil that is not an actual Fortran intrinsic but that is 1079 // an llvm intrinsic that does the same, but return a floating 1080 // point. 1081 auto ceil = genRuntimeCall("ceil", arg.getType(), {arg}); 1082 return builder.createConvert(loc, resultType, ceil); 1083 } 1084 1085 // CONJG 1086 mlir::Value IntrinsicLibrary::genConjg(mlir::Type resultType, 1087 llvm::ArrayRef<mlir::Value> args) { 1088 assert(args.size() == 1); 1089 if (resultType != args[0].getType()) 1090 llvm_unreachable("argument type mismatch"); 1091 1092 mlir::Value cplx = args[0]; 1093 auto imag = 1094 Fortran::lower::ComplexExprHelper{builder, loc}.extractComplexPart( 1095 cplx, /*isImagPart=*/true); 1096 auto negImag = builder.create<fir::NegfOp>(loc, imag); 1097 return Fortran::lower::ComplexExprHelper{builder, loc}.insertComplexPart( 1098 cplx, negImag, /*isImagPart=*/true); 1099 } 1100 1101 // DIM 1102 mlir::Value IntrinsicLibrary::genDim(mlir::Type resultType, 1103 llvm::ArrayRef<mlir::Value> args) { 1104 assert(args.size() == 2); 1105 if (resultType.isa<mlir::IntegerType>()) { 1106 auto zero = builder.createIntegerConstant(loc, resultType, 0); 1107 auto diff = builder.create<mlir::SubIOp>(loc, args[0], args[1]); 1108 auto cmp = 1109 builder.create<mlir::CmpIOp>(loc, mlir::CmpIPredicate::sgt, diff, zero); 1110 return builder.create<mlir::SelectOp>(loc, cmp, diff, zero); 1111 } 1112 assert(fir::isa_real(resultType) && "Only expects real and integer in DIM"); 1113 auto zero = builder.createRealZeroConstant(loc, resultType); 1114 auto diff = builder.create<fir::SubfOp>(loc, args[0], args[1]); 1115 auto cmp = 1116 builder.create<fir::CmpfOp>(loc, mlir::CmpFPredicate::OGT, diff, zero); 1117 return builder.create<mlir::SelectOp>(loc, cmp, diff, zero); 1118 } 1119 1120 // DPROD 1121 mlir::Value IntrinsicLibrary::genDprod(mlir::Type resultType, 1122 llvm::ArrayRef<mlir::Value> args) { 1123 assert(args.size() == 2); 1124 assert(fir::isa_real(resultType) && 1125 "Result must be double precision in DPROD"); 1126 auto a = builder.createConvert(loc, resultType, args[0]); 1127 auto b = builder.createConvert(loc, resultType, args[1]); 1128 return builder.create<fir::MulfOp>(loc, a, b); 1129 } 1130 1131 // FLOOR 1132 mlir::Value IntrinsicLibrary::genFloor(mlir::Type resultType, 1133 llvm::ArrayRef<mlir::Value> args) { 1134 // Optional KIND argument. 1135 assert(args.size() >= 1); 1136 auto arg = args[0]; 1137 // Use LLVM floor that returns real. 1138 auto floor = genRuntimeCall("floor", arg.getType(), {arg}); 1139 return builder.createConvert(loc, resultType, floor); 1140 } 1141 1142 // IAND 1143 mlir::Value IntrinsicLibrary::genIAnd(mlir::Type resultType, 1144 llvm::ArrayRef<mlir::Value> args) { 1145 assert(args.size() == 2); 1146 1147 return builder.create<mlir::AndOp>(loc, args[0], args[1]); 1148 } 1149 1150 // ICHAR 1151 mlir::Value IntrinsicLibrary::genIchar(mlir::Type resultType, 1152 llvm::ArrayRef<mlir::Value> args) { 1153 // There can be an optional kind in second argument. 1154 assert(args.size() >= 1); 1155 1156 auto arg = args[0]; 1157 Fortran::lower::CharacterExprHelper helper{builder, loc}; 1158 auto dataAndLen = helper.createUnboxChar(arg); 1159 auto charType = fir::CharacterType::get( 1160 builder.getContext(), helper.getCharacterKind(arg.getType())); 1161 auto refType = builder.getRefType(charType); 1162 auto charAddr = builder.createConvert(loc, refType, dataAndLen.first); 1163 auto charVal = builder.create<fir::LoadOp>(loc, charType, charAddr); 1164 return builder.createConvert(loc, resultType, charVal); 1165 } 1166 1167 // IEOR 1168 mlir::Value IntrinsicLibrary::genIEOr(mlir::Type resultType, 1169 llvm::ArrayRef<mlir::Value> args) { 1170 assert(args.size() == 2); 1171 return builder.create<mlir::XOrOp>(loc, args[0], args[1]); 1172 } 1173 1174 // IOR 1175 mlir::Value IntrinsicLibrary::genIOr(mlir::Type resultType, 1176 llvm::ArrayRef<mlir::Value> args) { 1177 assert(args.size() == 2); 1178 return builder.create<mlir::OrOp>(loc, args[0], args[1]); 1179 } 1180 1181 // LEN 1182 // Note that this is only used for unrestricted intrinsic. 1183 // Usage of LEN are otherwise rewritten as descriptor inquiries by the 1184 // front-end. 1185 fir::ExtendedValue 1186 IntrinsicLibrary::genLen(mlir::Type resultType, 1187 llvm::ArrayRef<fir::ExtendedValue> args) { 1188 // Optional KIND argument reflected in result type. 1189 assert(args.size() >= 1); 1190 mlir::Value len; 1191 if (const auto *charBox = args[0].getCharBox()) { 1192 len = charBox->getLen(); 1193 } else if (const auto *charBoxArray = args[0].getCharBox()) { 1194 len = charBoxArray->getLen(); 1195 } else { 1196 Fortran::lower::CharacterExprHelper helper{builder, loc}; 1197 len = helper.createUnboxChar(fir::getBase(args[0])).second; 1198 } 1199 1200 return builder.createConvert(loc, resultType, len); 1201 } 1202 1203 // LEN_TRIM 1204 fir::ExtendedValue 1205 IntrinsicLibrary::genLenTrim(mlir::Type resultType, 1206 llvm::ArrayRef<fir::ExtendedValue> args) { 1207 // Optional KIND argument reflected in result type. 1208 assert(args.size() >= 1); 1209 Fortran::lower::CharacterExprHelper helper{builder, loc}; 1210 auto len = helper.createLenTrim(fir::getBase(args[0])); 1211 return builder.createConvert(loc, resultType, len); 1212 } 1213 1214 // MERGE 1215 mlir::Value IntrinsicLibrary::genMerge(mlir::Type, 1216 llvm::ArrayRef<mlir::Value> args) { 1217 assert(args.size() == 3); 1218 1219 auto i1Type = mlir::IntegerType::get(1, builder.getContext()); 1220 auto mask = builder.createConvert(loc, i1Type, args[2]); 1221 return builder.create<mlir::SelectOp>(loc, mask, args[0], args[1]); 1222 } 1223 1224 // MOD 1225 mlir::Value IntrinsicLibrary::genMod(mlir::Type resultType, 1226 llvm::ArrayRef<mlir::Value> args) { 1227 assert(args.size() == 2); 1228 if (resultType.isa<mlir::IntegerType>()) 1229 return builder.create<mlir::SignedRemIOp>(loc, args[0], args[1]); 1230 1231 // Use runtime. Note that mlir::RemFOp implements floating point 1232 // remainder, but it does not work with fir::Real type. 1233 // TODO: consider using mlir::RemFOp when possible, that may help folding 1234 // and optimizations. 1235 return genRuntimeCall("mod", resultType, args); 1236 } 1237 1238 // NINT 1239 mlir::Value IntrinsicLibrary::genNint(mlir::Type resultType, 1240 llvm::ArrayRef<mlir::Value> args) { 1241 assert(args.size() >= 1); 1242 // Skip optional kind argument to search the runtime; it is already reflected 1243 // in result type. 1244 return genRuntimeCall("nint", resultType, {args[0]}); 1245 } 1246 1247 // SIGN 1248 mlir::Value IntrinsicLibrary::genSign(mlir::Type resultType, 1249 llvm::ArrayRef<mlir::Value> args) { 1250 assert(args.size() == 2); 1251 auto abs = genAbs(resultType, {args[0]}); 1252 if (resultType.isa<mlir::IntegerType>()) { 1253 auto zero = builder.createIntegerConstant(loc, resultType, 0); 1254 auto neg = builder.create<mlir::SubIOp>(loc, zero, abs); 1255 auto cmp = builder.create<mlir::CmpIOp>(loc, mlir::CmpIPredicate::slt, 1256 args[1], zero); 1257 return builder.create<mlir::SelectOp>(loc, cmp, neg, abs); 1258 } 1259 // TODO: Requirements when second argument is +0./0. 1260 auto zeroAttr = builder.getZeroAttr(resultType); 1261 auto zero = builder.create<mlir::ConstantOp>(loc, resultType, zeroAttr); 1262 auto neg = builder.create<fir::NegfOp>(loc, abs); 1263 auto cmp = 1264 builder.create<fir::CmpfOp>(loc, mlir::CmpFPredicate::OLT, args[1], zero); 1265 return builder.create<mlir::SelectOp>(loc, cmp, neg, abs); 1266 } 1267 1268 // Compare two FIR values and return boolean result as i1. 1269 template <Extremum extremum, ExtremumBehavior behavior> 1270 static mlir::Value createExtremumCompare(mlir::Location loc, 1271 Fortran::lower::FirOpBuilder &builder, 1272 mlir::Value left, mlir::Value right) { 1273 static constexpr auto integerPredicate = extremum == Extremum::Max 1274 ? mlir::CmpIPredicate::sgt 1275 : mlir::CmpIPredicate::slt; 1276 static constexpr auto orderedCmp = extremum == Extremum::Max 1277 ? mlir::CmpFPredicate::OGT 1278 : mlir::CmpFPredicate::OLT; 1279 auto type = left.getType(); 1280 mlir::Value result; 1281 if (fir::isa_real(type)) { 1282 // Note: the signaling/quit aspect of the result required by IEEE 1283 // cannot currently be obtained with LLVM without ad-hoc runtime. 1284 if constexpr (behavior == ExtremumBehavior::IeeeMinMaximumNumber) { 1285 // Return the number if one of the inputs is NaN and the other is 1286 // a number. 1287 auto leftIsResult = 1288 builder.create<fir::CmpfOp>(loc, orderedCmp, left, right); 1289 auto rightIsNan = builder.create<fir::CmpfOp>( 1290 loc, mlir::CmpFPredicate::UNE, right, right); 1291 result = builder.create<mlir::OrOp>(loc, leftIsResult, rightIsNan); 1292 } else if constexpr (behavior == ExtremumBehavior::IeeeMinMaximum) { 1293 // Always return NaNs if one the input is NaNs 1294 auto leftIsResult = 1295 builder.create<fir::CmpfOp>(loc, orderedCmp, left, right); 1296 auto leftIsNan = builder.create<fir::CmpfOp>( 1297 loc, mlir::CmpFPredicate::UNE, left, left); 1298 result = builder.create<mlir::OrOp>(loc, leftIsResult, leftIsNan); 1299 } else if constexpr (behavior == ExtremumBehavior::MinMaxss) { 1300 // If the left is a NaN, return the right whatever it is. 1301 result = builder.create<fir::CmpfOp>(loc, orderedCmp, left, right); 1302 } else if constexpr (behavior == ExtremumBehavior::PgfortranLlvm) { 1303 // If one of the operand is a NaN, return left whatever it is. 1304 static constexpr auto unorderedCmp = extremum == Extremum::Max 1305 ? mlir::CmpFPredicate::UGT 1306 : mlir::CmpFPredicate::ULT; 1307 result = builder.create<fir::CmpfOp>(loc, unorderedCmp, left, right); 1308 } else { 1309 // TODO: ieeeMinNum/ieeeMaxNum 1310 static_assert(behavior == ExtremumBehavior::IeeeMinMaxNum, 1311 "ieeeMinNum/ieeeMaxNum behavior not implemented"); 1312 } 1313 } else if (fir::isa_integer(type)) { 1314 result = builder.create<mlir::CmpIOp>(loc, integerPredicate, left, right); 1315 } else if (type.isa<fir::CharacterType>()) { 1316 // TODO: ! character min and max is tricky because the result 1317 // length is the length of the longest argument! 1318 // So we may need a temp. 1319 } 1320 assert(result); 1321 return result; 1322 } 1323 1324 // MIN and MAX 1325 template <Extremum extremum, ExtremumBehavior behavior> 1326 mlir::Value IntrinsicLibrary::genExtremum(mlir::Type, 1327 llvm::ArrayRef<mlir::Value> args) { 1328 assert(args.size() >= 1); 1329 mlir::Value result = args[0]; 1330 for (auto arg : args.drop_front()) { 1331 auto mask = 1332 createExtremumCompare<extremum, behavior>(loc, builder, result, arg); 1333 result = builder.create<mlir::SelectOp>(loc, mask, result, arg); 1334 } 1335 return result; 1336 } 1337 1338 //===----------------------------------------------------------------------===// 1339 // Public intrinsic call helpers 1340 //===----------------------------------------------------------------------===// 1341 1342 fir::ExtendedValue 1343 Fortran::lower::genIntrinsicCall(Fortran::lower::FirOpBuilder &builder, 1344 mlir::Location loc, llvm::StringRef name, 1345 mlir::Type resultType, 1346 llvm::ArrayRef<fir::ExtendedValue> args) { 1347 return IntrinsicLibrary{builder, loc}.genIntrinsicCall(name, resultType, 1348 args); 1349 } 1350 1351 mlir::Value Fortran::lower::genMax(Fortran::lower::FirOpBuilder &builder, 1352 mlir::Location loc, 1353 llvm::ArrayRef<mlir::Value> args) { 1354 assert(args.size() > 0 && "max requires at least one argument"); 1355 return IntrinsicLibrary{builder, loc} 1356 .genExtremum<Extremum::Max, ExtremumBehavior::MinMaxss>(args[0].getType(), 1357 args); 1358 } 1359 1360 mlir::Value Fortran::lower::genMin(Fortran::lower::FirOpBuilder &builder, 1361 mlir::Location loc, 1362 llvm::ArrayRef<mlir::Value> args) { 1363 assert(args.size() > 0 && "min requires at least one argument"); 1364 return IntrinsicLibrary{builder, loc} 1365 .genExtremum<Extremum::Min, ExtremumBehavior::MinMaxss>(args[0].getType(), 1366 args); 1367 } 1368 1369 mlir::Value Fortran::lower::genPow(Fortran::lower::FirOpBuilder &builder, 1370 mlir::Location loc, mlir::Type type, 1371 mlir::Value x, mlir::Value y) { 1372 return IntrinsicLibrary{builder, loc}.genRuntimeCall("pow", type, {x, y}); 1373 } 1374 1375 mlir::SymbolRefAttr Fortran::lower::getUnrestrictedIntrinsicSymbolRefAttr( 1376 Fortran::lower::FirOpBuilder &builder, mlir::Location loc, 1377 llvm::StringRef name, mlir::FunctionType signature) { 1378 return IntrinsicLibrary{builder, loc}.getUnrestrictedIntrinsicSymbolRefAttr( 1379 name, signature); 1380 } 1381