#include "PassDetail.h"
#include "mlir/Dialect/Affine/Analysis/AffineAnalysis.h"
#include "mlir/Dialect/Affine/Analysis/LoopAnalysis.h"
#include "mlir/Dialect/Affine/Analysis/Utils.h"
#include "mlir/Dialect/Affine/IR/AffineOps.h"
#include "mlir/Dialect/Affine/LoopUtils.h"
#include "mlir/Dialect/Affine/Utils.h"
#include "mlir/Dialect/Arithmetic/Utils/Utils.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/IR/Builders.h"
#include "mlir/Transforms/Passes.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/Support/Debug.h"
#define DEBUG_TYPE "affine-pipeline-data-transfer"
using namespace mlir;
namespace {
struct PipelineDataTransfer
: public AffinePipelineDataTransferBase<PipelineDataTransfer> {
void runOnOperation() override;
void runOnAffineForOp(AffineForOp forOp);
std::vector<AffineForOp> forOps;
};
}
std::unique_ptr<OperationPass<func::FuncOp>>
mlir::createPipelineDataTransferPass() {
return std::make_unique<PipelineDataTransfer>();
}
static unsigned getTagMemRefPos(Operation &dmaOp) {
assert((isa<AffineDmaStartOp, AffineDmaWaitOp>(dmaOp)));
if (auto dmaStartOp = dyn_cast<AffineDmaStartOp>(dmaOp)) {
return dmaStartOp.getTagMemRefOperandIndex();
}
return 0;
}
static bool doubleBuffer(Value oldMemRef, AffineForOp forOp) {
auto *forBody = forOp.getBody();
OpBuilder bInner(forBody, forBody->begin());
auto doubleShape = [&](MemRefType oldMemRefType) -> MemRefType {
ArrayRef<int64_t> oldShape = oldMemRefType.getShape();
SmallVector<int64_t, 4> newShape(1 + oldMemRefType.getRank());
newShape[0] = 2;
std::copy(oldShape.begin(), oldShape.end(), newShape.begin() + 1);
return MemRefType::Builder(oldMemRefType).setShape(newShape).setLayout({});
};
auto oldMemRefType = oldMemRef.getType().cast<MemRefType>();
auto newMemRefType = doubleShape(oldMemRefType);
OpBuilder bOuter(forOp);
SmallVector<Value, 4> allocOperands;
for (const auto &dim : llvm::enumerate(oldMemRefType.getShape())) {
if (dim.value() == ShapedType::kDynamicSize)
allocOperands.push_back(bOuter.createOrFold<memref::DimOp>(
forOp.getLoc(), oldMemRef, dim.index()));
}
Value newMemRef = bOuter.create<memref::AllocOp>(
forOp.getLoc(), newMemRefType, allocOperands);
auto d0 = bInner.getAffineDimExpr(0);
int64_t step = forOp.getStep();
auto modTwoMap =
AffineMap::get(1, 0, d0.floorDiv(step) % 2);
auto ivModTwoOp = bInner.create<AffineApplyOp>(forOp.getLoc(), modTwoMap,
forOp.getInductionVar());
if (failed(replaceAllMemRefUsesWith(
oldMemRef, newMemRef,
{ivModTwoOp},
AffineMap(),
{},
{},
&*forOp.getBody()->begin()))) {
LLVM_DEBUG(
forOp.emitError("memref replacement for double buffering failed"));
ivModTwoOp.erase();
return false;
}
bOuter.setInsertionPointAfter(forOp);
bOuter.create<memref::DeallocOp>(forOp.getLoc(), newMemRef);
return true;
}
void PipelineDataTransfer::runOnOperation() {
forOps.clear();
getOperation().walk([&](AffineForOp forOp) { forOps.push_back(forOp); });
for (auto forOp : forOps)
runOnAffineForOp(forOp);
}
static bool checkTagMatch(AffineDmaStartOp startOp, AffineDmaWaitOp waitOp) {
if (startOp.getTagMemRef() != waitOp.getTagMemRef())
return false;
auto startIndices = startOp.getTagIndices();
auto waitIndices = waitOp.getTagIndices();
for (auto it = startIndices.begin(), wIt = waitIndices.begin(),
e = startIndices.end();
it != e; ++it, ++wIt) {
if (*it != *wIt)
return false;
}
return true;
}
static void findMatchingStartFinishInsts(
AffineForOp forOp,
SmallVectorImpl<std::pair<Operation *, Operation *>> &startWaitPairs) {
SmallVector<AffineDmaStartOp, 4> outgoingDmaOps;
for (auto &op : *forOp.getBody()) {
auto dmaStartOp = dyn_cast<AffineDmaStartOp>(op);
if (dmaStartOp && dmaStartOp.isSrcMemorySpaceFaster())
outgoingDmaOps.push_back(dmaStartOp);
}
SmallVector<Operation *, 4> dmaStartInsts, dmaFinishInsts;
for (auto &op : *forOp.getBody()) {
if (isa<AffineDmaWaitOp>(op)) {
dmaFinishInsts.push_back(&op);
continue;
}
auto dmaStartOp = dyn_cast<AffineDmaStartOp>(op);
if (!dmaStartOp)
continue;
if (!dmaStartOp.isDestMemorySpaceFaster())
continue;
auto *it = outgoingDmaOps.begin();
for (; it != outgoingDmaOps.end(); ++it) {
if (it->getDstMemRef() == dmaStartOp.getSrcMemRef())
break;
}
if (it != outgoingDmaOps.end())
continue;
auto memref = dmaStartOp.getOperand(dmaStartOp.getFasterMemPos());
bool escapingUses = false;
for (auto *user : memref.getUsers()) {
if (isa<memref::DeallocOp>(user))
continue;
if (!forOp.getBody()->findAncestorOpInBlock(*user)) {
LLVM_DEBUG(llvm::dbgs()
<< "can't pipeline: buffer is live out of loop\n";);
escapingUses = true;
break;
}
}
if (!escapingUses)
dmaStartInsts.push_back(&op);
}
for (auto *dmaStartOp : dmaStartInsts) {
for (auto *dmaFinishOp : dmaFinishInsts) {
if (checkTagMatch(cast<AffineDmaStartOp>(dmaStartOp),
cast<AffineDmaWaitOp>(dmaFinishOp))) {
startWaitPairs.push_back({dmaStartOp, dmaFinishOp});
break;
}
}
}
}
void PipelineDataTransfer::runOnAffineForOp(AffineForOp forOp) {
auto mayBeConstTripCount = getConstantTripCount(forOp);
if (!mayBeConstTripCount) {
LLVM_DEBUG(forOp.emitRemark("won't pipeline due to unknown trip count"));
return;
}
SmallVector<std::pair<Operation *, Operation *>, 4> startWaitPairs;
findMatchingStartFinishInsts(forOp, startWaitPairs);
if (startWaitPairs.empty()) {
LLVM_DEBUG(forOp.emitRemark("No dma start/finish pairs\n"));
return;
}
for (auto &pair : startWaitPairs) {
auto *dmaStartOp = pair.first;
Value oldMemRef = dmaStartOp->getOperand(
cast<AffineDmaStartOp>(dmaStartOp).getFasterMemPos());
if (!doubleBuffer(oldMemRef, forOp)) {
LLVM_DEBUG(llvm::dbgs()
<< "double buffering failed for" << dmaStartOp << "\n";);
return;
}
if (auto *allocOp = oldMemRef.getDefiningOp()) {
if (oldMemRef.use_empty()) {
allocOp->erase();
} else if (oldMemRef.hasOneUse()) {
if (auto dealloc =
dyn_cast<memref::DeallocOp>(*oldMemRef.user_begin())) {
dealloc.erase();
allocOp->erase();
}
}
}
}
for (auto &pair : startWaitPairs) {
auto *dmaFinishOp = pair.second;
Value oldTagMemRef = dmaFinishOp->getOperand(getTagMemRefPos(*dmaFinishOp));
if (!doubleBuffer(oldTagMemRef, forOp)) {
LLVM_DEBUG(llvm::dbgs() << "tag double buffering failed\n";);
return;
}
if (auto *tagAllocOp = oldTagMemRef.getDefiningOp()) {
if (oldTagMemRef.use_empty()) {
tagAllocOp->erase();
} else if (oldTagMemRef.hasOneUse()) {
if (auto dealloc =
dyn_cast<memref::DeallocOp>(*oldTagMemRef.user_begin())) {
dealloc.erase();
tagAllocOp->erase();
}
}
}
}
startWaitPairs.clear();
findMatchingStartFinishInsts(forOp, startWaitPairs);
DenseMap<Operation *, unsigned> instShiftMap;
for (auto &pair : startWaitPairs) {
auto *dmaStartOp = pair.first;
assert(isa<AffineDmaStartOp>(dmaStartOp));
instShiftMap[dmaStartOp] = 0;
SmallVector<AffineApplyOp, 4> sliceOps;
mlir::createAffineComputationSlice(dmaStartOp, &sliceOps);
if (!sliceOps.empty()) {
for (auto sliceOp : sliceOps) {
instShiftMap[sliceOp.getOperation()] = 0;
}
} else {
SmallVector<Operation *, 4> affineApplyInsts;
SmallVector<Value, 4> operands(dmaStartOp->getOperands());
getReachableAffineApplyOps(operands, affineApplyInsts);
for (auto *op : affineApplyInsts) {
instShiftMap[op] = 0;
}
}
}
for (auto &op : forOp.getBody()->without_terminator())
if (instShiftMap.find(&op) == instShiftMap.end())
instShiftMap[&op] = 1;
SmallVector<uint64_t, 8> shifts(forOp.getBody()->getOperations().size());
unsigned s = 0;
for (auto &op : forOp.getBody()->without_terminator()) {
assert(instShiftMap.find(&op) != instShiftMap.end());
shifts[s++] = instShiftMap[&op];
LLVM_DEBUG({
OpBuilder b(&op);
op.setAttr("shift", b.getI64IntegerAttr(shifts[s - 1]));
});
}
if (!isOpwiseShiftValid(forOp, shifts)) {
LLVM_DEBUG(llvm::dbgs() << "Shifts invalid - unexpected\n";);
return;
}
if (failed(affineForOpBodySkew(forOp, shifts))) {
LLVM_DEBUG(llvm::dbgs() << "op body skewing failed - unexpected\n";);
return;
}
}