Adding a pass that is expected to run after the deallocation pipeline and will move buffer deallocations right after their last user or dependency, thus optimizing the allocation liveness.
162 lines
5.5 KiB
C++
162 lines
5.5 KiB
C++
//===- 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/Dialect/MemRef/IR/MemRef.h"
|
|
#include "mlir/IR/Operation.h"
|
|
#include "llvm/Support/Debug.h"
|
|
|
|
#define DEBUG_TYPE "optimize-allocation-liveness"
|
|
#define DBGS() (llvm::dbgs() << '[' << DEBUG_TYPE << "] ")
|
|
#define LDBG(X) LLVM_DEBUG(DBGS() << X << "\n")
|
|
|
|
namespace mlir {
|
|
namespace bufferization {
|
|
#define GEN_PASS_DEF_OPTIMIZEALLOCATIONLIVENESS
|
|
#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<MemoryEffectOpInterface>(user)) {
|
|
SmallVector<MemoryEffects::EffectInstance, 2> effects;
|
|
memEffectOp.getEffects(effects);
|
|
|
|
for (const auto &effect : effects) {
|
|
if (isa<MemoryEffects::Free>(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<MemoryEffects::EffectInstance, 2> effects;
|
|
memEffectOp.getEffects(effects);
|
|
for (const auto &effect : effects) {
|
|
if (isa<MemoryEffects::Allocate>(effect.getEffect())) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
struct OptimizeAllocationLiveness
|
|
: public bufferization::impl::OptimizeAllocationLivenessBase<
|
|
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);
|
|
|
|
auto deallocOp = findUserWithFreeSideEffect(allocOp->getResult(0));
|
|
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(allocOp->getResult(0));
|
|
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
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// OptimizeAllocatinliveness construction
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
std::unique_ptr<Pass>
|
|
mlir::bufferization::createOptimizeAllocationLivenessPass() {
|
|
return std::make_unique<OptimizeAllocationLiveness>();
|
|
}
|