| //===-- 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/Evaluate/fold.h" | |
| #include "flang/Lower/Bridge.h" | |
| #include "flang/Lower/ConvertCall.h" | |
| #include "flang/Lower/Mangler.h" | |
| #include "flang/Lower/OpenACC.h" | |
| #include "flang/Lower/OpenMP.h" | |
| #include "flang/Lower/PFTBuilder.h" | |
| #include "flang/Lower/StatementContext.h" | |
| #include "flang/Lower/Support/Utils.h" | |
| #include "flang/Optimizer/Builder/CUFCommon.h" | |
| #include "flang/Optimizer/Builder/Character.h" | |
| #include "flang/Optimizer/Builder/FIRBuilder.h" | |
| #include "flang/Optimizer/Builder/Todo.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 "flang/Support/Fortran.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(); | |
| } | |
| /// If \p proc refers to a base procedure that carries OpenMP DECLARE VARIANT | |
| /// entries, return the variant selected for the enclosing OpenMP context: a | |
| /// direct call to the base is then lowered as a call to that variant. Returns | |
| /// nullptr when \p proc is not such a base call, or when no variant matches the | |
| /// context, so the base procedure is used as usual. | |
| static const Fortran::semantics::Symbol * | |
| getOmpDeclareVariantCallee(const Fortran::evaluate::ProcedureDesignator &proc, | |
| Fortran::lower::AbstractConverter &converter) { | |
| const Fortran::semantics::Symbol *symbol = proc.GetSymbol(); | |
| if (!symbol) | |
| return nullptr; | |
| const Fortran::semantics::Symbol &ultimate{symbol->GetUltimate()}; | |
| // Only pay the cost of declare-variant resolution when the callee actually | |
| // carries variant entries; this avoids overhead on every other call. | |
| const auto *details = | |
| ultimate.detailsIf<Fortran::semantics::SubprogramDetails>(); | |
| if (!details || details->ompDeclareVariants().empty()) | |
| return nullptr; | |
| return Fortran::lower::omp::resolveDeclareVariantCallee(ultimate, converter); | |
| } | |
| /// 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 { | |
| // A matching OpenMP DECLARE VARIANT call targets the variant procedure. | |
| // This substitution applies only to an actual call to the base procedure. | |
| // Other references to the base (e.g. taking its address to pass it as an | |
| // actual argument or to associate it with a procedure pointer) go through | |
| // getProcMangledName and are intentionally not redirected to the variant. | |
| if (const Fortran::semantics::Symbol *variant = | |
| getOmpDeclareVariantCallee(procRef.proc(), converter)) | |
| return converter.mangleName(*variant); | |
| return getProcMangledName(procRef.proc(), converter); | |
| } | |
| const Fortran::semantics::Symbol * | |
| Fortran::lower::CallerInterface::getProcedureSymbol() const { | |
| // A matching OpenMP DECLARE VARIANT call targets the variant procedure. | |
| if (const Fortran::semantics::Symbol *variant = | |
| getOmpDeclareVariantCallee(procRef.proc(), converter)) | |
| return variant; | |
| 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 there 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))); | |
| } | |
| /// Return true when this dummy is in the subset for which CallInterface may | |
| /// safely emit the FIR marker that is later translated to LLVM `readonly`. | |
| /// | |
| /// For non-descriptor arguments, limit this to non-character intrinsic | |
| /// scalars. Array and derived-type arguments are excluded because | |
| /// compiler-generated copy-out may write back through a forwarded dummy | |
| /// argument without an INTENT contract. INTENT(IN) POINTER and ALLOCATABLE | |
| /// dummies are also supported: in their case, `readonly` only protects the | |
| /// descriptor storage, not data addressed by the descriptor. ASYNCHRONOUS | |
| /// memory may change underneath the callee; VOLATILE+INTENT(IN) is prohibited | |
| /// by the standard (C870) but is excluded defensively. TARGET does not | |
| /// invalidate `readonly`, which only constrains writes through the argument. | |
| static bool dummyArgCanUseLLVMReadonly( | |
| const Fortran::evaluate::characteristics::DummyDataObject &obj) { | |
| using Attrs = Fortran::evaluate::characteristics::DummyDataObject::Attr; | |
| using TypeAttrs = Fortran::evaluate::characteristics::TypeAndShape::Attr; | |
| const Fortran::common::TypeCategory category = obj.type.type().category(); | |
| const bool isSupportedIntrinsicScalar = | |
| obj.type.Rank() == 0 && !obj.type.attrs().test(TypeAttrs::AssumedRank) && | |
| category != Fortran::common::TypeCategory::Character && | |
| category != Fortran::common::TypeCategory::Derived; | |
| const bool isDescriptorDummy = | |
| obj.attrs.test(Attrs::Pointer) || obj.attrs.test(Attrs::Allocatable); | |
| return obj.intent == Fortran::common::Intent::In && | |
| (isSupportedIntrinsicScalar || isDescriptorDummy) && | |
| !obj.attrs.test(Attrs::Value) && | |
| !obj.attrs.test(Attrs::Asynchronous) && | |
| !obj.attrs.test(Attrs::Volatile); | |
| } | |
| 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() || | |
| (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) { | |
| auto procAttr = cuf::getProcAttribute(func.getContext(), | |
| *characteristic->cudaSubprogramAttrs); | |
| func.getOperation()->setAttr(cuf::getProcAttrName(), procAttr); | |
| // -fstack-arrays cannot be honored in device code: the device stack is far | |
| // smaller, and an automatic array that fits the host stack overflows it. | |
| // Recorded unconditionally so the opt-out is explicit in the IR, as on the | |
| // module. host_device is the host copy of the routine. | |
| cuf::ProcAttribute proc = procAttr.getValue(); | |
| if (proc != cuf::ProcAttribute::Host && | |
| proc != cuf::ProcAttribute::HostDevice) { | |
| cuf::setDeviceAllocationPolicy(func.getOperation()); | |
| } | |
| } | |
| 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() >= 1 && | |
| details->cudaLaunchBounds().size() <= 3 && | |
| "expect 1, 2, or 3 values"); | |
| auto maxTPBAttr = | |
| mlir::IntegerAttr::get(i64Ty, details->cudaLaunchBounds()[0]); | |
| // The minimum-blocks-per-multiprocessor operand is optional. | |
| mlir::IntegerAttr minBPMAttr; | |
| if (details->cudaLaunchBounds().size() > 1) | |
| 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); | |
| if (const Fortran::semantics::Symbol *sym = side().getProcedureSymbol()) { | |
| const Fortran::semantics::Symbol &ultimate{sym->GetUltimate()}; | |
| if (const auto *subpDetails{ | |
| ultimate.detailsIf<Fortran::semantics::SubprogramDetails>()}) { | |
| if (!subpDetails->openACCRoutineInfos().empty()) { | |
| genOpenACCRoutineConstruct(converter, module, func, | |
| subpDetails->openACCRoutineInfos()); | |
| } | |
| } else if (const auto *procDetails{ultimate.detailsIf< | |
| Fortran::semantics::ProcEntityDetails>()}) { | |
| if (!procDetails->openACCRoutineInfos().empty()) { | |
| genOpenACCRoutineConstruct(converter, module, func, | |
| procDetails->openACCRoutineInfos()); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| /// 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); | |
| llvm::SmallVector<mlir::NamedAttribute> attrs = dummyNameAttr(entity); | |
| // Propagate the INTENT(IN) contract for by-reference dummies handled by | |
| // the implicit-interface path. | |
| if (dummyArgCanUseLLVMReadonly(obj)) | |
| attrs.emplace_back( | |
| mlir::StringAttr::get(&mlirContext, fir::getReadOnlyAttrName()), | |
| mlir::UnitAttr::get(&mlirContext)); | |
| addFirOperand(refType, nextPassedArgPosition(), Property::BaseAddress, | |
| attrs); | |
| 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()); | |
| 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? | |
| // obj.attrs.test(Attrs::Asynchronous) does not impact the way the argument | |
| // is passed given flang implement asynch IO synchronously. However, it's | |
| // added to determine whether the argument is captured. | |
| // 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::Asynchronous)) | |
| addMLIRAttr(fir::getAsynchronousAttrName()); | |
| 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)); | |
| // Mark the supported subset of INTENT(IN) by-reference dummies readonly | |
| // (see dummyArgCanUseLLVMReadonly). | |
| if (dummyArgCanUseLLVMReadonly(obj)) | |
| addMLIRAttr(fir::getReadOnlyAttrName()); | |
| // TODO: intents that require special care (e.g finalization) | |
| if (obj.type.corank() > 0) | |
| 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> | |
| const bool isVolatile = obj.attrs.test(Attrs::Volatile); | |
| mlir::Type boxRefType = fir::ReferenceType::get(boxType, isVolatile); | |
| addFirOperand(boxRefType, nextPassedArgPosition(), Property::MutableBox, | |
| attrs); | |
| addPassedArg(PassEntityBy::MutableBox, entity, characteristics); | |
| } else if (obj.IsPassedByDescriptor(isBindC)) { | |
| // Pass as fir.box or fir.class | |
| 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; | |
| if (fir::isa_builtin_cdevptr_type(type)) | |
| fieldTy = | |
| mlir::cast<fir::RecordType>(fieldTy).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) { | |
| 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 = {}) { | |
| interface.inputs.emplace_back( | |
| FirPlaceHolder{type, entityPosition, p, attributes}); | |
| } | |
| void addFirResult(mlir::Type type, int entityPosition, Property p, | |
| llvm::ArrayRef<mlir::NamedAttribute> attributes = {}) { | |
| 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 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; | |
| // Set RECURSIVE if the attribute is explicitly present. This is only | |
| // used for debug info generation to maintain consistency with pre-F2018 | |
| // compilers. | |
| if (sym->attrs().test(Fortran::semantics::Attr::RECURSIVE)) | |
| flags = flags | fir::FortranProcedureFlagsEnum::recursive; | |
| } | |
| } | |
| if constexpr (std::is_same_v<Fortran::lower::CallerInterface, T>) | |
| if (Fortran::lower::isIntrinsicModuleProcRef(side().getCallDescription())) | |
| flags = flags | fir::FortranProcedureFlagsEnum::intrinsic; | |
| 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; | |
| } | |
| mlir::Type Fortran::lower::getDummyProcedurePointerType( | |
| const Fortran::semantics::Symbol &dummyProcPtr, | |
| Fortran::lower::AbstractConverter &converter) { | |
| std::optional<Fortran::evaluate::characteristics::Procedure> iface = | |
| Fortran::evaluate::characteristics::Procedure::Characterize( | |
| dummyProcPtr, converter.getFoldingContext()); | |
| mlir::Type procPtrType = getProcedureDesignatorType( | |
| iface.has_value() ? &*iface : nullptr, converter); | |
| return fir::ReferenceType::get(procPtrType); | |
| } | |
| 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(); | |
| } |