//===-- Lower/CallInterface.h -- Procedure call interface ------*- C++ -*-===//
//
// 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/
//
//===----------------------------------------------------------------------===//
//
// Utility that defines fir call interface for procedure both on caller and
// and callee side and get the related FuncOp.
// It does not emit any FIR code but for the created mlir::func::FuncOp, instead
// it provides back a container of Symbol (callee side)/ActualArgument (caller
// side) with additional information for each element describing how it must be
// plugged with the mlir::func::FuncOp.
// It handles the fact that hidden arguments may be inserted for the result.
// while lowering.
//
// This utility uses the characteristic of Fortran procedures to operate, which
// is a term and concept used in Fortran to refer to the signature of a function
// or subroutine.
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_LOWER_CALLINTERFACE_H
#define FORTRAN_LOWER_CALLINTERFACE_H
#include "flang/Common/reference.h"
#include "flang/Evaluate/characteristics.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/IR/BuiltinOps.h"
#include <memory>
#include <optional>
namespace Fortran::semantics {
class Symbol;
}
namespace mlir {
class Location;
}
namespace Fortran::lower {
class AbstractConverter;
class SymMap;
class HostAssociations;
namespace pft {
struct FunctionLikeUnit;
}
/// PassedEntityTypes helps abstract whether CallInterface is mapping a
/// Symbol to mlir::Value (callee side) or an ActualArgument to a position
/// inside the input vector for the CallOp (caller side. It will be up to the
/// CallInterface user to produce the mlir::Value that will go in this input
/// vector).
class CallerInterface;
class CalleeInterface;
template <typename T>
struct PassedEntityTypes {};
template <>
struct PassedEntityTypes<CallerInterface> {
using FortranEntity = const Fortran::evaluate::ActualArgument *;
using FirValue = int;
};
template <>
struct PassedEntityTypes<CalleeInterface> {
using FortranEntity =
std::optional<common::Reference<const semantics::Symbol>>;
using FirValue = mlir::Value;
};
/// Implementation helper
template <typename T>
class CallInterfaceImpl;
/// CallInterface defines all the logic to determine FIR function interfaces
/// from a characteristic, build the mlir::func::FuncOp and describe back the
/// argument mapping to its user.
/// The logic is shared between the callee and caller sides that it accepts as
/// a curiously recursive template to handle the few things that cannot be
/// shared between both sides (getting characteristics, mangled name, location).
/// It maps FIR arguments to front-end Symbol (callee side) or ActualArgument
/// (caller side) with the same code using the abstract FortranEntity type that
/// can be either a Symbol or an ActualArgument.
/// It works in two passes: a first pass over the characteristics that decides
/// how the interface must be. Then, the funcOp is created for it. Then a simple
/// pass over fir arguments finalize the interface information that must be
/// passed back to the user (and may require having the funcOp). All this
/// passes are driven from the CallInterface constructor.
template <typename T>
class CallInterface {
friend CallInterfaceImpl<T>;
public:
/// Enum the different ways an entity can be passed-by
enum class PassEntityBy {
BaseAddress,
BoxChar,
// passing a read-only descriptor
Box,
// passing a writable descriptor
MutableBox,
AddressAndLength,
/// Value means passed by value at the mlir level, it is not necessarily
/// implied by Fortran Value attribute.
Value,
/// ValueAttribute means dummy has the Fortran VALUE attribute.
BaseAddressValueAttribute,
CharBoxValueAttribute, // BoxChar with VALUE
// Passing a character procedure as a <procedure address, result length>
// tuple.
CharProcTuple,
BoxProcRef
};
/// Different properties of an entity that can be passed/returned.
/// One-to-One mapping with PassEntityBy but for
/// PassEntityBy::AddressAndLength that has two properties.
enum class Property {
BaseAddress,
BoxChar,
CharAddress,
CharLength,
CharProcTuple,
Box,
MutableBox,
Value,
BoxProcRef
};
using FortranEntity = typename PassedEntityTypes<T>::FortranEntity;
using FirValue = typename PassedEntityTypes<T>::FirValue;
/// FirPlaceHolder are place holders for the mlir inputs and outputs that are
/// created during the first pass before the mlir::func::FuncOp is created.
struct FirPlaceHolder {
FirPlaceHolder(mlir::Type t, int passedPosition, Property p,
llvm::ArrayRef<mlir::NamedAttribute> attrs)
: type{t}, passedEntityPosition{passedPosition}, property{p},
attributes{attrs} {}
/// Type for this input/output
mlir::Type type;
/// Position of related passedEntity in passedArguments.
/// (passedEntity is the passedResult this value is resultEntityPosition.
int passedEntityPosition;
static constexpr int resultEntityPosition = -1;
/// Indicate property of the entity passedEntityPosition that must be passed
/// through this argument.
Property property;
/// MLIR attributes for this argument
llvm::SmallVector<mlir::NamedAttribute> attributes;
};
/// PassedEntity is what is provided back to the CallInterface user.
/// It describe how the entity is plugged in the interface
struct PassedEntity {
/// Is the dummy argument optional?
bool isOptional() const;
/// Can the argument be modified by the callee?
bool mayBeModifiedByCall() const;
/// Can the argument be read by the callee?
bool mayBeReadByCall() const;
/// Does the argument have the specified IgnoreTKR flag?
bool testTKR(Fortran::common::IgnoreTKR flag) const;
/// Is the argument INTENT(OUT)
bool isIntentOut() const;
/// Does the argument have the CONTIGUOUS attribute or have explicit shape?
bool mustBeMadeContiguous() const;
/// Does the dummy argument have the VALUE attribute?
bool hasValueAttribute() const;
/// Does the dummy argument have the ALLOCATABLE attribute?
bool hasAllocatableAttribute() const;
/// May the dummy argument require INTENT(OUT) finalization
/// on entry to the invoked procedure? Provides conservative answer.
bool mayRequireIntentoutFinalization() const;
/// Is the dummy argument an explicit-shape or assumed-size array that
/// must be passed by descriptor? Sequence association imply the actual
/// argument shape/rank may differ with the dummy shape/rank (see F'2023
/// section 15.5.2.12), so care is needed when creating the descriptor
/// for the dummy argument.
bool isSequenceAssociatedDescriptor() const;
/// How entity is passed by.
PassEntityBy passBy;
/// What is the entity (SymbolRef for callee/ActualArgument* for caller)
/// What is the related mlir::func::FuncOp argument(s) (mlir::Value for
/// callee / index for the caller).
FortranEntity entity;
FirValue firArgument;
FirValue firLength; /* only for AddressAndLength */
/// Pointer to the argument characteristics. Nullptr for results.
const Fortran::evaluate::characteristics::DummyArgument *characteristics =
nullptr;
};
/// Return the mlir::func::FuncOp. Note that front block is added by this
/// utility if callee side.
mlir::func::FuncOp getFuncOp() const { return func; }
/// Number of MLIR inputs/outputs of the created FuncOp.
std::size_t getNumFIRArguments() const { return inputs.size(); }
std::size_t getNumFIRResults() const { return outputs.size(); }
/// Return the MLIR output types.
llvm::SmallVector<mlir::Type> getResultType() const;
/// Return a container of Symbol/ActualArgument* and how they must
/// be plugged with the mlir::func::FuncOp.
llvm::ArrayRef<PassedEntity> getPassedArguments() const {
return passedArguments;
}
/// In case the result must be passed by the caller, indicate how.
/// nullopt if the result is not passed by the caller.
std::optional<PassedEntity> getPassedResult() const { return passedResult; }
/// Returns the mlir function type
mlir::FunctionType genFunctionType();
/// determineInterface is the entry point of the first pass that defines the
/// interface and is required to get the mlir::func::FuncOp.
void
determineInterface(bool isImplicit,
const Fortran::evaluate::characteristics::Procedure &);
/// Does the caller need to allocate storage for the result ?
bool callerAllocateResult() const {
return mustPassResult() || mustSaveResult();
}
/// Is the Fortran result passed as an extra MLIR argument ?
bool mustPassResult() const { return passedResult.has_value(); }
/// Must the MLIR result be saved with a fir.save_result ?
bool mustSaveResult() const { return saveResult; }
/// Can the associated procedure be called via an implicit interface?
bool canBeCalledViaImplicitInterface() const {
return characteristic && characteristic->CanBeCalledViaImplicitInterface();
}
protected:
CallInterface(Fortran::lower::AbstractConverter &c) : converter{c} {}
/// CRTP handle.
T &side() { return *static_cast<T *>(this); }
/// Entry point to be called by child ctor to analyze the signature and
/// create/find the mlir::func::FuncOp. Child needs to be initialized first.
void declare();
/// Second pass entry point, once the mlir::func::FuncOp is created.
/// Nothing is done if it was already called.
void mapPassedEntities();
void mapBackInputToPassedEntity(const FirPlaceHolder &, FirValue);
llvm::SmallVector<FirPlaceHolder> outputs;
llvm::SmallVector<FirPlaceHolder> inputs;
mlir::func::FuncOp func;
llvm::SmallVector<PassedEntity> passedArguments;
std::optional<PassedEntity> passedResult;
bool saveResult = false;
Fortran::lower::AbstractConverter &converter;
/// Store characteristic once created, it is required for further information
/// (e.g. getting the length of character result)
std::optional<Fortran::evaluate::characteristics::Procedure> characteristic =
std::nullopt;
};
//===----------------------------------------------------------------------===//
// Caller side interface
//===----------------------------------------------------------------------===//
/// The CallerInterface provides the helpers needed by CallInterface
/// (getting the characteristic...) and a safe way for the user to
/// place the mlir::Value arguments into the input vector
/// once they are lowered.
class CallerInterface : public CallInterface<CallerInterface> {
public:
CallerInterface(const Fortran::evaluate::ProcedureRef &p,
Fortran::lower::AbstractConverter &c)
: CallInterface{c}, procRef{p} {
declare();
mapPassedEntities();
actualInputs.resize(getNumFIRArguments());
}
/// CRTP callbacks
bool hasAlternateReturns() const;
std::string getMangledName() const;
mlir::Location getCalleeLocation() const;
Fortran::evaluate::characteristics::Procedure characterize() const;
const Fortran::evaluate::ProcedureRef &getCallDescription() const {
return procRef;
}
/// Get the SubprogramDetails that defines the interface of this call if it is
/// known at the call site. Return nullptr if it is not known.
const Fortran::semantics::SubprogramDetails *getInterfaceDetails() const;
bool isMainProgram() const { return false; }
/// Returns true if this is a call to a procedure pointer of a dummy
/// procedure.
bool isIndirectCall() const;
/// Returns true if this is a call of a type-bound procedure with a
/// polymorphic entity.
bool requireDispatchCall() const;
/// Get the passed-object argument index. nullopt if there is no passed-object
/// index.
std::optional<unsigned> getPassArgIndex() const;
/// Get the passed-object if any. Crashes if there is a passed object
/// but it was not placed in the inputs yet. Return a null value
/// otherwise.
mlir::Value getIfPassedArg() const;
/// Return the procedure symbol if this is a call to a user defined
/// procedure.
const Fortran::semantics::Symbol *getProcedureSymbol() const;
/// Return the dummy argument symbol if this is a call to a user
/// defined procedure with explicit interface. Returns nullptr if there
/// is no user defined explicit interface.
const Fortran::semantics::Symbol *
getDummySymbol(const PassedEntity &entity) const;
/// Helpers to place the lowered arguments at the right place once they
/// have been lowered.
void placeInput(const PassedEntity &passedEntity, mlir::Value arg);
void placeAddressAndLengthInput(const PassedEntity &passedEntity,
mlir::Value addr, mlir::Value len);
/// Get lowered FIR argument given the Fortran argument.
mlir::Value getInput(const PassedEntity &passedEntity);
/// If this is a call to a procedure pointer or dummy, returns the related
/// procedure designator. Nullptr otherwise.
const Fortran::evaluate::ProcedureDesignator *getIfIndirectCall() const;
/// Get the input vector once it is complete.
llvm::ArrayRef<mlir::Value> getInputs() const {
if (!verifyActualInputs())
llvm::report_fatal_error("lowered arguments are incomplete");
return actualInputs;
}
/// Does the caller must map function interface symbols in order to evaluate
/// the result specification expressions (extents and lengths) ? If needed,
/// this mapping must be done after argument lowering, and before the call
/// itself.
bool mustMapInterfaceSymbolsForResult() const;
/// Must the caller map function interface symbols in order to evaluate
/// the specification expressions of a given dummy argument?
bool mustMapInterfaceSymbolsForDummyArgument(const PassedEntity &) const;
/// Visitor for specification expression. Boolean indicate the specification
/// expression is for the last extent of an assumed size array.
using ExprVisitor =
std::function<void(evaluate::Expr<evaluate::SomeType>, bool)>;
/// Walk the result non-deferred extent specification expressions.
void walkResultExtents(const ExprVisitor &) const;
/// Walk the result non-deferred length specification expressions.
void walkResultLengths(const ExprVisitor &) const;
/// Walk non-deferred extent specification expressions of a dummy argument.
void walkDummyArgumentExtents(const PassedEntity &,
const ExprVisitor &) const;
/// Walk non-deferred length specification expressions of a dummy argument.
void walkDummyArgumentLengths(const PassedEntity &,
const ExprVisitor &) const;
/// Get the mlir::Value that is passed as argument \p sym of the function
/// being called. The arguments must have been placed before calling this
/// function.
mlir::Value getArgumentValue(const semantics::Symbol &sym) const;
/// Returns the symbol for the result in the explicit interface. If this is
/// called on an intrinsic or function without explicit interface, this will
/// crash.
const Fortran::semantics::Symbol &getResultSymbol() const;
/// If some storage needs to be allocated for the result,
/// returns the storage type.
mlir::Type getResultStorageType() const;
/// Return FIR type of argument.
mlir::Type getDummyArgumentType(const PassedEntity &) const;
// Copy of base implementation.
static constexpr bool hasHostAssociated() { return false; }
mlir::Type getHostAssociatedTy() const {
llvm_unreachable("getting host associated type in CallerInterface");
}
private:
/// Check that the input vector is complete.
bool verifyActualInputs() const;
const Fortran::evaluate::ProcedureRef &procRef;
llvm::SmallVector<mlir::Value> actualInputs;
};
//===----------------------------------------------------------------------===//
// Callee side interface
//===----------------------------------------------------------------------===//
/// CalleeInterface only provides the helpers needed by CallInterface
/// to abstract the specificities of the callee side.
class CalleeInterface : public CallInterface<CalleeInterface> {
public:
CalleeInterface(Fortran::lower::pft::FunctionLikeUnit &f,
Fortran::lower::AbstractConverter &c)
: CallInterface{c}, funit{f} {
declare();
}
bool hasAlternateReturns() const;
std::string getMangledName() const;
mlir::Location getCalleeLocation() const;
Fortran::evaluate::characteristics::Procedure characterize() const;
bool isMainProgram() const;
Fortran::lower::pft::FunctionLikeUnit &getCallDescription() const {
return funit;
}
/// On the callee side it does not matter whether the procedure is
/// called through pointers or not.
bool isIndirectCall() const { return false; }
/// On the callee side it does not matter whether the procedure is called
/// through dynamic dispatch or not.
bool requireDispatchCall() const { return false; };
/// Return the procedure symbol if this is a call to a user defined
/// procedure.
const Fortran::semantics::Symbol *getProcedureSymbol() const;
/// Add mlir::func::FuncOp entry block and map fir block arguments to Fortran
/// dummy argument symbols.
mlir::func::FuncOp addEntryBlockAndMapArguments();
bool hasHostAssociated() const;
mlir::Type getHostAssociatedTy() const;
mlir::Value getHostAssociatedTuple() const;
private:
Fortran::lower::pft::FunctionLikeUnit &funit;
};
/// Translate a procedure characteristics to an mlir::FunctionType signature.
mlir::FunctionType
translateSignature(const Fortran::evaluate::ProcedureDesignator &,
Fortran::lower::AbstractConverter &);
/// Declare or find the mlir::func::FuncOp for the procedure designator
/// \p proc. If the mlir::func::FuncOp does not exist yet, declare it with
/// the signature translated from the ProcedureDesignator argument.
/// Due to Fortran implicit function typing rules, the returned FuncOp is not
/// guaranteed to have the signature from ProcedureDesignator if the FuncOp was
/// already declared.
mlir::func::FuncOp
getOrDeclareFunction(const Fortran::evaluate::ProcedureDesignator &,
Fortran::lower::AbstractConverter &);
/// Return the type of an argument that is a dummy procedure. This may be an
/// mlir::FunctionType, but it can also be a more elaborate type based on the
/// function type (like a tuple<function type, length type> for character
/// functions).
mlir::Type getDummyProcedureType(const Fortran::semantics::Symbol &dummyProc,
Fortran::lower::AbstractConverter &);
/// Return !fir.boxproc<() -> ()> type.
mlir::Type getUntypedBoxProcType(mlir::MLIRContext *context);
/// Return true if \p ty is "!fir.ref<i64>", which is the interface for
/// type(C_PTR/C_FUNPTR) passed by value.
bool isCPtrArgByValueType(mlir::Type ty);
/// Is it required to pass \p proc as a tuple<function address, result length> ?
// This is required to convey the length of character functions passed as dummy
// procedures.
bool mustPassLengthWithDummyProcedure(
const Fortran::evaluate::ProcedureDesignator &proc,
Fortran::lower::AbstractConverter &);
} // namespace Fortran::lower
#endif // FORTRAN_LOWER_FIRBUILDER_H