1 //===- MemoryAllocation.cpp -----------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8
9 #include "PassDetail.h"
10 #include "flang/Optimizer/Dialect/FIRDialect.h"
11 #include "flang/Optimizer/Dialect/FIROps.h"
12 #include "flang/Optimizer/Dialect/FIRType.h"
13 #include "flang/Optimizer/Transforms/Passes.h"
14 #include "mlir/Dialect/Func/IR/FuncOps.h"
15 #include "mlir/IR/Diagnostics.h"
16 #include "mlir/Pass/Pass.h"
17 #include "mlir/Transforms/DialectConversion.h"
18 #include "mlir/Transforms/Passes.h"
19 #include "llvm/ADT/TypeSwitch.h"
20
21 #define DEBUG_TYPE "flang-memory-allocation-opt"
22
23 // Number of elements in an array does not determine where it is allocated.
24 static constexpr std::size_t unlimitedArraySize = ~static_cast<std::size_t>(0);
25
26 namespace {
27 struct MemoryAllocationOptions {
28 // Always move dynamic array allocations to the heap. This may result in more
29 // heap fragmentation, so may impact performance negatively.
30 bool dynamicArrayOnHeap = false;
31
32 // Number of elements in array threshold for moving to heap. In environments
33 // with limited stack size, moving large arrays to the heap can avoid running
34 // out of stack space.
35 std::size_t maxStackArraySize = unlimitedArraySize;
36 };
37
38 class ReturnAnalysis {
39 public:
40 MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(ReturnAnalysis)
41
ReturnAnalysis(mlir::Operation * op)42 ReturnAnalysis(mlir::Operation *op) {
43 if (auto func = mlir::dyn_cast<mlir::func::FuncOp>(op))
44 for (mlir::Block &block : func)
45 for (mlir::Operation &i : block)
46 if (mlir::isa<mlir::func::ReturnOp>(i)) {
47 returnMap[op].push_back(&i);
48 break;
49 }
50 }
51
getReturns(mlir::Operation * func) const52 llvm::SmallVector<mlir::Operation *> getReturns(mlir::Operation *func) const {
53 auto iter = returnMap.find(func);
54 if (iter != returnMap.end())
55 return iter->second;
56 return {};
57 }
58
59 private:
60 llvm::DenseMap<mlir::Operation *, llvm::SmallVector<mlir::Operation *>>
61 returnMap;
62 };
63 } // namespace
64
65 /// Return `true` if this allocation is to remain on the stack (`fir.alloca`).
66 /// Otherwise the allocation should be moved to the heap (`fir.allocmem`).
keepStackAllocation(fir::AllocaOp alloca,mlir::Block * entry,const MemoryAllocationOptions & options)67 static inline bool keepStackAllocation(fir::AllocaOp alloca, mlir::Block *entry,
68 const MemoryAllocationOptions &options) {
69 // Limitation: only arrays allocated on the stack in the entry block are
70 // considered for now.
71 // TODO: Generalize the algorithm and placement of the freemem nodes.
72 if (alloca->getBlock() != entry)
73 return true;
74 if (auto seqTy = alloca.getInType().dyn_cast<fir::SequenceType>()) {
75 if (fir::hasDynamicSize(seqTy)) {
76 // Move all arrays with runtime determined size to the heap.
77 if (options.dynamicArrayOnHeap)
78 return false;
79 } else {
80 std::int64_t numberOfElements = 1;
81 for (std::int64_t i : seqTy.getShape()) {
82 numberOfElements *= i;
83 // If the count is suspicious, then don't change anything here.
84 if (numberOfElements <= 0)
85 return true;
86 }
87 // If the number of elements exceeds the threshold, move the allocation to
88 // the heap.
89 if (static_cast<std::size_t>(numberOfElements) >
90 options.maxStackArraySize) {
91 LLVM_DEBUG(llvm::dbgs()
92 << "memory allocation opt: found " << alloca << '\n');
93 return false;
94 }
95 }
96 }
97 return true;
98 }
99
100 namespace {
101 class AllocaOpConversion : public mlir::OpRewritePattern<fir::AllocaOp> {
102 public:
103 using OpRewritePattern::OpRewritePattern;
104
AllocaOpConversion(mlir::MLIRContext * ctx,llvm::ArrayRef<mlir::Operation * > rets)105 AllocaOpConversion(mlir::MLIRContext *ctx,
106 llvm::ArrayRef<mlir::Operation *> rets)
107 : OpRewritePattern(ctx), returnOps(rets) {}
108
109 mlir::LogicalResult
matchAndRewrite(fir::AllocaOp alloca,mlir::PatternRewriter & rewriter) const110 matchAndRewrite(fir::AllocaOp alloca,
111 mlir::PatternRewriter &rewriter) const override {
112 auto loc = alloca.getLoc();
113 mlir::Type varTy = alloca.getInType();
114 auto unpackName =
115 [](llvm::Optional<llvm::StringRef> opt) -> llvm::StringRef {
116 if (opt)
117 return *opt;
118 return {};
119 };
120 auto uniqName = unpackName(alloca.getUniqName());
121 auto bindcName = unpackName(alloca.getBindcName());
122 auto heap = rewriter.create<fir::AllocMemOp>(
123 loc, varTy, uniqName, bindcName, alloca.getTypeparams(),
124 alloca.getShape());
125 auto insPt = rewriter.saveInsertionPoint();
126 for (mlir::Operation *retOp : returnOps) {
127 rewriter.setInsertionPoint(retOp);
128 [[maybe_unused]] auto free = rewriter.create<fir::FreeMemOp>(loc, heap);
129 LLVM_DEBUG(llvm::dbgs() << "memory allocation opt: add free " << free
130 << " for " << heap << '\n');
131 }
132 rewriter.restoreInsertionPoint(insPt);
133 rewriter.replaceOpWithNewOp<fir::ConvertOp>(
134 alloca, fir::ReferenceType::get(varTy), heap);
135 LLVM_DEBUG(llvm::dbgs() << "memory allocation opt: replaced " << alloca
136 << " with " << heap << '\n');
137 return mlir::success();
138 }
139
140 private:
141 llvm::ArrayRef<mlir::Operation *> returnOps;
142 };
143
144 /// This pass can reclassify memory allocations (fir.alloca, fir.allocmem) based
145 /// on heuristics and settings. The intention is to allow better performance and
146 /// workarounds for conditions such as environments with limited stack space.
147 ///
148 /// Currently, implements two conversions from stack to heap allocation.
149 /// 1. If a stack allocation is an array larger than some threshold value
150 /// make it a heap allocation.
151 /// 2. If a stack allocation is an array with a runtime evaluated size make
152 /// it a heap allocation.
153 class MemoryAllocationOpt
154 : public fir::MemoryAllocationOptBase<MemoryAllocationOpt> {
155 public:
MemoryAllocationOpt()156 MemoryAllocationOpt() {
157 // Set options with default values. (See Passes.td.) Note that the
158 // command-line options, e.g. dynamicArrayOnHeap, are not set yet.
159 options = {dynamicArrayOnHeap, maxStackArraySize};
160 }
161
MemoryAllocationOpt(bool dynOnHeap,std::size_t maxStackSize)162 MemoryAllocationOpt(bool dynOnHeap, std::size_t maxStackSize) {
163 // Set options with default values. (See Passes.td.)
164 options = {dynOnHeap, maxStackSize};
165 }
166
167 /// Override `options` if command-line options have been set.
useCommandLineOptions()168 inline void useCommandLineOptions() {
169 if (dynamicArrayOnHeap)
170 options.dynamicArrayOnHeap = dynamicArrayOnHeap;
171 if (maxStackArraySize != unlimitedArraySize)
172 options.maxStackArraySize = maxStackArraySize;
173 }
174
runOnOperation()175 void runOnOperation() override {
176 auto *context = &getContext();
177 auto func = getOperation();
178 mlir::RewritePatternSet patterns(context);
179 mlir::ConversionTarget target(*context);
180
181 useCommandLineOptions();
182 LLVM_DEBUG(llvm::dbgs()
183 << "dynamic arrays on heap: " << options.dynamicArrayOnHeap
184 << "\nmaximum number of elements of array on stack: "
185 << options.maxStackArraySize << '\n');
186
187 // If func is a declaration, skip it.
188 if (func.empty())
189 return;
190
191 const auto &analysis = getAnalysis<ReturnAnalysis>();
192
193 target.addLegalDialect<fir::FIROpsDialect, mlir::arith::ArithmeticDialect,
194 mlir::func::FuncDialect>();
195 target.addDynamicallyLegalOp<fir::AllocaOp>([&](fir::AllocaOp alloca) {
196 return keepStackAllocation(alloca, &func.front(), options);
197 });
198
199 patterns.insert<AllocaOpConversion>(context, analysis.getReturns(func));
200 if (mlir::failed(
201 mlir::applyPartialConversion(func, target, std::move(patterns)))) {
202 mlir::emitError(func.getLoc(),
203 "error in memory allocation optimization\n");
204 signalPassFailure();
205 }
206 }
207
208 private:
209 MemoryAllocationOptions options;
210 };
211 } // namespace
212
createMemoryAllocationPass()213 std::unique_ptr<mlir::Pass> fir::createMemoryAllocationPass() {
214 return std::make_unique<MemoryAllocationOpt>();
215 }
216
217 std::unique_ptr<mlir::Pass>
createMemoryAllocationPass(bool dynOnHeap,std::size_t maxStackSize)218 fir::createMemoryAllocationPass(bool dynOnHeap, std::size_t maxStackSize) {
219 return std::make_unique<MemoryAllocationOpt>(dynOnHeap, maxStackSize);
220 }
221