//===-- ConvertExpr.cpp ---------------------------------------------------===// // // 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/ConvertExpr.h" #include "flang/Evaluate/fold.h" #include "flang/Evaluate/traverse.h" #include "flang/Lower/AbstractConverter.h" #include "flang/Lower/CallInterface.h" #include "flang/Lower/ConvertType.h" #include "flang/Lower/ConvertVariable.h" #include "flang/Lower/IntrinsicCall.h" #include "flang/Lower/StatementContext.h" #include "flang/Lower/SymbolMap.h" #include "flang/Lower/Todo.h" #include "flang/Optimizer/Builder/Complex.h" #include "flang/Optimizer/Dialect/FIROpsSupport.h" #include "flang/Semantics/expression.h" #include "flang/Semantics/symbol.h" #include "flang/Semantics/tools.h" #include "flang/Semantics/type.h" #include "mlir/Dialect/StandardOps/IR/Ops.h" #include "llvm/Support/Debug.h" #define DEBUG_TYPE "flang-lower-expr" //===----------------------------------------------------------------------===// // The composition and structure of Fortran::evaluate::Expr is defined in // the various header files in include/flang/Evaluate. You are referred // there for more information on these data structures. Generally speaking, // these data structures are a strongly typed family of abstract data types // that, composed as trees, describe the syntax of Fortran expressions. // // This part of the bridge can traverse these tree structures and lower them // to the correct FIR representation in SSA form. //===----------------------------------------------------------------------===// /// Place \p exv in memory if it is not already a memory reference. If /// \p forceValueType is provided, the value is first casted to the provided /// type before being stored (this is mainly intended for logicals whose value /// may be `i1` but needed to be stored as Fortran logicals). static fir::ExtendedValue placeScalarValueInMemory(fir::FirOpBuilder &builder, mlir::Location loc, const fir::ExtendedValue &exv, mlir::Type storageType) { mlir::Value valBase = fir::getBase(exv); if (fir::conformsWithPassByRef(valBase.getType())) return exv; assert(!fir::hasDynamicSize(storageType) && "only expect statically sized scalars to be by value"); // Since `a` is not itself a valid referent, determine its value and // create a temporary location at the beginning of the function for // referencing. mlir::Value val = builder.createConvert(loc, storageType, valBase); mlir::Value temp = builder.createTemporary( loc, storageType, llvm::ArrayRef{ Fortran::lower::getAdaptToByRefAttr(builder)}); builder.create(loc, val, temp); return fir::substBase(exv, temp); } /// Is this a variable wrapped in parentheses? template static bool isParenthesizedVariable(const A &) { return false; } template static bool isParenthesizedVariable(const Fortran::evaluate::Expr &expr) { using ExprVariant = decltype(Fortran::evaluate::Expr::u); using Parentheses = Fortran::evaluate::Parentheses; if constexpr (Fortran::common::HasMember) { if (const auto *parentheses = std::get_if(&expr.u)) return Fortran::evaluate::IsVariable(parentheses->left()); return false; } else { return std::visit([&](const auto &x) { return isParenthesizedVariable(x); }, expr.u); } } /// Generate a load of a value from an address. Beware that this will lose /// any dynamic type information for polymorphic entities (note that unlimited /// polymorphic cannot be loaded and must not be provided here). static fir::ExtendedValue genLoad(fir::FirOpBuilder &builder, mlir::Location loc, const fir::ExtendedValue &addr) { return addr.match( [](const fir::CharBoxValue &box) -> fir::ExtendedValue { return box; }, [&](const fir::UnboxedValue &v) -> fir::ExtendedValue { if (fir::unwrapRefType(fir::getBase(v).getType()) .isa()) return v; return builder.create(loc, fir::getBase(v)); }, [&](const fir::MutableBoxValue &box) -> fir::ExtendedValue { TODO(loc, "genLoad for MutableBoxValue"); }, [&](const fir::BoxValue &box) -> fir::ExtendedValue { TODO(loc, "genLoad for BoxValue"); }, [&](const auto &) -> fir::ExtendedValue { fir::emitFatalError( loc, "attempting to load whole array or procedure address"); }); } /// Is this a call to an elemental procedure with at least one array argument? static bool isElementalProcWithArrayArgs(const Fortran::evaluate::ProcedureRef &procRef) { if (procRef.IsElemental()) for (const std::optional &arg : procRef.arguments()) if (arg && arg->Rank() != 0) return true; return false; } /// If \p arg is the address of a function with a denoted host-association tuple /// argument, then return the host-associations tuple value of the current /// procedure. Otherwise, return nullptr. static mlir::Value argumentHostAssocs(Fortran::lower::AbstractConverter &converter, mlir::Value arg) { if (auto addr = mlir::dyn_cast_or_null(arg.getDefiningOp())) { auto &builder = converter.getFirOpBuilder(); if (auto funcOp = builder.getNamedFunction(addr.getSymbol())) if (fir::anyFuncArgsHaveAttr(funcOp, fir::getHostAssocAttrName())) return converter.hostAssocTupleValue(); } return {}; } namespace { /// Lowering of Fortran::evaluate::Expr expressions class ScalarExprLowering { public: using ExtValue = fir::ExtendedValue; explicit ScalarExprLowering(mlir::Location loc, Fortran::lower::AbstractConverter &converter, Fortran::lower::SymMap &symMap, Fortran::lower::StatementContext &stmtCtx) : location{loc}, converter{converter}, builder{converter.getFirOpBuilder()}, stmtCtx{stmtCtx}, symMap{symMap} { } ExtValue genExtAddr(const Fortran::lower::SomeExpr &expr) { return gen(expr); } /// Lower `expr` to be passed as a fir.box argument. Do not create a temp /// for the expr if it is a variable that can be described as a fir.box. ExtValue genBoxArg(const Fortran::lower::SomeExpr &expr) { bool saveUseBoxArg = useBoxArg; useBoxArg = true; ExtValue result = gen(expr); useBoxArg = saveUseBoxArg; return result; } ExtValue genExtValue(const Fortran::lower::SomeExpr &expr) { return genval(expr); } mlir::Location getLoc() { return location; } template mlir::Value genunbox(const A &expr) { ExtValue e = genval(expr); if (const fir::UnboxedValue *r = e.getUnboxed()) return *r; fir::emitFatalError(getLoc(), "unboxed expression expected"); } /// Generate an integral constant of `value` template mlir::Value genIntegerConstant(mlir::MLIRContext *context, std::int64_t value) { mlir::Type type = converter.genType(Fortran::common::TypeCategory::Integer, KIND); return builder.createIntegerConstant(getLoc(), type, value); } /// Generate a logical/boolean constant of `value` mlir::Value genBoolConstant(bool value) { return builder.createBool(getLoc(), value); } /// Generate a real constant with a value `value`. template mlir::Value genRealConstant(mlir::MLIRContext *context, const llvm::APFloat &value) { mlir::Type fltTy = Fortran::lower::convertReal(context, KIND); return builder.createRealConstant(getLoc(), fltTy, value); } /// Returns a reference to a symbol or its box/boxChar descriptor if it has /// one. ExtValue gen(Fortran::semantics::SymbolRef sym) { if (Fortran::lower::SymbolBox val = symMap.lookupSymbol(sym)) return val.match([&val](auto &) { return val.toExtendedValue(); }); LLVM_DEBUG(llvm::dbgs() << "unknown symbol: " << sym << "\nmap: " << symMap << '\n'); fir::emitFatalError(getLoc(), "symbol is not mapped to any IR value"); } ExtValue genLoad(const ExtValue &exv) { return ::genLoad(builder, getLoc(), exv); } ExtValue genval(Fortran::semantics::SymbolRef sym) { ExtValue var = gen(sym); if (const fir::UnboxedValue *s = var.getUnboxed()) if (fir::isReferenceLike(s->getType())) return genLoad(*s); return var; } ExtValue genval(const Fortran::evaluate::BOZLiteralConstant &) { TODO(getLoc(), "genval BOZ"); } /// Return indirection to function designated in ProcedureDesignator. /// The type of the function indirection is not guaranteed to match the one /// of the ProcedureDesignator due to Fortran implicit typing rules. ExtValue genval(const Fortran::evaluate::ProcedureDesignator &proc) { TODO(getLoc(), "genval ProcedureDesignator"); } ExtValue genval(const Fortran::evaluate::NullPointer &) { TODO(getLoc(), "genval NullPointer"); } ExtValue genval(const Fortran::evaluate::StructureConstructor &ctor) { TODO(getLoc(), "genval StructureConstructor"); } /// Lowering of an ac-do-variable, which is not a Symbol. ExtValue genval(const Fortran::evaluate::ImpliedDoIndex &var) { TODO(getLoc(), "genval ImpliedDoIndex"); } ExtValue genval(const Fortran::evaluate::DescriptorInquiry &desc) { TODO(getLoc(), "genval DescriptorInquiry"); } ExtValue genval(const Fortran::evaluate::TypeParamInquiry &) { TODO(getLoc(), "genval TypeParamInquiry"); } template ExtValue genval(const Fortran::evaluate::ComplexComponent &part) { TODO(getLoc(), "genval ComplexComponent"); } template ExtValue genval(const Fortran::evaluate::Negate> &op) { mlir::Value input = genunbox(op.left()); // Like LLVM, integer negation is the binary op "0 - value" mlir::Value zero = genIntegerConstant(builder.getContext(), 0); return builder.create(getLoc(), zero, input); } template ExtValue genval(const Fortran::evaluate::Negate> &op) { return builder.create(getLoc(), genunbox(op.left())); } template ExtValue genval(const Fortran::evaluate::Negate> &op) { return builder.create(getLoc(), genunbox(op.left())); } template mlir::Value createBinaryOp(const ExtValue &left, const ExtValue &right) { assert(fir::isUnboxedValue(left) && fir::isUnboxedValue(right)); mlir::Value lhs = fir::getBase(left); mlir::Value rhs = fir::getBase(right); assert(lhs.getType() == rhs.getType() && "types must be the same"); return builder.create(getLoc(), lhs, rhs); } template mlir::Value createBinaryOp(const A &ex) { ExtValue left = genval(ex.left()); return createBinaryOp(left, genval(ex.right())); } #undef GENBIN #define GENBIN(GenBinEvOp, GenBinTyCat, GenBinFirOp) \ template \ ExtValue genval(const Fortran::evaluate::GenBinEvOp> &x) { \ return createBinaryOp(x); \ } GENBIN(Add, Integer, mlir::arith::AddIOp) GENBIN(Add, Real, mlir::arith::AddFOp) GENBIN(Add, Complex, fir::AddcOp) GENBIN(Subtract, Integer, mlir::arith::SubIOp) GENBIN(Subtract, Real, mlir::arith::SubFOp) GENBIN(Subtract, Complex, fir::SubcOp) GENBIN(Multiply, Integer, mlir::arith::MulIOp) GENBIN(Multiply, Real, mlir::arith::MulFOp) GENBIN(Multiply, Complex, fir::MulcOp) GENBIN(Divide, Integer, mlir::arith::DivSIOp) GENBIN(Divide, Real, mlir::arith::DivFOp) GENBIN(Divide, Complex, fir::DivcOp) template ExtValue genval( const Fortran::evaluate::Power> &op) { TODO(getLoc(), "genval Power"); } template ExtValue genval( const Fortran::evaluate::RealToIntPower> &op) { TODO(getLoc(), "genval RealToInt"); } template ExtValue genval(const Fortran::evaluate::ComplexConstructor &op) { mlir::Value realPartValue = genunbox(op.left()); return fir::factory::Complex{builder, getLoc()}.createComplex( KIND, realPartValue, genunbox(op.right())); } template ExtValue genval(const Fortran::evaluate::Concat &op) { TODO(getLoc(), "genval Concat"); } /// MIN and MAX operations template ExtValue genval(const Fortran::evaluate::Extremum> &op) { TODO(getLoc(), "genval Extremum"); } template ExtValue genval(const Fortran::evaluate::SetLength &x) { TODO(getLoc(), "genval SetLength"); } template ExtValue genval(const Fortran::evaluate::Relational> &op) { TODO(getLoc(), "genval integer comparison"); } template ExtValue genval(const Fortran::evaluate::Relational> &op) { TODO(getLoc(), "genval real comparison"); } template ExtValue genval(const Fortran::evaluate::Relational> &op) { TODO(getLoc(), "genval complex comparison"); } template ExtValue genval(const Fortran::evaluate::Relational> &op) { TODO(getLoc(), "genval char comparison"); } ExtValue genval(const Fortran::evaluate::Relational &op) { TODO(getLoc(), "genval comparison"); } template ExtValue genval(const Fortran::evaluate::Convert, TC2> &convert) { mlir::Type ty = converter.genType(TC1, KIND); mlir::Value operand = genunbox(convert.left()); return builder.convertWithSemantics(getLoc(), ty, operand); } template ExtValue genval(const Fortran::evaluate::Parentheses &op) { TODO(getLoc(), "genval parentheses"); } template ExtValue genval(const Fortran::evaluate::Not &op) { TODO(getLoc(), "genval Not"); } template ExtValue genval(const Fortran::evaluate::LogicalOperation &op) { TODO(getLoc(), "genval LogicalOperation"); } /// Convert a scalar literal constant to IR. template ExtValue genScalarLit( const Fortran::evaluate::Scalar> &value) { if constexpr (TC == Fortran::common::TypeCategory::Integer) { return genIntegerConstant(builder.getContext(), value.ToInt64()); } else if constexpr (TC == Fortran::common::TypeCategory::Logical) { return genBoolConstant(value.IsTrue()); } else if constexpr (TC == Fortran::common::TypeCategory::Real) { std::string str = value.DumpHexadecimal(); if constexpr (KIND == 2) { llvm::APFloat floatVal{llvm::APFloatBase::IEEEhalf(), str}; return genRealConstant(builder.getContext(), floatVal); } else if constexpr (KIND == 3) { llvm::APFloat floatVal{llvm::APFloatBase::BFloat(), str}; return genRealConstant(builder.getContext(), floatVal); } else if constexpr (KIND == 4) { llvm::APFloat floatVal{llvm::APFloatBase::IEEEsingle(), str}; return genRealConstant(builder.getContext(), floatVal); } else if constexpr (KIND == 10) { llvm::APFloat floatVal{llvm::APFloatBase::x87DoubleExtended(), str}; return genRealConstant(builder.getContext(), floatVal); } else if constexpr (KIND == 16) { llvm::APFloat floatVal{llvm::APFloatBase::IEEEquad(), str}; return genRealConstant(builder.getContext(), floatVal); } else { // convert everything else to double llvm::APFloat floatVal{llvm::APFloatBase::IEEEdouble(), str}; return genRealConstant(builder.getContext(), floatVal); } } else if constexpr (TC == Fortran::common::TypeCategory::Complex) { using TR = Fortran::evaluate::Type; Fortran::evaluate::ComplexConstructor ctor( Fortran::evaluate::Expr{ Fortran::evaluate::Constant{value.REAL()}}, Fortran::evaluate::Expr{ Fortran::evaluate::Constant{value.AIMAG()}}); return genunbox(ctor); } else /*constexpr*/ { llvm_unreachable("unhandled constant"); } } /// Convert a ascii scalar literal CHARACTER to IR. (specialization) ExtValue genAsciiScalarLit(const Fortran::evaluate::Scalar> &value, int64_t len) { assert(value.size() == static_cast(len) && "value.size() doesn't match with len"); return fir::factory::createStringLiteral(builder, getLoc(), value); } template ExtValue genval(const Fortran::evaluate::Constant> &con) { if (con.Rank() > 0) TODO(getLoc(), "genval array constant"); std::optional>> opt = con.GetScalarValue(); assert(opt.has_value() && "constant has no value"); if constexpr (TC == Fortran::common::TypeCategory::Character) { if constexpr (KIND == 1) return genAsciiScalarLit(opt.value(), con.LEN()); TODO(getLoc(), "genval for Character with KIND != 1"); } else { return genScalarLit(opt.value()); } } fir::ExtendedValue genval( const Fortran::evaluate::Constant &con) { TODO(getLoc(), "genval constant derived"); } template ExtValue genval(const Fortran::evaluate::ArrayConstructor &) { TODO(getLoc(), "genval ArrayConstructor"); } ExtValue gen(const Fortran::evaluate::ComplexPart &x) { TODO(getLoc(), "gen ComplexPart"); } ExtValue genval(const Fortran::evaluate::ComplexPart &x) { TODO(getLoc(), "genval ComplexPart"); } ExtValue gen(const Fortran::evaluate::Substring &s) { TODO(getLoc(), "gen Substring"); } ExtValue genval(const Fortran::evaluate::Substring &ss) { TODO(getLoc(), "genval Substring"); } ExtValue genval(const Fortran::evaluate::Subscript &subs) { TODO(getLoc(), "genval Subscript"); } ExtValue gen(const Fortran::evaluate::DataRef &dref) { TODO(getLoc(), "gen DataRef"); } ExtValue genval(const Fortran::evaluate::DataRef &dref) { TODO(getLoc(), "genval DataRef"); } ExtValue gen(const Fortran::evaluate::Component &cmpt) { TODO(getLoc(), "gen Component"); } ExtValue genval(const Fortran::evaluate::Component &cmpt) { TODO(getLoc(), "genval Component"); } ExtValue genval(const Fortran::semantics::Bound &bound) { TODO(getLoc(), "genval Bound"); } ExtValue gen(const Fortran::evaluate::ArrayRef &aref) { TODO(getLoc(), "gen ArrayRef"); } ExtValue genval(const Fortran::evaluate::ArrayRef &aref) { TODO(getLoc(), "genval ArrayRef"); } ExtValue gen(const Fortran::evaluate::CoarrayRef &coref) { TODO(getLoc(), "gen CoarrayRef"); } ExtValue genval(const Fortran::evaluate::CoarrayRef &coref) { TODO(getLoc(), "genval CoarrayRef"); } template ExtValue gen(const Fortran::evaluate::Designator &des) { return std::visit([&](const auto &x) { return gen(x); }, des.u); } template ExtValue genval(const Fortran::evaluate::Designator &des) { return std::visit([&](const auto &x) { return genval(x); }, des.u); } mlir::Type genType(const Fortran::evaluate::DynamicType &dt) { if (dt.category() != Fortran::common::TypeCategory::Derived) return converter.genType(dt.category(), dt.kind()); TODO(getLoc(), "genType Derived Type"); } /// Lower a function reference template ExtValue genFunctionRef(const Fortran::evaluate::FunctionRef &funcRef) { if (!funcRef.GetType().has_value()) fir::emitFatalError(getLoc(), "internal: a function must have a type"); mlir::Type resTy = genType(*funcRef.GetType()); return genProcedureRef(funcRef, {resTy}); } /// Lower function call `funcRef` and return a reference to the resultant /// value. This is required for lowering expressions such as `f1(f2(v))`. template ExtValue gen(const Fortran::evaluate::FunctionRef &funcRef) { TODO(getLoc(), "gen FunctionRef"); } /// helper to detect statement functions static bool isStatementFunctionCall(const Fortran::evaluate::ProcedureRef &procRef) { if (const Fortran::semantics::Symbol *symbol = procRef.proc().GetSymbol()) if (const auto *details = symbol->detailsIf()) return details->stmtFunction().has_value(); return false; } /// Helper to package a Value and its properties into an ExtendedValue. static ExtValue toExtendedValue(mlir::Location loc, mlir::Value base, llvm::ArrayRef extents, llvm::ArrayRef lengths) { mlir::Type type = base.getType(); if (type.isa()) return fir::BoxValue(base, /*lbounds=*/{}, lengths, extents); type = fir::unwrapRefType(type); if (type.isa()) return fir::MutableBoxValue(base, lengths, /*mutableProperties*/ {}); if (auto seqTy = type.dyn_cast()) { if (seqTy.getDimension() != extents.size()) fir::emitFatalError(loc, "incorrect number of extents for array"); if (seqTy.getEleTy().isa()) { if (lengths.empty()) fir::emitFatalError(loc, "missing length for character"); assert(lengths.size() == 1); return fir::CharArrayBoxValue(base, lengths[0], extents); } return fir::ArrayBoxValue(base, extents); } if (type.isa()) { if (lengths.empty()) fir::emitFatalError(loc, "missing length for character"); assert(lengths.size() == 1); return fir::CharBoxValue(base, lengths[0]); } return base; } // Find the argument that corresponds to the host associations. // Verify some assumptions about how the signature was built here. [[maybe_unused]] static unsigned findHostAssocTuplePos(mlir::FuncOp fn) { // Scan the argument list from last to first as the host associations are // appended for now. for (unsigned i = fn.getNumArguments(); i > 0; --i) if (fn.getArgAttr(i - 1, fir::getHostAssocAttrName())) { // Host assoc tuple must be last argument (for now). assert(i == fn.getNumArguments() && "tuple must be last"); return i - 1; } llvm_unreachable("anyFuncArgsHaveAttr failed"); } /// Lower a non-elemental procedure reference and read allocatable and pointer /// results into normal values. ExtValue genProcedureRef(const Fortran::evaluate::ProcedureRef &procRef, llvm::Optional resultType) { ExtValue res = genRawProcedureRef(procRef, resultType); return res; } /// Given a call site for which the arguments were already lowered, generate /// the call and return the result. This function deals with explicit result /// allocation and lowering if needed. It also deals with passing the host /// link to internal procedures. ExtValue genCallOpAndResult(Fortran::lower::CallerInterface &caller, mlir::FunctionType callSiteType, llvm::Optional resultType) { mlir::Location loc = getLoc(); using PassBy = Fortran::lower::CallerInterface::PassEntityBy; // Handle cases where caller must allocate the result or a fir.box for it. bool mustPopSymMap = false; if (caller.mustMapInterfaceSymbols()) { symMap.pushScope(); mustPopSymMap = true; Fortran::lower::mapCallInterfaceSymbols(converter, caller, symMap); } // If this is an indirect call, retrieve the function address. Also retrieve // the result length if this is a character function (note that this length // will be used only if there is no explicit length in the local interface). mlir::Value funcPointer; mlir::Value charFuncPointerLength; if (caller.getIfIndirectCallSymbol()) { TODO(loc, "genCallOpAndResult indirect call"); } mlir::IndexType idxTy = builder.getIndexType(); auto lowerSpecExpr = [&](const auto &expr) -> mlir::Value { return builder.createConvert( loc, idxTy, fir::getBase(converter.genExprValue(expr, stmtCtx))); }; llvm::SmallVector resultLengths; auto allocatedResult = [&]() -> llvm::Optional { llvm::SmallVector extents; llvm::SmallVector lengths; if (!caller.callerAllocateResult()) return {}; mlir::Type type = caller.getResultStorageType(); if (type.isa()) caller.walkResultExtents([&](const Fortran::lower::SomeExpr &e) { extents.emplace_back(lowerSpecExpr(e)); }); caller.walkResultLengths([&](const Fortran::lower::SomeExpr &e) { lengths.emplace_back(lowerSpecExpr(e)); }); // Result length parameters should not be provided to box storage // allocation and save_results, but they are still useful information to // keep in the ExtendedValue if non-deferred. if (!type.isa()) { if (fir::isa_char(fir::unwrapSequenceType(type)) && lengths.empty()) { // Calling an assumed length function. This is only possible if this // is a call to a character dummy procedure. if (!charFuncPointerLength) fir::emitFatalError(loc, "failed to retrieve character function " "length while calling it"); lengths.push_back(charFuncPointerLength); } resultLengths = lengths; } if (!extents.empty() || !lengths.empty()) { TODO(loc, "genCallOpResult extents and length"); } mlir::Value temp = builder.createTemporary(loc, type, ".result", extents, resultLengths); return toExtendedValue(loc, temp, extents, lengths); }(); if (mustPopSymMap) symMap.popScope(); // Place allocated result or prepare the fir.save_result arguments. mlir::Value arrayResultShape; if (allocatedResult) { if (std::optional::PassedEntity> resultArg = caller.getPassedResult()) { if (resultArg->passBy == PassBy::AddressAndLength) caller.placeAddressAndLengthInput(*resultArg, fir::getBase(*allocatedResult), fir::getLen(*allocatedResult)); else if (resultArg->passBy == PassBy::BaseAddress) caller.placeInput(*resultArg, fir::getBase(*allocatedResult)); else fir::emitFatalError( loc, "only expect character scalar result to be passed by ref"); } else { assert(caller.mustSaveResult()); arrayResultShape = allocatedResult->match( [&](const fir::CharArrayBoxValue &) { return builder.createShape(loc, *allocatedResult); }, [&](const fir::ArrayBoxValue &) { return builder.createShape(loc, *allocatedResult); }, [&](const auto &) { return mlir::Value{}; }); } } // In older Fortran, procedure argument types are inferred. This may lead // different view of what the function signature is in different locations. // Casts are inserted as needed below to accommodate this. // The mlir::FuncOp type prevails, unless it has a different number of // arguments which can happen in legal program if it was passed as a dummy // procedure argument earlier with no further type information. mlir::SymbolRefAttr funcSymbolAttr; bool addHostAssociations = false; if (!funcPointer) { mlir::FunctionType funcOpType = caller.getFuncOp().getType(); mlir::SymbolRefAttr symbolAttr = builder.getSymbolRefAttr(caller.getMangledName()); if (callSiteType.getNumResults() == funcOpType.getNumResults() && callSiteType.getNumInputs() + 1 == funcOpType.getNumInputs() && fir::anyFuncArgsHaveAttr(caller.getFuncOp(), fir::getHostAssocAttrName())) { // The number of arguments is off by one, and we're lowering a function // with host associations. Modify call to include host associations // argument by appending the value at the end of the operands. assert(funcOpType.getInput(findHostAssocTuplePos(caller.getFuncOp())) == converter.hostAssocTupleValue().getType()); addHostAssociations = true; } if (!addHostAssociations && (callSiteType.getNumResults() != funcOpType.getNumResults() || callSiteType.getNumInputs() != funcOpType.getNumInputs())) { // Deal with argument number mismatch by making a function pointer so // that function type cast can be inserted. Do not emit a warning here // because this can happen in legal program if the function is not // defined here and it was first passed as an argument without any more // information. funcPointer = builder.create(loc, funcOpType, symbolAttr); } else if (callSiteType.getResults() != funcOpType.getResults()) { // Implicit interface result type mismatch are not standard Fortran, but // some compilers are not complaining about it. The front end is not // protecting lowering from this currently. Support this with a // discouraging warning. LLVM_DEBUG(mlir::emitWarning( loc, "a return type mismatch is not standard compliant and may " "lead to undefined behavior.")); // Cast the actual function to the current caller implicit type because // that is the behavior we would get if we could not see the definition. funcPointer = builder.create(loc, funcOpType, symbolAttr); } else { funcSymbolAttr = symbolAttr; } } mlir::FunctionType funcType = funcPointer ? callSiteType : caller.getFuncOp().getType(); llvm::SmallVector operands; // First operand of indirect call is the function pointer. Cast it to // required function type for the call to handle procedures that have a // compatible interface in Fortran, but that have different signatures in // FIR. if (funcPointer) { operands.push_back( funcPointer.getType().isa() ? builder.create(loc, funcType, funcPointer) : builder.createConvert(loc, funcType, funcPointer)); } // Deal with potential mismatches in arguments types. Passing an array to a // scalar argument should for instance be tolerated here. bool callingImplicitInterface = caller.canBeCalledViaImplicitInterface(); for (auto [fst, snd] : llvm::zip(caller.getInputs(), funcType.getInputs())) { // When passing arguments to a procedure that can be called an implicit // interface, allow character actual arguments to be passed to dummy // arguments of any type and vice versa mlir::Value cast; auto *context = builder.getContext(); if (snd.isa() && fst.getType().isa()) { auto funcTy = mlir::FunctionType::get(context, llvm::None, llvm::None); auto boxProcTy = builder.getBoxProcType(funcTy); if (mlir::Value host = argumentHostAssocs(converter, fst)) { cast = builder.create( loc, boxProcTy, llvm::ArrayRef{fst, host}); } else { cast = builder.create(loc, boxProcTy, fst); } } else { cast = builder.convertWithSemantics(loc, snd, fst, callingImplicitInterface); } operands.push_back(cast); } // Add host associations as necessary. if (addHostAssociations) operands.push_back(converter.hostAssocTupleValue()); auto call = builder.create(loc, funcType.getResults(), funcSymbolAttr, operands); if (caller.mustSaveResult()) builder.create( loc, call.getResult(0), fir::getBase(allocatedResult.getValue()), arrayResultShape, resultLengths); if (allocatedResult) { allocatedResult->match( [&](const fir::MutableBoxValue &box) { if (box.isAllocatable()) { TODO(loc, "allocatedResult for allocatable"); } }, [](const auto &) {}); return *allocatedResult; } if (!resultType.hasValue()) return mlir::Value{}; // subroutine call // For now, Fortran return values are implemented with a single MLIR // function return value. assert(call.getNumResults() == 1 && "Expected exactly one result in FUNCTION call"); return call.getResult(0); } /// Like genExtAddr, but ensure the address returned is a temporary even if \p /// expr is variable inside parentheses. ExtValue genTempExtAddr(const Fortran::lower::SomeExpr &expr) { // In general, genExtAddr might not create a temp for variable inside // parentheses to avoid creating array temporary in sub-expressions. It only // ensures the sub-expression is not re-associated with other parts of the // expression. In the call semantics, there is a difference between expr and // variable (see R1524). For expressions, a variable storage must not be // argument associated since it could be modified inside the call, or the // variable could also be modified by other means during the call. if (!isParenthesizedVariable(expr)) return genExtAddr(expr); mlir::Location loc = getLoc(); if (expr.Rank() > 0) TODO(loc, "genTempExtAddr array"); return genExtValue(expr).match( [&](const fir::CharBoxValue &boxChar) -> ExtValue { TODO(loc, "genTempExtAddr CharBoxValue"); }, [&](const fir::UnboxedValue &v) -> ExtValue { mlir::Type type = v.getType(); mlir::Value value = v; if (fir::isa_ref_type(type)) value = builder.create(loc, value); mlir::Value temp = builder.createTemporary(loc, value.getType()); builder.create(loc, value, temp); return temp; }, [&](const fir::BoxValue &x) -> ExtValue { // Derived type scalar that may be polymorphic. assert(!x.hasRank() && x.isDerived()); if (x.isDerivedWithLengthParameters()) fir::emitFatalError( loc, "making temps for derived type with length parameters"); // TODO: polymorphic aspects should be kept but for now the temp // created always has the declared type. mlir::Value var = fir::getBase(fir::factory::readBoxValue(builder, loc, x)); auto value = builder.create(loc, var); mlir::Value temp = builder.createTemporary(loc, value.getType()); builder.create(loc, value, temp); return temp; }, [&](const auto &) -> ExtValue { fir::emitFatalError(loc, "expr is not a scalar value"); }); } /// Helper structure to track potential copy-in of non contiguous variable /// argument into a contiguous temp. It is used to deallocate the temp that /// may have been created as well as to the copy-out from the temp to the /// variable after the call. struct CopyOutPair { ExtValue var; ExtValue temp; // Flag to indicate if the argument may have been modified by the // callee, in which case it must be copied-out to the variable. bool argMayBeModifiedByCall; // Optional boolean value that, if present and false, prevents // the copy-out and temp deallocation. llvm::Optional restrictCopyAndFreeAtRuntime; }; using CopyOutPairs = llvm::SmallVector; /// Helper to read any fir::BoxValue into other fir::ExtendedValue categories /// not based on fir.box. /// This will lose any non contiguous stride information and dynamic type and /// should only be called if \p exv is known to be contiguous or if its base /// address will be replaced by a contiguous one. If \p exv is not a /// fir::BoxValue, this is a no-op. ExtValue readIfBoxValue(const ExtValue &exv) { if (const auto *box = exv.getBoxOf()) return fir::factory::readBoxValue(builder, getLoc(), *box); return exv; } /// Lower a non-elemental procedure reference. ExtValue genRawProcedureRef(const Fortran::evaluate::ProcedureRef &procRef, llvm::Optional resultType) { mlir::Location loc = getLoc(); if (isElementalProcWithArrayArgs(procRef)) fir::emitFatalError(loc, "trying to lower elemental procedure with array " "arguments as normal procedure"); if (const Fortran::evaluate::SpecificIntrinsic *intrinsic = procRef.proc().GetSpecificIntrinsic()) return genIntrinsicRef(procRef, *intrinsic, resultType); if (isStatementFunctionCall(procRef)) TODO(loc, "Lower statement function call"); Fortran::lower::CallerInterface caller(procRef, converter); using PassBy = Fortran::lower::CallerInterface::PassEntityBy; llvm::SmallVector mutableModifiedByCall; // List of where temp must be copied into var after the call. CopyOutPairs copyOutPairs; mlir::FunctionType callSiteType = caller.genFunctionType(); // Lower the actual arguments and map the lowered values to the dummy // arguments. for (const Fortran::lower::CallInterface< Fortran::lower::CallerInterface>::PassedEntity &arg : caller.getPassedArguments()) { const auto *actual = arg.entity; mlir::Type argTy = callSiteType.getInput(arg.firArgument); if (!actual) { // Optional dummy argument for which there is no actual argument. caller.placeInput(arg, builder.create(loc, argTy)); continue; } const auto *expr = actual->UnwrapExpr(); if (!expr) TODO(loc, "assumed type actual argument lowering"); if (arg.passBy == PassBy::Value) { ExtValue argVal = genval(*expr); if (!fir::isUnboxedValue(argVal)) fir::emitFatalError( loc, "internal error: passing non trivial value by value"); caller.placeInput(arg, fir::getBase(argVal)); continue; } if (arg.passBy == PassBy::MutableBox) { TODO(loc, "arg passby MutableBox"); } const bool actualArgIsVariable = Fortran::evaluate::IsVariable(*expr); if (arg.passBy == PassBy::BaseAddress || arg.passBy == PassBy::BoxChar) { auto argAddr = [&]() -> ExtValue { ExtValue baseAddr; if (actualArgIsVariable && arg.isOptional()) { if (Fortran::evaluate::IsAllocatableOrPointerObject( *expr, converter.getFoldingContext())) { TODO(loc, "Allocatable or pointer argument"); } if (const Fortran::semantics::Symbol *wholeSymbol = Fortran::evaluate::UnwrapWholeSymbolOrComponentDataRef( *expr)) if (Fortran::semantics::IsOptional(*wholeSymbol)) { TODO(loc, "procedureref optional arg"); } // Fall through: The actual argument can safely be // copied-in/copied-out without any care if needed. } if (actualArgIsVariable && expr->Rank() > 0) { TODO(loc, "procedureref arrays"); } // Actual argument is a non optional/non pointer/non allocatable // scalar. if (actualArgIsVariable) return genExtAddr(*expr); // Actual argument is not a variable. Make sure a variable address is // not passed. return genTempExtAddr(*expr); }(); // Scalar and contiguous expressions may be lowered to a fir.box, // either to account for potential polymorphism, or because lowering // did not account for some contiguity hints. // Here, polymorphism does not matter (an entity of the declared type // is passed, not one of the dynamic type), and the expr is known to // be simply contiguous, so it is safe to unbox it and pass the // address without making a copy. argAddr = readIfBoxValue(argAddr); if (arg.passBy == PassBy::BaseAddress) { caller.placeInput(arg, fir::getBase(argAddr)); } else { TODO(loc, "procedureref PassBy::BoxChar"); } } else if (arg.passBy == PassBy::Box) { // Before lowering to an address, handle the allocatable/pointer actual // argument to optional fir.box dummy. It is legal to pass // unallocated/disassociated entity to an optional. In this case, an // absent fir.box must be created instead of a fir.box with a null value // (Fortran 2018 15.5.2.12 point 1). if (arg.isOptional() && Fortran::evaluate::IsAllocatableOrPointerObject( *expr, converter.getFoldingContext())) { TODO(loc, "optional allocatable or pointer argument"); } else { // Make sure a variable address is only passed if the expression is // actually a variable. mlir::Value box = actualArgIsVariable ? builder.createBox(loc, genBoxArg(*expr)) : builder.createBox(getLoc(), genTempExtAddr(*expr)); caller.placeInput(arg, box); } } else if (arg.passBy == PassBy::AddressAndLength) { ExtValue argRef = genExtAddr(*expr); caller.placeAddressAndLengthInput(arg, fir::getBase(argRef), fir::getLen(argRef)); } else if (arg.passBy == PassBy::CharProcTuple) { TODO(loc, "procedureref CharProcTuple"); } else { TODO(loc, "pass by value in non elemental function call"); } } ExtValue result = genCallOpAndResult(caller, callSiteType, resultType); // // Copy-out temps that were created for non contiguous variable arguments // if // // needed. // for (const auto ©OutPair : copyOutPairs) // genCopyOut(copyOutPair); return result; } template ExtValue genval(const Fortran::evaluate::FunctionRef &funcRef) { ExtValue result = genFunctionRef(funcRef); if (result.rank() == 0 && fir::isa_ref_type(fir::getBase(result).getType())) return genLoad(result); return result; } ExtValue genval(const Fortran::evaluate::ProcedureRef &procRef) { llvm::Optional resTy; if (procRef.hasAlternateReturns()) resTy = builder.getIndexType(); return genProcedureRef(procRef, resTy); } /// Generate a call to an intrinsic function. ExtValue genIntrinsicRef(const Fortran::evaluate::ProcedureRef &procRef, const Fortran::evaluate::SpecificIntrinsic &intrinsic, llvm::Optional resultType) { llvm::SmallVector operands; llvm::StringRef name = intrinsic.name; mlir::Location loc = getLoc(); const Fortran::lower::IntrinsicArgumentLoweringRules *argLowering = Fortran::lower::getIntrinsicArgumentLowering(name); for (const auto &[arg, dummy] : llvm::zip(procRef.arguments(), intrinsic.characteristics.value().dummyArguments)) { auto *expr = Fortran::evaluate::UnwrapExpr(arg); if (!expr) { // Absent optional. operands.emplace_back(Fortran::lower::getAbsentIntrinsicArgument()); continue; } if (!argLowering) { // No argument lowering instruction, lower by value. operands.emplace_back(genval(*expr)); continue; } // Ad-hoc argument lowering handling. Fortran::lower::ArgLoweringRule argRules = Fortran::lower::lowerIntrinsicArgumentAs(loc, *argLowering, dummy.name); switch (argRules.lowerAs) { case Fortran::lower::LowerIntrinsicArgAs::Value: operands.emplace_back(genval(*expr)); continue; case Fortran::lower::LowerIntrinsicArgAs::Addr: TODO(getLoc(), "argument lowering for Addr"); continue; case Fortran::lower::LowerIntrinsicArgAs::Box: TODO(getLoc(), "argument lowering for Box"); continue; case Fortran::lower::LowerIntrinsicArgAs::Inquired: TODO(getLoc(), "argument lowering for Inquired"); continue; } llvm_unreachable("bad switch"); } // Let the intrinsic library lower the intrinsic procedure call return Fortran::lower::genIntrinsicCall(builder, getLoc(), name, resultType, operands); } template ExtValue genval(const Fortran::evaluate::Expr &x) { if (isScalar(x)) return std::visit([&](const auto &e) { return genval(e); }, x.u); TODO(getLoc(), "genval Expr arrays"); } /// Helper to detect Transformational function reference. template bool isTransformationalRef(const T &) { return false; } template bool isTransformationalRef(const Fortran::evaluate::FunctionRef &funcRef) { return !funcRef.IsElemental() && funcRef.Rank(); } template bool isTransformationalRef(Fortran::evaluate::Expr expr) { return std::visit([&](const auto &e) { return isTransformationalRef(e); }, expr.u); } template ExtValue gen(const Fortran::evaluate::Expr &x) { // Whole array symbols or components, and results of transformational // functions already have a storage and the scalar expression lowering path // is used to not create a new temporary storage. if (isScalar(x) || Fortran::evaluate::UnwrapWholeSymbolOrComponentDataRef(x) || isTransformationalRef(x)) return std::visit([&](const auto &e) { return genref(e); }, x.u); TODO(getLoc(), "gen Expr non-scalar"); } template bool isScalar(const A &x) { return x.Rank() == 0; } template ExtValue genval(const Fortran::evaluate::Expr> &exp) { return std::visit([&](const auto &e) { return genval(e); }, exp.u); } using RefSet = std::tuple; template static constexpr bool inRefSet = Fortran::common::HasMember; template >> ExtValue genref(const A &a) { return gen(a); } template ExtValue genref(const A &a) { mlir::Type storageType = converter.genType(toEvExpr(a)); return placeScalarValueInMemory(builder, getLoc(), genval(a), storageType); } template typename T, typename B = std::decay_t>, std::enable_if_t< std::is_same_v> || std::is_same_v> || std::is_same_v>, bool> = true> ExtValue genref(const T &x) { return gen(x); } private: mlir::Location location; Fortran::lower::AbstractConverter &converter; fir::FirOpBuilder &builder; Fortran::lower::StatementContext &stmtCtx; Fortran::lower::SymMap &symMap; bool useBoxArg = false; // expression lowered as argument }; } // namespace fir::ExtendedValue Fortran::lower::createSomeExtendedExpression( mlir::Location loc, Fortran::lower::AbstractConverter &converter, const Fortran::lower::SomeExpr &expr, Fortran::lower::SymMap &symMap, Fortran::lower::StatementContext &stmtCtx) { LLVM_DEBUG(expr.AsFortran(llvm::dbgs() << "expr: ") << '\n'); return ScalarExprLowering{loc, converter, symMap, stmtCtx}.genval(expr); } fir::ExtendedValue Fortran::lower::createSomeExtendedAddress( mlir::Location loc, Fortran::lower::AbstractConverter &converter, const Fortran::lower::SomeExpr &expr, Fortran::lower::SymMap &symMap, Fortran::lower::StatementContext &stmtCtx) { LLVM_DEBUG(expr.AsFortran(llvm::dbgs() << "address: ") << '\n'); return ScalarExprLowering{loc, converter, symMap, stmtCtx}.gen(expr); } mlir::Value Fortran::lower::createSubroutineCall( AbstractConverter &converter, const evaluate::ProcedureRef &call, SymMap &symMap, StatementContext &stmtCtx) { mlir::Location loc = converter.getCurrentLocation(); // Simple subroutine call, with potential alternate return. auto res = Fortran::lower::createSomeExtendedExpression( loc, converter, toEvExpr(call), symMap, stmtCtx); return fir::getBase(res); }