17#include "mlir/Dialect/Affine/IR/AffineOps.h"
18#include "mlir/Dialect/MemRef/IR/MemRef.h"
19#include "mlir/Dialect/SCF/IR/SCF.h"
20#include "mlir/Interfaces/LoopLikeInterface.h"
21#include "mlir/Interfaces/SideEffectInterfaces.h"
22#include "mlir/Interfaces/ViewLikeInterface.h"
26#define GEN_PASS_DEF_HOISTLOOPALLOCATIONSPASS
27#include "Passes/Passes.h.inc"
39static Value getAllocatedBuffer(Operation *op) {
40 auto iface = dyn_cast<MemoryEffectOpInterface>(op);
41 if (!iface || op->getNumResults() != 1 ||
42 !isa<MemRefType>(op->getResult(0).getType()))
45 SmallVector<MemoryEffects::EffectInstance> effects;
46 iface.getEffects(effects);
48 bool allocates =
false;
49 for (
const MemoryEffects::EffectInstance &effect : effects) {
50 if (!isa<MemoryEffects::Allocate>(effect.getEffect()))
52 if (effect.getValue() != op->getResult(0))
56 return allocates ? op->getResult(0) : Value();
70static Operation *findPairedDealloc(Value buffer, Operation *loop) {
71 SmallVector<Value> worklist{buffer};
72 SmallPtrSet<Value, 4> seen;
73 Operation *dealloc =
nullptr;
75 while (!worklist.empty()) {
76 Value alias = worklist.pop_back_val();
77 if (!seen.insert(alias).second)
80 for (OpOperand &use : alias.getUses()) {
81 Operation *user = use.getOwner();
85 if (!loop->isProperAncestor(user))
88 if (hasSingleEffect<MemoryEffects::Free>(user, alias)) {
92 if (alias != buffer || dealloc)
100 if (isa<ViewLikeOpInterface>(user)) {
101 worklist.append(user->result_begin(), user->result_end());
107 if (user->hasTrait<OpTrait::IsTerminator>() || user->getNumRegions() != 0)
110 auto iface = dyn_cast<MemoryEffectOpInterface>(user);
114 SmallVector<MemoryEffects::EffectInstance> effects;
115 iface.getEffectsOnValue(alias, effects);
118 for (
const MemoryEffects::EffectInstance &effect : effects)
119 if (!isa<MemoryEffects::Read, MemoryEffects::Write>(effect.getEffect()))
124 if (llvm::any_of(user->getResultTypes(), llvm::IsaPred<MemRefType>))
136static bool fitsBudget(Value buffer, uint64_t maxHoistedBytes) {
137 if (maxHoistedBytes == 0)
140 auto type = cast<MemRefType>(buffer.getType());
141 if (!type.hasStaticShape() || !type.getElementType().isIntOrFloat())
144 uint64_t bytes = llvm::divideCeil(type.getElementTypeBitWidth(), 8);
145 return type.getNumElements() * bytes <= maxHoistedBytes;
150static bool isSupportedLoop(Operation *op) {
151 return isa<scf::ForOp, affine::AffineForOp>(op);
157 uint64_t maxHoistedBytes) {
158 if (!isSupportedLoop(loop))
161 SmallVector<Region *> regions = loop.getLoopRegions();
162 if (regions.size() != 1 || !regions.front()->hasOneBlock())
164 Block *body = ®ions.front()->front();
167 SmallVector<std::pair<Operation *, Operation *>> pairs;
168 for (Operation &op : *body) {
169 Value buffer = getAllocatedBuffer(&op);
170 if (!buffer || !fitsBudget(buffer, maxHoistedBytes))
175 if (!llvm::all_of(op.getOperands(), [&](Value operand) {
176 return loop.isDefinedOutsideOfLoop(operand);
180 Operation *dealloc = findPairedDealloc(buffer, loop);
187 if (!dealloc || dealloc->getBlock() != body)
190 pairs.emplace_back(&op, dealloc);
193 for (
auto [alloc, dealloc] : pairs) {
194 alloc->moveBefore(loop);
195 dealloc->moveAfter(loop);
197 return !pairs.empty();
202struct HoistLoopAllocationsPass
203 :
public enzyme::impl::HoistLoopAllocationsPassBase<
204 HoistLoopAllocationsPass> {
205 using HoistLoopAllocationsPassBase::HoistLoopAllocationsPassBase;
207 void runOnOperation()
override {
211 SmallVector<LoopLikeOpInterface> loops;
212 getOperation()->walk([&](LoopLikeOpInterface loop) {
213 if (isSupportedLoop(loop))
214 loops.push_back(loop);
217 for (LoopLikeOpInterface loop : loops)
bool hoistLoopAllocations(LoopLikeOpInterface loop, uint64_t maxHoistedBytes=0)
Moves allocation/deallocation pairs that are private to one iteration of loop out of it,...