blob: a3ce9a75da92bfb1d41af746615eb3b0c8f886ad [file] [edit]
//===- ACCEraseUnusedKernelAllocations.cpp - Drop dead kernel allocs ------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Erase an allocation inside acc.compute_region when the storage is never
// read or written. Covered allocations are fir.allocmem, fir.alloca,
// memref.alloc, and memref.alloca. fir.declare's debug effect and the
// matching free (fir.freemem or memref.dealloc) keep that chain alive
// through ordinary DCE, and a dynamic allocation would otherwise be lowered
// to a checked device malloc. An unused private-recipe allocation is deleted
// the same way as an unused source array.
//
//===----------------------------------------------------------------------===//
#include "flang/Optimizer/Dialect/FIROps.h"
#include "flang/Optimizer/OpenACC/Passes.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/Dialect/OpenACC/OpenACC.h"
#include "mlir/IR/Value.h"
#include "mlir/Interfaces/ViewLikeInterface.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallVector.h"
namespace fir::acc {
#define GEN_PASS_DEF_ACCERASEUNUSEDKERNELALLOCATIONS
#include "flang/Optimizer/OpenACC/Passes.h.inc"
} // namespace fir::acc
namespace {
using namespace mlir;
// fir.freemem or memref.dealloc of \p value.
bool isDeallocOf(Operation *user, Value value) {
if (auto freeMem = dyn_cast<fir::FreeMemOp>(user))
return freeMem.getHeapref() == value;
if (auto dealloc = dyn_cast<memref::DeallocOp>(user))
return dealloc.getMemref() == value;
return false;
}
// Heap and stack allocations that privatization may turn into a device malloc.
bool isErasedAlloc(Operation *op) {
return isa<fir::AllocMemOp, fir::AllocaOp, memref::AllocOp, memref::AllocaOp>(
op);
}
// True when every use of \p value is a free of that value, a view of that
// value (ViewLikeOpInterface, including fir.convert), or a fir.declare of
// that value, and the same is true of those results. No uses is included:
// the allocation is dead. fir.declare is not view-like; it only carries
// debug info for the memref.
bool isDeadAllocChain(Value value, SmallPtrSetImpl<Operation *> &bookkeeping) {
for (Operation *user : value.getUsers()) {
if (isDeallocOf(user, value)) {
bookkeeping.insert(user);
continue;
}
if (auto view = dyn_cast<ViewLikeOpInterface>(user)) {
if (view.getViewSource() != value)
return false;
bookkeeping.insert(user);
if (!isDeadAllocChain(view.getViewDest(), bookkeeping))
return false;
continue;
}
if (auto declare = dyn_cast<fir::DeclareOp>(user)) {
if (declare.getMemref() != value)
return false;
bookkeeping.insert(declare);
if (!isDeadAllocChain(declare.getResult(), bookkeeping))
return false;
continue;
}
return false;
}
return true;
}
// Erase users before the values they consume.
void eraseDeadOps(ArrayRef<Operation *> ops) {
SmallPtrSet<Operation *, 8> pending(ops.begin(), ops.end());
while (!pending.empty()) {
Operation *ready = nullptr;
for (Operation *op : pending) {
bool usedInSet = false;
for (Value result : op->getResults()) {
for (Operation *user : result.getUsers()) {
if (pending.contains(user)) {
usedInSet = true;
break;
}
}
if (usedInSet)
break;
}
if (!usedInSet) {
ready = op;
break;
}
}
if (!ready)
return;
pending.erase(ready);
ready->erase();
}
}
class ACCEraseUnusedKernelAllocations
: public fir::acc::impl::ACCEraseUnusedKernelAllocationsBase<
ACCEraseUnusedKernelAllocations> {
public:
void runOnOperation() override {
func::FuncOp func = getOperation();
SmallVector<Operation *> dead;
func.walk([&](Operation *op) {
if (!isErasedAlloc(op) || op->getNumResults() != 1)
return;
if (!op->getParentOfType<acc::ComputeRegionOp>())
return;
SmallPtrSet<Operation *, 8> bookkeeping;
if (!isDeadAllocChain(op->getResult(0), bookkeeping))
return;
dead.push_back(op);
});
for (Operation *alloc : dead) {
SmallPtrSet<Operation *, 8> bookkeeping;
if (!isDeadAllocChain(alloc->getResult(0), bookkeeping))
continue;
SmallVector<Operation *> toErase(bookkeeping.begin(), bookkeeping.end());
toErase.push_back(alloc);
eraseDeadOps(toErase);
}
}
};
} // namespace