llvm/flang/lib/Lower/CallInterface.cpp

//===-- CallInterface.cpp -- Procedure call interface ---------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#include "flang/Lower/CallInterface.h"
#include "flang/Common/Fortran.h"
#include "flang/Evaluate/fold.h"
#include "flang/Lower/Bridge.h"
#include "flang/Lower/Mangler.h"
#include "flang/Lower/PFTBuilder.h"
#include "flang/Lower/StatementContext.h"
#include "flang/Lower/Support/Utils.h"
#include "flang/Optimizer/Builder/Character.h"
#include "flang/Optimizer/Builder/FIRBuilder.h"
#include "flang/Optimizer/Builder/Todo.h"
#include "flang/Optimizer/Dialect/FIRDialect.h"
#include "flang/Optimizer/Dialect/FIROpsSupport.h"
#include "flang/Optimizer/Support/InternalNames.h"
#include "flang/Optimizer/Support/Utils.h"
#include "flang/Semantics/symbol.h"
#include "flang/Semantics/tools.h"
#include <optional>

static mlir::FunctionType
getProcedureType(const Fortran::evaluate::characteristics::Procedure &proc,
                 Fortran::lower::AbstractConverter &converter);

mlir::Type Fortran::lower::getUntypedBoxProcType(mlir::MLIRContext *context) {
  llvm::SmallVector<mlir::Type> resultTys;
  llvm::SmallVector<mlir::Type> inputTys;
  auto untypedFunc = mlir::FunctionType::get(context, inputTys, resultTys);
  return fir::BoxProcType::get(context, untypedFunc);
}

/// Return the type of a dummy procedure given its characteristic (if it has
/// one).
static mlir::Type getProcedureDesignatorType(
    const Fortran::evaluate::characteristics::Procedure *,
    Fortran::lower::AbstractConverter &converter) {
  // TODO: Get actual function type of the dummy procedure, at least when an
  // interface is given. The result type should be available even if the arity
  // and type of the arguments is not.
  // In general, that is a nice to have but we cannot guarantee to find the
  // function type that will match the one of the calls, we may not even know
  // how many arguments the dummy procedure accepts (e.g. if a procedure
  // pointer is only transiting through the current procedure without being
  // called), so a function type cast must always be inserted.
  return Fortran::lower::getUntypedBoxProcType(&converter.getMLIRContext());
}

//===----------------------------------------------------------------------===//
// Caller side interface implementation
//===----------------------------------------------------------------------===//

bool Fortran::lower::CallerInterface::hasAlternateReturns() const {
  return procRef.hasAlternateReturns();
}

/// Return the binding label (from BIND(C...)) or the mangled name of the
/// symbol.
static std::string
getProcMangledName(const Fortran::evaluate::ProcedureDesignator &proc,
                   Fortran::lower::AbstractConverter &converter) {
  if (const Fortran::semantics::Symbol *symbol = proc.GetSymbol())
    return converter.mangleName(symbol->GetUltimate());
  assert(proc.GetSpecificIntrinsic() &&
         "expected intrinsic procedure in designator");
  return proc.GetName();
}

std::string Fortran::lower::CallerInterface::getMangledName() const {
  return getProcMangledName(procRef.proc(), converter);
}

const Fortran::semantics::Symbol *
Fortran::lower::CallerInterface::getProcedureSymbol() const {
  return procRef.proc().GetSymbol();
}

bool Fortran::lower::CallerInterface::isIndirectCall() const {
  if (const Fortran::semantics::Symbol *symbol = procRef.proc().GetSymbol())
    return Fortran::semantics::IsPointer(*symbol) ||
           Fortran::semantics::IsDummy(*symbol);
  return false;
}

bool Fortran::lower::CallerInterface::requireDispatchCall() const {
  // Procedure pointer component reference do not require dispatch, but
  // have PASS/NOPASS argument.
  if (const Fortran::semantics::Symbol *sym = procRef.proc().GetSymbol())
    if (Fortran::semantics::IsPointer(*sym))
      return false;
  // calls with NOPASS attribute still have their component so check if it is
  // polymorphic.
  if (const Fortran::evaluate::Component *component =
          procRef.proc().GetComponent()) {
    if (Fortran::semantics::IsPolymorphic(component->base().GetLastSymbol()))
      return true;
  }
  // calls with PASS attribute have the passed-object already set in its
  // arguments. Just check if their is one.
  std::optional<unsigned> passArg = getPassArgIndex();
  if (passArg)
    return true;
  return false;
}

std::optional<unsigned>
Fortran::lower::CallerInterface::getPassArgIndex() const {
  unsigned passArgIdx = 0;
  std::optional<unsigned> passArg;
  for (const auto &arg : getCallDescription().arguments()) {
    if (arg && arg->isPassedObject()) {
      passArg = passArgIdx;
      break;
    }
    ++passArgIdx;
  }
  if (!passArg)
    return passArg;
  // Take into account result inserted as arguments.
  if (std::optional<Fortran::lower::CallInterface<
          Fortran::lower::CallerInterface>::PassedEntity>
          resultArg = getPassedResult()) {
    if (resultArg->passBy == PassEntityBy::AddressAndLength)
      passArg = *passArg + 2;
    else if (resultArg->passBy == PassEntityBy::BaseAddress)
      passArg = *passArg + 1;
  }
  return passArg;
}

mlir::Value Fortran::lower::CallerInterface::getIfPassedArg() const {
  if (std::optional<unsigned> passArg = getPassArgIndex()) {
    assert(actualInputs.size() > *passArg && actualInputs[*passArg] &&
           "passed arg was not set yet");
    return actualInputs[*passArg];
  }
  return {};
}

const Fortran::evaluate::ProcedureDesignator *
Fortran::lower::CallerInterface::getIfIndirectCall() const {
  if (const Fortran::semantics::Symbol *symbol = procRef.proc().GetSymbol())
    if (Fortran::semantics::IsPointer(*symbol) ||
        Fortran::semantics::IsDummy(*symbol))
      return &procRef.proc();
  return nullptr;
}

static mlir::Location
getProcedureDesignatorLoc(const Fortran::evaluate::ProcedureDesignator &proc,
                          Fortran::lower::AbstractConverter &converter) {
  // Note: If the callee is defined in the same file but after the current
  // unit we cannot get its location here and the funcOp is created at the
  // wrong location (i.e, the caller location).
  // To prevent this, it is up to the bridge to first declare all functions
  // defined in the translation unit before lowering any calls or procedure
  // designator references.
  if (const Fortran::semantics::Symbol *symbol = proc.GetSymbol())
    return converter.genLocation(symbol->name());
  // Use current location for intrinsics.
  return converter.getCurrentLocation();
}

mlir::Location Fortran::lower::CallerInterface::getCalleeLocation() const {
  return getProcedureDesignatorLoc(procRef.proc(), converter);
}

// Get dummy argument characteristic for a procedure with implicit interface
// from the actual argument characteristic. The actual argument may not be a F77
// entity. The attribute must be dropped and the shape, if any, must be made
// explicit.
static Fortran::evaluate::characteristics::DummyDataObject
asImplicitArg(Fortran::evaluate::characteristics::DummyDataObject &&dummy) {
  std::optional<Fortran::evaluate::Shape> shape =
      dummy.type.attrs().none()
          ? dummy.type.shape()
          : std::make_optional<Fortran::evaluate::Shape>(dummy.type.Rank());
  return Fortran::evaluate::characteristics::DummyDataObject(
      Fortran::evaluate::characteristics::TypeAndShape(dummy.type.type(),
                                                       std::move(shape)));
}

static Fortran::evaluate::characteristics::DummyArgument
asImplicitArg(Fortran::evaluate::characteristics::DummyArgument &&dummy) {
  return Fortran::common::visit(
      Fortran::common::visitors{
          [&](Fortran::evaluate::characteristics::DummyDataObject &obj) {
            return Fortran::evaluate::characteristics::DummyArgument(
                std::move(dummy.name), asImplicitArg(std::move(obj)));
          },
          [&](Fortran::evaluate::characteristics::DummyProcedure &proc) {
            return Fortran::evaluate::characteristics::DummyArgument(
                std::move(dummy.name), std::move(proc));
          },
          [](Fortran::evaluate::characteristics::AlternateReturn &x) {
            return Fortran::evaluate::characteristics::DummyArgument(
                std::move(x));
          }},
      dummy.u);
}

static bool isExternalDefinedInSameCompilationUnit(
    const Fortran::evaluate::ProcedureDesignator &proc) {
  if (const auto *symbol{proc.GetSymbol()})
    return symbol->has<Fortran::semantics::SubprogramDetails>() &&
           symbol->owner().IsGlobal();
  return false;
}

