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