//===-- 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 fir {
class FortranProcedureFlagsEnumAttr;
}

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();
  }

  /// Translate Fortran procedure attributes into FIR attribute.
  /// Return attribute is nullptr if the procedure has no attributes.
  fir::FortranProcedureFlagsEnumAttr
  getProcedureAttrs(mlir::MLIRContext *) const;

protected:
  CallInterface(Fortran::lower::AbstractConverter &c) : converter{c} {}
  /// CRTP handle.
  T &side() { return *static_cast<T *>(this); }
  const T &side() const { return *static_cast<const 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");
  }

  std::optional<mlir::Value> getOriginalPassArg() const {
    return originalPassArg;
  }
  void setOriginalPassArg(mlir::Value x) { originalPassArg = x; }

private:
  /// Check that the input vector is complete.
  bool verifyActualInputs() const;
  const Fortran::evaluate::ProcedureRef &procRef;
  llvm::SmallVector<mlir::Value> actualInputs;
  std::optional<mlir::Value> originalPassArg;
};

//===----------------------------------------------------------------------===//
// 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 the type of an argument that is a dummy procedure pointer. This
/// will be a reference to a boxed procedure.
mlir::Type
getDummyProcedurePointerType(const Fortran::semantics::Symbol &dummyProcPtr,
                             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
