mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-29 10:26:06 +00:00

This patch includes some changes which brings the code in line with llvm coding guidelines. -> Remove curlies for one line if statements. -> Remove else after return. -> Removes a few usage of auto. -> Add Doxygen comments Addresses post review comments in D120403 by @schweitz. Reviewed By: schweitz Differential Revision: https://reviews.llvm.org/D120657
647 lines
27 KiB
C++
647 lines
27 KiB
C++
//===-- IntrinsicCall.cpp -------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Helper routines for constructing the FIR dialect of MLIR. As FIR is a
|
|
// dialect of MLIR, it makes extensive use of MLIR interfaces and MLIR's coding
|
|
// style (https://mlir.llvm.org/getting_started/DeveloperGuide/) is used in this
|
|
// module.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "flang/Lower/IntrinsicCall.h"
|
|
#include "flang/Common/static-multimap-view.h"
|
|
#include "flang/Lower/SymbolMap.h"
|
|
#include "flang/Lower/Todo.h"
|
|
#include "flang/Optimizer/Builder/Complex.h"
|
|
#include "flang/Optimizer/Builder/FIRBuilder.h"
|
|
#include "flang/Optimizer/Builder/Runtime/RTBuilder.h"
|
|
#include "flang/Optimizer/Support/FatalError.h"
|
|
#include "llvm/Support/CommandLine.h"
|
|
|
|
#define DEBUG_TYPE "flang-lower-intrinsic"
|
|
|
|
#define PGMATH_DECLARE
|
|
#include "flang/Evaluate/pgmath.h.inc"
|
|
|
|
/// This file implements lowering of Fortran intrinsic procedures.
|
|
/// Intrinsics are lowered to a mix of FIR and MLIR operations as
|
|
/// well as call to runtime functions or LLVM intrinsics.
|
|
|
|
/// Lowering of intrinsic procedure calls is based on a map that associates
|
|
/// Fortran intrinsic generic names to FIR generator functions.
|
|
/// All generator functions are member functions of the IntrinsicLibrary class
|
|
/// and have the same interface.
|
|
/// If no generator is given for an intrinsic name, a math runtime library
|
|
/// is searched for an implementation and, if a runtime function is found,
|
|
/// a call is generated for it. LLVM intrinsics are handled as a math
|
|
/// runtime library here.
|
|
|
|
fir::ExtendedValue Fortran::lower::getAbsentIntrinsicArgument() {
|
|
return fir::UnboxedValue{};
|
|
}
|
|
|
|
// TODO error handling -> return a code or directly emit messages ?
|
|
struct IntrinsicLibrary {
|
|
|
|
// Constructors.
|
|
explicit IntrinsicLibrary(fir::FirOpBuilder &builder, mlir::Location loc)
|
|
: builder{builder}, loc{loc} {}
|
|
IntrinsicLibrary() = delete;
|
|
IntrinsicLibrary(const IntrinsicLibrary &) = delete;
|
|
|
|
/// Generate FIR for call to Fortran intrinsic \p name with arguments \p arg
|
|
/// and expected result type \p resultType.
|
|
fir::ExtendedValue genIntrinsicCall(llvm::StringRef name,
|
|
llvm::Optional<mlir::Type> resultType,
|
|
llvm::ArrayRef<fir::ExtendedValue> arg);
|
|
|
|
/// Search a runtime function that is associated to the generic intrinsic name
|
|
/// and whose signature matches the intrinsic arguments and result types.
|
|
/// If no such runtime function is found but a runtime function associated
|
|
/// with the Fortran generic exists and has the same number of arguments,
|
|
/// conversions will be inserted before and/or after the call. This is to
|
|
/// mainly to allow 16 bits float support even-though little or no math
|
|
/// runtime is currently available for it.
|
|
mlir::Value genRuntimeCall(llvm::StringRef name, mlir::Type,
|
|
llvm::ArrayRef<mlir::Value>);
|
|
|
|
using RuntimeCallGenerator = std::function<mlir::Value(
|
|
fir::FirOpBuilder &, mlir::Location, llvm::ArrayRef<mlir::Value>)>;
|
|
RuntimeCallGenerator
|
|
getRuntimeCallGenerator(llvm::StringRef name,
|
|
mlir::FunctionType soughtFuncType);
|
|
|
|
/// Lowering for the ABS intrinsic. The ABS intrinsic expects one argument in
|
|
/// the llvm::ArrayRef. The ABS intrinsic is lowered into MLIR/FIR operation
|
|
/// if the argument is an integer, into llvm intrinsics if the argument is
|
|
/// real and to the `hypot` math routine if the argument is of complex type.
|
|
mlir::Value genAbs(mlir::Type, llvm::ArrayRef<mlir::Value>);
|
|
/// Lowering for the IAND intrinsic. The IAND intrinsic expects two arguments
|
|
/// in the llvm::ArrayRef.
|
|
mlir::Value genIand(mlir::Type, llvm::ArrayRef<mlir::Value>);
|
|
/// Define the different FIR generators that can be mapped to intrinsic to
|
|
/// generate the related code. The intrinsic is lowered into an MLIR
|
|
/// arith::AndIOp.
|
|
using ElementalGenerator = decltype(&IntrinsicLibrary::genAbs);
|
|
using Generator = std::variant<ElementalGenerator>;
|
|
|
|
/// Generate calls to ElementalGenerator, handling the elemental aspects
|
|
template <typename GeneratorType>
|
|
fir::ExtendedValue
|
|
genElementalCall(GeneratorType, llvm::StringRef name, mlir::Type resultType,
|
|
llvm::ArrayRef<fir::ExtendedValue> args, bool outline);
|
|
|
|
/// Helper to invoke code generator for the intrinsics given arguments.
|
|
mlir::Value invokeGenerator(ElementalGenerator generator,
|
|
mlir::Type resultType,
|
|
llvm::ArrayRef<mlir::Value> args);
|
|
mlir::Value invokeGenerator(RuntimeCallGenerator generator,
|
|
mlir::Type resultType,
|
|
llvm::ArrayRef<mlir::Value> args);
|
|
fir::FirOpBuilder &builder;
|
|
mlir::Location loc;
|
|
};
|
|
|
|
struct IntrinsicDummyArgument {
|
|
const char *name = nullptr;
|
|
Fortran::lower::LowerIntrinsicArgAs lowerAs =
|
|
Fortran::lower::LowerIntrinsicArgAs::Value;
|
|
bool handleDynamicOptional = false;
|
|
};
|
|
|
|
struct Fortran::lower::IntrinsicArgumentLoweringRules {
|
|
/// There is no more than 7 non repeated arguments in Fortran intrinsics.
|
|
IntrinsicDummyArgument args[7];
|
|
constexpr bool hasDefaultRules() const { return args[0].name == nullptr; }
|
|
};
|
|
|
|
/// Structure describing what needs to be done to lower intrinsic "name".
|
|
struct IntrinsicHandler {
|
|
const char *name;
|
|
IntrinsicLibrary::Generator generator;
|
|
Fortran::lower::IntrinsicArgumentLoweringRules argLoweringRules = {};
|
|
};
|
|
|
|
using I = IntrinsicLibrary;
|
|
|
|
/// Table that drives the fir generation depending on the intrinsic.
|
|
/// one to one mapping with Fortran arguments. If no mapping is
|
|
/// defined here for a generic intrinsic, genRuntimeCall will be called
|
|
/// to look for a match in the runtime a emit a call. Note that the argument
|
|
/// lowering rules for an intrinsic need to be provided only if at least one
|
|
/// argument must not be lowered by value. In which case, the lowering rules
|
|
/// should be provided for all the intrinsic arguments for completeness.
|
|
static constexpr IntrinsicHandler handlers[]{
|
|
{"abs", &I::genAbs},
|
|
{"iand", &I::genIand},
|
|
};
|
|
|
|
static const IntrinsicHandler *findIntrinsicHandler(llvm::StringRef name) {
|
|
auto compare = [](const IntrinsicHandler &handler, llvm::StringRef name) {
|
|
return name.compare(handler.name) > 0;
|
|
};
|
|
auto result =
|
|
std::lower_bound(std::begin(handlers), std::end(handlers), name, compare);
|
|
return result != std::end(handlers) && result->name == name ? result
|
|
: nullptr;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Math runtime description and matching utility
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// Command line option to modify math runtime version used to implement
|
|
/// intrinsics.
|
|
enum MathRuntimeVersion { fastVersion, llvmOnly };
|
|
llvm::cl::opt<MathRuntimeVersion> mathRuntimeVersion(
|
|
"math-runtime", llvm::cl::desc("Select math runtime version:"),
|
|
llvm::cl::values(
|
|
clEnumValN(fastVersion, "fast", "use pgmath fast runtime"),
|
|
clEnumValN(llvmOnly, "llvm",
|
|
"only use LLVM intrinsics (may be incomplete)")),
|
|
llvm::cl::init(fastVersion));
|
|
|
|
struct RuntimeFunction {
|
|
// llvm::StringRef comparison operator are not constexpr, so use string_view.
|
|
using Key = std::string_view;
|
|
// Needed for implicit compare with keys.
|
|
constexpr operator Key() const { return key; }
|
|
Key key; // intrinsic name
|
|
llvm::StringRef symbol;
|
|
fir::runtime::FuncTypeBuilderFunc typeGenerator;
|
|
};
|
|
|
|
#define RUNTIME_STATIC_DESCRIPTION(name, func) \
|
|
{#name, #func, fir::runtime::RuntimeTableKey<decltype(func)>::getTypeModel()},
|
|
static constexpr RuntimeFunction pgmathFast[] = {
|
|
#define PGMATH_FAST
|
|
#define PGMATH_USE_ALL_TYPES(name, func) RUNTIME_STATIC_DESCRIPTION(name, func)
|
|
#include "flang/Evaluate/pgmath.h.inc"
|
|
};
|
|
|
|
static mlir::FunctionType genF32F32FuncType(mlir::MLIRContext *context) {
|
|
mlir::Type t = mlir::FloatType::getF32(context);
|
|
return mlir::FunctionType::get(context, {t}, {t});
|
|
}
|
|
|
|
static mlir::FunctionType genF64F64FuncType(mlir::MLIRContext *context) {
|
|
mlir::Type t = mlir::FloatType::getF64(context);
|
|
return mlir::FunctionType::get(context, {t}, {t});
|
|
}
|
|
|
|
static mlir::FunctionType genF32F32F32FuncType(mlir::MLIRContext *context) {
|
|
auto t = mlir::FloatType::getF32(context);
|
|
return mlir::FunctionType::get(context, {t, t}, {t});
|
|
}
|
|
|
|
static mlir::FunctionType genF64F64F64FuncType(mlir::MLIRContext *context) {
|
|
auto t = mlir::FloatType::getF64(context);
|
|
return mlir::FunctionType::get(context, {t, t}, {t});
|
|
}
|
|
|
|
// TODO : Fill-up this table with more intrinsic.
|
|
// Note: These are also defined as operations in LLVM dialect. See if this
|
|
// can be use and has advantages.
|
|
static constexpr RuntimeFunction llvmIntrinsics[] = {
|
|
{"abs", "llvm.fabs.f32", genF32F32FuncType},
|
|
{"abs", "llvm.fabs.f64", genF64F64FuncType},
|
|
{"pow", "llvm.pow.f32", genF32F32F32FuncType},
|
|
{"pow", "llvm.pow.f64", genF64F64F64FuncType},
|
|
};
|
|
|
|
// This helper class computes a "distance" between two function types.
|
|
// The distance measures how many narrowing conversions of actual arguments
|
|
// and result of "from" must be made in order to use "to" instead of "from".
|
|
// For instance, the distance between ACOS(REAL(10)) and ACOS(REAL(8)) is
|
|
// greater than the one between ACOS(REAL(10)) and ACOS(REAL(16)). This means
|
|
// if no implementation of ACOS(REAL(10)) is available, it is better to use
|
|
// ACOS(REAL(16)) with casts rather than ACOS(REAL(8)).
|
|
// Note that this is not a symmetric distance and the order of "from" and "to"
|
|
// arguments matters, d(foo, bar) may not be the same as d(bar, foo) because it
|
|
// may be safe to replace foo by bar, but not the opposite.
|
|
class FunctionDistance {
|
|
public:
|
|
FunctionDistance() : infinite{true} {}
|
|
|
|
FunctionDistance(mlir::FunctionType from, mlir::FunctionType to) {
|
|
unsigned nInputs = from.getNumInputs();
|
|
unsigned nResults = from.getNumResults();
|
|
if (nResults != to.getNumResults() || nInputs != to.getNumInputs()) {
|
|
infinite = true;
|
|
} else {
|
|
for (decltype(nInputs) i = 0; i < nInputs && !infinite; ++i)
|
|
addArgumentDistance(from.getInput(i), to.getInput(i));
|
|
for (decltype(nResults) i = 0; i < nResults && !infinite; ++i)
|
|
addResultDistance(to.getResult(i), from.getResult(i));
|
|
}
|
|
}
|
|
|
|
/// Beware both d1.isSmallerThan(d2) *and* d2.isSmallerThan(d1) may be
|
|
/// false if both d1 and d2 are infinite. This implies that
|
|
/// d1.isSmallerThan(d2) is not equivalent to !d2.isSmallerThan(d1)
|
|
bool isSmallerThan(const FunctionDistance &d) const {
|
|
return !infinite &&
|
|
(d.infinite || std::lexicographical_compare(
|
|
conversions.begin(), conversions.end(),
|
|
d.conversions.begin(), d.conversions.end()));
|
|
}
|
|
|
|
bool isLosingPrecision() const {
|
|
return conversions[narrowingArg] != 0 || conversions[extendingResult] != 0;
|
|
}
|
|
|
|
bool isInfinite() const { return infinite; }
|
|
|
|
private:
|
|
enum class Conversion { Forbidden, None, Narrow, Extend };
|
|
|
|
void addArgumentDistance(mlir::Type from, mlir::Type to) {
|
|
switch (conversionBetweenTypes(from, to)) {
|
|
case Conversion::Forbidden:
|
|
infinite = true;
|
|
break;
|
|
case Conversion::None:
|
|
break;
|
|
case Conversion::Narrow:
|
|
conversions[narrowingArg]++;
|
|
break;
|
|
case Conversion::Extend:
|
|
conversions[nonNarrowingArg]++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void addResultDistance(mlir::Type from, mlir::Type to) {
|
|
switch (conversionBetweenTypes(from, to)) {
|
|
case Conversion::Forbidden:
|
|
infinite = true;
|
|
break;
|
|
case Conversion::None:
|
|
break;
|
|
case Conversion::Narrow:
|
|
conversions[nonExtendingResult]++;
|
|
break;
|
|
case Conversion::Extend:
|
|
conversions[extendingResult]++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Floating point can be mlir::FloatType or fir::real
|
|
static unsigned getFloatingPointWidth(mlir::Type t) {
|
|
if (auto f{t.dyn_cast<mlir::FloatType>()})
|
|
return f.getWidth();
|
|
// FIXME: Get width another way for fir.real/complex
|
|
// - use fir/KindMapping.h and llvm::Type
|
|
// - or use evaluate/type.h
|
|
if (auto r{t.dyn_cast<fir::RealType>()})
|
|
return r.getFKind() * 4;
|
|
if (auto cplx{t.dyn_cast<fir::ComplexType>()})
|
|
return cplx.getFKind() * 4;
|
|
llvm_unreachable("not a floating-point type");
|
|
}
|
|
|
|
static Conversion conversionBetweenTypes(mlir::Type from, mlir::Type to) {
|
|
if (from == to)
|
|
return Conversion::None;
|
|
|
|
if (auto fromIntTy{from.dyn_cast<mlir::IntegerType>()}) {
|
|
if (auto toIntTy{to.dyn_cast<mlir::IntegerType>()}) {
|
|
return fromIntTy.getWidth() > toIntTy.getWidth() ? Conversion::Narrow
|
|
: Conversion::Extend;
|
|
}
|
|
}
|
|
|
|
if (fir::isa_real(from) && fir::isa_real(to)) {
|
|
return getFloatingPointWidth(from) > getFloatingPointWidth(to)
|
|
? Conversion::Narrow
|
|
: Conversion::Extend;
|
|
}
|
|
|
|
if (auto fromCplxTy{from.dyn_cast<fir::ComplexType>()}) {
|
|
if (auto toCplxTy{to.dyn_cast<fir::ComplexType>()}) {
|
|
return getFloatingPointWidth(fromCplxTy) >
|
|
getFloatingPointWidth(toCplxTy)
|
|
? Conversion::Narrow
|
|
: Conversion::Extend;
|
|
}
|
|
}
|
|
// Notes:
|
|
// - No conversion between character types, specialization of runtime
|
|
// functions should be made instead.
|
|
// - It is not clear there is a use case for automatic conversions
|
|
// around Logical and it may damage hidden information in the physical
|
|
// storage so do not do it.
|
|
return Conversion::Forbidden;
|
|
}
|
|
|
|
// Below are indexes to access data in conversions.
|
|
// The order in data does matter for lexicographical_compare
|
|
enum {
|
|
narrowingArg = 0, // usually bad
|
|
extendingResult, // usually bad
|
|
nonExtendingResult, // usually ok
|
|
nonNarrowingArg, // usually ok
|
|
dataSize
|
|
};
|
|
|
|
std::array<int, dataSize> conversions = {};
|
|
bool infinite = false; // When forbidden conversion or wrong argument number
|
|
};
|
|
|
|
/// Build mlir::FuncOp from runtime symbol description and add
|
|
/// fir.runtime attribute.
|
|
static mlir::FuncOp getFuncOp(mlir::Location loc, fir::FirOpBuilder &builder,
|
|
const RuntimeFunction &runtime) {
|
|
mlir::FuncOp function = builder.addNamedFunction(
|
|
loc, runtime.symbol, runtime.typeGenerator(builder.getContext()));
|
|
function->setAttr("fir.runtime", builder.getUnitAttr());
|
|
return function;
|
|
}
|
|
|
|
/// Select runtime function that has the smallest distance to the intrinsic
|
|
/// function type and that will not imply narrowing arguments or extending the
|
|
/// result.
|
|
/// If nothing is found, the mlir::FuncOp will contain a nullptr.
|
|
mlir::FuncOp searchFunctionInLibrary(
|
|
mlir::Location loc, fir::FirOpBuilder &builder,
|
|
const Fortran::common::StaticMultimapView<RuntimeFunction> &lib,
|
|
llvm::StringRef name, mlir::FunctionType funcType,
|
|
const RuntimeFunction **bestNearMatch,
|
|
FunctionDistance &bestMatchDistance) {
|
|
std::pair<const RuntimeFunction *, const RuntimeFunction *> range =
|
|
lib.equal_range(name);
|
|
for (auto iter = range.first; iter != range.second && iter; ++iter) {
|
|
const RuntimeFunction &impl = *iter;
|
|
mlir::FunctionType implType = impl.typeGenerator(builder.getContext());
|
|
if (funcType == implType)
|
|
return getFuncOp(loc, builder, impl); // exact match
|
|
|
|
FunctionDistance distance(funcType, implType);
|
|
if (distance.isSmallerThan(bestMatchDistance)) {
|
|
*bestNearMatch = &impl;
|
|
bestMatchDistance = std::move(distance);
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
/// Search runtime for the best runtime function given an intrinsic name
|
|
/// and interface. The interface may not be a perfect match in which case
|
|
/// the caller is responsible to insert argument and return value conversions.
|
|
/// If nothing is found, the mlir::FuncOp will contain a nullptr.
|
|
static mlir::FuncOp getRuntimeFunction(mlir::Location loc,
|
|
fir::FirOpBuilder &builder,
|
|
llvm::StringRef name,
|
|
mlir::FunctionType funcType) {
|
|
const RuntimeFunction *bestNearMatch = nullptr;
|
|
FunctionDistance bestMatchDistance{};
|
|
mlir::FuncOp match;
|
|
using RtMap = Fortran::common::StaticMultimapView<RuntimeFunction>;
|
|
static constexpr RtMap pgmathF(pgmathFast);
|
|
static_assert(pgmathF.Verify() && "map must be sorted");
|
|
if (mathRuntimeVersion == fastVersion) {
|
|
match = searchFunctionInLibrary(loc, builder, pgmathF, name, funcType,
|
|
&bestNearMatch, bestMatchDistance);
|
|
} else {
|
|
assert(mathRuntimeVersion == llvmOnly && "unknown math runtime");
|
|
}
|
|
if (match)
|
|
return match;
|
|
|
|
// Go through llvm intrinsics if not exact match in libpgmath or if
|
|
// mathRuntimeVersion == llvmOnly
|
|
static constexpr RtMap llvmIntr(llvmIntrinsics);
|
|
static_assert(llvmIntr.Verify() && "map must be sorted");
|
|
if (mlir::FuncOp exactMatch =
|
|
searchFunctionInLibrary(loc, builder, llvmIntr, name, funcType,
|
|
&bestNearMatch, bestMatchDistance))
|
|
return exactMatch;
|
|
|
|
if (bestNearMatch != nullptr) {
|
|
if (bestMatchDistance.isLosingPrecision()) {
|
|
// Using this runtime version requires narrowing the arguments
|
|
// or extending the result. It is not numerically safe. There
|
|
// is currently no quad math library that was described in
|
|
// lowering and could be used here. Emit an error and continue
|
|
// generating the code with the narrowing cast so that the user
|
|
// can get a complete list of the problematic intrinsic calls.
|
|
std::string message("TODO: no math runtime available for '");
|
|
llvm::raw_string_ostream sstream(message);
|
|
if (name == "pow") {
|
|
assert(funcType.getNumInputs() == 2 &&
|
|
"power operator has two arguments");
|
|
sstream << funcType.getInput(0) << " ** " << funcType.getInput(1);
|
|
} else {
|
|
sstream << name << "(";
|
|
if (funcType.getNumInputs() > 0)
|
|
sstream << funcType.getInput(0);
|
|
for (mlir::Type argType : funcType.getInputs().drop_front())
|
|
sstream << ", " << argType;
|
|
sstream << ")";
|
|
}
|
|
sstream << "'";
|
|
mlir::emitError(loc, message);
|
|
}
|
|
return getFuncOp(loc, builder, *bestNearMatch);
|
|
}
|
|
return {};
|
|
}
|
|
|
|
/// Helpers to get function type from arguments and result type.
|
|
static mlir::FunctionType getFunctionType(llvm::Optional<mlir::Type> resultType,
|
|
llvm::ArrayRef<mlir::Value> arguments,
|
|
fir::FirOpBuilder &builder) {
|
|
llvm::SmallVector<mlir::Type> argTypes;
|
|
for (mlir::Value arg : arguments)
|
|
argTypes.push_back(arg.getType());
|
|
llvm::SmallVector<mlir::Type> resTypes;
|
|
if (resultType)
|
|
resTypes.push_back(*resultType);
|
|
return mlir::FunctionType::get(builder.getModule().getContext(), argTypes,
|
|
resTypes);
|
|
}
|
|
//===----------------------------------------------------------------------===//
|
|
// IntrinsicLibrary
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
template <typename GeneratorType>
|
|
fir::ExtendedValue IntrinsicLibrary::genElementalCall(
|
|
GeneratorType generator, llvm::StringRef name, mlir::Type resultType,
|
|
llvm::ArrayRef<fir::ExtendedValue> args, bool outline) {
|
|
llvm::SmallVector<mlir::Value> scalarArgs;
|
|
for (const fir::ExtendedValue &arg : args)
|
|
if (arg.getUnboxed() || arg.getCharBox())
|
|
scalarArgs.emplace_back(fir::getBase(arg));
|
|
else
|
|
fir::emitFatalError(loc, "nonscalar intrinsic argument");
|
|
return invokeGenerator(generator, resultType, scalarArgs);
|
|
}
|
|
|
|
static fir::ExtendedValue
|
|
invokeHandler(IntrinsicLibrary::ElementalGenerator generator,
|
|
const IntrinsicHandler &handler,
|
|
llvm::Optional<mlir::Type> resultType,
|
|
llvm::ArrayRef<fir::ExtendedValue> args, bool outline,
|
|
IntrinsicLibrary &lib) {
|
|
assert(resultType && "expect elemental intrinsic to be functions");
|
|
return lib.genElementalCall(generator, handler.name, *resultType, args,
|
|
outline);
|
|
}
|
|
|
|
fir::ExtendedValue
|
|
IntrinsicLibrary::genIntrinsicCall(llvm::StringRef name,
|
|
llvm::Optional<mlir::Type> resultType,
|
|
llvm::ArrayRef<fir::ExtendedValue> args) {
|
|
if (const IntrinsicHandler *handler = findIntrinsicHandler(name)) {
|
|
bool outline = false;
|
|
return std::visit(
|
|
[&](auto &generator) -> fir::ExtendedValue {
|
|
return invokeHandler(generator, *handler, resultType, args, outline,
|
|
*this);
|
|
},
|
|
handler->generator);
|
|
}
|
|
|
|
TODO(loc, "genIntrinsicCall runtime");
|
|
return {};
|
|
}
|
|
|
|
mlir::Value
|
|
IntrinsicLibrary::invokeGenerator(ElementalGenerator generator,
|
|
mlir::Type resultType,
|
|
llvm::ArrayRef<mlir::Value> args) {
|
|
return std::invoke(generator, *this, resultType, args);
|
|
}
|
|
|
|
mlir::Value
|
|
IntrinsicLibrary::invokeGenerator(RuntimeCallGenerator generator,
|
|
mlir::Type resultType,
|
|
llvm::ArrayRef<mlir::Value> args) {
|
|
return generator(builder, loc, args);
|
|
}
|
|
IntrinsicLibrary::RuntimeCallGenerator
|
|
IntrinsicLibrary::getRuntimeCallGenerator(llvm::StringRef name,
|
|
mlir::FunctionType soughtFuncType) {
|
|
mlir::FuncOp funcOp = getRuntimeFunction(loc, builder, name, soughtFuncType);
|
|
if (!funcOp) {
|
|
mlir::emitError(loc,
|
|
"TODO: missing intrinsic lowering: " + llvm::Twine(name));
|
|
llvm::errs() << "requested type was: " << soughtFuncType << "\n";
|
|
exit(1);
|
|
}
|
|
|
|
mlir::FunctionType actualFuncType = funcOp.getType();
|
|
assert(actualFuncType.getNumResults() == soughtFuncType.getNumResults() &&
|
|
actualFuncType.getNumInputs() == soughtFuncType.getNumInputs() &&
|
|
actualFuncType.getNumResults() == 1 && "Bad intrinsic match");
|
|
|
|
return [funcOp, actualFuncType,
|
|
soughtFuncType](fir::FirOpBuilder &builder, mlir::Location loc,
|
|
llvm::ArrayRef<mlir::Value> args) {
|
|
llvm::SmallVector<mlir::Value> convertedArguments;
|
|
for (auto [fst, snd] : llvm::zip(actualFuncType.getInputs(), args))
|
|
convertedArguments.push_back(builder.createConvert(loc, fst, snd));
|
|
auto call = builder.create<fir::CallOp>(loc, funcOp, convertedArguments);
|
|
mlir::Type soughtType = soughtFuncType.getResult(0);
|
|
return builder.createConvert(loc, soughtType, call.getResult(0));
|
|
};
|
|
}
|
|
//===----------------------------------------------------------------------===//
|
|
// Code generators for the intrinsic
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
mlir::Value IntrinsicLibrary::genRuntimeCall(llvm::StringRef name,
|
|
mlir::Type resultType,
|
|
llvm::ArrayRef<mlir::Value> args) {
|
|
mlir::FunctionType soughtFuncType =
|
|
getFunctionType(resultType, args, builder);
|
|
return getRuntimeCallGenerator(name, soughtFuncType)(builder, loc, args);
|
|
}
|
|
|
|
// ABS
|
|
mlir::Value IntrinsicLibrary::genAbs(mlir::Type resultType,
|
|
llvm::ArrayRef<mlir::Value> args) {
|
|
assert(args.size() == 1);
|
|
mlir::Value arg = args[0];
|
|
mlir::Type type = arg.getType();
|
|
if (fir::isa_real(type)) {
|
|
// Runtime call to fp abs. An alternative would be to use mlir
|
|
// math::AbsFOp but it does not support all fir floating point types.
|
|
return genRuntimeCall("abs", resultType, args);
|
|
}
|
|
if (auto intType = type.dyn_cast<mlir::IntegerType>()) {
|
|
// At the time of this implementation there is no abs op in mlir.
|
|
// So, implement abs here without branching.
|
|
mlir::Value shift =
|
|
builder.createIntegerConstant(loc, intType, intType.getWidth() - 1);
|
|
auto mask = builder.create<mlir::arith::ShRSIOp>(loc, arg, shift);
|
|
auto xored = builder.create<mlir::arith::XOrIOp>(loc, arg, mask);
|
|
return builder.create<mlir::arith::SubIOp>(loc, xored, mask);
|
|
}
|
|
if (fir::isa_complex(type)) {
|
|
// Use HYPOT to fulfill the no underflow/overflow requirement.
|
|
auto parts = fir::factory::Complex{builder, loc}.extractParts(arg);
|
|
llvm::SmallVector<mlir::Value> args = {parts.first, parts.second};
|
|
return genRuntimeCall("hypot", resultType, args);
|
|
}
|
|
llvm_unreachable("unexpected type in ABS argument");
|
|
}
|
|
|
|
// IAND
|
|
mlir::Value IntrinsicLibrary::genIand(mlir::Type resultType,
|
|
llvm::ArrayRef<mlir::Value> args) {
|
|
assert(args.size() == 2);
|
|
return builder.create<mlir::arith::AndIOp>(loc, args[0], args[1]);
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Argument lowering rules interface
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
const Fortran::lower::IntrinsicArgumentLoweringRules *
|
|
Fortran::lower::getIntrinsicArgumentLowering(llvm::StringRef intrinsicName) {
|
|
if (const IntrinsicHandler *handler = findIntrinsicHandler(intrinsicName))
|
|
if (!handler->argLoweringRules.hasDefaultRules())
|
|
return &handler->argLoweringRules;
|
|
return nullptr;
|
|
}
|
|
|
|
/// Return how argument \p argName should be lowered given the rules for the
|
|
/// intrinsic function.
|
|
Fortran::lower::ArgLoweringRule Fortran::lower::lowerIntrinsicArgumentAs(
|
|
mlir::Location loc, const IntrinsicArgumentLoweringRules &rules,
|
|
llvm::StringRef argName) {
|
|
for (const IntrinsicDummyArgument &arg : rules.args) {
|
|
if (arg.name && arg.name == argName)
|
|
return {arg.lowerAs, arg.handleDynamicOptional};
|
|
}
|
|
fir::emitFatalError(
|
|
loc, "internal: unknown intrinsic argument name in lowering '" + argName +
|
|
"'");
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Public intrinsic call helpers
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
fir::ExtendedValue
|
|
Fortran::lower::genIntrinsicCall(fir::FirOpBuilder &builder, mlir::Location loc,
|
|
llvm::StringRef name,
|
|
llvm::Optional<mlir::Type> resultType,
|
|
llvm::ArrayRef<fir::ExtendedValue> args) {
|
|
return IntrinsicLibrary{builder, loc}.genIntrinsicCall(name, resultType,
|
|
args);
|
|
}
|
|
|
|
mlir::Value Fortran::lower::genPow(fir::FirOpBuilder &builder,
|
|
mlir::Location loc, mlir::Type type,
|
|
mlir::Value x, mlir::Value y) {
|
|
return IntrinsicLibrary{builder, loc}.genRuntimeCall("pow", type, {x, y});
|
|
}
|