Fortran::evaluate::characteristics::Procedure
Fortran::lower::CallerInterface::characterize() const {
  Fortran::evaluate::FoldingContext &foldingContext =
      converter.getFoldingContext();
  std::optional<Fortran::evaluate::characteristics::Procedure> characteristic =
      Fortran::evaluate::characteristics::Procedure::Characterize(
          procRef.proc(), foldingContext, /*emitError=*/false);
  assert(characteristic && "Failed to get characteristic from procRef");
  // The characteristic may not contain the argument characteristic if the
  // ProcedureDesignator has no interface, or may mismatch in case of implicit
  // interface.
  if (!characteristic->HasExplicitInterface() ||
      (converter.getLoweringOptions().getLowerToHighLevelFIR() &&
       isExternalDefinedInSameCompilationUnit(procRef.proc()) &&
       characteristic->CanBeCalledViaImplicitInterface())) {
    // In HLFIR lowering, calls to subprogram with implicit interfaces are
    // always prepared according to the actual arguments. This is to support
    // cases where the implicit interfaces are "abused" in old and not so old
    // Fortran code (e.g, passing REAL(8) to CHARACTER(8), passing object
    // pointers to procedure dummies, passing regular procedure dummies to
    // character procedure dummies, omitted arguments....).
    // In all those case, if the subprogram definition is in the same
    // compilation unit, the "characteristic" from Characterize will be the one
    // from the definition, in case of "abuses" (for which semantics raise a
    // warning), lowering will be placed in a difficult position if it is given
    // the dummy characteristic from the definition and an actual that has
    // seemingly nothing to do with it: it would need to battle to anticipate
    // and handle these mismatches (e.g., be able to prepare a fir.boxchar<>
    // from a fir.real<> and so one). This was the approach of the lowering to
    // FIR, and usually lead to compiler bug every time a new "abuse" was met in
    // the wild.
    // Instead, in HLFIR, the dummy characteristic is always computed from the
    // actual for subprogram with implicit interfaces, and in case of call site
    // vs fun.func MLIR function type signature mismatch, a function cast is
    // done before placing the call. This is a hammer that should cover all
    // cases and behave like existing compiler that "do not see" the definition
    // when placing the call.
    characteristic->dummyArguments.clear();
    for (const std::optional<Fortran::evaluate::ActualArgument> &arg :
         procRef.arguments()) {
      // "arg" may be null if this is a call with missing arguments compared
      // to the subprogram definition. Do not compute any characteristic
      // in this case.
      if (arg.has_value()) {
        if (arg.value().isAlternateReturn()) {
          characteristic->dummyArguments.emplace_back(
              Fortran::evaluate::characteristics::AlternateReturn{});
        } else {
          // Argument cannot be optional with implicit interface
          const Fortran::lower::SomeExpr *expr = arg.value().UnwrapExpr();
          assert(expr && "argument in call with implicit interface cannot be "
                         "assumed type");
          std::optional<Fortran::evaluate::characteristics::DummyArgument>
              argCharacteristic =
                  Fortran::evaluate::characteristics::DummyArgument::FromActual(
                      "actual", *expr, foldingContext,
                      /*forImplicitInterface=*/true);
          assert(argCharacteristic &&
                 "failed to characterize argument in implicit call");
          characteristic->dummyArguments.emplace_back(
              asImplicitArg(std::move(*argCharacteristic)));
        }
      }
    }
  }
  return *characteristic;
}

void Fortran::lower::CallerInterface::placeInput(
    const PassedEntity &passedEntity, mlir::Value arg) {
  assert(static_cast<int>(actualInputs.size()) > passedEntity.firArgument &&
         passedEntity.firArgument >= 0 &&
         passedEntity.passBy != CallInterface::PassEntityBy::AddressAndLength &&
         "bad arg position");
  actualInputs[passedEntity.firArgument] = arg;
}

void Fortran::lower::CallerInterface::placeAddressAndLengthInput(
    const PassedEntity &passedEntity, mlir::Value addr, mlir::Value len) {
  assert(static_cast<int>(actualInputs.size()) > passedEntity.firArgument &&
         static_cast<int>(actualInputs.size()) > passedEntity.firLength &&
         passedEntity.firArgument >= 0 && passedEntity.firLength >= 0 &&
         passedEntity.passBy == CallInterface::PassEntityBy::AddressAndLength &&
         "bad arg position");
  actualInputs[passedEntity.firArgument] = addr;
  actualInputs[passedEntity.firLength] = len;
}

bool Fortran::lower::CallerInterface::verifyActualInputs() const {
  if (getNumFIRArguments() != actualInputs.size())
    return false;
  for (mlir::Value arg : actualInputs) {
    if (!arg)
      return false;
  }
  return true;
}

mlir::Value
Fortran::lower::CallerInterface::getInput(const PassedEntity &passedEntity) {
  return actualInputs[passedEntity.firArgument];
}

static void walkLengths(
    const Fortran::evaluate::characteristics::TypeAndShape &typeAndShape,
    const Fortran::lower::CallerInterface::ExprVisitor &visitor,
    Fortran::lower::AbstractConverter &converter) {
  Fortran::evaluate::DynamicType dynamicType = typeAndShape.type();
  // Visit length specification expressions that are explicit.
  if (dynamicType.category() == Fortran::common::TypeCategory::Character) {
    if (std::optional<Fortran::evaluate::ExtentExpr> length =
            dynamicType.GetCharLength())
      visitor(toEvExpr(*length), /*assumedSize=*/false);
  } else if (dynamicType.category() == Fortran::common::TypeCategory::Derived &&
             !dynamicType.IsUnlimitedPolymorphic()) {
    const Fortran::semantics::DerivedTypeSpec &derivedTypeSpec =
        dynamicType.GetDerivedTypeSpec();
    if (Fortran::semantics::CountLenParameters(derivedTypeSpec) > 0)
      TODO(converter.getCurrentLocation(),
           "function result with derived type length parameters");
  }
}

void Fortran::lower::CallerInterface::walkResultLengths(
    const ExprVisitor &visitor) const {
  assert(characteristic && "characteristic was not computed");
  const Fortran::evaluate::characteristics::FunctionResult &result =
      characteristic->functionResult.value();
  const Fortran::evaluate::characteristics::TypeAndShape *typeAndShape =
      result.GetTypeAndShape();
  assert(typeAndShape && "no result type");
  return walkLengths(*typeAndShape, visitor, converter);
}

void Fortran::lower::CallerInterface::walkDummyArgumentLengths(
    const PassedEntity &passedEntity, const ExprVisitor &visitor) const {
  if (!passedEntity.characteristics)
    return;
  if (const auto *dummy =
          std::get_if<Fortran::evaluate::characteristics::DummyDataObject>(
              &passedEntity.characteristics->u))
    walkLengths(dummy->type, visitor, converter);
}

// Compute extent expr from shapeSpec of an explicit shape.
static Fortran::evaluate::ExtentExpr
getExtentExpr(const Fortran::semantics::ShapeSpec &shapeSpec) {
  if (shapeSpec.ubound().isStar())
    // F'2023 18.5.3 point 5.
    return Fortran::evaluate::ExtentExpr{-1};
  const auto &ubound = shapeSpec.ubound().GetExplicit();
  const auto &lbound = shapeSpec.lbound().GetExplicit();
  assert(lbound && ubound && "shape must be explicit");
  return Fortran::common::Clone(*ubound) - Fortran::common::Clone(*lbound) +
         Fortran::evaluate::ExtentExpr{1};
}

static void
walkExtents(const Fortran::semantics::Symbol &symbol,
            const Fortran::lower::CallerInterface::ExprVisitor &visitor) {
  if (const auto *objectDetails =
          symbol.detailsIf<Fortran::semantics::ObjectEntityDetails>())
    if (objectDetails->shape().IsExplicitShape() ||
        Fortran::semantics::IsAssumedSizeArray(symbol))
      for (const Fortran::semantics::ShapeSpec &shapeSpec :
           objectDetails->shape())
        visitor(Fortran::evaluate::AsGenericExpr(getExtentExpr(shapeSpec)),
                /*assumedSize=*/shapeSpec.ubound().isStar());
}

void Fortran::lower::CallerInterface::walkResultExtents(
    const ExprVisitor &visitor) const {
  // Walk directly the result symbol shape (the characteristic shape may contain
  // descriptor inquiries to it that would fail to lower on the caller side).
  const Fortran::semantics::SubprogramDetails *interfaceDetails =
      getInterfaceDetails();
  if (interfaceDetails) {
    walkExtents(interfaceDetails->result(), visitor);
  } else {
    if (procRef.Rank() != 0)
      fir::emitFatalError(
          converter.getCurrentLocation(),
          "only scalar functions may not have an interface symbol");
  }
}

void Fortran::lower::CallerInterface::walkDummyArgumentExtents(
    const PassedEntity &passedEntity, const ExprVisitor &visitor) const {
  const Fortran::semantics::SubprogramDetails *interfaceDetails =
      getInterfaceDetails();
  if (!interfaceDetails)
    return;
  const Fortran::semantics::Symbol *dummy = getDummySymbol(passedEntity);
  assert(dummy && "dummy symbol was not set");
  walkExtents(*dummy, visitor);
}

bool Fortran::lower::CallerInterface::mustMapInterfaceSymbolsForResult() const {
  assert(characteristic && "characteristic was not computed");
  const std::optional<Fortran::evaluate::characteristics::FunctionResult>
      &result = characteristic->functionResult;
  if (!result || result->CanBeReturnedViaImplicitInterface() ||
      !getInterfaceDetails() || result->IsProcedurePointer())
    return false;
  bool allResultSpecExprConstant = true;
  auto visitor = [&](const Fortran::lower::SomeExpr &e, bool) {
    allResultSpecExprConstant &= Fortran::evaluate::IsConstantExpr(e);
  };
  walkResultLengths(visitor);
  walkResultExtents(visitor);
  return !allResultSpecExprConstant;
}

bool Fortran::lower::CallerInterface::mustMapInterfaceSymbolsForDummyArgument(
    const PassedEntity &arg) const {
  bool allResultSpecExprConstant = true;
  auto visitor = [&](const Fortran::lower::SomeExpr &e, bool) {
    allResultSpecExprConstant &= Fortran::evaluate::IsConstantExpr(e);
  };
  walkDummyArgumentLengths(arg, visitor);
  walkDummyArgumentExtents(arg, visitor);
  return !allResultSpecExprConstant;
}

