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