llvm-project/flang/lib/Optimizer/Transforms/SetRuntimeCallAttributes.cpp
Peter Klausler 4ea5aa09de
[flang][NFC] Restore I/O runtime API header name (#132423)
flang/include/flang/Runtime/io-api.h was changed into io-api-consts.h,
then wrapped into a new io-api.h that includes io-api-consts.h, does
some redundant includes and declarations, and then declares the
prototype of one function, InquiryKeywordHashDecode.

Make that function static in io-stmt.cpp prior to its sole call site,
then undo the renaming, to reduce confusion and redundancy.
2025-03-26 12:09:16 -07:00

259 lines
10 KiB
C++

//===- SetRuntimeCallAttributes.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
//
//===----------------------------------------------------------------------===//
//===----------------------------------------------------------------------===//
/// \file
/// SetRuntimeCallAttributesPass looks for fir.call operations
/// that are calling into Fortran runtime, and tries to set different
/// attributes on them to enable more optimizations in LLVM backend
/// (granted that they are preserved all the way to LLVM IR).
/// This pass is currently only attaching fir.call wide atttributes,
/// such as ones corresponding to llvm.memory, nosync, nocallbac, etc.
/// It is not designed to attach attributes to the arguments and the results
/// of a call.
//===----------------------------------------------------------------------===//
#include "flang/Common/static-multimap-view.h"
#include "flang/Optimizer/Builder/Runtime/RTBuilder.h"
#include "flang/Optimizer/Dialect/FIRDialect.h"
#include "flang/Optimizer/Dialect/FIROpsSupport.h"
#include "flang/Optimizer/Support/InternalNames.h"
#include "flang/Optimizer/Transforms/Passes.h"
#include "flang/Runtime/io-api.h"
#include "mlir/Dialect/LLVMIR/LLVMAttrs.h"
namespace fir {
#define GEN_PASS_DEF_SETRUNTIMECALLATTRIBUTES
#include "flang/Optimizer/Transforms/Passes.h.inc"
} // namespace fir
#define DEBUG_TYPE "set-runtime-call-attrs"
using namespace Fortran::runtime;
using namespace Fortran::runtime::io;
#define mkIOKey(X) FirmkKey(IONAME(X))
#define mkRTKey(X) FirmkKey(RTNAME(X))
// Return LLVM dialect MemoryEffectsAttr for the given Fortran runtime call.
// This function is computing a generic value of this attribute
// by analyzing the arguments and their types.
// It tries to figure out if an "indirect" memory access is possible
// during this call. If it is not possible, then the memory effects
// are:
// * other = NoModRef
// * argMem = ModRef
// * inaccessibleMem = ModRef
//
// Otherwise, it returns an empty attribute meaning ModRef for all kinds
// of memory.
//
// The attribute deduction is conservative in a sense that it applies
// to most of the runtime calls, but it may still be incorrect for some
// runtime calls.
static mlir::LLVM::MemoryEffectsAttr getGenericMemoryAttr(fir::CallOp callOp) {
bool maybeIndirectAccess = false;
for (auto arg : callOp.getArgOperands()) {
mlir::Type argType = arg.getType();
if (mlir::isa<fir::BaseBoxType>(argType)) {
// If it is a null/absent box, then this particular call
// cannot access memory indirectly through the box's
// base_addr.
auto def = arg.getDefiningOp();
if (!mlir::isa_and_nonnull<fir::ZeroOp, fir::AbsentOp>(def)) {
maybeIndirectAccess = true;
break;
}
}
if (auto refType = mlir::dyn_cast<fir::ReferenceType>(argType)) {
if (!fir::isa_trivial(refType.getElementType())) {
maybeIndirectAccess = true;
break;
}
}
if (auto ptrType = mlir::dyn_cast<mlir::LLVM::LLVMPointerType>(argType)) {
maybeIndirectAccess = true;
break;
}
}
if (!maybeIndirectAccess) {
return mlir::LLVM::MemoryEffectsAttr::get(
callOp->getContext(),
{/*other=*/mlir::LLVM::ModRefInfo::NoModRef,
/*argMem=*/mlir::LLVM::ModRefInfo::ModRef,
/*inaccessibleMem=*/mlir::LLVM::ModRefInfo::ModRef});
}
return {};
}
namespace {
class SetRuntimeCallAttributesPass
: public fir::impl::SetRuntimeCallAttributesBase<
SetRuntimeCallAttributesPass> {
public:
void runOnOperation() override;
};
// A helper to match a type against a list of types.
template <typename T, typename... Ts>
constexpr bool IsAny = std::disjunction_v<std::is_same<T, Ts>...>;
} // end anonymous namespace
// MemoryAttrDesc type provides get() method for computing
// mlir::LLVM::MemoryEffectsAttr for the given Fortran runtime call.
// If needed, add specializations for particular runtime calls.
namespace {
// Default implementation just uses getGenericMemoryAttr().
// Note that it may be incorrect for some runtime calls.
template <typename KEY, typename Enable = void>
struct MemoryAttrDesc {
static mlir::LLVM::MemoryEffectsAttr get(fir::CallOp callOp) {
return getGenericMemoryAttr(callOp);
}
};
} // end anonymous namespace
// NosyncAttrDesc type provides get() method for computing
// LLVM nosync attribute for the given call.
namespace {
// Default implementation always returns LLVM nosync.
// This should be true for the majority of the Fortran runtime calls.
template <typename KEY, typename Enable = void>
struct NosyncAttrDesc {
static std::optional<mlir::NamedAttribute> get(fir::CallOp callOp) {
// TODO: replace llvm.nosync with an LLVM dialect callback.
return mlir::NamedAttribute("llvm.nosync",
mlir::UnitAttr::get(callOp->getContext()));
}
};
} // end anonymous namespace
// NocallbackAttrDesc type provides get() method for computing
// LLVM nocallback attribute for the given call.
namespace {
// Default implementation always returns LLVM nocallback.
// It must be specialized for Fortran runtime functions that may call
// user functions during their execution (e.g. defined IO, assignment).
template <typename KEY, typename Enable = void>
struct NocallbackAttrDesc {
static std::optional<mlir::NamedAttribute> get(fir::CallOp callOp) {
// TODO: replace llvm.nocallback with an LLVM dialect callback.
return mlir::NamedAttribute("llvm.nocallback",
mlir::UnitAttr::get(callOp->getContext()));
}
};
// Derived types IO may call back into a Fortran module.
// This specialization is conservative for Input/OutputDerivedType,
// and it might be improved by checking if the NonTbpDefinedIoTable
// pointer argument is null.
template <typename KEY>
struct NocallbackAttrDesc<
KEY, std::enable_if_t<
IsAny<KEY, mkIOKey(OutputDerivedType), mkIOKey(InputDerivedType),
mkIOKey(OutputNamelist), mkIOKey(InputNamelist)>>> {
static std::optional<mlir::NamedAttribute> get(fir::CallOp) {
return std::nullopt;
}
};
} // end anonymous namespace
namespace {
// RuntimeFunction provides different callbacks that compute values
// of fir.call attributes for a Fortran runtime function.
struct RuntimeFunction {
using MemoryAttrGeneratorTy = mlir::LLVM::MemoryEffectsAttr (*)(fir::CallOp);
using NamedAttrGeneratorTy =
std::optional<mlir::NamedAttribute> (*)(fir::CallOp);
using Key = std::string_view;
constexpr operator Key() const { return key; }
Key key;
MemoryAttrGeneratorTy memoryAttrGenerator;
NamedAttrGeneratorTy nosyncAttrGenerator;
NamedAttrGeneratorTy nocallbackAttrGenerator;
};
// Helper type to create a RuntimeFunction descriptor given
// the KEY and a function name.
template <typename KEY>
struct RuntimeFactory {
static constexpr RuntimeFunction create(const char name[]) {
// GCC 7 does not recognize this as a constant expression:
// ((const char *)RuntimeFunction<>::name) == nullptr
// This comparison comes from the basic_string_view(const char *)
// constructor. We have to use the other constructor
// that takes explicit length parameter.
return RuntimeFunction{
std::string_view{name, std::char_traits<char>::length(name)},
MemoryAttrDesc<KEY>::get, NosyncAttrDesc<KEY>::get,
NocallbackAttrDesc<KEY>::get};
}
};
} // end anonymous namespace
#define KNOWN_IO_FUNC(X) RuntimeFactory<mkIOKey(X)>::create(mkIOKey(X)::name)
#define KNOWN_RUNTIME_FUNC(X) \
RuntimeFactory<mkRTKey(X)>::create(mkRTKey(X)::name)
// A table of RuntimeFunction descriptors for all recognized
// Fortran runtime functions.
static constexpr RuntimeFunction runtimeFuncsTable[] = {
#include "flang/Optimizer/Transforms/RuntimeFunctions.inc"
};
static constexpr Fortran::common::StaticMultimapView<RuntimeFunction>
runtimeFuncs(runtimeFuncsTable);
static_assert(runtimeFuncs.Verify() && "map must be sorted");
// Set attributes for the given Fortran runtime call.
// The symbolTable is used to cache the name lookups in the module.
static void setRuntimeCallAttributes(fir::CallOp callOp,
mlir::SymbolTableCollection &symbolTable) {
auto iface = mlir::cast<mlir::CallOpInterface>(callOp.getOperation());
auto funcOp = mlir::dyn_cast_or_null<mlir::func::FuncOp>(
iface.resolveCallableInTable(&symbolTable));
if (!funcOp || !funcOp->hasAttrOfType<mlir::UnitAttr>(
fir::FIROpsDialect::getFirRuntimeAttrName()))
return;
llvm::StringRef name = funcOp.getName();
if (auto range = runtimeFuncs.equal_range(name);
range.first != range.second) {
// There should not be duplicate entries.
assert(range.first + 1 == range.second);
const RuntimeFunction &desc = *range.first;
LLVM_DEBUG(llvm::dbgs()
<< "Identified runtime function call: " << desc.key << '\n');
if (mlir::LLVM::MemoryEffectsAttr memoryAttr =
desc.memoryAttrGenerator(callOp))
callOp->setAttr(fir::FIROpsDialect::getFirCallMemoryAttrName(),
memoryAttr);
if (auto attr = desc.nosyncAttrGenerator(callOp))
callOp->setAttr(attr->getName(), attr->getValue());
if (auto attr = desc.nocallbackAttrGenerator(callOp))
callOp->setAttr(attr->getName(), attr->getValue());
LLVM_DEBUG(llvm::dbgs() << "Operation with attrs: " << callOp << '\n');
}
}
void SetRuntimeCallAttributesPass::runOnOperation() {
mlir::func::FuncOp funcOp = getOperation();
// Exit early for declarations to skip the debug output for them.
if (funcOp.isDeclaration())
return;
LLVM_DEBUG(llvm::dbgs() << "=== Begin " DEBUG_TYPE " ===\n");
LLVM_DEBUG(llvm::dbgs() << "Func-name:" << funcOp.getSymName() << "\n");
mlir::SymbolTableCollection symbolTable;
funcOp.walk([&](fir::CallOp callOp) {
setRuntimeCallAttributes(callOp, symbolTable);
});
LLVM_DEBUG(llvm::dbgs() << "=== End " DEBUG_TYPE " ===\n");
}