mlir::Value Fortran::lower::CallerInterface::getArgumentValue(
    const semantics::Symbol &sym) const {
  mlir::Location loc = converter.getCurrentLocation();
  const Fortran::semantics::SubprogramDetails *ifaceDetails =
      getInterfaceDetails();
  if (!ifaceDetails)
    fir::emitFatalError(
        loc, "mapping actual and dummy arguments requires an interface");
  const std::vector<Fortran::semantics::Symbol *> &dummies =
      ifaceDetails->dummyArgs();
  auto it = std::find(dummies.begin(), dummies.end(), &sym);
  if (it == dummies.end())
    fir::emitFatalError(loc, "symbol is not a dummy in this call");
  FirValue mlirArgIndex = passedArguments[it - dummies.begin()].firArgument;
  return actualInputs[mlirArgIndex];
}

const Fortran::semantics::Symbol *
Fortran::lower::CallerInterface::getDummySymbol(
    const PassedEntity &passedEntity) const {
  const Fortran::semantics::SubprogramDetails *ifaceDetails =
      getInterfaceDetails();
  if (!ifaceDetails)
    return nullptr;
  std::size_t argPosition = 0;
  for (const auto &arg : getPassedArguments()) {
    if (&arg == &passedEntity)
      break;
    ++argPosition;
  }
  if (argPosition >= ifaceDetails->dummyArgs().size())
    return nullptr;
  return ifaceDetails->dummyArgs()[argPosition];
}

mlir::Type Fortran::lower::CallerInterface::getResultStorageType() const {
  if (passedResult)
    return fir::dyn_cast_ptrEleTy(inputs[passedResult->firArgument].type);
  assert(saveResult && !outputs.empty());
  return outputs[0].type;
}

mlir::Type Fortran::lower::CallerInterface::getDummyArgumentType(
    const PassedEntity &passedEntity) const {
  return inputs[passedEntity.firArgument].type;
}

const Fortran::semantics::Symbol &
Fortran::lower::CallerInterface::getResultSymbol() const {
  mlir::Location loc = converter.getCurrentLocation();
  const Fortran::semantics::SubprogramDetails *ifaceDetails =
      getInterfaceDetails();
  if (!ifaceDetails)
    fir::emitFatalError(
        loc, "mapping actual and dummy arguments requires an interface");
  return ifaceDetails->result();
}

const Fortran::semantics::SubprogramDetails *
Fortran::lower::CallerInterface::getInterfaceDetails() const {
  if (const Fortran::semantics::Symbol *iface =
          procRef.proc().GetInterfaceSymbol())
    return iface->GetUltimate()
        .detailsIf<Fortran::semantics::SubprogramDetails>();
  return nullptr;
}

//===----------------------------------------------------------------------===//
// Callee side interface implementation
//===----------------------------------------------------------------------===//

bool Fortran::lower::CalleeInterface::hasAlternateReturns() const {
  return !funit.isMainProgram() &&
         Fortran::semantics::HasAlternateReturns(funit.getSubprogramSymbol());
}

std::string Fortran::lower::CalleeInterface::getMangledName() const {
  if (funit.isMainProgram())
    return fir::NameUniquer::doProgramEntry().str();
  return converter.mangleName(funit.getSubprogramSymbol());
}

const Fortran::semantics::Symbol *
Fortran::lower::CalleeInterface::getProcedureSymbol() const {
  if (funit.isMainProgram())
    return funit.getMainProgramSymbol();
  return &funit.getSubprogramSymbol();
}

mlir::Location Fortran::lower::CalleeInterface::getCalleeLocation() const {
  // FIXME: do NOT use unknown for the anonymous PROGRAM case. We probably
  // should just stash the location in the funit regardless.
  return converter.genLocation(funit.getStartingSourceLoc());
}

Fortran::evaluate::characteristics::Procedure
Fortran::lower::CalleeInterface::characterize() const {
  Fortran::evaluate::FoldingContext &foldingContext =
      converter.getFoldingContext();
  std::optional<Fortran::evaluate::characteristics::Procedure> characteristic =
      Fortran::evaluate::characteristics::Procedure::Characterize(
          funit.getSubprogramSymbol(), foldingContext);
  assert(characteristic && "Fail to get characteristic from symbol");
  return *characteristic;
}

bool Fortran::lower::CalleeInterface::isMainProgram() const {
  return funit.isMainProgram();
}

mlir::func::FuncOp
Fortran::lower::CalleeInterface::addEntryBlockAndMapArguments() {
  // Check for bugs in the front end. The front end must not present multiple
  // definitions of the same procedure.
  if (!func.getBlocks().empty())
    fir::emitFatalError(func.getLoc(),
                        "cannot process subprogram that was already processed");

  // On the callee side, directly map the mlir::value argument of the function
  // block to the Fortran symbols.
  func.addEntryBlock();
  mapPassedEntities();
  return func;
}

bool Fortran::lower::CalleeInterface::hasHostAssociated() const {
  return funit.parentHasTupleHostAssoc();
}

mlir::Type Fortran::lower::CalleeInterface::getHostAssociatedTy() const {
  assert(hasHostAssociated());
  return funit.parentHostAssoc().getArgumentType(converter);
}

mlir::Value Fortran::lower::CalleeInterface::getHostAssociatedTuple() const {
  assert(hasHostAssociated() || !funit.getHostAssoc().empty());
  return converter.hostAssocTupleValue();
}

//===----------------------------------------------------------------------===//
// CallInterface implementation: this part is common to both caller and callee.
//===----------------------------------------------------------------------===//

static void addSymbolAttribute(mlir::func::FuncOp func,
                               const Fortran::semantics::Symbol &sym,
                               fir::FortranProcedureFlagsEnumAttr procAttrs,
                               mlir::MLIRContext &mlirContext) {
  const Fortran::semantics::Symbol &ultimate = sym.GetUltimate();
  // The link between an internal procedure and its host procedure is lost
  // in FIR if the host is BIND(C) since the internal mangling will not
  // allow retrieving the host bind(C) name, and therefore func.func symbol.
  // Preserve it as an attribute so that this can be later retrieved.
  if (Fortran::semantics::ClassifyProcedure(ultimate) ==
      Fortran::semantics::ProcedureDefinitionClass::Internal) {
    if (ultimate.owner().kind() ==
        Fortran::semantics::Scope::Kind::Subprogram) {
      if (const Fortran::semantics::Symbol *hostProcedure =
              ultimate.owner().symbol()) {
        std::string hostName = Fortran::lower::mangle::mangleName(
            *hostProcedure, /*keepExternalInScope=*/true);
        func->setAttr(
            fir::getHostSymbolAttrName(),
            mlir::SymbolRefAttr::get(
                &mlirContext, mlir::StringAttr::get(&mlirContext, hostName)));
      }
    } else if (ultimate.owner().kind() ==
               Fortran::semantics::Scope::Kind::MainProgram) {
      func->setAttr(fir::getHostSymbolAttrName(),
                    mlir::SymbolRefAttr::get(
                        &mlirContext,
                        mlir::StringAttr::get(
                            &mlirContext, fir::NameUniquer::doProgramEntry())));
    }
  }

  if (procAttrs)
    func->setAttr(fir::getFortranProcedureFlagsAttrName(), procAttrs);

  // Only add this on bind(C) functions for which the symbol is not reflected in
  // the current context.
  if (!Fortran::semantics::IsBindCProcedure(sym))
    return;
  std::string name =
      Fortran::lower::mangle::mangleName(sym, /*keepExternalInScope=*/true);
  func->setAttr(fir::getSymbolAttrName(),
                mlir::StringAttr::get(&mlirContext, name));
}

static void
setCUDAAttributes(mlir::func::FuncOp func,
                  const Fortran::semantics::Symbol *sym,
                  std::optional<Fortran::evaluate::characteristics::Procedure>
                      characteristic) {
  if (characteristic && characteristic->cudaSubprogramAttrs) {
    func.getOperation()->setAttr(
        cuf::getProcAttrName(),
        cuf::getProcAttribute(func.getContext(),
                              *characteristic->cudaSubprogramAttrs));
  }

  if (sym) {
    if (auto details =
            sym->GetUltimate()
                .detailsIf<Fortran::semantics::SubprogramDetails>()) {
      mlir::Type i64Ty = mlir::IntegerType::get(func.getContext(), 64);
      if (!details->cudaLaunchBounds().empty()) {
        assert(details->cudaLaunchBounds().size() >= 2 &&
               "expect at least 2 values");
        auto maxTPBAttr =
            mlir::IntegerAttr::get(i64Ty, details->cudaLaunchBounds()[0]);
        auto minBPMAttr =
            mlir::IntegerAttr::get(i64Ty, details->cudaLaunchBounds()[1]);
        mlir::IntegerAttr ubAttr;
        if (details->cudaLaunchBounds().size() > 2)
          ubAttr =
              mlir::IntegerAttr::get(i64Ty, details->cudaLaunchBounds()[2]);
        func.getOperation()->setAttr(
            cuf::getLaunchBoundsAttrName(),
            cuf::LaunchBoundsAttr::get(func.getContext(), maxTPBAttr,
                                       minBPMAttr, ubAttr));
      }

      if (!details->cudaClusterDims().empty()) {
        assert(details->cudaClusterDims().size() == 3 && "expect 3 values");
        auto xAttr =
            mlir::IntegerAttr::get(i64Ty, details->cudaClusterDims()[0]);
        auto yAttr =
            mlir::IntegerAttr::get(i64Ty, details->cudaClusterDims()[1]);
        auto zAttr =
            mlir::IntegerAttr::get(i64Ty, details->cudaClusterDims()[2]);
        func.getOperation()->setAttr(
            cuf::getClusterDimsAttrName(),
            cuf::ClusterDimsAttr::get(func.getContext(), xAttr, yAttr, zAttr));
      }
    }
  }
}

