22#include "llvm/ADT/SmallPtrSet.h"
23#include "llvm/Support/Debug.h"
26#define DEBUG_TYPE "static-memory-planner"
30#define GEN_PASS_DEF_STATICMEMORYPLANNERANALYSISPASS
31#include "mlir/Dialect/Bufferization/Transforms/Passes.h.inc"
42struct AllocationCandidate {
43 memref::AllocOp alloc;
44 SmallVector<memref::DeallocOp> deallocs;
46 int64_t sizeInBytes = 0;
47 int64_t alignment = 1;
73 if (
auto dealloc = dyn_cast<memref::DeallocOp>(user))
74 deallocs.push_back(dealloc);
82findFreedAllocs(memref::DeallocOp dealloc,
85 for (
Value source :
analysis.resolveReverse(dealloc.getMemref()))
86 if (
auto allocOp = source.getDefiningOp<memref::AllocOp>())
87 allocs.push_back(allocOp);
92static int64_t computeSizeInBytes(MemRefType memrefType) {
93 int64_t numElements = memrefType.getNumElements();
94 unsigned elementSizeInBits = memrefType.getElementTypeBitWidth();
95 return (numElements * elementSizeInBits + 7) / 8;
106 Block *planBlock =
nullptr;
107 if (!candidates.empty()) {
108 planBlock = candidates.front().alloc->getBlock();
111 opIndex[&op] = idx++;
115 for (
auto &candidate : candidates) {
119 info.
timeStart = opIndex.lookup(candidate.alloc.getOperation());
124 for (memref::DeallocOp d : candidate.deallocs) {
126 timeEnd = std::max(timeEnd, opIndex.lookup(anchor));
129 allocInfos.push_back(info);
130 arenaAlignment = std::lcm(arenaAlignment, candidate.alignment);
132 return arenaAlignment;
154collectCandidates(FunctionOpInterface funcOp,
156 llvm::Statistic &numSkipDynamic,
157 llvm::Statistic &numSkipNested, llvm::Statistic &numEligible,
160 if (funcOp.getFunctionBody().empty())
162 Block *planBlock = &funcOp.getFunctionBody().
front();
164 bool walkFailed =
false;
166 MemRefType memrefType = allocOp.getType();
167 if (!memrefType.hasStaticShape()) {
174 if (allocOp->getBlock() != planBlock) {
180 collectDeallocs(allocOp.getResult(), analysis, deallocs);
182 if (deallocs.empty()) {
183 allocOp.emitError(
"no dealloc found; run the deallocation pipeline "
189 for (memref::DeallocOp d : deallocs) {
193 allocOp.emitError(
"unstructured control flow is not supported");
199 for (memref::AllocOp freed : findFreedAllocs(d, analysis)) {
200 if (freed->getBlock() != planBlock) {
208 AllocationCandidate candidate;
209 candidate.alloc = allocOp;
210 candidate.deallocs = deallocs;
211 candidate.sizeInBytes = computeSizeInBytes(memrefType);
212 candidate.alignment = allocOp.getAlignment().value_or(1);
213 candidates.push_back(candidate);
217 return failure(walkFailed);
222static FailureOr<Value> createArena(
OpBuilder &builder,
223 FunctionOpInterface funcOp,
224 StringRef arenaMode,
int64_t totalSize,
228 if (arenaMode ==
"allocate") {
229 auto arenaType = MemRefType::get({totalSize}, builder.
getI8Type());
231 memref::AllocOp::create(builder, loc, arenaType,
ValueRange{},
233 LLVM_DEBUG(llvm::dbgs()
234 <<
"[static-memory-planner] created arena via AllocOp: size="
235 << totalSize <<
" bytes, alignment=" << arenaAlignment
237 return arenaAlloc.getResult();
240 if (arenaMode ==
"arg") {
241 if (funcOp.getNumArguments() == 0)
242 return funcOp->emitError(
243 "arena-mode=arg requires at least one function argument");
245 Value arenaValue = funcOp.getArgument(0);
246 auto arenaType = dyn_cast<MemRefType>(arenaValue.
getType());
247 if (!arenaType || !arenaType.getElementType().isInteger(8) ||
248 arenaType.getRank() != 1)
249 return funcOp->emitError(
250 "arena-mode=arg requires first argument to be memref<...xi8>");
252 LLVM_DEBUG(llvm::dbgs()
253 <<
"[static-memory-planner] using arena from function arg 0\n");
257 return funcOp->emitError(
"invalid arena-mode: '" + arenaMode +
258 "' (must be 'allocate' or 'arg')");
268 for (
auto &candidate : candidates) {
270 Location loc = candidate.alloc.getLoc();
271 MemRefType originalType = candidate.alloc.getType();
275 auto view = memref::ViewOp::create(builder, loc, originalType, arenaValue,
277 candidate.alloc.getResult().replaceAllUsesWith(view.getResult());
278 allocsToErase.push_back(candidate.alloc.getOperation());
280 for (memref::DeallocOp d : candidate.deallocs)
281 deallocsToErase.insert(d.getOperation());
297struct StaticMemoryPlannerAnalysisPass
298 :
public bufferization::impl::StaticMemoryPlannerAnalysisPassBase<
299 StaticMemoryPlannerAnalysisPass> {
301 using Base = bufferization::impl::StaticMemoryPlannerAnalysisPassBase<
302 StaticMemoryPlannerAnalysisPass>;
305 void runOnOperation()
override;
308void StaticMemoryPlannerAnalysisPass::runOnOperation() {
309 auto funcOp = llvm::cast<FunctionOpInterface>(getOperation());
312 for (Type resultType : funcOp.getResultTypes()) {
313 if (isa<BaseMemRefType>(resultType)) {
314 funcOp->emitError(
"static-memory-planner does not support functions "
315 "with memref return types");
316 return signalPassFailure();
323 BufferViewFlowAnalysis
analysis(funcOp);
324 SmallVector<AllocationCandidate> candidates;
325 if (
failed(collectCandidates(funcOp, analysis, numSkipDynamic, numSkipNested,
326 numEligible, candidates)))
327 return signalPassFailure();
329 if (candidates.empty())
333 SmallVector<bufferization::MemoryPlannerAlloc> allocInfos;
334 int64_t arenaAlignment = buildAllocInfos(candidates, allocInfos);
337 SmallVector<int64_t> offsets;
339 case bufferization::MemoryPlannerAlgorithm::Trivial:
342 case bufferization::MemoryPlannerAlgorithm::BestFit:
348 int64_t totalSize = 0;
349 for (
size_t i = 0; i < candidates.size(); ++i) {
350 candidates[i].offset = offsets[i];
351 totalSize = std::max(totalSize, offsets[i] + candidates[i].sizeInBytes);
352 LLVM_DEBUG(llvm::dbgs()
353 <<
"[static-memory-planner] offset=" << candidates[i].offset
354 <<
" size=" << candidates[i].sizeInBytes
355 <<
" alignment=" << candidates[i].alignment <<
"\n");
359 Operation *firstAlloc = candidates.front().alloc;
360 OpBuilder builder(firstAlloc);
361 FailureOr<Value> arenaValue =
362 createArena(builder, funcOp, arenaMode, totalSize, arenaAlignment);
364 return signalPassFailure();
367 rewriteAllocations(candidates, *arenaValue);
Block represents an ordered list of Operations.
Operation * findAncestorOpInBlock(Operation &op)
Returns 'op' if 'op' lies in this block, or otherwise finds the ancestor operation of 'op' that lies ...
A straight-forward alias analysis which ensures that all dependencies of all values will be determine...
IntegerAttr getI64IntegerAttr(int64_t value)
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
This class helps build Operations.
Operation is the basic unit of execution within MLIR.
std::enable_if_t< llvm::function_traits< std::decay_t< FnT > >::num_args==1, RetT > walk(FnT &&callback)
Walk the operation by calling the callback for each nested operation (including this one),...
user_range getUsers()
Returns a range of all users.
This class provides an abstraction over the different types of ranges over Values.
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Type getType() const
Return the type of this value.
A utility result that is used to signal how to proceed with an ongoing walk:
static WalkResult advance()
static WalkResult interrupt()
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
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.
Include the generated interface declarations.
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Descriptor for a single allocation to be placed by the memory planner.