//===-- Allocatable.cpp -- Allocatable statements lowering ----------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // Coding style: https://mlir.llvm.org/getting_started/DeveloperGuide/ // //===----------------------------------------------------------------------===// #include "flang/Lower/Allocatable.h" #include "flang/Evaluate/tools.h" #include "flang/Lower/AbstractConverter.h" #include "flang/Lower/ConvertType.h" #include "flang/Lower/ConvertVariable.h" #include "flang/Lower/IterationSpace.h" #include "flang/Lower/Mangler.h" #include "flang/Lower/OpenACC.h" #include "flang/Lower/PFTBuilder.h" #include "flang/Lower/Runtime.h" #include "flang/Lower/StatementContext.h" #include "flang/Optimizer/Builder/FIRBuilder.h" #include "flang/Optimizer/Builder/Runtime/RTBuilder.h" #include "flang/Optimizer/Builder/Todo.h" #include "flang/Optimizer/Dialect/FIROps.h" #include "flang/Optimizer/Dialect/FIROpsSupport.h" #include "flang/Optimizer/Support/FatalError.h" #include "flang/Optimizer/Support/InternalNames.h" #include "flang/Parser/parse-tree.h" #include "flang/Runtime/allocatable.h" #include "flang/Runtime/pointer.h" #include "flang/Semantics/tools.h" #include "flang/Semantics/type.h" #include "llvm/Support/CommandLine.h" /// By default fir memory operation fir::AllocMemOp/fir::FreeMemOp are used. /// This switch allow forcing the use of runtime and descriptors for everything. /// This is mainly intended as a debug switch. static llvm::cl::opt useAllocateRuntime( "use-alloc-runtime", llvm::cl::desc("Lower allocations to fortran runtime calls"), llvm::cl::init(false)); /// Switch to force lowering of allocatable and pointers to descriptors in all /// cases. This is now turned on by default since that is what will happen with /// HLFIR lowering, so this allows getting early feedback of the impact. /// If this turns out to cause performance regressions, a dedicated fir.box /// "discretization pass" would make more sense to cover all the fir.box usage /// (taking advantage of any future inlining for instance). static llvm::cl::opt useDescForMutableBox( "use-desc-for-alloc", llvm::cl::desc("Always use descriptors for POINTER and ALLOCATABLE"), llvm::cl::init(true)); //===----------------------------------------------------------------------===// // Error management //===----------------------------------------------------------------------===// namespace { // Manage STAT and ERRMSG specifier information across a sequence of runtime // calls for an ALLOCATE/DEALLOCATE stmt. struct ErrorManager { void init(Fortran::lower::AbstractConverter &converter, mlir::Location loc, const Fortran::lower::SomeExpr *statExpr, const Fortran::lower::SomeExpr *errMsgExpr) { Fortran::lower::StatementContext stmtCtx; fir::FirOpBuilder &builder = converter.getFirOpBuilder(); hasStat = builder.createBool(loc, statExpr != nullptr); statAddr = statExpr ? fir::getBase(converter.genExprAddr(loc, statExpr, stmtCtx)) : mlir::Value{}; errMsgAddr = statExpr && errMsgExpr ? builder.createBox(loc, converter.genExprAddr(loc, errMsgExpr, stmtCtx)) : builder.create( loc, fir::BoxType::get(mlir::NoneType::get(builder.getContext()))); sourceFile = fir::factory::locationToFilename(builder, loc); sourceLine = fir::factory::locationToLineNo(builder, loc, builder.getIntegerType(32)); } bool hasStatSpec() const { return static_cast(statAddr); } void genStatCheck(fir::FirOpBuilder &builder, mlir::Location loc) { if (statValue) { mlir::Value zero = builder.createIntegerConstant(loc, statValue.getType(), 0); auto cmp = builder.create( loc, mlir::arith::CmpIPredicate::eq, statValue, zero); auto ifOp = builder.create(loc, cmp, /*withElseRegion=*/false); builder.setInsertionPointToStart(&ifOp.getThenRegion().front()); } } void assignStat(fir::FirOpBuilder &builder, mlir::Location loc, mlir::Value stat) { if (hasStatSpec()) { assert(stat && "missing stat value"); mlir::Value castStat = builder.createConvert( loc, fir::dyn_cast_ptrEleTy(statAddr.getType()), stat); builder.create(loc, castStat, statAddr); statValue = stat; } } mlir::Value hasStat; mlir::Value errMsgAddr; mlir::Value sourceFile; mlir::Value sourceLine; private: mlir::Value statAddr; // STAT variable address mlir::Value statValue; // current runtime STAT value }; //===----------------------------------------------------------------------===// // Allocatables runtime call generators //===----------------------------------------------------------------------===// using namespace Fortran::runtime; /// Generate a runtime call to set the bounds of an allocatable or pointer /// descriptor. static void genRuntimeSetBounds(fir::FirOpBuilder &builder, mlir::Location loc, const fir::MutableBoxValue &box, mlir::Value dimIndex, mlir::Value lowerBound, mlir::Value upperBound) { mlir::func::FuncOp callee = box.isPointer() ? fir::runtime::getRuntimeFunc(loc, builder) : fir::runtime::getRuntimeFunc( loc, builder); llvm::SmallVector args{box.getAddr(), dimIndex, lowerBound, upperBound}; llvm::SmallVector operands; for (auto [fst, snd] : llvm::zip(args, callee.getFunctionType().getInputs())) operands.emplace_back(builder.createConvert(loc, snd, fst)); builder.create(loc, callee, operands); } /// Generate runtime call to set the lengths of a character allocatable or /// pointer descriptor. static void genRuntimeInitCharacter(fir::FirOpBuilder &builder, mlir::Location loc, const fir::MutableBoxValue &box, mlir::Value len, int64_t kind = 0) { mlir::func::FuncOp callee = box.isPointer() ? fir::runtime::getRuntimeFunc( loc, builder) : fir::runtime::getRuntimeFunc(loc, builder); llvm::ArrayRef inputTypes = callee.getFunctionType().getInputs(); if (inputTypes.size() != 5) fir::emitFatalError( loc, "AllocatableInitCharacter runtime interface not as expected"); llvm::SmallVector args; args.push_back(builder.createConvert(loc, inputTypes[0], box.getAddr())); args.push_back(builder.createConvert(loc, inputTypes[1], len)); if (kind == 0) kind = box.getEleTy().cast().getFKind(); args.push_back(builder.createIntegerConstant(loc, inputTypes[2], kind)); int rank = box.rank(); args.push_back(builder.createIntegerConstant(loc, inputTypes[3], rank)); // TODO: coarrays int corank = 0; args.push_back(builder.createIntegerConstant(loc, inputTypes[4], corank)); builder.create(loc, callee, args); } /// Generate a sequence of runtime calls to allocate memory. static mlir::Value genRuntimeAllocate(fir::FirOpBuilder &builder, mlir::Location loc, const fir::MutableBoxValue &box, ErrorManager &errorManager) { mlir::func::FuncOp callee = box.isPointer() ? fir::runtime::getRuntimeFunc(loc, builder) : fir::runtime::getRuntimeFunc(loc, builder); llvm::SmallVector args{ box.getAddr(), errorManager.hasStat, errorManager.errMsgAddr, errorManager.sourceFile, errorManager.sourceLine}; llvm::SmallVector operands; for (auto [fst, snd] : llvm::zip(args, callee.getFunctionType().getInputs())) operands.emplace_back(builder.createConvert(loc, snd, fst)); return builder.create(loc, callee, operands).getResult(0); } /// Generate a sequence of runtime calls to allocate memory and assign with the /// \p source. static mlir::Value genRuntimeAllocateSource(fir::FirOpBuilder &builder, mlir::Location loc, const fir::MutableBoxValue &box, fir::ExtendedValue source, ErrorManager &errorManager) { mlir::func::FuncOp callee = box.isPointer() ? fir::runtime::getRuntimeFunc( loc, builder) : fir::runtime::getRuntimeFunc( loc, builder); llvm::SmallVector args{ box.getAddr(), fir::getBase(source), errorManager.hasStat, errorManager.errMsgAddr, errorManager.sourceFile, errorManager.sourceLine}; llvm::SmallVector operands; for (auto [fst, snd] : llvm::zip(args, callee.getFunctionType().getInputs())) operands.emplace_back(builder.createConvert(loc, snd, fst)); return builder.create(loc, callee, operands).getResult(0); } /// Generate runtime call to apply mold to the descriptor. static void genRuntimeAllocateApplyMold(fir::FirOpBuilder &builder, mlir::Location loc, const fir::MutableBoxValue &box, fir::ExtendedValue mold, int rank) { mlir::func::FuncOp callee = box.isPointer() ? fir::runtime::getRuntimeFunc(loc, builder) : fir::runtime::getRuntimeFunc( loc, builder); llvm::SmallVector args{ fir::factory::getMutableIRBox(builder, loc, box), fir::getBase(mold), builder.createIntegerConstant( loc, callee.getFunctionType().getInputs()[2], rank)}; llvm::SmallVector operands; for (auto [fst, snd] : llvm::zip(args, callee.getFunctionType().getInputs())) operands.emplace_back(builder.createConvert(loc, snd, fst)); builder.create(loc, callee, operands); } /// Generate a runtime call to deallocate memory. static mlir::Value genRuntimeDeallocate(fir::FirOpBuilder &builder, mlir::Location loc, const fir::MutableBoxValue &box, ErrorManager &errorManager, mlir::Value declaredTypeDesc = {}) { // Ensure fir.box is up-to-date before passing it to deallocate runtime. mlir::Value boxAddress = fir::factory::getMutableIRBox(builder, loc, box); mlir::func::FuncOp callee; llvm::SmallVector args; llvm::SmallVector operands; if (box.isPolymorphic() || box.isUnlimitedPolymorphic()) { callee = box.isPointer() ? fir::runtime::getRuntimeFunc(loc, builder) : fir::runtime::getRuntimeFunc(loc, builder); if (!declaredTypeDesc) declaredTypeDesc = builder.createNullConstant(loc); operands = fir::runtime::createArguments( builder, loc, callee.getFunctionType(), boxAddress, declaredTypeDesc, errorManager.hasStat, errorManager.errMsgAddr, errorManager.sourceFile, errorManager.sourceLine); } else { callee = box.isPointer() ? fir::runtime::getRuntimeFunc( loc, builder) : fir::runtime::getRuntimeFunc( loc, builder); operands = fir::runtime::createArguments( builder, loc, callee.getFunctionType(), boxAddress, errorManager.hasStat, errorManager.errMsgAddr, errorManager.sourceFile, errorManager.sourceLine); } return builder.create(loc, callee, operands).getResult(0); } //===----------------------------------------------------------------------===// // Allocate statement implementation //===----------------------------------------------------------------------===// /// Helper to get symbol from AllocateObject. static const Fortran::semantics::Symbol & unwrapSymbol(const Fortran::parser::AllocateObject &allocObj) { const Fortran::parser::Name &lastName = Fortran::parser::GetLastName(allocObj); assert(lastName.symbol); return *lastName.symbol; } static fir::MutableBoxValue genMutableBoxValue(Fortran::lower::AbstractConverter &converter, mlir::Location loc, const Fortran::parser::AllocateObject &allocObj) { const Fortran::lower::SomeExpr *expr = Fortran::semantics::GetExpr(allocObj); assert(expr && "semantic analysis failure"); return converter.genExprMutableBox(loc, *expr); } /// Implement Allocate statement lowering. class AllocateStmtHelper { public: AllocateStmtHelper(Fortran::lower::AbstractConverter &converter, const Fortran::parser::AllocateStmt &stmt, mlir::Location loc) : converter{converter}, builder{converter.getFirOpBuilder()}, stmt{stmt}, loc{loc} {} void lower() { visitAllocateOptions(); lowerAllocateLengthParameters(); errorManager.init(converter, loc, statExpr, errMsgExpr); Fortran::lower::StatementContext stmtCtx; if (sourceExpr) sourceExv = converter.genExprBox(loc, *sourceExpr, stmtCtx); if (moldExpr) moldExv = converter.genExprBox(loc, *moldExpr, stmtCtx); mlir::OpBuilder::InsertPoint insertPt = builder.saveInsertionPoint(); for (const auto &allocation : std::get>(stmt.t)) lowerAllocation(unwrapAllocation(allocation)); builder.restoreInsertionPoint(insertPt); } private: struct Allocation { const Fortran::parser::Allocation &alloc; const Fortran::semantics::DeclTypeSpec &type; bool hasCoarraySpec() const { return std::get>( alloc.t) .has_value(); } const Fortran::parser::AllocateObject &getAllocObj() const { return std::get(alloc.t); } const Fortran::semantics::Symbol &getSymbol() const { return unwrapSymbol(getAllocObj()); } const std::list &getShapeSpecs() const { return std::get>(alloc.t); } }; Allocation unwrapAllocation(const Fortran::parser::Allocation &alloc) { const auto &allocObj = std::get(alloc.t); const Fortran::semantics::Symbol &symbol = unwrapSymbol(allocObj); assert(symbol.GetType()); return Allocation{alloc, *symbol.GetType()}; } void visitAllocateOptions() { for (const auto &allocOption : std::get>(stmt.t)) std::visit( Fortran::common::visitors{ [&](const Fortran::parser::StatOrErrmsg &statOrErr) { std::visit( Fortran::common::visitors{ [&](const Fortran::parser::StatVariable &statVar) { statExpr = Fortran::semantics::GetExpr(statVar); }, [&](const Fortran::parser::MsgVariable &errMsgVar) { errMsgExpr = Fortran::semantics::GetExpr(errMsgVar); }, }, statOrErr.u); }, [&](const Fortran::parser::AllocOpt::Source &source) { sourceExpr = Fortran::semantics::GetExpr(source.v.value()); }, [&](const Fortran::parser::AllocOpt::Mold &mold) { moldExpr = Fortran::semantics::GetExpr(mold.v.value()); }, [&](const Fortran::parser::AllocOpt::Stream &stream) { streamExpr = Fortran::semantics::GetExpr(stream.v.value()); }, [&](const Fortran::parser::AllocOpt::Pinned &pinned) { pinnedExpr = Fortran::semantics::GetExpr(pinned.v.value()); }, }, allocOption.u); } void lowerAllocation(const Allocation &alloc) { fir::MutableBoxValue boxAddr = genMutableBoxValue(converter, loc, alloc.getAllocObj()); if (sourceExpr) genSourceMoldAllocation(alloc, boxAddr, /*isSource=*/true); else if (moldExpr) genSourceMoldAllocation(alloc, boxAddr, /*isSource=*/false); else genSimpleAllocation(alloc, boxAddr); } static bool lowerBoundsAreOnes(const Allocation &alloc) { for (const Fortran::parser::AllocateShapeSpec &shapeSpec : alloc.getShapeSpecs()) if (std::get<0>(shapeSpec.t)) return false; return true; } /// Build name for the fir::allocmem generated for alloc. std::string mangleAlloc(const Allocation &alloc) { return converter.mangleName(alloc.getSymbol()) + ".alloc"; } /// Generate allocation without runtime calls. /// Only for intrinsic types. No coarrays, no polymorphism. No error recovery. void genInlinedAllocation(const Allocation &alloc, const fir::MutableBoxValue &box) { llvm::SmallVector lbounds; llvm::SmallVector extents; Fortran::lower::StatementContext stmtCtx; mlir::Type idxTy = builder.getIndexType(); bool lBoundsAreOnes = lowerBoundsAreOnes(alloc); mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1); for (const Fortran::parser::AllocateShapeSpec &shapeSpec : alloc.getShapeSpecs()) { mlir::Value lb; if (!lBoundsAreOnes) { if (const std::optional &lbExpr = std::get<0>(shapeSpec.t)) { lb = fir::getBase(converter.genExprValue( loc, Fortran::semantics::GetExpr(*lbExpr), stmtCtx)); lb = builder.createConvert(loc, idxTy, lb); } else { lb = one; } lbounds.emplace_back(lb); } mlir::Value ub = fir::getBase(converter.genExprValue( loc, Fortran::semantics::GetExpr(std::get<1>(shapeSpec.t)), stmtCtx)); ub = builder.createConvert(loc, idxTy, ub); if (lb) { mlir::Value diff = builder.create(loc, ub, lb); extents.emplace_back( builder.create(loc, diff, one)); } else { extents.emplace_back(ub); } } fir::factory::genInlinedAllocation(builder, loc, box, lbounds, extents, lenParams, mangleAlloc(alloc), /*mustBeHeap=*/true); } void postAllocationAction(const Allocation &alloc) { if (alloc.getSymbol().test(Fortran::semantics::Symbol::Flag::AccDeclare)) Fortran::lower::attachDeclarePostAllocAction(converter, builder, alloc.getSymbol()); } void genSimpleAllocation(const Allocation &alloc, const fir::MutableBoxValue &box) { if (!box.isDerived() && !errorManager.hasStatSpec() && !alloc.type.IsPolymorphic() && !alloc.hasCoarraySpec() && !useAllocateRuntime && !box.isPointer() && !Fortran::semantics::HasCUDAAttr(alloc.getSymbol())) { // Pointers must use PointerAllocate so that their deallocations // can be validated. genInlinedAllocation(alloc, box); postAllocationAction(alloc); return; } // Generate a sequence of runtime calls. errorManager.genStatCheck(builder, loc); genAllocateObjectInit(box); if (alloc.hasCoarraySpec()) TODO(loc, "coarray: allocation of a coarray object"); if (alloc.type.IsPolymorphic()) genSetType(alloc, box, loc); genSetDeferredLengthParameters(alloc, box); genAllocateObjectBounds(alloc, box); mlir::Value stat; if (!Fortran::semantics::HasCUDAAttr(alloc.getSymbol())) stat = genRuntimeAllocate(builder, loc, box, errorManager); else stat = genCudaAllocate(builder, loc, box, errorManager, alloc.getSymbol()); fir::factory::syncMutableBoxFromIRBox(builder, loc, box); postAllocationAction(alloc); errorManager.assignStat(builder, loc, stat); } /// Lower the length parameters that may be specified in the optional /// type specification. void lowerAllocateLengthParameters() { const Fortran::semantics::DeclTypeSpec *typeSpec = getIfAllocateStmtTypeSpec(); if (!typeSpec) return; if (const Fortran::semantics::DerivedTypeSpec *derived = typeSpec->AsDerived()) if (Fortran::semantics::CountLenParameters(*derived) > 0) TODO(loc, "setting derived type params in allocation"); if (typeSpec->category() == Fortran::semantics::DeclTypeSpec::Category::Character) { Fortran::semantics::ParamValue lenParam = typeSpec->characterTypeSpec().length(); if (Fortran::semantics::MaybeIntExpr intExpr = lenParam.GetExplicit()) { Fortran::lower::StatementContext stmtCtx; Fortran::lower::SomeExpr lenExpr{*intExpr}; lenParams.push_back( fir::getBase(converter.genExprValue(loc, lenExpr, stmtCtx))); } } } // Set length parameters in the box stored in boxAddr. // This must be called before setting the bounds because it may use // Init runtime calls that may set the bounds to zero. void genSetDeferredLengthParameters(const Allocation &alloc, const fir::MutableBoxValue &box) { if (lenParams.empty()) return; // TODO: in case a length parameter was not deferred, insert a runtime check // that the length is the same (AllocatableCheckLengthParameter runtime // call). if (box.isCharacter()) genRuntimeInitCharacter(builder, loc, box, lenParams[0]); if (box.isDerived()) TODO(loc, "derived type length parameters in allocate"); } void genAllocateObjectInit(const fir::MutableBoxValue &box) { if (box.isPointer()) { // For pointers, the descriptor may still be uninitialized (see Fortran // 2018 19.5.2.2). The allocation runtime needs to be given a descriptor // with initialized rank, types and attributes. Initialize the descriptor // here to ensure these constraints are fulfilled. mlir::Value nullPointer = fir::factory::createUnallocatedBox( builder, loc, box.getBoxTy(), box.nonDeferredLenParams()); builder.create(loc, nullPointer, box.getAddr()); } else { assert(box.isAllocatable() && "must be an allocatable"); // For allocatables, sync the MutableBoxValue and descriptor before the // calls in case it is tracked locally by a set of variables. fir::factory::getMutableIRBox(builder, loc, box); } } void genAllocateObjectBounds(const Allocation &alloc, const fir::MutableBoxValue &box) { // Set bounds for arrays mlir::Type idxTy = builder.getIndexType(); mlir::Type i32Ty = builder.getIntegerType(32); Fortran::lower::StatementContext stmtCtx; for (const auto &iter : llvm::enumerate(alloc.getShapeSpecs())) { mlir::Value lb; const auto &bounds = iter.value().t; if (const std::optional &lbExpr = std::get<0>(bounds)) lb = fir::getBase(converter.genExprValue( loc, Fortran::semantics::GetExpr(*lbExpr), stmtCtx)); else lb = builder.createIntegerConstant(loc, idxTy, 1); mlir::Value ub = fir::getBase(converter.genExprValue( loc, Fortran::semantics::GetExpr(std::get<1>(bounds)), stmtCtx)); mlir::Value dimIndex = builder.createIntegerConstant(loc, i32Ty, iter.index()); // Runtime call genRuntimeSetBounds(builder, loc, box, dimIndex, lb, ub); } if (sourceExpr && sourceExpr->Rank() > 0 && alloc.getShapeSpecs().size() == 0) { // If the alloc object does not have shape list, get the bounds from the // source expression. mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1); const auto *sourceBox = sourceExv.getBoxOf(); assert(sourceBox && "source expression should be lowered to one box"); for (int i = 0; i < sourceExpr->Rank(); ++i) { auto dimVal = builder.createIntegerConstant(loc, idxTy, i); auto dimInfo = builder.create( loc, idxTy, idxTy, idxTy, sourceBox->getAddr(), dimVal); mlir::Value lb = fir::factory::readLowerBound(builder, loc, sourceExv, i, one); mlir::Value extent = dimInfo.getResult(1); mlir::Value ub = builder.create( loc, builder.create(loc, extent, lb), one); mlir::Value dimIndex = builder.createIntegerConstant(loc, i32Ty, i); genRuntimeSetBounds(builder, loc, box, dimIndex, lb, ub); } } } void genSourceMoldAllocation(const Allocation &alloc, const fir::MutableBoxValue &box, bool isSource) { fir::ExtendedValue exv = isSource ? sourceExv : moldExv; ; // Generate a sequence of runtime calls. errorManager.genStatCheck(builder, loc); genAllocateObjectInit(box); if (alloc.hasCoarraySpec()) TODO(loc, "coarray: allocation of a coarray object"); // Set length of the allocate object if it has. Otherwise, get the length // from source for the deferred length parameter. const bool isDeferredLengthCharacter = box.isCharacter() && !box.hasNonDeferredLenParams(); if (lenParams.empty() && isDeferredLengthCharacter) lenParams.push_back(fir::factory::readCharLen(builder, loc, exv)); if (!isSource || alloc.type.IsPolymorphic()) genRuntimeAllocateApplyMold(builder, loc, box, exv, alloc.getSymbol().Rank()); if (isDeferredLengthCharacter) genSetDeferredLengthParameters(alloc, box); genAllocateObjectBounds(alloc, box); mlir::Value stat; if (Fortran::semantics::HasCUDAAttr(alloc.getSymbol())) stat = genCudaAllocate(builder, loc, box, errorManager, alloc.getSymbol()); else if (isSource) stat = genRuntimeAllocateSource(builder, loc, box, exv, errorManager); else stat = genRuntimeAllocate(builder, loc, box, errorManager); fir::factory::syncMutableBoxFromIRBox(builder, loc, box); postAllocationAction(alloc); errorManager.assignStat(builder, loc, stat); } /// Generate call to PointerNullifyDerived or AllocatableInitDerived /// to set the dynamic type information. void genInitDerived(const fir::MutableBoxValue &box, mlir::Value typeDescAddr, int rank, int corank = 0) { mlir::func::FuncOp callee = box.isPointer() ? fir::runtime::getRuntimeFunc( loc, builder) : fir::runtime::getRuntimeFunc(loc, builder); llvm::ArrayRef inputTypes = callee.getFunctionType().getInputs(); llvm::SmallVector args; args.push_back(builder.createConvert(loc, inputTypes[0], box.getAddr())); args.push_back(builder.createConvert(loc, inputTypes[1], typeDescAddr)); mlir::Value rankValue = builder.createIntegerConstant(loc, inputTypes[2], rank); mlir::Value corankValue = builder.createIntegerConstant(loc, inputTypes[3], corank); args.push_back(rankValue); args.push_back(corankValue); builder.create(loc, callee, args); } /// Generate call to PointerNullifyIntrinsic or AllocatableInitIntrinsic to /// set the dynamic type information for a polymorphic entity from an /// intrinsic type spec. void genInitIntrinsic(const fir::MutableBoxValue &box, const TypeCategory category, int64_t kind, int rank, int corank = 0) { mlir::func::FuncOp callee = box.isPointer() ? fir::runtime::getRuntimeFunc( loc, builder) : fir::runtime::getRuntimeFunc(loc, builder); llvm::ArrayRef inputTypes = callee.getFunctionType().getInputs(); llvm::SmallVector args; args.push_back(builder.createConvert(loc, inputTypes[0], box.getAddr())); mlir::Value categoryValue = builder.createIntegerConstant( loc, inputTypes[1], static_cast(category)); mlir::Value kindValue = builder.createIntegerConstant(loc, inputTypes[2], kind); mlir::Value rankValue = builder.createIntegerConstant(loc, inputTypes[3], rank); mlir::Value corankValue = builder.createIntegerConstant(loc, inputTypes[4], corank); args.push_back(categoryValue); args.push_back(kindValue); args.push_back(rankValue); args.push_back(corankValue); builder.create(loc, callee, args); } /// Generate call to the AllocatableInitDerived to set up the type descriptor /// and other part of the descriptor for derived type. void genSetType(const Allocation &alloc, const fir::MutableBoxValue &box, mlir::Location loc) { const Fortran::semantics::DeclTypeSpec *typeSpec = getIfAllocateStmtTypeSpec(); // No type spec provided in allocate statement so the declared type spec is // used. if (!typeSpec) typeSpec = &alloc.type; assert(typeSpec && "type spec missing for polymorphic allocation"); // Set up the descriptor for allocation for intrinsic type spec on // unlimited polymorphic entity. if (typeSpec->AsIntrinsic() && fir::isUnlimitedPolymorphicType(fir::getBase(box).getType())) { if (typeSpec->AsIntrinsic()->category() == TypeCategory::Character) { genRuntimeInitCharacter( builder, loc, box, lenParams[0], Fortran::evaluate::ToInt64(typeSpec->AsIntrinsic()->kind()) .value()); } else { genInitIntrinsic( box, typeSpec->AsIntrinsic()->category(), Fortran::evaluate::ToInt64(typeSpec->AsIntrinsic()->kind()).value(), alloc.getSymbol().Rank()); } return; } // Do not generate calls for non derived-type type spec. if (!typeSpec->AsDerived()) return; auto typeDescAddr = Fortran::lower::getTypeDescAddr( converter, loc, typeSpec->derivedTypeSpec()); genInitDerived(box, typeDescAddr, alloc.getSymbol().Rank()); } /// Returns a pointer to the DeclTypeSpec if a type-spec is provided in the /// allocate statement. Returns a null pointer otherwise. const Fortran::semantics::DeclTypeSpec *getIfAllocateStmtTypeSpec() const { if (const auto &typeSpec = std::get>(stmt.t)) return typeSpec->declTypeSpec; return nullptr; } mlir::Value genCudaAllocate(fir::FirOpBuilder &builder, mlir::Location loc, const fir::MutableBoxValue &box, ErrorManager &errorManager, const Fortran::semantics::Symbol &sym) { Fortran::lower::StatementContext stmtCtx; fir::CUDADataAttributeAttr cudaAttr = Fortran::lower::translateSymbolCUDADataAttribute(builder.getContext(), sym); mlir::Value errmsg = errMsgExpr ? errorManager.errMsgAddr : nullptr; mlir::Value stream = streamExpr ? fir::getBase(converter.genExprValue(loc, *streamExpr, stmtCtx)) : nullptr; mlir::Value pinned = pinnedExpr ? fir::getBase(converter.genExprAddr(loc, *pinnedExpr, stmtCtx)) : nullptr; mlir::Value source = sourceExpr ? fir::getBase(sourceExv) : nullptr; // Keep return type the same as a standard AllocatableAllocate call. mlir::Type retTy = fir::runtime::getModel()(builder.getContext()); return builder .create( loc, retTy, box.getAddr(), errmsg, stream, pinned, source, cudaAttr, errorManager.hasStatSpec() ? builder.getUnitAttr() : nullptr) .getResult(); } Fortran::lower::AbstractConverter &converter; fir::FirOpBuilder &builder; const Fortran::parser::AllocateStmt &stmt; const Fortran::lower::SomeExpr *sourceExpr{nullptr}; const Fortran::lower::SomeExpr *moldExpr{nullptr}; const Fortran::lower::SomeExpr *statExpr{nullptr}; const Fortran::lower::SomeExpr *errMsgExpr{nullptr}; const Fortran::lower::SomeExpr *pinnedExpr{nullptr}; const Fortran::lower::SomeExpr *streamExpr{nullptr}; // If the allocate has a type spec, lenParams contains the // value of the length parameters that were specified inside. llvm::SmallVector lenParams; ErrorManager errorManager; // 9.7.1.2(7) The source-expr is evaluated exactly once for each AllocateStmt. fir::ExtendedValue sourceExv; fir::ExtendedValue moldExv; mlir::Location loc; }; } // namespace void Fortran::lower::genAllocateStmt( Fortran::lower::AbstractConverter &converter, const Fortran::parser::AllocateStmt &stmt, mlir::Location loc) { AllocateStmtHelper{converter, stmt, loc}.lower(); } //===----------------------------------------------------------------------===// // Deallocate statement implementation //===----------------------------------------------------------------------===// static void preDeallocationAction(Fortran::lower::AbstractConverter &converter, fir::FirOpBuilder &builder, mlir::Value beginOpValue, const Fortran::semantics::Symbol &sym) { if (sym.test(Fortran::semantics::Symbol::Flag::AccDeclare)) Fortran::lower::attachDeclarePreDeallocAction(converter, builder, beginOpValue, sym); } static void postDeallocationAction(Fortran::lower::AbstractConverter &converter, fir::FirOpBuilder &builder, const Fortran::semantics::Symbol &sym) { if (sym.test(Fortran::semantics::Symbol::Flag::AccDeclare)) Fortran::lower::attachDeclarePostDeallocAction(converter, builder, sym); } static mlir::Value genCudaDeallocate(fir::FirOpBuilder &builder, mlir::Location loc, const fir::MutableBoxValue &box, ErrorManager &errorManager, const Fortran::semantics::Symbol &sym) { fir::CUDADataAttributeAttr cudaAttr = Fortran::lower::translateSymbolCUDADataAttribute(builder.getContext(), sym); mlir::Value errmsg = mlir::isa(errorManager.errMsgAddr.getDefiningOp()) ? nullptr : errorManager.errMsgAddr; // Keep return type the same as a standard AllocatableAllocate call. mlir::Type retTy = fir::runtime::getModel()(builder.getContext()); return builder .create( loc, retTy, box.getAddr(), errmsg, cudaAttr, errorManager.hasStatSpec() ? builder.getUnitAttr() : nullptr) .getResult(); } // Generate deallocation of a pointer/allocatable. static mlir::Value genDeallocate(fir::FirOpBuilder &builder, Fortran::lower::AbstractConverter &converter, mlir::Location loc, const fir::MutableBoxValue &box, ErrorManager &errorManager, mlir::Value declaredTypeDesc = {}, const Fortran::semantics::Symbol *symbol = nullptr) { bool isCudaSymbol = symbol && Fortran::semantics::HasCUDAAttr(*symbol); // Deallocate intrinsic types inline. if (!box.isDerived() && !box.isPolymorphic() && !box.isUnlimitedPolymorphic() && !errorManager.hasStatSpec() && !useAllocateRuntime && !box.isPointer() && !isCudaSymbol) { // Pointers must use PointerDeallocate so that their deallocations // can be validated. mlir::Value ret = fir::factory::genFreemem(builder, loc, box); if (symbol) postDeallocationAction(converter, builder, *symbol); return ret; } // Use runtime calls to deallocate descriptor cases. Sync MutableBoxValue // with its descriptor before and after calls if needed. errorManager.genStatCheck(builder, loc); mlir::Value stat; if (!isCudaSymbol) stat = genRuntimeDeallocate(builder, loc, box, errorManager, declaredTypeDesc); else stat = genCudaDeallocate(builder, loc, box, errorManager, *symbol); fir::factory::syncMutableBoxFromIRBox(builder, loc, box); if (symbol) postDeallocationAction(converter, builder, *symbol); errorManager.assignStat(builder, loc, stat); return stat; } void Fortran::lower::genDeallocateBox( Fortran::lower::AbstractConverter &converter, const fir::MutableBoxValue &box, mlir::Location loc, mlir::Value declaredTypeDesc) { const Fortran::lower::SomeExpr *statExpr = nullptr; const Fortran::lower::SomeExpr *errMsgExpr = nullptr; ErrorManager errorManager; errorManager.init(converter, loc, statExpr, errMsgExpr); fir::FirOpBuilder &builder = converter.getFirOpBuilder(); genDeallocate(builder, converter, loc, box, errorManager, declaredTypeDesc); } void Fortran::lower::genDeallocateIfAllocated( Fortran::lower::AbstractConverter &converter, const fir::MutableBoxValue &box, mlir::Location loc) { fir::FirOpBuilder &builder = converter.getFirOpBuilder(); mlir::Value isAllocated = fir::factory::genIsAllocatedOrAssociatedTest(builder, loc, box); builder.genIfThen(loc, isAllocated) .genThen([&]() { if (mlir::Type eleType = box.getEleTy(); eleType.isa() && box.isPolymorphic()) { mlir::Value declaredTypeDesc = builder.create( loc, mlir::TypeAttr::get(eleType)); genDeallocateBox(converter, box, loc, declaredTypeDesc); } else { genDeallocateBox(converter, box, loc); } }) .end(); } void Fortran::lower::genDeallocateStmt( Fortran::lower::AbstractConverter &converter, const Fortran::parser::DeallocateStmt &stmt, mlir::Location loc) { const Fortran::lower::SomeExpr *statExpr = nullptr; const Fortran::lower::SomeExpr *errMsgExpr = nullptr; for (const Fortran::parser::StatOrErrmsg &statOrErr : std::get>(stmt.t)) std::visit(Fortran::common::visitors{ [&](const Fortran::parser::StatVariable &statVar) { statExpr = Fortran::semantics::GetExpr(statVar); }, [&](const Fortran::parser::MsgVariable &errMsgVar) { errMsgExpr = Fortran::semantics::GetExpr(errMsgVar); }, }, statOrErr.u); ErrorManager errorManager; errorManager.init(converter, loc, statExpr, errMsgExpr); fir::FirOpBuilder &builder = converter.getFirOpBuilder(); mlir::OpBuilder::InsertPoint insertPt = builder.saveInsertionPoint(); for (const Fortran::parser::AllocateObject &allocateObject : std::get>(stmt.t)) { const Fortran::semantics::Symbol &symbol = unwrapSymbol(allocateObject); fir::MutableBoxValue box = genMutableBoxValue(converter, loc, allocateObject); mlir::Value declaredTypeDesc = {}; if (box.isPolymorphic()) { mlir::Type eleType = box.getEleTy(); if (eleType.isa()) if (const Fortran::semantics::DerivedTypeSpec *derivedTypeSpec = symbol.GetType()->AsDerived()) { declaredTypeDesc = Fortran::lower::getTypeDescAddr(converter, loc, *derivedTypeSpec); } } mlir::Value beginOpValue = genDeallocate( builder, converter, loc, box, errorManager, declaredTypeDesc, &symbol); preDeallocationAction(converter, builder, beginOpValue, symbol); } builder.restoreInsertionPoint(insertPt); } //===----------------------------------------------------------------------===// // MutableBoxValue creation implementation //===----------------------------------------------------------------------===// /// Is this symbol a pointer to a pointer array that does not have the /// CONTIGUOUS attribute ? static inline bool isNonContiguousArrayPointer(const Fortran::semantics::Symbol &sym) { return Fortran::semantics::IsPointer(sym) && sym.Rank() != 0 && !sym.attrs().test(Fortran::semantics::Attr::CONTIGUOUS); } /// Is this symbol a polymorphic pointer? static inline bool isPolymorphicPointer(const Fortran::semantics::Symbol &sym) { return Fortran::semantics::IsPointer(sym) && Fortran::semantics::IsPolymorphic(sym); } /// Is this symbol a polymorphic allocatable? static inline bool isPolymorphicAllocatable(const Fortran::semantics::Symbol &sym) { return Fortran::semantics::IsAllocatable(sym) && Fortran::semantics::IsPolymorphic(sym); } /// Is this a local procedure symbol in a procedure that contains internal /// procedures ? static bool mayBeCapturedInInternalProc(const Fortran::semantics::Symbol &sym) { const Fortran::semantics::Scope &owner = sym.owner(); Fortran::semantics::Scope::Kind kind = owner.kind(); // Test if this is a procedure scope that contains a subprogram scope that is // not an interface. if (kind == Fortran::semantics::Scope::Kind::Subprogram || kind == Fortran::semantics::Scope::Kind::MainProgram) for (const Fortran::semantics::Scope &childScope : owner.children()) if (childScope.kind() == Fortran::semantics::Scope::Kind::Subprogram) if (const Fortran::semantics::Symbol *childSym = childScope.symbol()) if (const auto *details = childSym->detailsIf()) if (!details->isInterface()) return true; return false; } /// In case it is safe to track the properties in variables outside a /// descriptor, create the variables to hold the mutable properties of the /// entity var. The variables are not initialized here. static fir::MutableProperties createMutableProperties(Fortran::lower::AbstractConverter &converter, mlir::Location loc, const Fortran::lower::pft::Variable &var, mlir::ValueRange nonDeferredParams, bool alwaysUseBox) { fir::FirOpBuilder &builder = converter.getFirOpBuilder(); const Fortran::semantics::Symbol &sym = var.getSymbol(); // Globals and dummies may be associated, creating local variables would // require keeping the values and descriptor before and after every single // impure calls in the current scope (not only the ones taking the variable as // arguments. All.) Volatile means the variable may change in ways not defined // per Fortran, so lowering can most likely not keep the descriptor and values // in sync as needed. // Pointers to non contiguous arrays need to be represented with a fir.box to // account for the discontiguity. // Pointer/Allocatable in internal procedure are descriptors in the host link, // and it would increase complexity to sync this descriptor with the local // values every time the host link is escaping. if (alwaysUseBox || var.isGlobal() || Fortran::semantics::IsDummy(sym) || Fortran::semantics::IsFunctionResult(sym) || sym.attrs().test(Fortran::semantics::Attr::VOLATILE) || isNonContiguousArrayPointer(sym) || useAllocateRuntime || useDescForMutableBox || mayBeCapturedInInternalProc(sym) || isPolymorphicPointer(sym) || isPolymorphicAllocatable(sym)) return {}; fir::MutableProperties mutableProperties; std::string name = converter.mangleName(sym); mlir::Type baseAddrTy = converter.genType(sym); if (auto boxType = baseAddrTy.dyn_cast()) baseAddrTy = boxType.getEleTy(); // Allocate and set a variable to hold the address. // It will be set to null in setUnallocatedStatus. mutableProperties.addr = builder.allocateLocal( loc, baseAddrTy, name + ".addr", "", /*shape=*/std::nullopt, /*typeparams=*/std::nullopt); // Allocate variables to hold lower bounds and extents. int rank = sym.Rank(); mlir::Type idxTy = builder.getIndexType(); for (decltype(rank) i = 0; i < rank; ++i) { mlir::Value lboundVar = builder.allocateLocal( loc, idxTy, name + ".lb" + std::to_string(i), "", /*shape=*/std::nullopt, /*typeparams=*/std::nullopt); mlir::Value extentVar = builder.allocateLocal( loc, idxTy, name + ".ext" + std::to_string(i), "", /*shape=*/std::nullopt, /*typeparams=*/std::nullopt); mutableProperties.lbounds.emplace_back(lboundVar); mutableProperties.extents.emplace_back(extentVar); } // Allocate variable to hold deferred length parameters. mlir::Type eleTy = baseAddrTy; if (auto newTy = fir::dyn_cast_ptrEleTy(eleTy)) eleTy = newTy; if (auto seqTy = eleTy.dyn_cast()) eleTy = seqTy.getEleTy(); if (auto record = eleTy.dyn_cast()) if (record.getNumLenParams() != 0) TODO(loc, "deferred length type parameters."); if (fir::isa_char(eleTy) && nonDeferredParams.empty()) { mlir::Value lenVar = builder.allocateLocal(loc, builder.getCharacterLengthType(), name + ".len", "", /*shape=*/std::nullopt, /*typeparams=*/std::nullopt); mutableProperties.deferredParams.emplace_back(lenVar); } return mutableProperties; } fir::MutableBoxValue Fortran::lower::createMutableBox( Fortran::lower::AbstractConverter &converter, mlir::Location loc, const Fortran::lower::pft::Variable &var, mlir::Value boxAddr, mlir::ValueRange nonDeferredParams, bool alwaysUseBox) { fir::MutableProperties mutableProperties = createMutableProperties( converter, loc, var, nonDeferredParams, alwaysUseBox); fir::MutableBoxValue box(boxAddr, nonDeferredParams, mutableProperties); fir::FirOpBuilder &builder = converter.getFirOpBuilder(); if (!var.isGlobal() && !Fortran::semantics::IsDummy(var.getSymbol())) fir::factory::disassociateMutableBox(builder, loc, box, /*polymorphicSetType=*/false); return box; } //===----------------------------------------------------------------------===// // MutableBoxValue reading interface implementation //===----------------------------------------------------------------------===// bool Fortran::lower::isArraySectionWithoutVectorSubscript( const Fortran::lower::SomeExpr &expr) { return expr.Rank() > 0 && Fortran::evaluate::IsVariable(expr) && !Fortran::evaluate::UnwrapWholeSymbolDataRef(expr) && !Fortran::evaluate::HasVectorSubscript(expr); } void Fortran::lower::associateMutableBox( Fortran::lower::AbstractConverter &converter, mlir::Location loc, const fir::MutableBoxValue &box, const Fortran::lower::SomeExpr &source, mlir::ValueRange lbounds, Fortran::lower::StatementContext &stmtCtx) { fir::FirOpBuilder &builder = converter.getFirOpBuilder(); if (Fortran::evaluate::UnwrapExpr(source)) { fir::factory::disassociateMutableBox(builder, loc, box); return; } if (converter.getLoweringOptions().getLowerToHighLevelFIR()) { fir::ExtendedValue rhs = converter.genExprAddr(loc, source, stmtCtx); fir::factory::associateMutableBox(builder, loc, box, rhs, lbounds); return; } // The right hand side is not be evaluated into a temp. Array sections can // typically be represented as a value of type `!fir.box`. However, an // expression that uses vector subscripts cannot be emboxed. In that case, // generate a reference to avoid having to later use a fir.rebox to implement // the pointer association. fir::ExtendedValue rhs = isArraySectionWithoutVectorSubscript(source) ? converter.genExprBox(loc, source, stmtCtx) : converter.genExprAddr(loc, source, stmtCtx); fir::factory::associateMutableBox(builder, loc, box, rhs, lbounds); } bool Fortran::lower::isWholeAllocatable(const Fortran::lower::SomeExpr &expr) { if (const Fortran::semantics::Symbol *sym = Fortran::evaluate::UnwrapWholeSymbolOrComponentDataRef(expr)) return Fortran::semantics::IsAllocatable(*sym); return false; } bool Fortran::lower::isWholePointer(const Fortran::lower::SomeExpr &expr) { if (const Fortran::semantics::Symbol *sym = Fortran::evaluate::UnwrapWholeSymbolOrComponentDataRef(expr)) return Fortran::semantics::IsPointer(*sym); return false; } mlir::Value Fortran::lower::getAssumedCharAllocatableOrPointerLen( fir::FirOpBuilder &builder, mlir::Location loc, const Fortran::semantics::Symbol &sym, mlir::Value box) { // Read length from fir.box (explicit expr cannot safely be re-evaluated // here). auto readLength = [&]() { fir::BoxValue boxLoad = builder.create(loc, fir::getBase(box)).getResult(); return fir::factory::readCharLen(builder, loc, boxLoad); }; if (Fortran::semantics::IsOptional(sym)) { mlir::IndexType idxTy = builder.getIndexType(); // It is not safe to unconditionally read boxes of optionals in case // they are absents. According to 15.5.2.12 3 (9), it is illegal to // inquire the length of absent optional, even if non deferred, so // it's fine to use undefOp in this case. auto isPresent = builder.create(loc, builder.getI1Type(), fir::getBase(box)); mlir::Value len = builder.genIfOp(loc, {idxTy}, isPresent, true) .genThen( [&]() { builder.create(loc, readLength()); }) .genElse([&]() { auto undef = builder.create(loc, idxTy); builder.create(loc, undef.getResult()); }) .getResults()[0]; return len; } return readLength(); } mlir::Value Fortran::lower::getTypeDescAddr( AbstractConverter &converter, mlir::Location loc, const Fortran::semantics::DerivedTypeSpec &typeSpec) { mlir::Type typeDesc = Fortran::lower::translateDerivedTypeToFIRType(converter, typeSpec); fir::FirOpBuilder &builder = converter.getFirOpBuilder(); return builder.create(loc, mlir::TypeAttr::get(typeDesc)); }