/// Declare drives the different actions to be performed while analyzing the
/// signature and building/finding the mlir::func::FuncOp.
template <typename T>
void Fortran::lower::CallInterface<T>::declare() {
  if (!side().isMainProgram()) {
    characteristic.emplace(side().characterize());
    bool isImplicit = characteristic->CanBeCalledViaImplicitInterface();
    determineInterface(isImplicit, *characteristic);
  }
  // No input/output for main program

  // Create / get funcOp for direct calls. For indirect calls (only meaningful
  // on the caller side), no funcOp has to be created here. The mlir::Value
  // holding the indirection is used when creating the fir::CallOp.
  if (!side().isIndirectCall()) {
    std::string name = side().getMangledName();
    mlir::ModuleOp module = converter.getModuleOp();
    mlir::SymbolTable *symbolTable = converter.getMLIRSymbolTable();
    func = fir::FirOpBuilder::getNamedFunction(module, symbolTable, name);
    if (!func) {
      mlir::Location loc = side().getCalleeLocation();
      mlir::MLIRContext &mlirContext = converter.getMLIRContext();
      mlir::FunctionType ty = genFunctionType();
      func =
          fir::FirOpBuilder::createFunction(loc, module, name, ty, symbolTable);
      if (const Fortran::semantics::Symbol *sym = side().getProcedureSymbol()) {
        if (side().isMainProgram()) {
          func->setAttr(fir::getSymbolAttrName(),
                        mlir::StringAttr::get(&converter.getMLIRContext(),
                                              sym->name().ToString()));
        } else {
          addSymbolAttribute(func, *sym, getProcedureAttrs(&mlirContext),
                             mlirContext);
        }
      }
      for (const auto &placeHolder : llvm::enumerate(inputs))
        if (!placeHolder.value().attributes.empty())
          func.setArgAttrs(placeHolder.index(), placeHolder.value().attributes);

      setCUDAAttributes(func, side().getProcedureSymbol(), characteristic);
    }
  }
}

/// Once the signature has been analyzed and the mlir::func::FuncOp was
/// built/found, map the fir inputs to Fortran entities (the symbols or
/// expressions).
template <typename T>
void Fortran::lower::CallInterface<T>::mapPassedEntities() {
  // map back fir inputs to passed entities
  if constexpr (std::is_same_v<T, Fortran::lower::CalleeInterface>) {
    assert(inputs.size() == func.front().getArguments().size() &&
           "function previously created with different number of arguments");
    for (auto [fst, snd] : llvm::zip(inputs, func.front().getArguments()))
      mapBackInputToPassedEntity(fst, snd);
  } else {
    // On the caller side, map the index of the mlir argument position
    // to Fortran ActualArguments.
    int firPosition = 0;
    for (const FirPlaceHolder &placeHolder : inputs)
      mapBackInputToPassedEntity(placeHolder, firPosition++);
  }
}

template <typename T>
void Fortran::lower::CallInterface<T>::mapBackInputToPassedEntity(
    const FirPlaceHolder &placeHolder, FirValue firValue) {
  PassedEntity &passedEntity =
      placeHolder.passedEntityPosition == FirPlaceHolder::resultEntityPosition
          ? passedResult.value()
          : passedArguments[placeHolder.passedEntityPosition];
  if (placeHolder.property == Property::CharLength)
    passedEntity.firLength = firValue;
  else
    passedEntity.firArgument = firValue;
}

/// Helpers to access ActualArgument/Symbols
static const Fortran::evaluate::ActualArguments &
getEntityContainer(const Fortran::evaluate::ProcedureRef &proc) {
  return proc.arguments();
}

static const std::vector<Fortran::semantics::Symbol *> &
getEntityContainer(Fortran::lower::pft::FunctionLikeUnit &funit) {
  return funit.getSubprogramSymbol()
      .get<Fortran::semantics::SubprogramDetails>()
      .dummyArgs();
}

static const Fortran::evaluate::ActualArgument *getDataObjectEntity(
    const std::optional<Fortran::evaluate::ActualArgument> &arg) {
  if (arg)
    return &*arg;
  return nullptr;
}

static const Fortran::semantics::Symbol &
getDataObjectEntity(const Fortran::semantics::Symbol *arg) {
  assert(arg && "expect symbol for data object entity");
  return *arg;
}

static const Fortran::evaluate::ActualArgument *
getResultEntity(const Fortran::evaluate::ProcedureRef &) {
  return nullptr;
}

static const Fortran::semantics::Symbol &
getResultEntity(Fortran::lower::pft::FunctionLikeUnit &funit) {
  return funit.getSubprogramSymbol()
      .get<Fortran::semantics::SubprogramDetails>()
      .result();
}

/// Bypass helpers to manipulate entities since they are not any symbol/actual
/// argument to associate. See SignatureBuilder below.
using FakeEntity = bool;
using FakeEntities = llvm::SmallVector<FakeEntity>;
static FakeEntities
getEntityContainer(const Fortran::evaluate::characteristics::Procedure &proc) {
  FakeEntities enities(proc.dummyArguments.size());
  return enities;
}
static const FakeEntity &getDataObjectEntity(const FakeEntity &e) { return e; }
static FakeEntity
getResultEntity(const Fortran::evaluate::characteristics::Procedure &proc) {
  return false;
}

/// This is the actual part that defines the FIR interface based on the
/// characteristic. It directly mutates the CallInterface members.
template <typename T>
class Fortran::lower::CallInterfaceImpl {
  using CallInterface = Fortran::lower::CallInterface<T>;
  using PassEntityBy = typename CallInterface::PassEntityBy;
  using PassedEntity = typename CallInterface::PassedEntity;
  using FirValue = typename CallInterface::FirValue;
  using FortranEntity = typename CallInterface::FortranEntity;
  using FirPlaceHolder = typename CallInterface::FirPlaceHolder;
  using Property = typename CallInterface::Property;
  using TypeAndShape = Fortran::evaluate::characteristics::TypeAndShape;
  using DummyCharacteristics =
      Fortran::evaluate::characteristics::DummyArgument;

public:
  CallInterfaceImpl(CallInterface &i)
      : interface(i), mlirContext{i.converter.getMLIRContext()} {}

  void buildImplicitInterface(
      const Fortran::evaluate::characteristics::Procedure &procedure) {
    // Handle result
    if (const std::optional<Fortran::evaluate::characteristics::FunctionResult>
            &result = procedure.functionResult)
      handleImplicitResult(*result, procedure.IsBindC());
    else if (interface.side().hasAlternateReturns())
      addFirResult(mlir::IndexType::get(&mlirContext),
                   FirPlaceHolder::resultEntityPosition, Property::Value);
    // Handle arguments
    const auto &argumentEntities =
        getEntityContainer(interface.side().getCallDescription());
    for (auto pair : llvm::zip(procedure.dummyArguments, argumentEntities)) {
      const Fortran::evaluate::characteristics::DummyArgument
          &argCharacteristics = std::get<0>(pair);
      Fortran::common::visit(
          Fortran::common::visitors{
              [&](const auto &dummy) {
                const auto &entity = getDataObjectEntity(std::get<1>(pair));
                handleImplicitDummy(&argCharacteristics, dummy, entity);
              },
              [&](const Fortran::evaluate::characteristics::AlternateReturn &) {
                // nothing to do
              },
          },
          argCharacteristics.u);
    }
  }

  void buildExplicitInterface(
      const Fortran::evaluate::characteristics::Procedure &procedure) {
    bool isBindC = procedure.IsBindC();
    // Handle result
    if (const std::optional<Fortran::evaluate::characteristics::FunctionResult>
            &result = procedure.functionResult) {
      if (result->CanBeReturnedViaImplicitInterface())
        handleImplicitResult(*result, isBindC);
      else
        handleExplicitResult(*result);
    } else if (interface.side().hasAlternateReturns()) {
      addFirResult(mlir::IndexType::get(&mlirContext),
                   FirPlaceHolder::resultEntityPosition, Property::Value);
    }
    // Handle arguments
    const auto &argumentEntities =
        getEntityContainer(interface.side().getCallDescription());
    for (auto pair : llvm::zip(procedure.dummyArguments, argumentEntities)) {
      const Fortran::evaluate::characteristics::DummyArgument
          &argCharacteristics = std::get<0>(pair);
      Fortran::common::visit(
          Fortran::common::visitors{
              [&](const Fortran::evaluate::characteristics::DummyDataObject
                      &dummy) {
                const auto &entity = getDataObjectEntity(std::get<1>(pair));
                if (!isBindC && dummy.CanBePassedViaImplicitInterface())
                  handleImplicitDummy(&argCharacteristics, dummy, entity);
                else
                  handleExplicitDummy(&argCharacteristics, dummy, entity,
                                      isBindC);
              },
              [&](const Fortran::evaluate::characteristics::DummyProcedure
                      &dummy) {
                const auto &entity = getDataObjectEntity(std::get<1>(pair));
                handleImplicitDummy(&argCharacteristics, dummy, entity);
              },
              [&](const Fortran::evaluate::characteristics::AlternateReturn &) {
                // nothing to do
              },
          },
          argCharacteristics.u);
    }
  }

