//===-- ConvertConstant.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/ConvertConstant.h" #include "flang/Evaluate/expression.h" #include "flang/Lower/ConvertType.h" #include "flang/Lower/Mangler.h" #include "flang/Optimizer/Builder/Complex.h" #include "flang/Optimizer/Builder/Todo.h" /// Convert string, \p s, to an APFloat value. Recognize and handle Inf and /// NaN strings as well. \p s is assumed to not contain any spaces. static llvm::APFloat consAPFloat(const llvm::fltSemantics &fsem, llvm::StringRef s) { assert(!s.contains(' ')); if (s.compare_insensitive("-inf") == 0) return llvm::APFloat::getInf(fsem, /*negative=*/true); if (s.compare_insensitive("inf") == 0 || s.compare_insensitive("+inf") == 0) return llvm::APFloat::getInf(fsem); // TODO: Add support for quiet and signaling NaNs. if (s.compare_insensitive("-nan") == 0) return llvm::APFloat::getNaN(fsem, /*negative=*/true); if (s.compare_insensitive("nan") == 0 || s.compare_insensitive("+nan") == 0) return llvm::APFloat::getNaN(fsem); return {fsem, s}; } //===----------------------------------------------------------------------===// // Fortran::lower::tryCreatingDenseGlobal implementation //===----------------------------------------------------------------------===// /// Generate an mlir attribute from a literal value template static mlir::Attribute convertToAttribute( fir::FirOpBuilder &builder, const Fortran::evaluate::Scalar> &value, mlir::Type type) { if constexpr (TC == Fortran::common::TypeCategory::Integer) { return builder.getIntegerAttr(type, value.ToInt64()); } else if constexpr (TC == Fortran::common::TypeCategory::Logical) { return builder.getIntegerAttr(type, value.IsTrue()); } else { static_assert(TC == Fortran::common::TypeCategory::Real, "type values cannot be converted to attributes"); std::string str = value.DumpHexadecimal(); auto floatVal = consAPFloat(builder.getKindMap().getFloatSemantics(KIND), str); return builder.getFloatAttr(type, floatVal); } return {}; } namespace { /// Helper class to lower an array constant to a global with an MLIR dense /// attribute. /// /// If we have a rank-1 array of integer, real, or logical, then we can /// create a global array with the dense attribute. /// /// The mlir tensor type can only handle integer, real, or logical. It /// does not currently support nested structures which is required for /// complex. /// /// Also, we currently handle just rank-1 since tensor type assumes /// row major array ordering. We will need to reorder the dimensions /// in the tensor type to support Fortran's column major array ordering. /// How to create this tensor type is to be determined. class DenseGlobalBuilder { public: static fir::GlobalOp tryCreating(fir::FirOpBuilder &builder, mlir::Location loc, mlir::Type symTy, llvm::StringRef globalName, mlir::StringAttr linkage, bool isConst, const Fortran::lower::SomeExpr &initExpr) { DenseGlobalBuilder globalBuilder; std::visit( Fortran::common::visitors{ [&](const Fortran::evaluate::Expr & x) { globalBuilder.tryConvertingToAttributes(builder, x); }, [&](const Fortran::evaluate::Expr & x) { globalBuilder.tryConvertingToAttributes(builder, x); }, [&](const Fortran::evaluate::Expr &x) { globalBuilder.tryConvertingToAttributes(builder, x); }, [](const auto &) {}, }, initExpr.u); return globalBuilder.tryCreatingGlobal(builder, loc, symTy, globalName, linkage, isConst); } template static fir::GlobalOp tryCreating( fir::FirOpBuilder &builder, mlir::Location loc, mlir::Type symTy, llvm::StringRef globalName, mlir::StringAttr linkage, bool isConst, const Fortran::evaluate::Constant> &constant) { DenseGlobalBuilder globalBuilder; globalBuilder.tryConvertingToAttributes(builder, constant); return globalBuilder.tryCreatingGlobal(builder, loc, symTy, globalName, linkage, isConst); } private: DenseGlobalBuilder() = default; /// Try converting an evaluate::Constant to a list of MLIR attributes. template void tryConvertingToAttributes( fir::FirOpBuilder &builder, const Fortran::evaluate::Constant> &constant) { static_assert(TC != Fortran::common::TypeCategory::Character, "must be numerical or logical"); if (constant.Rank() != 1) return; auto attrTc = TC == Fortran::common::TypeCategory::Logical ? Fortran::common::TypeCategory::Integer : TC; attributeElementType = Fortran::lower::getFIRType( builder.getContext(), attrTc, KIND, std::nullopt); for (auto element : constant.values()) attributes.push_back( convertToAttribute(builder, element, attributeElementType)); } /// Try converting an evaluate::Expr to a list of MLIR attributes. template void tryConvertingToAttributes(fir::FirOpBuilder &builder, const Fortran::evaluate::Expr &expr) { std::visit( [&](const auto &x) { using TR = Fortran::evaluate::ResultType; if (const auto *constant = std::get_if>(&x.u)) tryConvertingToAttributes(builder, *constant); }, expr.u); } /// Create a fir::Global if MLIR attributes have been successfully created by /// tryConvertingToAttributes. fir::GlobalOp tryCreatingGlobal(fir::FirOpBuilder &builder, mlir::Location loc, mlir::Type symTy, llvm::StringRef globalName, mlir::StringAttr linkage, bool isConst) const { // Not a rank 1 "trivial" intrinsic constant array, or empty array. if (!attributeElementType || attributes.empty()) return {}; auto tensorTy = mlir::RankedTensorType::get(attributes.size(), attributeElementType); auto init = mlir::DenseElementsAttr::get(tensorTy, attributes); return builder.createGlobal(loc, symTy, globalName, linkage, init, isConst); } llvm::SmallVector attributes; mlir::Type attributeElementType; }; } // namespace fir::GlobalOp Fortran::lower::tryCreatingDenseGlobal( fir::FirOpBuilder &builder, mlir::Location loc, mlir::Type symTy, llvm::StringRef globalName, mlir::StringAttr linkage, bool isConst, const Fortran::lower::SomeExpr &initExpr) { return DenseGlobalBuilder::tryCreating(builder, loc, symTy, globalName, linkage, isConst, initExpr); } //===----------------------------------------------------------------------===// // Fortran::lower::IntrinsicConstantBuilder::gen // Lower an array constant to a fir::ExtendedValue. //===----------------------------------------------------------------------===// /// Generate a real constant with a value `value`. template static mlir::Value genRealConstant(fir::FirOpBuilder &builder, mlir::Location loc, const llvm::APFloat &value) { mlir::Type fltTy = Fortran::lower::convertReal(builder.getContext(), KIND); return builder.createRealConstant(loc, fltTy, value); } /// Convert a scalar literal constant to IR. template static mlir::Value genScalarLit( fir::FirOpBuilder &builder, mlir::Location loc, const Fortran::evaluate::Scalar> &value) { if constexpr (TC == Fortran::common::TypeCategory::Integer) { mlir::Type ty = Fortran::lower::getFIRType(builder.getContext(), TC, KIND, std::nullopt); if (KIND == 16) { auto bigInt = llvm::APInt(ty.getIntOrFloatBitWidth(), value.SignedDecimal(), 10); return builder.create( loc, ty, mlir::IntegerAttr::get(ty, bigInt)); } return builder.createIntegerConstant(loc, ty, value.ToInt64()); } else if constexpr (TC == Fortran::common::TypeCategory::Logical) { return builder.createBool(loc, value.IsTrue()); } else if constexpr (TC == Fortran::common::TypeCategory::Real) { std::string str = value.DumpHexadecimal(); if constexpr (KIND == 2) { auto floatVal = consAPFloat(llvm::APFloatBase::IEEEhalf(), str); return genRealConstant(builder, loc, floatVal); } else if constexpr (KIND == 3) { auto floatVal = consAPFloat(llvm::APFloatBase::BFloat(), str); return genRealConstant(builder, loc, floatVal); } else if constexpr (KIND == 4) { auto floatVal = consAPFloat(llvm::APFloatBase::IEEEsingle(), str); return genRealConstant(builder, loc, floatVal); } else if constexpr (KIND == 10) { auto floatVal = consAPFloat(llvm::APFloatBase::x87DoubleExtended(), str); return genRealConstant(builder, loc, floatVal); } else if constexpr (KIND == 16) { auto floatVal = consAPFloat(llvm::APFloatBase::IEEEquad(), str); return genRealConstant(builder, loc, floatVal); } else { // convert everything else to double auto floatVal = consAPFloat(llvm::APFloatBase::IEEEdouble(), str); return genRealConstant(builder, loc, floatVal); } } else if constexpr (TC == Fortran::common::TypeCategory::Complex) { mlir::Value realPart = genScalarLit(builder, loc, value.REAL()); mlir::Value imagPart = genScalarLit(builder, loc, value.AIMAG()); return fir::factory::Complex{builder, loc}.createComplex(KIND, realPart, imagPart); } else /*constexpr*/ { llvm_unreachable("unhandled constant"); } } /// Create fir::string_lit from a scalar character constant. template static fir::StringLitOp createStringLitOp(fir::FirOpBuilder &builder, mlir::Location loc, const Fortran::evaluate::Scalar> &value, [[maybe_unused]] int64_t len) { if constexpr (KIND == 1) { assert(value.size() == static_cast(len)); return builder.createStringLitOp(loc, value); } else { using ET = typename std::decay_t::value_type; fir::CharacterType type = fir::CharacterType::get(builder.getContext(), KIND, len); mlir::MLIRContext *context = builder.getContext(); std::int64_t size = static_cast(value.size()); mlir::ShapedType shape = mlir::RankedTensorType::get( llvm::ArrayRef{size}, mlir::IntegerType::get(builder.getContext(), sizeof(ET) * 8)); auto denseAttr = mlir::DenseElementsAttr::get( shape, llvm::ArrayRef{value.data(), value.size()}); auto denseTag = mlir::StringAttr::get(context, fir::StringLitOp::xlist()); mlir::NamedAttribute dataAttr(denseTag, denseAttr); auto sizeTag = mlir::StringAttr::get(context, fir::StringLitOp::size()); mlir::NamedAttribute sizeAttr(sizeTag, builder.getI64IntegerAttr(len)); llvm::SmallVector attrs = {dataAttr, sizeAttr}; return builder.create( loc, llvm::ArrayRef{type}, std::nullopt, attrs); } } /// Convert a scalar literal CHARACTER to IR. template static mlir::Value genScalarLit(fir::FirOpBuilder &builder, mlir::Location loc, const Fortran::evaluate::Scalar> &value, int64_t len, bool outlineInReadOnlyMemory) { // When in an initializer context, construct the literal op itself and do // not construct another constant object in rodata. if (!outlineInReadOnlyMemory) return createStringLitOp(builder, loc, value, len); // Otherwise, the string is in a plain old expression so "outline" the value // in read only data by hash consing it to a constant literal object. // ASCII global constants are created using an mlir string attribute. if constexpr (KIND == 1) { return fir::getBase(fir::factory::createStringLiteral(builder, loc, value)); } auto size = builder.getKindMap().getCharacterBitsize(KIND) / 8 * value.size(); llvm::StringRef strVal(reinterpret_cast(value.c_str()), size); std::string globalName = fir::factory::uniqueCGIdent("cl", strVal); fir::GlobalOp global = builder.getNamedGlobal(globalName); fir::CharacterType type = fir::CharacterType::get(builder.getContext(), KIND, len); if (!global) global = builder.createGlobalConstant( loc, type, globalName, [&](fir::FirOpBuilder &builder) { fir::StringLitOp str = createStringLitOp(builder, loc, value, len); builder.create(loc, str); }, builder.createLinkOnceLinkage()); return builder.create(loc, global.resultType(), global.getSymbol()); } /// Create an evaluate::Constant array to a fir.array<> value /// built with a chain of fir.insert or fir.insert_on_range operations. /// This is intended to be called when building the body of a fir.global. template static mlir::Value genInlinedArrayLit( fir::FirOpBuilder &builder, mlir::Location loc, mlir::Type arrayTy, const Fortran::evaluate::Constant> &con) { mlir::IndexType idxTy = builder.getIndexType(); Fortran::evaluate::ConstantSubscripts subscripts = con.lbounds(); auto createIdx = [&]() { llvm::SmallVector idx; for (size_t i = 0; i < subscripts.size(); ++i) idx.push_back( builder.getIntegerAttr(idxTy, subscripts[i] - con.lbounds()[i])); return idx; }; mlir::Value array = builder.create(loc, arrayTy); if (Fortran::evaluate::GetSize(con.shape()) == 0) return array; if constexpr (TC == Fortran::common::TypeCategory::Character) { do { mlir::Value elementVal = genScalarLit(builder, loc, con.At(subscripts), con.LEN(), /*outlineInReadOnlyMemory=*/false); array = builder.create( loc, arrayTy, array, elementVal, builder.getArrayAttr(createIdx())); } while (con.IncrementSubscripts(subscripts)); } else { llvm::SmallVector rangeStartIdx; uint64_t rangeSize = 0; mlir::Type eleTy = arrayTy.cast().getEleTy(); do { auto getElementVal = [&]() { return builder.createConvert( loc, eleTy, genScalarLit(builder, loc, con.At(subscripts))); }; Fortran::evaluate::ConstantSubscripts nextSubscripts = subscripts; bool nextIsSame = con.IncrementSubscripts(nextSubscripts) && con.At(subscripts) == con.At(nextSubscripts); if (!rangeSize && !nextIsSame) { // single (non-range) value array = builder.create( loc, arrayTy, array, getElementVal(), builder.getArrayAttr(createIdx())); } else if (!rangeSize) { // start a range rangeStartIdx = createIdx(); rangeSize = 1; } else if (nextIsSame) { // expand a range ++rangeSize; } else { // end a range llvm::SmallVector rangeBounds; llvm::SmallVector idx = createIdx(); for (size_t i = 0; i < idx.size(); ++i) { rangeBounds.push_back(rangeStartIdx[i] .cast() .getValue() .getSExtValue()); rangeBounds.push_back( idx[i].cast().getValue().getSExtValue()); } array = builder.create( loc, arrayTy, array, getElementVal(), builder.getIndexVectorAttr(rangeBounds)); rangeSize = 0; } } while (con.IncrementSubscripts(subscripts)); } return array; } /// Convert an evaluate::Constant array into a fir.ref> value /// that points to the storage of a fir.global in read only memory and is /// initialized with the value of the constant. /// This should not be called while generating the body of a fir.global. template static mlir::Value genOutlineArrayLit( fir::FirOpBuilder &builder, mlir::Location loc, mlir::Type arrayTy, const Fortran::evaluate::Constant> &constant) { std::string globalName = Fortran::lower::mangle::mangleArrayLiteral(constant); fir::GlobalOp global = builder.getNamedGlobal(globalName); if (!global) { // Using a dense attribute for the initial value instead of creating an // intialization body speeds up MLIR/LLVM compilation, but this is not // always possible. if constexpr (TC == Fortran::common::TypeCategory::Logical || TC == Fortran::common::TypeCategory::Integer || TC == Fortran::common::TypeCategory::Real) { global = DenseGlobalBuilder::tryCreating( builder, loc, arrayTy, globalName, builder.createInternalLinkage(), true, constant); } if (!global) global = builder.createGlobalConstant( loc, arrayTy, globalName, [&](fir::FirOpBuilder &builder) { mlir::Value result = genInlinedArrayLit(builder, loc, arrayTy, constant); builder.create(loc, result); }, builder.createInternalLinkage()); } return builder.create(loc, global.resultType(), global.getSymbol()); } /// Convert an evaluate::Constant array into an fir::ExtendedValue. template static fir::ExtendedValue genArrayLit( fir::FirOpBuilder &builder, mlir::Location loc, const Fortran::evaluate::Constant> &con, bool outlineInReadOnlyMemory) { Fortran::evaluate::ConstantSubscript size = Fortran::evaluate::GetSize(con.shape()); if (size > std::numeric_limits::max()) // llvm::SmallVector has limited size TODO(loc, "Creation of very large array constants"); fir::SequenceType::Shape shape(con.shape().begin(), con.shape().end()); llvm::SmallVector typeParams; if constexpr (TC == Fortran::common::TypeCategory::Character) typeParams.push_back(con.LEN()); mlir::Type eleTy = Fortran::lower::getFIRType(builder.getContext(), TC, KIND, typeParams); auto arrayTy = fir::SequenceType::get(shape, eleTy); mlir::Value array = outlineInReadOnlyMemory ? genOutlineArrayLit(builder, loc, arrayTy, con) : genInlinedArrayLit(builder, loc, arrayTy, con); mlir::IndexType idxTy = builder.getIndexType(); llvm::SmallVector extents; for (auto extent : shape) extents.push_back(builder.createIntegerConstant(loc, idxTy, extent)); // Convert lower bounds if they are not all ones. llvm::SmallVector lbounds; if (llvm::any_of(con.lbounds(), [](auto lb) { return lb != 1; })) for (auto lb : con.lbounds()) lbounds.push_back(builder.createIntegerConstant(loc, idxTy, lb)); if constexpr (TC == Fortran::common::TypeCategory::Character) { mlir::Value len = builder.createIntegerConstant(loc, idxTy, con.LEN()); return fir::CharArrayBoxValue{array, len, extents, lbounds}; } else { return fir::ArrayBoxValue{array, extents, lbounds}; } } template fir::ExtendedValue Fortran::lower::ConstantBuilder>::gen( fir::FirOpBuilder &builder, mlir::Location loc, const Fortran::evaluate::Constant> &constant, bool outlineBigConstantsInReadOnlyMemory) { if (constant.Rank() > 0) return genArrayLit(builder, loc, constant, outlineBigConstantsInReadOnlyMemory); std::optional>> opt = constant.GetScalarValue(); assert(opt.has_value() && "constant has no value"); if constexpr (TC == Fortran::common::TypeCategory::Character) { auto value = genScalarLit(builder, loc, opt.value(), constant.LEN(), outlineBigConstantsInReadOnlyMemory); mlir::Value len = builder.createIntegerConstant( loc, builder.getCharacterLengthType(), constant.LEN()); return fir::CharBoxValue{value, len}; } else { return genScalarLit(builder, loc, opt.value()); } } using namespace Fortran::evaluate; FOR_EACH_INTRINSIC_KIND(template class Fortran::lower::ConstantBuilder, )