| 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 | |