  void appendHostAssocTupleArg(mlir::Type tupTy) {
    mlir::MLIRContext *ctxt = tupTy.getContext();
    addFirOperand(tupTy, nextPassedArgPosition(), Property::BaseAddress,
                  {mlir::NamedAttribute{
                      mlir::StringAttr::get(ctxt, fir::getHostAssocAttrName()),
                      mlir::UnitAttr::get(ctxt)}});
    interface.passedArguments.emplace_back(
        PassedEntity{PassEntityBy::BaseAddress, std::nullopt,
                     interface.side().getHostAssociatedTuple(), emptyValue()});
  }

  static std::optional<Fortran::evaluate::DynamicType> getResultDynamicType(
      const Fortran::evaluate::characteristics::Procedure &procedure) {
    if (const std::optional<Fortran::evaluate::characteristics::FunctionResult>
            &result = procedure.functionResult)
      if (const auto *resultTypeAndShape = result->GetTypeAndShape())
        return resultTypeAndShape->type();
    return std::nullopt;
  }

  static bool mustPassLengthWithDummyProcedure(
      const Fortran::evaluate::characteristics::Procedure &procedure) {
    // When passing a character function designator `bar` as dummy procedure to
    // `foo` (e.g. `foo(bar)`), pass the result length of `bar` to `foo` so that
    // `bar` can be called inside `foo` even if its length is assumed there.
    // From an ABI perspective, the extra length argument must be handled
    // exactly as if passing a character object. Using an argument of
    // fir.boxchar type gives the expected behavior: after codegen, the
    // fir.boxchar lengths are added after all the arguments as extra value
    // arguments (the extra arguments order is the order of the fir.boxchar).

    // This ABI is compatible with ifort, nag, nvfortran, and xlf, but not
    // gfortran. Gfortran does not pass the length and is therefore unable to
    // handle later call to `bar` in `foo` where the length would be assumed. If
    // the result is an array, nag and ifort and xlf still pass the length, but
    // not nvfortran (and gfortran). It is not clear it is possible to call an
    // array function with assumed length (f18 forbides defining such
    // interfaces). Hence, passing the length is most likely useless, but stick
    // with ifort/nag/xlf interface here.
    if (std::optional<Fortran::evaluate::DynamicType> type =
            getResultDynamicType(procedure))
      return type->category() == Fortran::common::TypeCategory::Character;
    return false;
  }

private:
  void handleImplicitResult(
      const Fortran::evaluate::characteristics::FunctionResult &result,
      bool isBindC) {
    if (auto proc{result.IsProcedurePointer()}) {
      mlir::Type mlirType = fir::BoxProcType::get(
          &mlirContext, getProcedureType(*proc, interface.converter));
      addFirResult(mlirType, FirPlaceHolder::resultEntityPosition,
                   Property::Value);
      return;
    }
    const Fortran::evaluate::characteristics::TypeAndShape *typeAndShape =
        result.GetTypeAndShape();
    assert(typeAndShape && "expect type for non proc pointer result");
    Fortran::evaluate::DynamicType dynamicType = typeAndShape->type();
    // Character result allocated by caller and passed as hidden arguments
    if (dynamicType.category() == Fortran::common::TypeCategory::Character) {
      if (isBindC) {
        mlir::Type mlirType = translateDynamicType(dynamicType);
        addFirResult(mlirType, FirPlaceHolder::resultEntityPosition,
                     Property::Value);
      } else {
        handleImplicitCharacterResult(dynamicType);
      }
    } else if (dynamicType.category() ==
               Fortran::common::TypeCategory::Derived) {
      if (!dynamicType.GetDerivedTypeSpec().IsVectorType()) {
        // Derived result need to be allocated by the caller and the result
        // value must be saved. Derived type in implicit interface cannot have
        // length parameters.
        setSaveResult();
      }
      mlir::Type mlirType = translateDynamicType(dynamicType);
      addFirResult(mlirType, FirPlaceHolder::resultEntityPosition,
                   Property::Value);
    } else {
      // All result other than characters/derived are simply returned by value
      // in implicit interfaces
      mlir::Type mlirType =
          getConverter().genType(dynamicType.category(), dynamicType.kind());
      addFirResult(mlirType, FirPlaceHolder::resultEntityPosition,
                   Property::Value);
    }
  }
  void
  handleImplicitCharacterResult(const Fortran::evaluate::DynamicType &type) {
    int resultPosition = FirPlaceHolder::resultEntityPosition;
    setPassedResult(PassEntityBy::AddressAndLength,
                    getResultEntity(interface.side().getCallDescription()));
    mlir::Type lenTy = mlir::IndexType::get(&mlirContext);
    std::optional<std::int64_t> constantLen = type.knownLength();
    fir::CharacterType::LenType len =
        constantLen ? *constantLen : fir::CharacterType::unknownLen();
    mlir::Type charRefTy = fir::ReferenceType::get(
        fir::CharacterType::get(&mlirContext, type.kind(), len));
    mlir::Type boxCharTy = fir::BoxCharType::get(&mlirContext, type.kind());
    addFirOperand(charRefTy, resultPosition, Property::CharAddress);
    addFirOperand(lenTy, resultPosition, Property::CharLength);
    /// For now, also return it by boxchar
    addFirResult(boxCharTy, resultPosition, Property::BoxChar);
  }

  /// Return a vector with an attribute with the name of the argument if this
  /// is a callee interface and the name is available. Otherwise, just return
  /// an empty vector.
  llvm::SmallVector<mlir::NamedAttribute>
  dummyNameAttr(const FortranEntity &entity) {
    if constexpr (std::is_same_v<FortranEntity,
                                 std::optional<Fortran::common::Reference<
                                     const Fortran::semantics::Symbol>>>) {
      if (entity.has_value()) {
        const Fortran::semantics::Symbol *argument = &*entity.value();
        // "fir.bindc_name" is used for arguments for the sake of consistency
        // with other attributes carrying surface syntax names in FIR.
        return {mlir::NamedAttribute(
            mlir::StringAttr::get(&mlirContext, "fir.bindc_name"),
            mlir::StringAttr::get(&mlirContext,
                                  toStringRef(argument->name())))};
      }
    }
    return {};
  }

  mlir::Type
  getRefType(Fortran::evaluate::DynamicType dynamicType,
             const Fortran::evaluate::characteristics::DummyDataObject &obj) {
    mlir::Type type = translateDynamicType(dynamicType);
    if (std::optional<fir::SequenceType::Shape> bounds = getBounds(obj.type))
      type = fir::SequenceType::get(*bounds, type);
    return fir::ReferenceType::get(type);
  }

  void handleImplicitDummy(
      const DummyCharacteristics *characteristics,
      const Fortran::evaluate::characteristics::DummyDataObject &obj,
      const FortranEntity &entity) {
    Fortran::evaluate::DynamicType dynamicType = obj.type.type();
    if constexpr (std::is_same_v<FortranEntity,
                                 const Fortran::evaluate::ActualArgument *>) {
      if (entity) {
        if (entity->isPercentVal()) {
          mlir::Type type = translateDynamicType(dynamicType);
          addFirOperand(type, nextPassedArgPosition(), Property::Value,
                        dummyNameAttr(entity));
          addPassedArg(PassEntityBy::Value, entity, characteristics);
          return;
        }
        if (entity->isPercentRef()) {
          mlir::Type refType = getRefType(dynamicType, obj);
          addFirOperand(refType, nextPassedArgPosition(), Property::BaseAddress,
                        dummyNameAttr(entity));
          addPassedArg(PassEntityBy::BaseAddress, entity, characteristics);
          return;
        }
      }
    }
    if (dynamicType.category() == Fortran::common::TypeCategory::Character) {
      mlir::Type boxCharTy =
          fir::BoxCharType::get(&mlirContext, dynamicType.kind());
      addFirOperand(boxCharTy, nextPassedArgPosition(), Property::BoxChar,
                    dummyNameAttr(entity));
      addPassedArg(PassEntityBy::BoxChar, entity, characteristics);
    } else {
      // non-PDT derived type allowed in implicit interface.
      mlir::Type refType = getRefType(dynamicType, obj);
      addFirOperand(refType, nextPassedArgPosition(), Property::BaseAddress,
                    dummyNameAttr(entity));
      addPassedArg(PassEntityBy::BaseAddress, entity, characteristics);
    }
  }

  mlir::Type
  translateDynamicType(const Fortran::evaluate::DynamicType &dynamicType) {
    Fortran::common::TypeCategory cat = dynamicType.category();
    // DERIVED
    if (cat == Fortran::common::TypeCategory::Derived) {
      if (dynamicType.IsUnlimitedPolymorphic())
        return mlir::NoneType::get(&mlirContext);
      return getConverter().genType(dynamicType.GetDerivedTypeSpec());
    }
    // CHARACTER with compile time constant length.
    if (cat == Fortran::common::TypeCategory::Character)
      if (std::optional<std::int64_t> constantLen =
              toInt64(dynamicType.GetCharLength()))
        return getConverter().genType(cat, dynamicType.kind(), {*constantLen});
    // INTEGER, REAL, LOGICAL, COMPLEX, and CHARACTER with dynamic length.
    return getConverter().genType(cat, dynamicType.kind());
  }

