//===- OptimizeAllocationLiveness.cpp - impl. optimize allocation liveness pass //-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This file implements a pass for optimizing allocation liveness. // The pass moves the deallocation operation after the last user of the // allocated buffer. //===----------------------------------------------------------------------===// #include "mlir/Dialect/Bufferization/Transforms/BufferViewFlowAnalysis.h" #include "mlir/Dialect/Bufferization/Transforms/Passes.h" #include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/IR/Operation.h" #include "mlir/IR/Value.h" #include "mlir/Interfaces/SideEffectInterfaces.h" #include "llvm/Support/DebugLog.h" #define DEBUG_TYPE "optimize-allocation-liveness" namespace mlir { namespace bufferization { #define GEN_PASS_DEF_OPTIMIZEALLOCATIONLIVENESSPASS #include "mlir/Dialect/Bufferization/Transforms/Passes.h.inc" } // namespace bufferization } // namespace mlir using namespace mlir; namespace { //===----------------------------------------------------------------------===// // Helper functions //===----------------------------------------------------------------------===// /// Return true if `a` happens before `b`, i.e., `a` or one of its ancestors /// properly dominates `b` and `b` is not inside `a`. static bool happensBefore(Operation *a, Operation *b) { do { if (a->isProperAncestor(b)) return false; if (Operation *bAncestor = a->getBlock()->findAncestorOpInBlock(*b)) { return a->isBeforeInBlock(bAncestor); } } while ((a = a->getParentOp())); return false; } /// This method searches for a user of value that is a dealloc operation. /// If multiple users with free effect are found, return nullptr. Operation *findUserWithFreeSideEffect(Value value) { Operation *freeOpUser = nullptr; for (Operation *user : value.getUsers()) { if (MemoryEffectOpInterface memEffectOp = dyn_cast(user)) { SmallVector effects; memEffectOp.getEffects(effects); for (const auto &effect : effects) { if (isa(effect.getEffect())) { if (freeOpUser) { LDBG() << "Multiple users with free effect found: " << *freeOpUser << " and " << *user; return nullptr; } freeOpUser = user; } } } } return freeOpUser; } /// Checks if the given op allocates memory. static bool hasMemoryAllocEffect(MemoryEffectOpInterface memEffectOp) { SmallVector effects; memEffectOp.getEffects(effects); for (const auto &effect : effects) { if (isa(effect.getEffect())) { return true; } } return false; } /// Extracts OpResult's with Allocate effects from given op static SmallVector collectAllocations(MemoryEffectOpInterface allocOp) { SmallVector effects; allocOp.getEffects(effects); SmallVector allocResults; for (const MemoryEffects::EffectInstance &it : effects) if (isa(it.getEffect())) if (auto val = it.getValue(); val && val.getDefiningOp() == allocOp) allocResults.push_back(cast(val)); return allocResults; } struct OptimizeAllocationLiveness : public bufferization::impl::OptimizeAllocationLivenessPassBase< OptimizeAllocationLiveness> { public: OptimizeAllocationLiveness() = default; void runOnOperation() override { func::FuncOp func = getOperation(); if (func.isExternal()) return; BufferViewFlowAnalysis analysis = BufferViewFlowAnalysis(func); func.walk([&](MemoryEffectOpInterface memEffectOp) -> WalkResult { if (!hasMemoryAllocEffect(memEffectOp)) return WalkResult::advance(); auto allocOp = memEffectOp; LDBG() << "Checking alloc op: " << allocOp; SmallVector allocationResults = collectAllocations(allocOp); // Multiple allocations from a single op are not considered here yet. if (allocationResults.size() != 1) return WalkResult::advance(); OpResult allocResult = allocationResults[0]; LDBG() << "On allocation result: " << allocResult; auto *deallocOp = findUserWithFreeSideEffect(allocResult); if (!deallocOp || (deallocOp->getBlock() != allocOp->getBlock())) { // The pass handles allocations that have a single dealloc op in the // same block. We also should not hoist the dealloc op out of // conditionals. return WalkResult::advance(); } Operation *lastUser = nullptr; const BufferViewFlowAnalysis::ValueSetT &deps = analysis.resolve(allocResult); for (auto dep : llvm::make_early_inc_range(deps)) { for (auto *user : dep.getUsers()) { // We are looking for a non dealloc op user. // check if user is the dealloc op itself. if (user == deallocOp) continue; // find the ancestor of user that is in the same block as the allocOp. auto *topUser = allocOp->getBlock()->findAncestorOpInBlock(*user); if (!lastUser || happensBefore(lastUser, topUser)) { lastUser = topUser; } } } if (lastUser == nullptr) { return WalkResult::advance(); } LDBG() << "Last user found: " << *lastUser; assert(lastUser->getBlock() == allocOp->getBlock()); assert(lastUser->getBlock() == deallocOp->getBlock()); // Move the dealloc op after the last user. deallocOp->moveAfter(lastUser); LDBG() << "Moved dealloc op after: " << *lastUser; return WalkResult::advance(); }); } }; } // end anonymous namespace