//===-- 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/Mangler.h" #include "flang/Lower/PFTBuilder.h" #include "flang/Lower/Support/Utils.h" #include "flang/Lower/Todo.h" #include "flang/Optimizer/Builder/FIRBuilder.h" #include "flang/Optimizer/Dialect/FIRDialect.h" #include "flang/Optimizer/Dialect/FIROpsSupport.h" #include "flang/Optimizer/Support/InternalNames.h" #include "flang/Semantics/symbol.h" #include "flang/Semantics/tools.h" //===----------------------------------------------------------------------===// // BIND(C) mangling helpers //===----------------------------------------------------------------------===// // Return the binding label (from BIND(C...)) or the mangled name of a symbol. static std::string getMangledName(const Fortran::semantics::Symbol &symbol) { const std::string *bindName = symbol.GetBindName(); return bindName ? *bindName : Fortran::lower::mangle::mangleName(symbol); } //===----------------------------------------------------------------------===// // 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 ::getMangledName(funit.getSubprogramSymbol()); } const Fortran::semantics::Symbol * Fortran::lower::CalleeInterface::getProcedureSymbol() const { if (funit.isMainProgram()) return nullptr; 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 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::FuncOp Fortran::lower::CalleeInterface::addEntryBlockAndMapArguments() { // On the callee side, directly map the mlir::value argument of // the function block to the Fortran symbols. func.addEntryBlock(); mapPassedEntities(); return func; } //===----------------------------------------------------------------------===// // CallInterface implementation: this part is common to both callee and caller // sides. //===----------------------------------------------------------------------===// static void addSymbolAttribute(mlir::FuncOp func, const Fortran::semantics::Symbol &sym, mlir::MLIRContext &mlirContext) { // 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)); } /// Declare drives the different actions to be performed while analyzing the /// signature and building/finding the mlir::FuncOp. template void Fortran::lower::CallInterface::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(); func = fir::FirOpBuilder::getNamedFunction(module, name); if (!func) { mlir::Location loc = side().getCalleeLocation(); mlir::FunctionType ty = genFunctionType(); func = fir::FirOpBuilder::createFunction(loc, module, name, ty); if (const Fortran::semantics::Symbol *sym = side().getProcedureSymbol()) addSymbolAttribute(func, *sym, converter.getMLIRContext()); for (const auto &placeHolder : llvm::enumerate(inputs)) if (!placeHolder.value().attributes.empty()) func.setArgAttrs(placeHolder.index(), placeHolder.value().attributes); } } } /// Once the signature has been analyzed and the mlir::FuncOp was built/found, /// map the fir inputs to Fortran entities (the symbols or expressions). template void Fortran::lower::CallInterface::mapPassedEntities() { // map back fir inputs to passed entities if constexpr (std::is_same_v) { 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 void Fortran::lower::CallInterface::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; } static const std::vector & getEntityContainer(Fortran::lower::pft::FunctionLikeUnit &funit) { return funit.getSubprogramSymbol() .get() .dummyArgs(); } static const Fortran::semantics::Symbol & getDataObjectEntity(const Fortran::semantics::Symbol *arg) { assert(arg && "expect symbol for data object entity"); return *arg; } //===----------------------------------------------------------------------===// // CallInterface implementation: this part is common to both caller and caller // sides. //===----------------------------------------------------------------------===// /// This is the actual part that defines the FIR interface based on the /// characteristic. It directly mutates the CallInterface members. template class Fortran::lower::CallInterfaceImpl { using CallInterface = Fortran::lower::CallInterface; using PassEntityBy = typename CallInterface::PassEntityBy; using PassedEntity = typename CallInterface::PassedEntity; 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 &result = procedure.functionResult) handleImplicitResult(*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); std::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) { // Handle result if (const std::optional &result = procedure.functionResult) { if (result->CanBeReturnedViaImplicitInterface()) handleImplicitResult(*result); else handleExplicitResult(*result); } else if (interface.side().hasAlternateReturns()) { addFirResult(mlir::IndexType::get(&mlirContext), FirPlaceHolder::resultEntityPosition, Property::Value); } } private: void handleImplicitResult( const Fortran::evaluate::characteristics::FunctionResult &result) { if (result.IsProcedurePointer()) TODO(interface.converter.getCurrentLocation(), "procedure pointer result not yet handled"); const Fortran::evaluate::characteristics::TypeAndShape *typeAndShape = result.GetTypeAndShape(); assert(typeAndShape && "expect type for non proc pointer result"); Fortran::evaluate::DynamicType dynamicType = typeAndShape->type(); if (dynamicType.category() == Fortran::common::TypeCategory::Character) { TODO(interface.converter.getCurrentLocation(), "implicit result character type"); } else if (dynamicType.category() == Fortran::common::TypeCategory::Derived) { TODO(interface.converter.getCurrentLocation(), "implicit result derived type"); } 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 handleExplicitResult( const Fortran::evaluate::characteristics::FunctionResult &result) { using Attr = Fortran::evaluate::characteristics::FunctionResult::Attr; if (result.IsProcedurePointer()) TODO(interface.converter.getCurrentLocation(), "procedure pointer results"); const Fortran::evaluate::characteristics::TypeAndShape *typeAndShape = result.GetTypeAndShape(); assert(typeAndShape && "expect type for non proc pointer result"); Fortran::evaluate::DynamicType dynamicType = typeAndShape->type(); if (dynamicType.category() == Fortran::common::TypeCategory::Character) { TODO(interface.converter.getCurrentLocation(), "implicit result character type"); } else if (dynamicType.category() == Fortran::common::TypeCategory::Derived) { TODO(interface.converter.getCurrentLocation(), "implicit result derived type"); } mlir::Type mlirType = getConverter().genType(dynamicType.category(), dynamicType.kind()); fir::SequenceType::Shape bounds = getBounds(typeAndShape->shape()); if (!bounds.empty()) mlirType = fir::SequenceType::get(bounds, mlirType); if (result.attrs.test(Attr::Allocatable)) mlirType = fir::BoxType::get(fir::HeapType::get(mlirType)); if (result.attrs.test(Attr::Pointer)) mlirType = fir::BoxType::get(fir::PointerType::get(mlirType)); addFirResult(mlirType, FirPlaceHolder::resultEntityPosition, Property::Value); } fir::SequenceType::Shape getBounds(const Fortran::evaluate::Shape &shape) { fir::SequenceType::Shape bounds; for (Fortran::evaluate::MaybeExtentExpr extentExpr : shape) { fir::SequenceType::Extent extent = fir::SequenceType::getUnknownExtent(); if (std::optional constantExtent = toInt64(std::move(extentExpr))) extent = *constantExtent; bounds.push_back(extent); } return bounds; } template std::optional toInt64(A &&expr) { return Fortran::evaluate::ToInt64(Fortran::evaluate::Fold( getConverter().getFoldingContext(), std::move(expr))); } /// 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 dummyNameAttr(const FortranEntity &entity) { if constexpr (std::is_same_v>>) { 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 {}; } void handleImplicitDummy( const DummyCharacteristics *characteristics, const Fortran::evaluate::characteristics::DummyDataObject &obj, const FortranEntity &entity) { Fortran::evaluate::DynamicType dynamicType = obj.type.type(); 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. Fortran::common::TypeCategory cat = dynamicType.category(); mlir::Type type = getConverter().genType(cat, dynamicType.kind()); fir::SequenceType::Shape bounds = getBounds(obj.type.shape()); if (!bounds.empty()) type = fir::SequenceType::get(bounds, type); mlir::Type refType = fir::ReferenceType::get(type); addFirOperand(refType, nextPassedArgPosition(), Property::BaseAddress, dummyNameAttr(entity)); addPassedArg(PassEntityBy::BaseAddress, entity, characteristics); } } void handleImplicitDummy( const DummyCharacteristics *characteristics, const Fortran::evaluate::characteristics::DummyProcedure &proc, const FortranEntity &entity) { TODO(interface.converter.getCurrentLocation(), "handleImlicitDummy DummyProcedure"); } void addFirOperand(mlir::Type type, int entityPosition, Property p, llvm::ArrayRef attributes = llvm::None) { interface.inputs.emplace_back( FirPlaceHolder{type, entityPosition, p, attributes}); } void addFirResult(mlir::Type type, int entityPosition, Property p, llvm::ArrayRef attributes = llvm::None) { 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, {}, {}, characteristics}); } int nextPassedArgPosition() { return interface.passedArguments.size(); } Fortran::lower::AbstractConverter &getConverter() { return interface.converter; } CallInterface &interface; mlir::MLIRContext &mlirContext; }; template void Fortran::lower::CallInterface::determineInterface( bool isImplicit, const Fortran::evaluate::characteristics::Procedure &procedure) { CallInterfaceImpl impl(*this); if (isImplicit) impl.buildImplicitInterface(procedure); else impl.buildExplicitInterface(procedure); } template mlir::FunctionType Fortran::lower::CallInterface::genFunctionType() { llvm::SmallVector returnTys; llvm::SmallVector 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 class Fortran::lower::CallInterface;