  void handleExplicitDummy(
      const DummyCharacteristics *characteristics,
      const Fortran::evaluate::characteristics::DummyDataObject &obj,
      const FortranEntity &entity, bool isBindC) {
    using Attrs = Fortran::evaluate::characteristics::DummyDataObject::Attr;

    bool isValueAttr = false;
    [[maybe_unused]] mlir::Location loc =
        interface.converter.getCurrentLocation();
    llvm::SmallVector<mlir::NamedAttribute> attrs = dummyNameAttr(entity);
    auto addMLIRAttr = [&](llvm::StringRef attr) {
      attrs.emplace_back(mlir::StringAttr::get(&mlirContext, attr),
                         mlir::UnitAttr::get(&mlirContext));
    };
    if (obj.attrs.test(Attrs::Optional))
      addMLIRAttr(fir::getOptionalAttrName());
    // Skipping obj.attrs.test(Attrs::Asynchronous), this does not impact the
    // way the argument is passed given flang implement asynch IO synchronously.
    // TODO: it would be safer to treat them as volatile because since Fortran
    // 2018 asynchronous can also be used for C defined asynchronous user
    // processes (see 18.10.4 Asynchronous communication).
    if (obj.attrs.test(Attrs::Contiguous))
      addMLIRAttr(fir::getContiguousAttrName());
    if (obj.attrs.test(Attrs::Value))
      isValueAttr = true; // TODO: do we want an mlir::Attribute as well?
    if (obj.attrs.test(Attrs::Volatile))
      TODO(loc, "VOLATILE in procedure interface");
    if (obj.attrs.test(Attrs::Target))
      addMLIRAttr(fir::getTargetAttrName());
    if (obj.cudaDataAttr)
      attrs.emplace_back(
          mlir::StringAttr::get(&mlirContext, cuf::getDataAttrName()),
          cuf::getDataAttribute(&mlirContext, obj.cudaDataAttr));

    // TODO: intents that require special care (e.g finalization)

    using ShapeAttr = Fortran::evaluate::characteristics::TypeAndShape::Attr;
    const Fortran::evaluate::characteristics::TypeAndShape::Attrs &shapeAttrs =
        obj.type.attrs();
    if (shapeAttrs.test(ShapeAttr::Coarray))
      TODO(loc, "coarray: dummy argument coarray in procedure interface");

    // So far assume that if the argument cannot be passed by implicit interface
    // it must be by box. That may no be always true (e.g for simple optionals)

    Fortran::evaluate::DynamicType dynamicType = obj.type.type();
    mlir::Type type = translateDynamicType(dynamicType);
    if (std::optional<fir::SequenceType::Shape> bounds = getBounds(obj.type))
      type = fir::SequenceType::get(*bounds, type);
    if (obj.attrs.test(Attrs::Allocatable))
      type = fir::HeapType::get(type);
    if (obj.attrs.test(Attrs::Pointer))
      type = fir::PointerType::get(type);
    mlir::Type boxType = fir::wrapInClassOrBoxType(
        type, obj.type.type().IsPolymorphic(), obj.type.type().IsAssumedType());

    if (obj.attrs.test(Attrs::Allocatable) || obj.attrs.test(Attrs::Pointer)) {
      // Pass as fir.ref<fir.box> or fir.ref<fir.class>
      mlir::Type boxRefType = fir::ReferenceType::get(boxType);
      addFirOperand(boxRefType, nextPassedArgPosition(), Property::MutableBox,
                    attrs);
      addPassedArg(PassEntityBy::MutableBox, entity, characteristics);
    } else if (obj.IsPassedByDescriptor(isBindC)) {
      // Pass as fir.box or fir.class
      if (isValueAttr &&
          !getConverter().getLoweringOptions().getLowerToHighLevelFIR())
        TODO(loc, "assumed shape dummy argument with VALUE attribute");
      addFirOperand(boxType, nextPassedArgPosition(), Property::Box, attrs);
      addPassedArg(PassEntityBy::Box, entity, characteristics);
    } else if (dynamicType.category() ==
               Fortran::common::TypeCategory::Character) {
      if (isValueAttr && isBindC) {
        // Pass as fir.char<1>
        mlir::Type charTy =
            fir::CharacterType::getSingleton(&mlirContext, dynamicType.kind());
        addFirOperand(charTy, nextPassedArgPosition(), Property::Value, attrs);
        addPassedArg(PassEntityBy::Value, entity, characteristics);
      } else {
        // Pass as fir.box_char
        mlir::Type boxCharTy =
            fir::BoxCharType::get(&mlirContext, dynamicType.kind());
        addFirOperand(boxCharTy, nextPassedArgPosition(), Property::BoxChar,
                      attrs);
        addPassedArg(isValueAttr ? PassEntityBy::CharBoxValueAttribute
                                 : PassEntityBy::BoxChar,
                     entity, characteristics);
      }
    } else {
      // Pass as fir.ref unless it's by VALUE and BIND(C). Also pass-by-value
      // for numerical/logical scalar without OPTIONAL so that the behavior is
      // consistent with gfortran/nvfortran.
      // TODO: pass-by-value for derived type is not supported yet
      mlir::Type passType = fir::ReferenceType::get(type);
      PassEntityBy passBy = PassEntityBy::BaseAddress;
      Property prop = Property::BaseAddress;
      if (isValueAttr) {
        bool isBuiltinCptrType = fir::isa_builtin_cptr_type(type);
        if (isBindC || (!mlir::isa<fir::SequenceType>(type) &&
                        !obj.attrs.test(Attrs::Optional) &&
                        (dynamicType.category() !=
                             Fortran::common::TypeCategory::Derived ||
                         isBuiltinCptrType))) {
          passBy = PassEntityBy::Value;
          prop = Property::Value;
          if (isBuiltinCptrType) {
            auto recTy = mlir::dyn_cast<fir::RecordType>(type);
            mlir::Type fieldTy = recTy.getTypeList()[0].second;
            passType = fir::ReferenceType::get(fieldTy);
          } else {
            passType = type;
          }
        } else {
          passBy = PassEntityBy::BaseAddressValueAttribute;
        }
      }
      addFirOperand(passType, nextPassedArgPosition(), prop, attrs);
      addPassedArg(passBy, entity, characteristics);
    }
  }

  void handleImplicitDummy(
      const DummyCharacteristics *characteristics,
      const Fortran::evaluate::characteristics::DummyProcedure &proc,
      const FortranEntity &entity) {
    if (!interface.converter.getLoweringOptions().getLowerToHighLevelFIR() &&
        proc.attrs.test(
            Fortran::evaluate::characteristics::DummyProcedure::Attr::Pointer))
      TODO(interface.converter.getCurrentLocation(),
           "procedure pointer arguments");
    const Fortran::evaluate::characteristics::Procedure &procedure =
        proc.procedure.value();
    mlir::Type funcType =
        getProcedureDesignatorType(&procedure, interface.converter);
    if (proc.attrs.test(Fortran::evaluate::characteristics::DummyProcedure::
                            Attr::Pointer)) {
      // Prodecure pointer dummy argument.
      funcType = fir::ReferenceType::get(funcType);
      addFirOperand(funcType, nextPassedArgPosition(), Property::BoxProcRef);
      addPassedArg(PassEntityBy::BoxProcRef, entity, characteristics);
      return;
    }
    // Otherwise, it is a dummy procedure.
    std::optional<Fortran::evaluate::DynamicType> resultTy =
        getResultDynamicType(procedure);
    if (resultTy && mustPassLengthWithDummyProcedure(procedure)) {
      // The result length of dummy procedures that are character functions must
      // be passed so that the dummy procedure can be called if it has assumed
      // length on the callee side.
      mlir::Type tupleType =
          fir::factory::getCharacterProcedureTupleType(funcType);
      llvm::StringRef charProcAttr = fir::getCharacterProcedureDummyAttrName();
      addFirOperand(tupleType, nextPassedArgPosition(), Property::CharProcTuple,
                    {mlir::NamedAttribute{
                        mlir::StringAttr::get(&mlirContext, charProcAttr),
                        mlir::UnitAttr::get(&mlirContext)}});
      addPassedArg(PassEntityBy::CharProcTuple, entity, characteristics);
      return;
    }
    addFirOperand(funcType, nextPassedArgPosition(), Property::BaseAddress);
    addPassedArg(PassEntityBy::BaseAddress, entity, characteristics);
  }

  void handleExplicitResult(
      const Fortran::evaluate::characteristics::FunctionResult &result) {
    using Attr = Fortran::evaluate::characteristics::FunctionResult::Attr;
    mlir::Type mlirType;
    if (auto proc{result.IsProcedurePointer()}) {
      mlirType = fir::BoxProcType::get(
          &mlirContext, getProcedureType(*proc, interface.converter));
      addFirResult(mlirType, FirPlaceHolder::resultEntityPosition,
                   Property::Value);
      return;
    }
    const Fortran::evaluate::characteristics::TypeAndShape *typeAndShape =
        result.GetTypeAndShape();
    assert(typeAndShape && "expect type for non proc pointer result");
    mlirType = translateDynamicType(typeAndShape->type());
    const auto *resTypeAndShape{result.GetTypeAndShape()};
    bool resIsPolymorphic =
        resTypeAndShape && resTypeAndShape->type().IsPolymorphic();
    bool resIsAssumedType =
        resTypeAndShape && resTypeAndShape->type().IsAssumedType();
    if (std::optional<fir::SequenceType::Shape> bounds =
            getBounds(*typeAndShape))
      mlirType = fir::SequenceType::get(*bounds, mlirType);
    if (result.attrs.test(Attr::Allocatable))
      mlirType = fir::wrapInClassOrBoxType(fir::HeapType::get(mlirType),
                                           resIsPolymorphic, resIsAssumedType);
    if (result.attrs.test(Attr::Pointer))
      mlirType = fir::wrapInClassOrBoxType(fir::PointerType::get(mlirType),
                                           resIsPolymorphic, resIsAssumedType);

    if (fir::isa_char(mlirType)) {
      // Character scalar results must be passed as arguments in lowering so
      // that an assumed length character function callee can access the
      // result length. A function with a result requiring an explicit
      // interface does not have to be compatible with assumed length
      // function, but most compilers supports it.
      handleImplicitCharacterResult(typeAndShape->type());
      return;
    }

    addFirResult(mlirType, FirPlaceHolder::resultEntityPosition,
                 Property::Value);
    // Explicit results require the caller to allocate the storage and save the
    // function result in the storage with a fir.save_result.
    setSaveResult();
  }

