2020-06-05 06:55:26 +08:00
|
|
|
//===- Hoisting.cpp - Linalg hoisting transformations ---------------------===//
|
|
|
|
//
|
|
|
|
// 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 functions concerned with hoisting invariant operations
|
|
|
|
// in the context of Linalg transformations.
|
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
#include "mlir/Dialect/Linalg/Transforms/Hoisting.h"
|
2020-06-05 18:35:46 +08:00
|
|
|
#include "mlir/Analysis/SliceAnalysis.h"
|
2020-06-05 06:55:26 +08:00
|
|
|
#include "mlir/Dialect/Linalg/IR/LinalgOps.h"
|
|
|
|
#include "mlir/Dialect/SCF/SCF.h"
|
2020-06-05 18:35:46 +08:00
|
|
|
#include "mlir/Dialect/SCF/Utils.h"
|
2020-06-05 06:55:26 +08:00
|
|
|
#include "mlir/Dialect/StandardOps/IR/Ops.h"
|
2020-06-05 18:35:46 +08:00
|
|
|
#include "mlir/Dialect/Vector/VectorOps.h"
|
2020-11-17 02:52:10 +08:00
|
|
|
#include "mlir/Dialect/Vector/VectorUtils.h"
|
2020-11-20 02:43:12 +08:00
|
|
|
#include "mlir/IR/BuiltinOps.h"
|
2020-06-05 18:35:46 +08:00
|
|
|
#include "mlir/IR/Dominance.h"
|
|
|
|
#include "mlir/Transforms/LoopUtils.h"
|
2020-06-05 06:55:26 +08:00
|
|
|
#include "llvm/ADT/StringRef.h"
|
|
|
|
#include "llvm/Support/Debug.h"
|
|
|
|
|
|
|
|
#define DEBUG_TYPE "linalg-hoisting"
|
|
|
|
|
|
|
|
#define DBGS() (dbgs() << '[' << DEBUG_TYPE << "] ")
|
|
|
|
|
|
|
|
using namespace mlir;
|
|
|
|
using namespace mlir::linalg;
|
|
|
|
|
|
|
|
using llvm::dbgs;
|
|
|
|
|
|
|
|
void mlir::linalg::hoistViewAllocOps(FuncOp func) {
|
|
|
|
bool changed = true;
|
|
|
|
while (changed) {
|
|
|
|
changed = false;
|
|
|
|
func.walk([&changed](Operation *op) {
|
2020-06-25 02:49:30 +08:00
|
|
|
if (!isa<AllocOp, AllocaOp, DeallocOp>(op))
|
2020-06-05 06:55:26 +08:00
|
|
|
return;
|
|
|
|
|
|
|
|
LLVM_DEBUG(DBGS() << "Candidate for hoisting: " << *op << "\n");
|
|
|
|
auto loop = dyn_cast<scf::ForOp>(op->getParentOp());
|
|
|
|
LLVM_DEBUG(DBGS() << "Parent op: " << *op->getParentOp() << "\n");
|
|
|
|
|
|
|
|
// Only hoist out of immediately enclosing scf::ForOp.
|
|
|
|
if (!loop)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// If any operand is defined inside the loop don't hoist.
|
|
|
|
if (llvm::any_of(op->getOperands(), [&](Value v) {
|
|
|
|
return !loop.isDefinedOutsideOfLoop(v);
|
|
|
|
}))
|
|
|
|
return;
|
|
|
|
|
|
|
|
LLVM_DEBUG(DBGS() << "All operands defined outside \n");
|
|
|
|
|
|
|
|
// If alloc has other uses than ViewLikeOp and DeallocOp don't hoist.
|
|
|
|
Value v;
|
|
|
|
if (op->getNumResults() > 0) {
|
|
|
|
assert(op->getNumResults() == 1 && "Unexpected multi-result alloc");
|
|
|
|
v = op->getResult(0);
|
|
|
|
}
|
|
|
|
if (v && !llvm::all_of(v.getUses(), [&](OpOperand &operand) {
|
2020-06-25 02:49:30 +08:00
|
|
|
return isa<ViewLikeOpInterface, DeallocOp>(operand.getOwner());
|
2020-06-05 06:55:26 +08:00
|
|
|
})) {
|
|
|
|
LLVM_DEBUG(DBGS() << "Found non view-like or dealloc use: bail\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Move AllocOp before the loop.
|
2020-06-25 02:49:30 +08:00
|
|
|
if (isa<AllocOp, AllocaOp>(op))
|
2020-06-05 06:55:26 +08:00
|
|
|
loop.moveOutOfLoop({op});
|
|
|
|
else // Move DeallocOp outside of the loop.
|
|
|
|
op->moveAfter(loop);
|
|
|
|
changed = true;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2020-06-05 18:35:46 +08:00
|
|
|
|
|
|
|
void mlir::linalg::hoistRedundantVectorTransfers(FuncOp func) {
|
|
|
|
bool changed = true;
|
|
|
|
while (changed) {
|
|
|
|
changed = false;
|
|
|
|
|
|
|
|
func.walk([&](vector::TransferReadOp transferRead) {
|
|
|
|
LLVM_DEBUG(DBGS() << "Candidate for hoisting: "
|
|
|
|
<< *transferRead.getOperation() << "\n");
|
2020-12-09 18:50:18 +08:00
|
|
|
auto loop = dyn_cast<scf::ForOp>(transferRead->getParentOp());
|
|
|
|
LLVM_DEBUG(DBGS() << "Parent op: " << *transferRead->getParentOp()
|
2020-06-05 18:35:46 +08:00
|
|
|
<< "\n");
|
|
|
|
if (!loop)
|
|
|
|
return WalkResult::advance();
|
|
|
|
|
|
|
|
if (failed(moveLoopInvariantCode(
|
|
|
|
cast<LoopLikeOpInterface>(loop.getOperation()))))
|
|
|
|
llvm_unreachable(
|
|
|
|
"Unexpected failure to move invariant code out of loop");
|
|
|
|
|
|
|
|
LLVM_DEBUG(DBGS() << "Candidate read: " << *transferRead.getOperation()
|
|
|
|
<< "\n");
|
|
|
|
|
|
|
|
llvm::SetVector<Operation *> forwardSlice;
|
|
|
|
getForwardSlice(transferRead, &forwardSlice);
|
|
|
|
|
|
|
|
// Look for the last TransferWriteOp in the forwardSlice of
|
|
|
|
// `transferRead` that operates on the same memref.
|
|
|
|
vector::TransferWriteOp transferWrite;
|
|
|
|
for (auto *sliceOp : llvm::reverse(forwardSlice)) {
|
|
|
|
auto candidateWrite = dyn_cast<vector::TransferWriteOp>(sliceOp);
|
2020-12-18 08:26:07 +08:00
|
|
|
if (!candidateWrite || candidateWrite.source() != transferRead.source())
|
2020-06-05 18:35:46 +08:00
|
|
|
continue;
|
|
|
|
transferWrite = candidateWrite;
|
|
|
|
}
|
|
|
|
|
|
|
|
// All operands of the TransferRead must be defined outside of the loop.
|
|
|
|
for (auto operand : transferRead.getOperands())
|
|
|
|
if (!loop.isDefinedOutsideOfLoop(operand))
|
|
|
|
return WalkResult::advance();
|
|
|
|
|
|
|
|
// Only hoist transfer_read / transfer_write pairs for now.
|
|
|
|
if (!transferWrite)
|
|
|
|
return WalkResult::advance();
|
|
|
|
|
|
|
|
LLVM_DEBUG(DBGS() << "Candidate: " << *transferWrite.getOperation()
|
|
|
|
<< "\n");
|
|
|
|
|
|
|
|
// Approximate aliasing by checking that:
|
|
|
|
// 1. indices are the same,
|
2020-07-11 05:55:04 +08:00
|
|
|
// 2. no other operations in the loop access the same memref except
|
|
|
|
// for transfer_read/transfer_write accessing statically disjoint
|
|
|
|
// slices.
|
|
|
|
if (transferRead.indices() != transferWrite.indices() &&
|
|
|
|
transferRead.getVectorType() == transferWrite.getVectorType())
|
2020-06-05 18:35:46 +08:00
|
|
|
return WalkResult::advance();
|
|
|
|
|
|
|
|
// TODO: may want to memoize this information for performance but it
|
|
|
|
// likely gets invalidated often.
|
|
|
|
DominanceInfo dom(loop);
|
|
|
|
if (!dom.properlyDominates(transferRead.getOperation(), transferWrite))
|
|
|
|
return WalkResult::advance();
|
2020-12-18 08:26:07 +08:00
|
|
|
for (auto &use : transferRead.source().getUses()) {
|
2020-07-11 05:55:04 +08:00
|
|
|
if (!dom.properlyDominates(loop, use.getOwner()))
|
|
|
|
continue;
|
|
|
|
if (use.getOwner() == transferRead.getOperation() ||
|
|
|
|
use.getOwner() == transferWrite.getOperation())
|
|
|
|
continue;
|
|
|
|
if (auto transferWriteUse =
|
|
|
|
dyn_cast<vector::TransferWriteOp>(use.getOwner())) {
|
2020-11-17 02:52:10 +08:00
|
|
|
if (!isDisjointTransferSet(
|
2020-07-20 20:15:31 +08:00
|
|
|
cast<VectorTransferOpInterface>(transferWrite.getOperation()),
|
|
|
|
cast<VectorTransferOpInterface>(
|
|
|
|
transferWriteUse.getOperation())))
|
2020-07-11 05:55:04 +08:00
|
|
|
return WalkResult::advance();
|
|
|
|
} else if (auto transferReadUse =
|
|
|
|
dyn_cast<vector::TransferReadOp>(use.getOwner())) {
|
2020-11-17 02:52:10 +08:00
|
|
|
if (!isDisjointTransferSet(
|
2020-07-20 20:15:31 +08:00
|
|
|
cast<VectorTransferOpInterface>(transferWrite.getOperation()),
|
|
|
|
cast<VectorTransferOpInterface>(
|
|
|
|
transferReadUse.getOperation())))
|
2020-07-11 05:55:04 +08:00
|
|
|
return WalkResult::advance();
|
|
|
|
} else {
|
|
|
|
// Unknown use, we cannot prove that it doesn't alias with the
|
|
|
|
// transferRead/transferWrite operations.
|
2020-06-05 18:35:46 +08:00
|
|
|
return WalkResult::advance();
|
2020-07-11 05:55:04 +08:00
|
|
|
}
|
|
|
|
}
|
2020-06-05 18:35:46 +08:00
|
|
|
|
|
|
|
// Hoist read before.
|
|
|
|
if (failed(loop.moveOutOfLoop({transferRead})))
|
|
|
|
llvm_unreachable(
|
|
|
|
"Unexpected failure to move transfer read out of loop");
|
|
|
|
|
|
|
|
// Hoist write after.
|
2020-11-28 20:35:55 +08:00
|
|
|
transferWrite->moveAfter(loop);
|
2020-06-05 18:35:46 +08:00
|
|
|
|
|
|
|
// Rewrite `loop` with new yields by cloning and erase the original loop.
|
|
|
|
OpBuilder b(transferRead);
|
|
|
|
auto newForOp = cloneWithNewYields(b, loop, transferRead.vector(),
|
|
|
|
transferWrite.vector());
|
|
|
|
|
|
|
|
// Transfer write has been hoisted, need to update the written value to
|
|
|
|
// the value yielded by the newForOp.
|
|
|
|
transferWrite.vector().replaceAllUsesWith(
|
|
|
|
newForOp.getResults().take_back()[0]);
|
|
|
|
|
|
|
|
changed = true;
|
|
|
|
loop.erase();
|
|
|
|
// Need to interrupt and restart because erasing the loop messes up the
|
|
|
|
// walk.
|
|
|
|
return WalkResult::interrupt();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|