
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.
259 lines
10 KiB
C++
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");
|
|
}
|