  // Return nullopt for scalars, empty vector for assumed rank, and a vector
  // with the shape (may contain unknown extents) for arrays.
  std::optional<fir::SequenceType::Shape> getBounds(
      const Fortran::evaluate::characteristics::TypeAndShape &typeAndShape) {
    if (typeAndShape.shape() && typeAndShape.shape()->empty())
      return std::nullopt;
    fir::SequenceType::Shape bounds;
    if (typeAndShape.shape())
      for (const std::optional<Fortran::evaluate::ExtentExpr> &extent :
           *typeAndShape.shape()) {
        fir::SequenceType::Extent bound = fir::SequenceType::getUnknownExtent();
        if (std::optional<std::int64_t> i = toInt64(extent))
          bound = *i;
        bounds.emplace_back(bound);
      }
    return bounds;
  }
  std::optional<std::int64_t>
  toInt64(std::optional<
          Fortran::evaluate::Expr<Fortran::evaluate::SubscriptInteger>>
              expr) {
    if (expr)
      return Fortran::evaluate::ToInt64(Fortran::evaluate::Fold(
          getConverter().getFoldingContext(), toEvExpr(*expr)));
    return std::nullopt;
  }
  void addFirOperand(
      mlir::Type type, int entityPosition, Property p,
      llvm::ArrayRef<mlir::NamedAttribute> attributes = std::nullopt) {
    interface.inputs.emplace_back(
        FirPlaceHolder{type, entityPosition, p, attributes});
  }
  void
  addFirResult(mlir::Type type, int entityPosition, Property p,
               llvm::ArrayRef<mlir::NamedAttribute> attributes = std::nullopt) {
    interface.outputs.emplace_back(
        FirPlaceHolder{type, entityPosition, p, attributes});
  }
  void addPassedArg(PassEntityBy p, FortranEntity entity,
                    const DummyCharacteristics *characteristics) {
    interface.passedArguments.emplace_back(
        PassedEntity{p, entity, emptyValue(), emptyValue(), characteristics});
  }
  void setPassedResult(PassEntityBy p, FortranEntity entity) {
    interface.passedResult =
        PassedEntity{p, entity, emptyValue(), emptyValue()};
  }
  void setSaveResult() { interface.saveResult = true; }
  int nextPassedArgPosition() { return interface.passedArguments.size(); }

  static FirValue emptyValue() {
    if constexpr (std::is_same_v<Fortran::lower::CalleeInterface, T>) {
      return {};
    } else {
      return -1;
    }
  }

  Fortran::lower::AbstractConverter &getConverter() {
    return interface.converter;
  }
  CallInterface &interface;
  mlir::MLIRContext &mlirContext;
};

template <typename T>
bool Fortran::lower::CallInterface<T>::PassedEntity::isOptional() const {
  if (!characteristics)
    return false;
  return characteristics->IsOptional();
}
template <typename T>
bool Fortran::lower::CallInterface<T>::PassedEntity::mayBeModifiedByCall()
    const {
  if (!characteristics)
    return true;
  if (characteristics->GetIntent() == Fortran::common::Intent::In)
    return false;
  return !hasValueAttribute();
}
template <typename T>
bool Fortran::lower::CallInterface<T>::PassedEntity::mayBeReadByCall() const {
  if (!characteristics)
    return true;
  return characteristics->GetIntent() != Fortran::common::Intent::Out;
}

template <typename T>
bool Fortran::lower::CallInterface<T>::PassedEntity::testTKR(
    Fortran::common::IgnoreTKR flag) const {
  if (!characteristics)
    return false;
  const auto *dummy =
      std::get_if<Fortran::evaluate::characteristics::DummyDataObject>(
          &characteristics->u);
  if (!dummy)
    return false;
  return dummy->ignoreTKR.test(flag);
}

template <typename T>
bool Fortran::lower::CallInterface<T>::PassedEntity::isIntentOut() const {
  if (!characteristics)
    return true;
  return characteristics->GetIntent() == Fortran::common::Intent::Out;
}
template <typename T>
bool Fortran::lower::CallInterface<T>::PassedEntity::mustBeMadeContiguous()
    const {
  if (!characteristics)
    return true;
  const auto *dummy =
      std::get_if<Fortran::evaluate::characteristics::DummyDataObject>(
          &characteristics->u);
  if (!dummy)
    return false;
  const auto &shapeAttrs = dummy->type.attrs();
  using ShapeAttrs = Fortran::evaluate::characteristics::TypeAndShape::Attr;
  if (shapeAttrs.test(ShapeAttrs::AssumedRank) ||
      shapeAttrs.test(ShapeAttrs::AssumedShape))
    return dummy->attrs.test(
        Fortran::evaluate::characteristics::DummyDataObject::Attr::Contiguous);
  if (shapeAttrs.test(ShapeAttrs::DeferredShape))
    return false;
  // Explicit shape arrays are contiguous.
  return dummy->type.Rank() > 0;
}

template <typename T>
bool Fortran::lower::CallInterface<T>::PassedEntity::hasValueAttribute() const {
  if (!characteristics)
    return false;
  const auto *dummy =
      std::get_if<Fortran::evaluate::characteristics::DummyDataObject>(
          &characteristics->u);
  return dummy &&
         dummy->attrs.test(
             Fortran::evaluate::characteristics::DummyDataObject::Attr::Value);
}

template <typename T>
bool Fortran::lower::CallInterface<T>::PassedEntity::hasAllocatableAttribute()
    const {
  if (!characteristics)
    return false;
  const auto *dummy =
      std::get_if<Fortran::evaluate::characteristics::DummyDataObject>(
          &characteristics->u);
  using Attrs = Fortran::evaluate::characteristics::DummyDataObject::Attr;
  return dummy && dummy->attrs.test(Attrs::Allocatable);
}

template <typename T>
bool Fortran::lower::CallInterface<
    T>::PassedEntity::mayRequireIntentoutFinalization() const {
  // Conservatively assume that the finalization is needed.
  if (!characteristics)
    return true;

  // No INTENT(OUT) dummy arguments do not require finalization on entry.
  if (!isIntentOut())
    return false;

  const auto *dummy =
      std::get_if<Fortran::evaluate::characteristics::DummyDataObject>(
          &characteristics->u);
  if (!dummy)
    return true;

  // POINTER/ALLOCATABLE dummy arguments do not require finalization.
  using Attrs = Fortran::evaluate::characteristics::DummyDataObject::Attr;
  if (dummy->attrs.test(Attrs::Allocatable) ||
      dummy->attrs.test(Attrs::Pointer))
    return false;

  // Polymorphic and unlimited polymorphic INTENT(OUT) dummy arguments
  // may need finalization.
  const Fortran::evaluate::DynamicType &type = dummy->type.type();
  if (type.IsPolymorphic() || type.IsUnlimitedPolymorphic())
    return true;

  // INTENT(OUT) dummy arguments of derived types require finalization,
  // if their type has finalization.
  const Fortran::semantics::DerivedTypeSpec *derived =
      Fortran::evaluate::GetDerivedTypeSpec(type);
  if (!derived)
    return false;

  return Fortran::semantics::IsFinalizable(*derived);
}

template <typename T>
bool Fortran::lower::CallInterface<
    T>::PassedEntity::isSequenceAssociatedDescriptor() const {
  if (!characteristics || passBy != PassEntityBy::Box)
    return false;
  const auto *dummy =
      std::get_if<Fortran::evaluate::characteristics::DummyDataObject>(
          &characteristics->u);
  return dummy && dummy->type.CanBeSequenceAssociated();
}

template <typename T>
void Fortran::lower::CallInterface<T>::determineInterface(
    bool isImplicit,
    const Fortran::evaluate::characteristics::Procedure &procedure) {
  CallInterfaceImpl<T> impl(*this);
  if (isImplicit)
    impl.buildImplicitInterface(procedure);
  else
    impl.buildExplicitInterface(procedure);
  // We only expect the extra host asspciations argument from the callee side as
  // the definition of internal procedures will be present, and we'll always
  // have a FuncOp definition in the ModuleOp, when lowering.
  if constexpr (std::is_same_v<T, Fortran::lower::CalleeInterface>) {
    if (side().hasHostAssociated())
      impl.appendHostAssocTupleArg(side().getHostAssociatedTy());
  }
}

template <typename T>
mlir::FunctionType Fortran::lower::CallInterface<T>::genFunctionType() {
  llvm::SmallVector<mlir::Type> returnTys;
  llvm::SmallVector<mlir::Type> inputTys;
  for (const FirPlaceHolder &placeHolder : outputs)
    returnTys.emplace_back(placeHolder.type);
  for (const FirPlaceHolder &placeHolder : inputs)
    inputTys.emplace_back(placeHolder.type);
  return mlir::FunctionType::get(&converter.getMLIRContext(), inputTys,
                                 returnTys);
}

template <typename T>
llvm::SmallVector<mlir::Type>
Fortran::lower::CallInterface<T>::getResultType() const {
  llvm::SmallVector<mlir::Type> types;
  for (const FirPlaceHolder &out : outputs)
    types.emplace_back(out.type);
  return types;
}

