MLIR 23.0.0git
StaticMemoryPlannerAnalysis.cpp
Go to the documentation of this file.
1//===- StaticMemoryPlannerAnalysis.cpp - Static memory planning -----------===//
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// Transforms memref.alloc/memref.dealloc pairs into a single arena allocation
10// with memref.view. Delegates offset computation to planning algorithms in
11// StaticMemoryPlanning.h.
12//
13//===----------------------------------------------------------------------===//
14
19#include "mlir/IR/Builders.h"
21#include "llvm/Support/Debug.h"
22#include <numeric>
23
24#define DEBUG_TYPE "static-memory-planner"
25
26namespace mlir {
27namespace bufferization {
28#define GEN_PASS_DEF_STATICMEMORYPLANNERANALYSISPASS
29#include "mlir/Dialect/Bufferization/Transforms/Passes.h.inc"
30} // namespace bufferization
31} // namespace mlir
32
33using namespace mlir;
34
35namespace {
36
37/// A candidate allocation with its matching deallocation and assigned offset.
38struct AllocationCandidate {
39 memref::AllocOp alloc;
40 memref::DeallocOp dealloc;
41 int64_t offset = 0; // Offset in bytes from arena start (assigned by planner)
42 int64_t sizeInBytes = 0; // Size in bytes
43 int64_t alignment = 1; // Required alignment in bytes
44};
45
46//===----------------------------------------------------------------------===//
47// Helper utilities
48//===----------------------------------------------------------------------===//
49
50/// Finds the unique dealloc operation for a given alloc value.
51/// Returns nullptr if there are zero or multiple deallocs.
52static memref::DeallocOp findUniqueDealloc(Value allocValue) {
53 memref::DeallocOp deallocOp = nullptr;
54 for (Operation *user : allocValue.getUsers()) {
55 if (auto dealloc = dyn_cast<memref::DeallocOp>(user)) {
56 if (deallocOp)
57 return nullptr; // Multiple deallocs found
58 deallocOp = dealloc;
59 }
60 }
61 return deallocOp;
62}
63
64/// Compute the size in bytes for a memref type.
65static int64_t computeSizeInBytes(MemRefType memrefType) {
66 int64_t numElements = memrefType.getNumElements();
67 unsigned elementSizeInBits = memrefType.getElementTypeBitWidth();
68 return (numElements * elementSizeInBits + 7) / 8; // Round up to bytes
69}
70
71/// Build lifetime-annotated allocation descriptors from candidates.
72/// Returns the arena alignment (LCM of all individual alignments).
73static int64_t buildAllocInfos(
76 int64_t arenaAlignment = 1;
77 for (auto &candidate : candidates) {
79 info.sizeInBytes = candidate.sizeInBytes;
80 info.alignment = candidate.alignment;
81
82 Block *block = candidate.alloc->getBlock();
83 int64_t opIdx = 0;
84 for (Operation &op : *block) {
85 if (&op == candidate.alloc.getOperation())
86 info.timeStart = opIdx;
87 if (&op == candidate.dealloc.getOperation())
88 info.timeEnd = opIdx;
89 ++opIdx;
90 }
91
92 allocInfos.push_back(info);
93 arenaAlignment = std::lcm(arenaAlignment, candidate.alignment);
94 }
95 return arenaAlignment;
96}
97
98/// Collect alloc/dealloc pairs eligible for arena placement.
99/// An allocation is eligible if it has a static shape and a unique dealloc
100/// in the same block.
102collectCandidates(FunctionOpInterface funcOp, llvm::Statistic &numSkipDynamic,
103 llvm::Statistic &numSkipNoDealloc,
104 llvm::Statistic &numEligible) {
106
107 funcOp->walk([&](memref::AllocOp allocOp) {
108 MemRefType memrefType = allocOp.getType();
109
110 // Skip dynamic shapes
111 if (!memrefType.hasStaticShape()) {
112 ++numSkipDynamic;
113 return;
114 }
115
116 // Find unique dealloc in the same block
117 memref::DeallocOp deallocOp = findUniqueDealloc(allocOp.getResult());
118 if (!deallocOp) {
119 ++numSkipNoDealloc;
120 return;
121 }
122
123 if (deallocOp->getBlock() != allocOp->getBlock()) {
124 ++numSkipNoDealloc;
125 return;
126 }
127
128 // This allocation is eligible
129 ++numEligible;
130 AllocationCandidate candidate;
131 candidate.alloc = allocOp;
132 candidate.dealloc = deallocOp;
133 candidate.sizeInBytes = computeSizeInBytes(memrefType);
134 candidate.alignment = allocOp.getAlignment().value_or(1);
135 candidates.push_back(candidate);
136 });
137
138 return candidates;
139}
140
141/// Create or obtain the arena buffer based on the arena mode.
142/// Returns failure if the mode is invalid or preconditions aren't met.
143static FailureOr<Value> createArena(OpBuilder &builder,
144 FunctionOpInterface funcOp,
145 StringRef arenaMode, int64_t totalSize,
146 int64_t arenaAlignment) {
147 Location loc = funcOp->getLoc();
148
149 if (arenaMode == "allocate") {
150 auto arenaType = MemRefType::get({totalSize}, builder.getI8Type());
151 auto arenaAlloc =
152 memref::AllocOp::create(builder, loc, arenaType, ValueRange{},
153 builder.getI64IntegerAttr(arenaAlignment));
154 LLVM_DEBUG(llvm::dbgs()
155 << "[static-memory-planner] created arena via AllocOp: size="
156 << totalSize << " bytes, alignment=" << arenaAlignment
157 << " bytes\n");
158 return arenaAlloc.getResult();
159 }
160
161 if (arenaMode == "arg") {
162 if (funcOp.getNumArguments() == 0)
163 return funcOp->emitError(
164 "arena-mode=arg requires at least one function argument");
165
166 Value arenaValue = funcOp.getArgument(0);
167 auto arenaType = dyn_cast<MemRefType>(arenaValue.getType());
168 if (!arenaType || !arenaType.getElementType().isInteger(8) ||
169 arenaType.getRank() != 1)
170 return funcOp->emitError(
171 "arena-mode=arg requires first argument to be memref<...xi8>");
172
173 LLVM_DEBUG(llvm::dbgs()
174 << "[static-memory-planner] using arena from function arg 0\n");
175 return arenaValue;
176 }
177
178 return funcOp->emitError("invalid arena-mode: '" + arenaMode +
179 "' (must be 'allocate' or 'arg')");
180}
181
182/// Replace each alloc/dealloc pair with a memref.view into the arena.
183static void rewriteAllocations(MutableArrayRef<AllocationCandidate> candidates,
184 Value arenaValue) {
185 for (auto &candidate : candidates) {
186 OpBuilder builder(candidate.alloc);
187 Location loc = candidate.alloc.getLoc();
188 MemRefType originalType = candidate.alloc.getType();
189
190 Value offsetIndex =
191 arith::ConstantIndexOp::create(builder, loc, candidate.offset);
192 auto view = memref::ViewOp::create(builder, loc, originalType, arenaValue,
193 offsetIndex, SmallVector<Value>{});
194
195 candidate.alloc.getResult().replaceAllUsesWith(view.getResult());
196 candidate.alloc.erase();
197 candidate.dealloc.erase();
198 }
199}
200
201//===----------------------------------------------------------------------===//
202// StaticMemoryPlannerAnalysisPass
203//===----------------------------------------------------------------------===//
204
205struct StaticMemoryPlannerAnalysisPass
207 StaticMemoryPlannerAnalysisPass> {
208public:
209 using Base = bufferization::impl::StaticMemoryPlannerAnalysisPassBase<
210 StaticMemoryPlannerAnalysisPass>;
211 using Base::Base;
212
213 void runOnOperation() override;
214};
215
216void StaticMemoryPlannerAnalysisPass::runOnOperation() {
217 auto funcOp = llvm::cast<FunctionOpInterface>(getOperation());
218
219 // Step 0: Check for memref return types (not supported)
220 for (Type resultType : funcOp.getResultTypes()) {
221 if (isa<BaseMemRefType>(resultType)) {
222 funcOp->emitError("static-memory-planner does not support functions "
223 "with memref return types");
224 return signalPassFailure();
225 }
226 }
227
228 // Step 1: Collect eligible allocation candidates.
229 SmallVector<AllocationCandidate> candidates =
230 collectCandidates(funcOp, numSkipDynamic, numSkipNoDealloc, numEligible);
231
232 if (candidates.empty())
233 return;
234
235 // Step 2: Build allocation descriptors with lifetime info.
236 SmallVector<bufferization::MemoryPlannerAlloc> allocInfos;
237 int64_t arenaAlignment = buildAllocInfos(candidates, allocInfos);
238
239 // Step 3: Run the planning algorithm.
240 SmallVector<int64_t> offsets;
241 switch (algorithm) {
242 case bufferization::MemoryPlannerAlgorithm::Trivial:
243 offsets = bufferization::trivialMemoryPlanner(arenaAlignment, allocInfos);
244 break;
245 case bufferization::MemoryPlannerAlgorithm::BestFit:
246 offsets = bufferization::bestFitMemoryPlanner(arenaAlignment, allocInfos);
247 break;
248 }
249
250 // Step 4: Compute total arena size and assign offsets.
251 int64_t totalSize = 0;
252 for (size_t i = 0; i < candidates.size(); ++i) {
253 candidates[i].offset = offsets[i];
254 totalSize = std::max(totalSize, offsets[i] + candidates[i].sizeInBytes);
255 LLVM_DEBUG(llvm::dbgs()
256 << "[static-memory-planner] offset=" << candidates[i].offset
257 << " size=" << candidates[i].sizeInBytes
258 << " alignment=" << candidates[i].alignment << "\n");
259 }
260
261 // Step 5: Obtain arena based on arena mode.
262 Operation *firstAlloc = candidates.front().alloc;
263 OpBuilder builder(firstAlloc);
264 FailureOr<Value> arenaValue =
265 createArena(builder, funcOp, arenaMode, totalSize, arenaAlignment);
266 if (failed(arenaValue))
267 return signalPassFailure();
268
269 // Step 6: Replace each alloc with memref.view into the arena.
270 rewriteAllocations(candidates, *arenaValue);
271}
272
273} // end anonymous namespace
Block represents an ordered list of Operations.
Definition Block.h:33
IntegerAttr getI64IntegerAttr(int64_t value)
Definition Builders.cpp:116
IntegerType getI8Type()
Definition Builders.cpp:63
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
This class helps build Operations.
Definition Builders.h:209
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
user_range getUsers() const
Definition Value.h:218
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:384
llvm::SmallVector< int64_t > trivialMemoryPlanner(int64_t arenaAlignment, llvm::ArrayRef< MemoryPlannerAlloc > allocs)
Sequential packing without lifetime overlap.
llvm::SmallVector< int64_t > bestFitMemoryPlanner(int64_t arenaAlignment, llvm::ArrayRef< MemoryPlannerAlloc > allocs)
Best-fit packing with lifetime-aware gap reuse.
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
Include the generated interface declarations.
Descriptor for a single allocation to be placed by the memory planner.