1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
|
//===- MemoryAllocation.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
//
//===----------------------------------------------------------------------===//
#include "flang/Optimizer/Dialect/FIRDialect.h"
#include "flang/Optimizer/Dialect/FIROps.h"
#include "flang/Optimizer/Dialect/FIRType.h"
#include "flang/Optimizer/Transforms/MemoryUtils.h"
#include "flang/Optimizer/Transforms/Passes.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/IR/Diagnostics.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Transforms/DialectConversion.h"
#include "mlir/Transforms/Passes.h"
#include "llvm/ADT/TypeSwitch.h"
namespace fir {
#define GEN_PASS_DEF_MEMORYALLOCATIONOPT
#include "flang/Optimizer/Transforms/Passes.h.inc"
} // namespace fir
#define DEBUG_TYPE "flang-memory-allocation-opt"
// Number of elements in an array does not determine where it is allocated.
static constexpr std::size_t unlimitedArraySize = ~static_cast<std::size_t>(0);
/// Return `true` if this allocation is to remain on the stack (`fir.alloca`).
/// Otherwise the allocation should be moved to the heap (`fir.allocmem`).
static inline bool
keepStackAllocation(fir::AllocaOp alloca,
const fir::MemoryAllocationOptOptions &options) {
// Move all arrays and character with runtime determined size to the heap.
if (options.dynamicArrayOnHeap && alloca.isDynamic())
return false;
// TODO: use data layout to reason in terms of byte size to cover all "big"
// entities, which may be scalar derived types.
if (auto seqTy = mlir::dyn_cast<fir::SequenceType>(alloca.getInType())) {
if (!fir::hasDynamicSize(seqTy)) {
std::int64_t numberOfElements = 1;
for (std::int64_t i : seqTy.getShape()) {
numberOfElements *= i;
// If the count is suspicious, then don't change anything here.
if (numberOfElements <= 0)
return true;
}
// If the number of elements exceeds the threshold, move the allocation to
// the heap.
if (static_cast<std::size_t>(numberOfElements) >
options.maxStackArraySize) {
return false;
}
}
}
return true;
}
static mlir::Value genAllocmem(mlir::OpBuilder &builder, fir::AllocaOp alloca,
bool deallocPointsDominateAlloc) {
mlir::Type varTy = alloca.getInType();
auto unpackName = [](std::optional<llvm::StringRef> opt) -> llvm::StringRef {
if (opt)
return *opt;
return {};
};
llvm::StringRef uniqName = unpackName(alloca.getUniqName());
llvm::StringRef bindcName = unpackName(alloca.getBindcName());
auto heap = builder.create<fir::AllocMemOp>(alloca.getLoc(), varTy, uniqName,
bindcName, alloca.getTypeparams(),
alloca.getShape());
LLVM_DEBUG(llvm::dbgs() << "memory allocation opt: replaced " << alloca
<< " with " << heap << '\n');
return heap;
}
static void genFreemem(mlir::Location loc, mlir::OpBuilder &builder,
mlir::Value allocmem) {
[[maybe_unused]] auto free = builder.create<fir::FreeMemOp>(loc, allocmem);
LLVM_DEBUG(llvm::dbgs() << "memory allocation opt: add free " << free
<< " for " << allocmem << '\n');
}
/// This pass can reclassify memory allocations (fir.alloca, fir.allocmem) based
/// on heuristics and settings. The intention is to allow better performance and
/// workarounds for conditions such as environments with limited stack space.
///
/// Currently, implements two conversions from stack to heap allocation.
/// 1. If a stack allocation is an array larger than some threshold value
/// make it a heap allocation.
/// 2. If a stack allocation is an array with a runtime evaluated size make
/// it a heap allocation.
namespace {
class MemoryAllocationOpt
: public fir::impl::MemoryAllocationOptBase<MemoryAllocationOpt> {
public:
MemoryAllocationOpt() {
// Set options with default values. (See Passes.td.) Note that the
// command-line options, e.g. dynamicArrayOnHeap, are not set yet.
options = {dynamicArrayOnHeap, maxStackArraySize};
}
MemoryAllocationOpt(bool dynOnHeap, std::size_t maxStackSize) {
// Set options with default values. (See Passes.td.)
options = {dynOnHeap, maxStackSize};
}
MemoryAllocationOpt(const fir::MemoryAllocationOptOptions &options)
: options{options} {}
/// Override `options` if command-line options have been set.
inline void useCommandLineOptions() {
if (dynamicArrayOnHeap)
options.dynamicArrayOnHeap = dynamicArrayOnHeap;
if (maxStackArraySize != unlimitedArraySize)
options.maxStackArraySize = maxStackArraySize;
}
void runOnOperation() override {
auto *context = &getContext();
auto func = getOperation();
mlir::RewritePatternSet patterns(context);
mlir::ConversionTarget target(*context);
useCommandLineOptions();
LLVM_DEBUG(llvm::dbgs()
<< "dynamic arrays on heap: " << options.dynamicArrayOnHeap
<< "\nmaximum number of elements of array on stack: "
<< options.maxStackArraySize << '\n');
// If func is a declaration, skip it.
if (func.empty())
return;
auto tryReplacing = [&](fir::AllocaOp alloca) {
bool res = !keepStackAllocation(alloca, options);
if (res) {
LLVM_DEBUG(llvm::dbgs()
<< "memory allocation opt: found " << alloca << '\n');
}
return res;
};
mlir::IRRewriter rewriter(context);
fir::replaceAllocas(rewriter, func.getOperation(), tryReplacing,
genAllocmem, genFreemem);
}
private:
fir::MemoryAllocationOptOptions options;
};
} // namespace
|