template <typename T>
fir::FortranProcedureFlagsEnumAttr
Fortran::lower::CallInterface<T>::getProcedureAttrs(
    mlir::MLIRContext *mlirContext) const {
  fir::FortranProcedureFlagsEnum flags = fir::FortranProcedureFlagsEnum::none;
  if (characteristic) {
    if (characteristic->IsBindC())
      flags = flags | fir::FortranProcedureFlagsEnum::bind_c;
    if (characteristic->IsPure())
      flags = flags | fir::FortranProcedureFlagsEnum::pure;
    if (characteristic->IsElemental())
      flags = flags | fir::FortranProcedureFlagsEnum::elemental;
    // TODO:
    // - SIMPLE: F2023, not yet handled by semantics.
  }

  if constexpr (std::is_same_v<Fortran::lower::CalleeInterface, T>) {
    // Only gather and set NON_RECURSIVE for procedure definition. It is
    // meaningless on calls since this is not part of Fortran characteristics
    // (Fortran 2023 15.3.1) so there is no way to always know if the procedure
    // called is recursive or not.
    if (const Fortran::semantics::Symbol *sym = side().getProcedureSymbol()) {
      // Note: By default procedures are RECURSIVE unless
      // -fno-automatic/-save/-Msave is set. NON_RECURSIVE is is made explicit
      // in that case in FIR.
      if (sym->attrs().test(Fortran::semantics::Attr::NON_RECURSIVE) ||
          (sym->owner().context().languageFeatures().IsEnabled(
               Fortran::common::LanguageFeature::DefaultSave) &&
           !sym->attrs().test(Fortran::semantics::Attr::RECURSIVE))) {
        flags = flags | fir::FortranProcedureFlagsEnum::non_recursive;
      }
    }
  }
  if (flags != fir::FortranProcedureFlagsEnum::none)
    return fir::FortranProcedureFlagsEnumAttr::get(mlirContext, flags);
  return nullptr;
}

template class Fortran::lower::CallInterface<Fortran::lower::CalleeInterface>;
template class Fortran::lower::CallInterface<Fortran::lower::CallerInterface>;

//===----------------------------------------------------------------------===//
// Function Type Translation
//===----------------------------------------------------------------------===//

/// Build signature from characteristics when there is no Fortran entity to
/// associate with the arguments (i.e, this is not a call site or a procedure
/// declaration. This is needed when dealing with function pointers/dummy
/// arguments.

class SignatureBuilder;
template <>
struct Fortran::lower::PassedEntityTypes<SignatureBuilder> {
  using FortranEntity = FakeEntity;
  using FirValue = int;
};

/// SignatureBuilder is a CRTP implementation of CallInterface intended to
/// help translating characteristics::Procedure to mlir::FunctionType using
/// the CallInterface translation.
class SignatureBuilder
    : public Fortran::lower::CallInterface<SignatureBuilder> {
public:
  SignatureBuilder(const Fortran::evaluate::characteristics::Procedure &p,
                   Fortran::lower::AbstractConverter &c, bool forceImplicit)
      : CallInterface{c}, proc{p} {
    bool isImplicit = forceImplicit || proc.CanBeCalledViaImplicitInterface();
    determineInterface(isImplicit, proc);
  }
  SignatureBuilder(const Fortran::evaluate::ProcedureDesignator &procDes,
                   Fortran::lower::AbstractConverter &c)
      : CallInterface{c}, procDesignator{&procDes},
        proc{Fortran::evaluate::characteristics::Procedure::Characterize(
                 procDes, converter.getFoldingContext(), /*emitError=*/false)
                 .value()} {}
  /// Does the procedure characteristics being translated have alternate
  /// returns ?
  bool hasAlternateReturns() const {
    for (const Fortran::evaluate::characteristics::DummyArgument &dummy :
         proc.dummyArguments)
      if (std::holds_alternative<
              Fortran::evaluate::characteristics::AlternateReturn>(dummy.u))
        return true;
    return false;
  };

  /// This is only here to fulfill CRTP dependencies and should not be called.
  std::string getMangledName() const {
    if (procDesignator)
      return getProcMangledName(*procDesignator, converter);
    fir::emitFatalError(
        converter.getCurrentLocation(),
        "should not query name when only building function type");
  }

  /// This is only here to fulfill CRTP dependencies and should not be called.
  mlir::Location getCalleeLocation() const {
    if (procDesignator)
      return getProcedureDesignatorLoc(*procDesignator, converter);
    return converter.getCurrentLocation();
  }

  const Fortran::semantics::Symbol *getProcedureSymbol() const {
    if (procDesignator)
      return procDesignator->GetSymbol();
    return nullptr;
  };

  Fortran::evaluate::characteristics::Procedure characterize() const {
    return proc;
  }
  /// SignatureBuilder cannot be used on main program.
  static constexpr bool isMainProgram() { return false; }

  /// Return the characteristics::Procedure that is being translated to
  /// mlir::FunctionType.
  const Fortran::evaluate::characteristics::Procedure &
  getCallDescription() const {
    return proc;
  }

  /// This is not the description of an indirect call.
  static constexpr bool isIndirectCall() { return false; }

  /// Return the translated signature.
  mlir::FunctionType getFunctionType() {
    if (interfaceDetermined)
      fir::emitFatalError(converter.getCurrentLocation(),
                          "SignatureBuilder should only be used once");
    // Most unrestricted intrinsic characteristics have the Elemental attribute
    // which triggers CanBeCalledViaImplicitInterface to return false. However,
    // using implicit interface rules is just fine here.
    bool forceImplicit =
        procDesignator && procDesignator->GetSpecificIntrinsic();
    bool isImplicit = forceImplicit || proc.CanBeCalledViaImplicitInterface();
    determineInterface(isImplicit, proc);
    interfaceDetermined = true;
    return genFunctionType();
  }

  mlir::func::FuncOp getOrCreateFuncOp() {
    if (interfaceDetermined)
      fir::emitFatalError(converter.getCurrentLocation(),
                          "SignatureBuilder should only be used once");
    declare();
    interfaceDetermined = true;
    return getFuncOp();
  }

  // Copy of base implementation.
  static constexpr bool hasHostAssociated() { return false; }
  mlir::Type getHostAssociatedTy() const {
    llvm_unreachable("getting host associated type in SignatureBuilder");
  }

private:
  const Fortran::evaluate::ProcedureDesignator *procDesignator = nullptr;
  Fortran::evaluate::characteristics::Procedure proc;
  bool interfaceDetermined = false;
};

mlir::FunctionType Fortran::lower::translateSignature(
    const Fortran::evaluate::ProcedureDesignator &proc,
    Fortran::lower::AbstractConverter &converter) {
  return SignatureBuilder{proc, converter}.getFunctionType();
}

mlir::func::FuncOp Fortran::lower::getOrDeclareFunction(
    const Fortran::evaluate::ProcedureDesignator &proc,
    Fortran::lower::AbstractConverter &converter) {
  mlir::ModuleOp module = converter.getModuleOp();
  std::string name = getProcMangledName(proc, converter);
  mlir::func::FuncOp func = fir::FirOpBuilder::getNamedFunction(
      module, converter.getMLIRSymbolTable(), name);
  if (func)
    return func;

  // getOrDeclareFunction is only used for functions not defined in the current
  // program unit, so use the location of the procedure designator symbol, which
  // is the first occurrence of the procedure in the program unit.
  return SignatureBuilder{proc, converter}.getOrCreateFuncOp();
}

// Is it required to pass a dummy procedure with \p characteristics as a tuple
// containing the function address and the result length ?
static bool mustPassLengthWithDummyProcedure(
    const std::optional<Fortran::evaluate::characteristics::Procedure>
        &characteristics) {
  return characteristics &&
         Fortran::lower::CallInterfaceImpl<SignatureBuilder>::
             mustPassLengthWithDummyProcedure(*characteristics);
}

bool Fortran::lower::mustPassLengthWithDummyProcedure(
    const Fortran::evaluate::ProcedureDesignator &procedure,
    Fortran::lower::AbstractConverter &converter) {
  std::optional<Fortran::evaluate::characteristics::Procedure> characteristics =
      Fortran::evaluate::characteristics::Procedure::Characterize(
          procedure, converter.getFoldingContext(), /*emitError=*/false);
  return ::mustPassLengthWithDummyProcedure(characteristics);
}

mlir::Type Fortran::lower::getDummyProcedureType(
    const Fortran::semantics::Symbol &dummyProc,
    Fortran::lower::AbstractConverter &converter) {
  std::optional<Fortran::evaluate::characteristics::Procedure> iface =
      Fortran::evaluate::characteristics::Procedure::Characterize(
          dummyProc, converter.getFoldingContext());
  mlir::Type procType = getProcedureDesignatorType(
      iface.has_value() ? &*iface : nullptr, converter);
  if (::mustPassLengthWithDummyProcedure(iface))
    return fir::factory::getCharacterProcedureTupleType(procType);
  return procType;
}

bool Fortran::lower::isCPtrArgByValueType(mlir::Type ty) {
  return mlir::isa<fir::ReferenceType>(ty) &&
         fir::isa_integer(fir::unwrapRefType(ty));
}

// Return the mlir::FunctionType of a procedure
static mlir::FunctionType
getProcedureType(const Fortran::evaluate::characteristics::Procedure &proc,
                 Fortran::lower::AbstractConverter &converter) {
  return SignatureBuilder{proc, converter, false}.genFunctionType();
}