//===-- HlfirIntrinsics.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
//
//===----------------------------------------------------------------------===//
//
// Coding style: https://mlir.llvm.org/getting_started/DeveloperGuide/
//
//===----------------------------------------------------------------------===//
#include "flang/Lower/HlfirIntrinsics.h"
#include "flang/Optimizer/Builder/BoxValue.h"
#include "flang/Optimizer/Builder/FIRBuilder.h"
#include "flang/Optimizer/Builder/HLFIRTools.h"
#include "flang/Optimizer/Builder/IntrinsicCall.h"
#include "flang/Optimizer/Builder/MutableBox.h"
#include "flang/Optimizer/Builder/Todo.h"
#include "flang/Optimizer/Dialect/FIRType.h"
#include "flang/Optimizer/HLFIR/HLFIRDialect.h"
#include "flang/Optimizer/HLFIR/HLFIROps.h"
#include "mlir/IR/Value.h"
#include "llvm/ADT/SmallVector.h"
#include <mlir/IR/ValueRange.h>
namespace {
class HlfirTransformationalIntrinsic {
public:
explicit HlfirTransformationalIntrinsic(fir::FirOpBuilder &builder,
mlir::Location loc)
: builder(builder), loc(loc) {}
virtual ~HlfirTransformationalIntrinsic() = default;
hlfir::EntityWithAttributes
lower(const Fortran::lower::PreparedActualArguments &loweredActuals,
const fir::IntrinsicArgumentLoweringRules *argLowering,
mlir::Type stmtResultType) {
mlir::Value res = lowerImpl(loweredActuals, argLowering, stmtResultType);
for (const hlfir::CleanupFunction &fn : cleanupFns)
fn();
return {hlfir::EntityWithAttributes{res}};
}
protected:
fir::FirOpBuilder &builder;
mlir::Location loc;
llvm::SmallVector<hlfir::CleanupFunction, 3> cleanupFns;
virtual mlir::Value
lowerImpl(const Fortran::lower::PreparedActualArguments &loweredActuals,
const fir::IntrinsicArgumentLoweringRules *argLowering,
mlir::Type stmtResultType) = 0;
llvm::SmallVector<mlir::Value> getOperandVector(
const Fortran::lower::PreparedActualArguments &loweredActuals,
const fir::IntrinsicArgumentLoweringRules *argLowering);
mlir::Type computeResultType(mlir::Value argArray, mlir::Type stmtResultType);
template <typename OP, typename... BUILD_ARGS>
inline OP createOp(BUILD_ARGS... args) {
return builder.create<OP>(loc, args...);
}
mlir::Value loadBoxAddress(
const std::optional<Fortran::lower::PreparedActualArgument> &arg);
void addCleanup(std::optional<hlfir::CleanupFunction> cleanup) {
if (cleanup)
cleanupFns.emplace_back(std::move(*cleanup));
}
};
template <typename OP, bool HAS_MASK>
class HlfirReductionIntrinsic : public HlfirTransformationalIntrinsic {
public:
using HlfirTransformationalIntrinsic::HlfirTransformationalIntrinsic;
protected:
mlir::Value
lowerImpl(const Fortran::lower::PreparedActualArguments &loweredActuals,
const fir::IntrinsicArgumentLoweringRules *argLowering,
mlir::Type stmtResultType) override;
};
using HlfirSumLowering = HlfirReductionIntrinsic<hlfir::SumOp, true>;
using HlfirProductLowering = HlfirReductionIntrinsic<hlfir::ProductOp, true>;
using HlfirMaxvalLowering = HlfirReductionIntrinsic<hlfir::MaxvalOp, true>;
using HlfirMinvalLowering = HlfirReductionIntrinsic<hlfir::MinvalOp, true>;
using HlfirAnyLowering = HlfirReductionIntrinsic<hlfir::AnyOp, false>;
using HlfirAllLowering = HlfirReductionIntrinsic<hlfir::AllOp, false>;
template <typename OP>
class HlfirMinMaxLocIntrinsic : public HlfirTransformationalIntrinsic {
public:
using HlfirTransformationalIntrinsic::HlfirTransformationalIntrinsic;
protected:
mlir::Value
lowerImpl(const Fortran::lower::PreparedActualArguments &loweredActuals,
const fir::IntrinsicArgumentLoweringRules *argLowering,
mlir::Type stmtResultType) override;
};
using HlfirMinlocLowering = HlfirMinMaxLocIntrinsic<hlfir::MinlocOp>;
using HlfirMaxlocLowering = HlfirMinMaxLocIntrinsic<hlfir::MaxlocOp>;
template <typename OP>
class HlfirProductIntrinsic : public HlfirTransformationalIntrinsic {
public:
using HlfirTransformationalIntrinsic::HlfirTransformationalIntrinsic;
protected:
mlir::Value
lowerImpl(const Fortran::lower::PreparedActualArguments &loweredActuals,
const fir::IntrinsicArgumentLoweringRules *argLowering,
mlir::Type stmtResultType) override;
};
using HlfirMatmulLowering = HlfirProductIntrinsic<hlfir::MatmulOp>;
using HlfirDotProductLowering = HlfirProductIntrinsic<hlfir::DotProductOp>;
class HlfirTransposeLowering : public HlfirTransformationalIntrinsic {
public:
using HlfirTransformationalIntrinsic::HlfirTransformationalIntrinsic;
protected:
mlir::Value
lowerImpl(const Fortran::lower::PreparedActualArguments &loweredActuals,
const fir::IntrinsicArgumentLoweringRules *argLowering,
mlir::Type stmtResultType) override;
};
class HlfirCountLowering : public HlfirTransformationalIntrinsic {
public:
using HlfirTransformationalIntrinsic::HlfirTransformationalIntrinsic;
protected:
mlir::Value
lowerImpl(const Fortran::lower::PreparedActualArguments &loweredActuals,
const fir::IntrinsicArgumentLoweringRules *argLowering,
mlir::Type stmtResultType) override;
};
class HlfirCharExtremumLowering : public HlfirTransformationalIntrinsic {
public:
HlfirCharExtremumLowering(fir::FirOpBuilder &builder, mlir::Location loc,
hlfir::CharExtremumPredicate pred)
: HlfirTransformationalIntrinsic(builder, loc), pred{pred} {}
protected:
mlir::Value
lowerImpl(const Fortran::lower::PreparedActualArguments &loweredActuals,
const fir::IntrinsicArgumentLoweringRules *argLowering,
mlir::Type stmtResultType) override;
protected:
hlfir::CharExtremumPredicate pred;
};
} // namespace
mlir::Value HlfirTransformationalIntrinsic::loadBoxAddress(
const std::optional<Fortran::lower::PreparedActualArgument> &arg) {
if (!arg)
return mlir::Value{};
hlfir::Entity actual = arg->getActual(loc, builder);
if (!arg->handleDynamicOptional()) {
if (actual.isMutableBox()) {
// this is a box address type but is not dynamically optional. Just load
// the box, assuming it is well formed (!fir.ref<!fir.box<...>> ->
// !fir.box<...>)
return builder.create<fir::LoadOp>(loc, actual.getBase());
}
return actual;
}
auto [exv, cleanup] = hlfir::translateToExtendedValue(loc, builder, actual);
addCleanup(cleanup);
mlir::Value isPresent = arg->getIsPresent();
// createBox will not do create any invalid memory dereferences if exv is
// absent. The created fir.box will not be usable, but the SelectOp below
// ensures it won't be.
mlir::Value box = builder.createBox(loc, exv);
mlir::Type boxType = box.getType();
auto absent = builder.create<fir::AbsentOp>(loc, boxType);
auto boxOrAbsent = builder.create<mlir::arith::SelectOp>(
loc, boxType, isPresent, box, absent);
return boxOrAbsent;
}
static mlir::Value loadOptionalValue(
mlir::Location loc, fir::FirOpBuilder &builder,
const std::optional<Fortran::lower::PreparedActualArgument> &arg,
hlfir::Entity actual) {
if (!arg->handleDynamicOptional())
return hlfir::loadTrivialScalar(loc, builder, actual);
mlir::Value isPresent = arg->getIsPresent();
mlir::Type eleType = hlfir::getFortranElementType(actual.getType());
return builder
.genIfOp(loc, {eleType}, isPresent,
/*withElseRegion=*/true)
.genThen([&]() {
assert(actual.isScalar() && fir::isa_trivial(eleType) &&
"must be a numerical or logical scalar");
hlfir::Entity val = hlfir::loadTrivialScalar(loc, builder, actual);
builder.create<fir::ResultOp>(loc, val);
})
.genElse([&]() {
mlir::Value zero = fir::factory::createZeroValue(builder, loc, eleType);
builder.create<fir::ResultOp>(loc, zero);
})
.getResults()[0];
}
llvm::SmallVector<mlir::Value> HlfirTransformationalIntrinsic::getOperandVector(
const Fortran::lower::PreparedActualArguments &loweredActuals,
const fir::IntrinsicArgumentLoweringRules *argLowering) {
llvm::SmallVector<mlir::Value> operands;
operands.reserve(loweredActuals.size());
for (size_t i = 0; i < loweredActuals.size(); ++i) {
std::optional<Fortran::lower::PreparedActualArgument> arg =
loweredActuals[i];
if (!arg) {
operands.emplace_back();
continue;
}
hlfir::Entity actual = arg->getActual(loc, builder);
mlir::Value valArg;
if (!argLowering) {
valArg = hlfir::loadTrivialScalar(loc, builder, actual);
} else {
fir::ArgLoweringRule argRules =
fir::lowerIntrinsicArgumentAs(*argLowering, i);
if (argRules.lowerAs == fir::LowerIntrinsicArgAs::Box)
valArg = loadBoxAddress(arg);
else if (!argRules.handleDynamicOptional &&
argRules.lowerAs != fir::LowerIntrinsicArgAs::Inquired)
valArg = hlfir::derefPointersAndAllocatables(loc, builder, actual);
else if (argRules.handleDynamicOptional &&
argRules.lowerAs == fir::LowerIntrinsicArgAs::Value)
valArg = loadOptionalValue(loc, builder, arg, actual);
else if (argRules.handleDynamicOptional)
TODO(loc, "hlfir transformational intrinsic dynamically optional "
"argument without box lowering");
else
valArg = actual.getBase();
}
operands.emplace_back(valArg);
}
return operands;
}
mlir::Type
HlfirTransformationalIntrinsic::computeResultType(mlir::Value argArray,
mlir::Type stmtResultType) {
mlir::Type normalisedResult =
hlfir::getFortranElementOrSequenceType(stmtResultType);
if (auto array = mlir::dyn_cast<fir::SequenceType>(normalisedResult)) {
hlfir::ExprType::Shape resultShape =
hlfir::ExprType::Shape{array.getShape()};
mlir::Type elementType = array.getEleTy();
return hlfir::ExprType::get(builder.getContext(), resultShape, elementType,
/*polymorphic=*/false);
} else if (auto resCharType =
mlir::dyn_cast<fir::CharacterType>(stmtResultType)) {
normalisedResult = hlfir::ExprType::get(
builder.getContext(), hlfir::ExprType::Shape{}, resCharType, false);
}
return normalisedResult;
}
template <typename OP, bool HAS_MASK>
mlir::Value HlfirReductionIntrinsic<OP, HAS_MASK>::lowerImpl(
const Fortran::lower::PreparedActualArguments &loweredActuals,
const fir::IntrinsicArgumentLoweringRules *argLowering,
mlir::Type stmtResultType) {
auto operands = getOperandVector(loweredActuals, argLowering);
mlir::Value array = operands[0];
mlir::Value dim = operands[1];
// dim, mask can be NULL if these arguments are not given
if (dim)
dim = hlfir::loadTrivialScalar(loc, builder, hlfir::Entity{dim});
mlir::Type resultTy = computeResultType(array, stmtResultType);
OP op;
if constexpr (HAS_MASK)
op = createOp<OP>(resultTy, array, dim,
/*mask=*/operands[2]);
else
op = createOp<OP>(resultTy, array, dim);
return op;
}
template <typename OP>
mlir::Value HlfirMinMaxLocIntrinsic<OP>::lowerImpl(
const Fortran::lower::PreparedActualArguments &loweredActuals,
const fir::IntrinsicArgumentLoweringRules *argLowering,
mlir::Type stmtResultType) {
auto operands = getOperandVector(loweredActuals, argLowering);
mlir::Value array = operands[0];
mlir::Value dim = operands[1];
mlir::Value mask = operands[2];
mlir::Value back = operands[4];
// dim, mask and back can be NULL if these arguments are not given.
if (dim)
dim = hlfir::loadTrivialScalar(loc, builder, hlfir::Entity{dim});
if (back)
back = hlfir::loadTrivialScalar(loc, builder, hlfir::Entity{back});
mlir::Type resultTy = computeResultType(array, stmtResultType);
return createOp<OP>(resultTy, array, dim, mask, back);
}
template <typename OP>
mlir::Value HlfirProductIntrinsic<OP>::lowerImpl(
const Fortran::lower::PreparedActualArguments &loweredActuals,
const fir::IntrinsicArgumentLoweringRules *argLowering,
mlir::Type stmtResultType) {
auto operands = getOperandVector(loweredActuals, argLowering);
mlir::Type resultType = computeResultType(operands[0], stmtResultType);
return createOp<OP>(resultType, operands[0], operands[1]);
}
mlir::Value HlfirTransposeLowering::lowerImpl(
const Fortran::lower::PreparedActualArguments &loweredActuals,
const fir::IntrinsicArgumentLoweringRules *argLowering,
mlir::Type stmtResultType) {
auto operands = getOperandVector(loweredActuals, argLowering);
hlfir::ExprType::Shape resultShape;
mlir::Type normalisedResult =
hlfir::getFortranElementOrSequenceType(stmtResultType);
auto array = mlir::cast<fir::SequenceType>(normalisedResult);
llvm::ArrayRef<int64_t> arrayShape = array.getShape();
assert(arrayShape.size() == 2 && "arguments to transpose have a rank of 2");
mlir::Type elementType = array.getEleTy();
resultShape.push_back(arrayShape[0]);
resultShape.push_back(arrayShape[1]);
if (auto resCharType = mlir::dyn_cast<fir::CharacterType>(elementType))
if (!resCharType.hasConstantLen()) {
// The FunctionRef expression might have imprecise character
// type at this point, and we can improve it by propagating
// the constant length from the argument.
auto argCharType = mlir::dyn_cast<fir::CharacterType>(
hlfir::getFortranElementType(operands[0].getType()));
if (argCharType && argCharType.hasConstantLen())
elementType = fir::CharacterType::get(
builder.getContext(), resCharType.getFKind(), argCharType.getLen());
}
mlir::Type resultTy =
hlfir::ExprType::get(builder.getContext(), resultShape, elementType,
fir::isPolymorphicType(stmtResultType));
return createOp<hlfir::TransposeOp>(resultTy, operands[0]);
}
mlir::Value HlfirCountLowering::lowerImpl(
const Fortran::lower::PreparedActualArguments &loweredActuals,
const fir::IntrinsicArgumentLoweringRules *argLowering,
mlir::Type stmtResultType) {
auto operands = getOperandVector(loweredActuals, argLowering);
mlir::Value array = operands[0];
mlir::Value dim = operands[1];
if (dim)
dim = hlfir::loadTrivialScalar(loc, builder, hlfir::Entity{dim});
mlir::Type resultType = computeResultType(array, stmtResultType);
return createOp<hlfir::CountOp>(resultType, array, dim);
}
mlir::Value HlfirCharExtremumLowering::lowerImpl(
const Fortran::lower::PreparedActualArguments &loweredActuals,
const fir::IntrinsicArgumentLoweringRules *argLowering,
mlir::Type stmtResultType) {
auto operands = getOperandVector(loweredActuals, argLowering);
assert(operands.size() >= 2);
return createOp<hlfir::CharExtremumOp>(pred, mlir::ValueRange{operands});
}
std::optional<hlfir::EntityWithAttributes> Fortran::lower::lowerHlfirIntrinsic(
fir::FirOpBuilder &builder, mlir::Location loc, const std::string &name,
const Fortran::lower::PreparedActualArguments &loweredActuals,
const fir::IntrinsicArgumentLoweringRules *argLowering,
mlir::Type stmtResultType) {
// If the result is of a derived type that may need finalization,
// we have to use DestroyOp with 'finalize' attribute for the result
// of the intrinsic operation.
if (name == "sum")
return HlfirSumLowering{builder, loc}.lower(loweredActuals, argLowering,
stmtResultType);
if (name == "product")
return HlfirProductLowering{builder, loc}.lower(loweredActuals, argLowering,
stmtResultType);
if (name == "any")
return HlfirAnyLowering{builder, loc}.lower(loweredActuals, argLowering,
stmtResultType);
if (name == "all")
return HlfirAllLowering{builder, loc}.lower(loweredActuals, argLowering,
stmtResultType);
if (name == "matmul")
return HlfirMatmulLowering{builder, loc}.lower(loweredActuals, argLowering,
stmtResultType);
if (name == "dot_product")
return HlfirDotProductLowering{builder, loc}.lower(
loweredActuals, argLowering, stmtResultType);
// FIXME: the result may need finalization.
if (name == "transpose")
return HlfirTransposeLowering{builder, loc}.lower(
loweredActuals, argLowering, stmtResultType);
if (name == "count")
return HlfirCountLowering{builder, loc}.lower(loweredActuals, argLowering,
stmtResultType);
if (name == "maxval")
return HlfirMaxvalLowering{builder, loc}.lower(loweredActuals, argLowering,
stmtResultType);
if (name == "minval")
return HlfirMinvalLowering{builder, loc}.lower(loweredActuals, argLowering,
stmtResultType);
if (name == "minloc")
return HlfirMinlocLowering{builder, loc}.lower(loweredActuals, argLowering,
stmtResultType);
if (name == "maxloc")
return HlfirMaxlocLowering{builder, loc}.lower(loweredActuals, argLowering,
stmtResultType);
if (mlir::isa<fir::CharacterType>(stmtResultType)) {
if (name == "min")
return HlfirCharExtremumLowering{builder, loc,
hlfir::CharExtremumPredicate::min}
.lower(loweredActuals, argLowering, stmtResultType);
if (name == "max")
return HlfirCharExtremumLowering{builder, loc,
hlfir::CharExtremumPredicate::max}
.lower(loweredActuals, argLowering, stmtResultType);
}
return std::nullopt;
}