#include "mlir/Dialect/Affine/LoopUtils.h"
#include "mlir/Analysis/SliceAnalysis.h"
#include "mlir/Dialect/Affine/Analysis/LoopAnalysis.h"
#include "mlir/Dialect/Affine/Analysis/Utils.h"
#include "mlir/Dialect/Affine/IR/AffineValueMap.h"
#include "mlir/Dialect/Affine/Utils.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/Dialect/SCF/IR/SCF.h"
#include "mlir/IR/IRMapping.h"
#include "mlir/IR/IntegerSet.h"
#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
#include "llvm/ADT/MapVector.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"
#include <optional>
#define DEBUG_TYPE "loop-utils"
using namespace mlir;
using namespace affine;
using namespace presburger;
using llvm::SmallMapVector;
static void
getCleanupLoopLowerBound(AffineForOp forOp, unsigned unrollFactor,
AffineMap &cleanupLbMap,
SmallVectorImpl<Value> &cleanupLbOperands) {
AffineMap tripCountMap;
SmallVector<Value, 4> tripCountOperands;
getTripCountMapAndOperands(forOp, &tripCountMap, &tripCountOperands);
if (!tripCountMap) {
cleanupLbMap = AffineMap();
return;
}
OpBuilder b(forOp);
auto lbMap = forOp.getLowerBoundMap();
auto lb = b.create<AffineApplyOp>(forOp.getLoc(), lbMap,
forOp.getLowerBoundOperands());
SmallVector<AffineExpr, 4> bumpExprs(tripCountMap.getNumResults());
SmallVector<Value, 4> bumpValues(tripCountMap.getNumResults());
int64_t step = forOp.getStepAsInt();
for (unsigned i = 0, e = tripCountMap.getNumResults(); i < e; i++) {
auto tripCountExpr = tripCountMap.getResult(i);
bumpExprs[i] = (tripCountExpr - tripCountExpr % unrollFactor) * step;
auto bumpMap = AffineMap::get(tripCountMap.getNumDims(),
tripCountMap.getNumSymbols(), bumpExprs[i]);
bumpValues[i] =
b.create<AffineApplyOp>(forOp.getLoc(), bumpMap, tripCountOperands);
}
SmallVector<AffineExpr, 4> newUbExprs(tripCountMap.getNumResults());
for (unsigned i = 0, e = bumpExprs.size(); i < e; i++)
newUbExprs[i] = b.getAffineDimExpr(0) + b.getAffineDimExpr(i + 1);
cleanupLbOperands.clear();
cleanupLbOperands.push_back(lb);
cleanupLbOperands.append(bumpValues.begin(), bumpValues.end());
cleanupLbMap = AffineMap::get(1 + tripCountMap.getNumResults(), 0, newUbExprs,
b.getContext());
fullyComposeAffineMapAndOperands(&cleanupLbMap, &cleanupLbOperands);
cleanupLbMap = simplifyAffineMap(cleanupLbMap);
canonicalizeMapAndOperands(&cleanupLbMap, &cleanupLbOperands);
for (auto v : bumpValues)
if (v.use_empty())
v.getDefiningOp()->erase();
if (lb.use_empty())
lb.erase();
}
static void replaceIterArgsAndYieldResults(AffineForOp forOp) {
auto iterOperands = forOp.getInits();
auto iterArgs = forOp.getRegionIterArgs();
for (auto e : llvm::zip(iterOperands, iterArgs))
std::get<1>(e).replaceAllUsesWith(std::get<0>(e));
auto outerResults = forOp.getResults();
auto innerResults = forOp.getBody()->getTerminator()->getOperands();
for (auto e : llvm::zip(outerResults, innerResults))
std::get<0>(e).replaceAllUsesWith(std::get<1>(e));
}
LogicalResult mlir::affine::promoteIfSingleIteration(AffineForOp forOp) {
std::optional<uint64_t> tripCount = getConstantTripCount(forOp);
if (!tripCount || *tripCount != 1)
return failure();
if (forOp.getLowerBoundMap().getNumResults() != 1)
return failure();
auto iv = forOp.getInductionVar();
auto *parentBlock = forOp->getBlock();
if (!iv.use_empty()) {
if (forOp.hasConstantLowerBound()) {
OpBuilder topBuilder(forOp->getParentOfType<func::FuncOp>().getBody());
auto constOp = topBuilder.create<arith::ConstantIndexOp>(
forOp.getLoc(), forOp.getConstantLowerBound());
iv.replaceAllUsesWith(constOp);
} else {
auto lbOperands = forOp.getLowerBoundOperands();
auto lbMap = forOp.getLowerBoundMap();
OpBuilder builder(forOp);
if (lbMap == builder.getDimIdentityMap()) {
iv.replaceAllUsesWith(lbOperands[0]);
} else {
auto affineApplyOp =
builder.create<AffineApplyOp>(forOp.getLoc(), lbMap, lbOperands);
iv.replaceAllUsesWith(affineApplyOp);
}
}
}
replaceIterArgsAndYieldResults(forOp);
forOp.getBody()->back().erase();
parentBlock->getOperations().splice(Block::iterator(forOp),
forOp.getBody()->getOperations());
forOp.erase();
return success();
}
static AffineForOp generateShiftedLoop(
AffineMap lbMap, AffineMap ubMap,
const std::vector<std::pair<uint64_t, ArrayRef<Operation *>>> &opGroupQueue,
unsigned offset, AffineForOp srcForOp, OpBuilder b) {
auto lbOperands = srcForOp.getLowerBoundOperands();
auto ubOperands = srcForOp.getUpperBoundOperands();
assert(lbMap.getNumInputs() == lbOperands.size());
assert(ubMap.getNumInputs() == ubOperands.size());
auto loopChunk =
b.create<AffineForOp>(srcForOp.getLoc(), lbOperands, lbMap, ubOperands,
ubMap, srcForOp.getStepAsInt());
auto loopChunkIV = loopChunk.getInductionVar();
auto srcIV = srcForOp.getInductionVar();
IRMapping operandMap;
auto bodyBuilder = OpBuilder::atBlockTerminator(loopChunk.getBody());
for (const auto &it : llvm::drop_begin(opGroupQueue, offset)) {
uint64_t shift = it.first;
auto ops = it.second;
if (!srcIV.use_empty() && shift != 0) {
auto ivRemap = bodyBuilder.create<AffineApplyOp>(
srcForOp.getLoc(),
bodyBuilder.getSingleDimShiftAffineMap(
-static_cast<int64_t>(srcForOp.getStepAsInt() * shift)),
loopChunkIV);
operandMap.map(srcIV, ivRemap);
} else {
operandMap.map(srcIV, loopChunkIV);
}
for (auto *op : ops)
bodyBuilder.clone(*op, operandMap);
};
if (succeeded(promoteIfSingleIteration(loopChunk)))
return AffineForOp();
return loopChunk;
}
LogicalResult mlir::affine::affineForOpBodySkew(AffineForOp forOp,
ArrayRef<uint64_t> shifts,
bool unrollPrologueEpilogue) {
assert(forOp.getBody()->getOperations().size() == shifts.size() &&
"too few/many shifts");
if (forOp.getBody()->begin() == std::prev(forOp.getBody()->end()))
return success();
auto mayBeConstTripCount = getConstantTripCount(forOp);
if (!mayBeConstTripCount) {
LLVM_DEBUG(forOp.emitRemark("non-constant trip count loop not handled"));
return success();
}
uint64_t tripCount = *mayBeConstTripCount;
assert(isOpwiseShiftValid(forOp, shifts) &&
"shifts will lead to an invalid transformation\n");
int64_t step = forOp.getStepAsInt();
unsigned numChildOps = shifts.size();
uint64_t maxShift = *llvm::max_element(shifts);
if (maxShift >= numChildOps) {
forOp.emitWarning("not shifting because shifts are unrealistically large");
return success();
}
std::vector<std::vector<Operation *>> sortedOpGroups(maxShift + 1);
unsigned pos = 0;
for (auto &op : forOp.getBody()->without_terminator()) {
auto shift = shifts[pos++];
sortedOpGroups[shift].push_back(&op);
}
AffineForOp prologue, epilogue;
std::vector<std::pair<uint64_t, ArrayRef<Operation *>>> opGroupQueue;
auto origLbMap = forOp.getLowerBoundMap();
uint64_t lbShift = 0;
OpBuilder b(forOp);
for (uint64_t d = 0, e = sortedOpGroups.size(); d < e; ++d) {
if (sortedOpGroups[d].empty())
continue;
if (!opGroupQueue.empty()) {
assert(d > 0 &&
"Queue expected to be empty when the first block is found");
AffineForOp res;
if (lbShift + tripCount * step < d * step) {
res = generateShiftedLoop(
b.getShiftedAffineMap(origLbMap, lbShift),
b.getShiftedAffineMap(origLbMap, lbShift + tripCount * step),
opGroupQueue, 0, forOp, b);
opGroupQueue.clear();
lbShift += tripCount * step;
} else {
res = generateShiftedLoop(b.getShiftedAffineMap(origLbMap, lbShift),
b.getShiftedAffineMap(origLbMap, d),
opGroupQueue, 0, forOp, b);
lbShift = d * step;
}
if (res) {
RewritePatternSet patterns(res.getContext());
AffineForOp::getCanonicalizationPatterns(patterns, res.getContext());
GreedyRewriteConfig config;
config.strictMode = GreedyRewriteStrictness::ExistingOps;
bool erased;
(void)applyOpPatternsAndFold(res.getOperation(), std::move(patterns),
config, nullptr, &erased);
if (!erased && !prologue)
prologue = res;
if (!erased)
epilogue = res;
}
} else {
lbShift = d * step;
}
opGroupQueue.emplace_back(d, sortedOpGroups[d]);
}
for (unsigned i = 0, e = opGroupQueue.size(); i < e; ++i) {
uint64_t ubShift = (opGroupQueue[i].first + tripCount) * step;
epilogue = generateShiftedLoop(b.getShiftedAffineMap(origLbMap, lbShift),
b.getShiftedAffineMap(origLbMap, ubShift),
opGroupQueue, i, forOp, b);
lbShift = ubShift;
if (!prologue)
prologue = epilogue;
}
forOp.erase();
if (unrollPrologueEpilogue && prologue)
(void)loopUnrollFull(prologue);
if (unrollPrologueEpilogue && !epilogue && epilogue != prologue)
(void)loopUnrollFull(epilogue);
return success();
}
static LogicalResult
checkIfHyperRectangular(MutableArrayRef<AffineForOp> input) {
FlatAffineValueConstraints cst;
SmallVector<Operation *, 8> ops(input.begin(), input.end());
if (input.size() <= 1)
return success();
if (failed(getIndexSet(ops, &cst))) {
LLVM_DEBUG(llvm::dbgs() << "Index set computation failed!\n");
return failure();
}
if (!cst.isHyperRectangular(0, input.size())) {
LLVM_DEBUG(llvm::dbgs()
<< "Non-hyperrectangular nests not supported for tiling!\n");
return failure();
}
return success();
}
template <typename t>
static LogicalResult performPreTilingChecks(MutableArrayRef<AffineForOp> input,
ArrayRef<t> tileSizes) {
assert(input.size() == tileSizes.size() && "Too few/many tile sizes");
if (llvm::any_of(input,
[](AffineForOp op) { return op.getNumResults() > 0; })) {
LLVM_DEBUG(llvm::dbgs()
<< "Cannot tile nest where a loop has yield values\n");
return failure();
}
if (!isPerfectlyNested(input)) {
LLVM_DEBUG(llvm::dbgs() << "input loops not perfectly nested");
return failure();
}
if (failed(checkIfHyperRectangular(input)))
return failure();
return success();
}
static void moveLoopBodyImpl(AffineForOp src, AffineForOp dest,
Block::iterator loc) {
auto &ops = src.getBody()->getOperations();
dest.getBody()->getOperations().splice(loc, ops, ops.begin(),
std::prev(ops.end()));
}
static void moveLoopBody(AffineForOp src, AffineForOp dest) {
moveLoopBodyImpl(src, dest, dest.getBody()->begin());
}
static void constructTiledLoopNest(MutableArrayRef<AffineForOp> origLoops,
AffineForOp rootAffineForOp, unsigned width,
MutableArrayRef<AffineForOp> tiledLoops) {
Location loc = rootAffineForOp.getLoc();
Operation *topLoop = rootAffineForOp.getOperation();
AffineForOp innermostPointLoop;
for (unsigned i = 0; i < width; i++) {
OpBuilder b(topLoop);
AffineForOp pointLoop = b.create<AffineForOp>(loc, 0, 0);
pointLoop.getBody()->getOperations().splice(
pointLoop.getBody()->begin(), topLoop->getBlock()->getOperations(),
topLoop);
tiledLoops[2 * width - 1 - i] = pointLoop;
topLoop = pointLoop.getOperation();
if (i == 0)
innermostPointLoop = pointLoop;
}
for (unsigned i = width; i < 2 * width; i++) {
OpBuilder b(topLoop);
AffineForOp tileSpaceLoop = b.create<AffineForOp>(loc, 0, 0);
tileSpaceLoop.getBody()->getOperations().splice(
tileSpaceLoop.getBody()->begin(), topLoop->getBlock()->getOperations(),
topLoop);
tiledLoops[2 * width - i - 1] = tileSpaceLoop;
topLoop = tileSpaceLoop.getOperation();
}
moveLoopBody(origLoops.back(), innermostPointLoop);
}
static void setIntraTileBoundsParametric(OpBuilder &b, AffineForOp origLoop,
AffineForOp newInterTileLoop,
AffineForOp newIntraTileLoop,
Value tileSize) {
assert(origLoop.hasConstantLowerBound() &&
"expected input loops to have constant lower bound.");
AffineExpr origLowerBoundExpr;
origLowerBoundExpr =
b.getAffineConstantExpr(origLoop.getConstantLowerBound());
SmallVector<Value, 4> lbOperands, ubOperands;
AffineBound lb = origLoop.getLowerBound();
AffineBound ub = origLoop.getUpperBound();
lbOperands.reserve(lb.getNumOperands() + 2);
ubOperands.reserve(ub.getNumOperands() + 2);
AffineMap origLbMap = lb.getMap();
AffineMap origUbMap = ub.getMap();
for (unsigned j = 0, e = origLbMap.getNumDims(); j < e; ++j)
lbOperands.push_back(lb.getOperand(j));
for (unsigned j = 0, e = origUbMap.getNumDims(); j < e; ++j)
ubOperands.push_back(ub.getOperand(j));
lbOperands.push_back(newInterTileLoop.getInductionVar());
ubOperands.push_back(newInterTileLoop.getInductionVar());
AffineExpr lbLoopIvExpr = b.getAffineDimExpr(lbOperands.size() - 1);
AffineExpr ubLoopIvExpr = b.getAffineDimExpr(ubOperands.size() - 1);
for (unsigned j = 0, e = origLbMap.getNumSymbols(); j < e; ++j)
lbOperands.push_back(lb.getOperand(origLbMap.getNumDims() + j));
for (unsigned j = 0, e = origUbMap.getNumSymbols(); j < e; ++j)
ubOperands.push_back(ub.getOperand(origUbMap.getNumDims() + j));
lbOperands.push_back(tileSize);
ubOperands.push_back(tileSize);
SmallVector<AffineExpr, 4> lbBoundExprs;
SmallVector<AffineExpr, 4> ubBoundExprs;
lbBoundExprs.reserve(origLbMap.getNumResults());
ubBoundExprs.reserve(origUbMap.getNumResults());
AffineExpr lbTileParameter = b.getAffineSymbolExpr(origLbMap.getNumSymbols());
AffineExpr ubTileParameter = b.getAffineSymbolExpr(origUbMap.getNumSymbols());
lbBoundExprs.push_back(
((lbLoopIvExpr - origLowerBoundExpr) * lbTileParameter) +
origLowerBoundExpr);
AffineExpr origLoopStep = b.getAffineConstantExpr(origLoop.getStepAsInt());
ubBoundExprs.push_back(
((ubLoopIvExpr - origLowerBoundExpr) * ubTileParameter) +
(ubTileParameter * origLoopStep) + origLowerBoundExpr);
ubBoundExprs.append(origUbMap.getResults().begin(),
origUbMap.getResults().end());
AffineMap lbMap =
AffineMap::get(origLbMap.getNumDims() + 1, origLbMap.getNumSymbols() + 1,
lbBoundExprs, b.getContext());
newIntraTileLoop.setLowerBound(lbOperands, lbMap);
AffineMap ubMap =
AffineMap::get(origUbMap.getNumDims() + 1, origUbMap.getNumSymbols() + 1,
ubBoundExprs, b.getContext());
newIntraTileLoop.setUpperBound(ubOperands, ubMap);
newIntraTileLoop.setStep(origLoop.getStepAsInt());
}
static void setInterTileBoundsParametric(OpBuilder &b, AffineForOp origLoop,
AffineForOp newLoop, Value tileSize) {
OperandRange newLbOperands = origLoop.getLowerBoundOperands();
newLoop.setLowerBound(newLbOperands, origLoop.getLowerBoundMap());
assert(origLoop.hasConstantLowerBound() &&
"expected input loops to have constant lower bound.");
AffineExpr origLowerBoundExpr;
origLowerBoundExpr =
b.getAffineConstantExpr(origLoop.getConstantLowerBound());
SmallVector<Value, 4> ubOperands;
AffineBound ub = origLoop.getUpperBound();
ubOperands.reserve(ub.getNumOperands() + 1);
AffineMap origUbMap = ub.getMap();
for (unsigned j = 0, e = origUbMap.getNumDims(); j < e; ++j)
ubOperands.push_back(ub.getOperand(j));
for (unsigned j = 0, e = origUbMap.getNumSymbols(); j < e; ++j)
ubOperands.push_back(ub.getOperand(origUbMap.getNumDims() + j));
ubOperands.push_back(tileSize);
AffineExpr tileParameter = b.getAffineSymbolExpr(origUbMap.getNumSymbols());
SmallVector<AffineExpr, 4> boundExprs;
boundExprs.reserve(origUbMap.getNumResults());
int64_t origUpperBound;
AffineExpr origUpperBoundExpr;
if (origLoop.hasConstantUpperBound()) {
origUpperBound = origLoop.getConstantUpperBound();
origUpperBoundExpr = b.getAffineConstantExpr(origUpperBound);
boundExprs.push_back(
origLowerBoundExpr +
(origUpperBoundExpr - origLowerBoundExpr).ceilDiv(tileParameter));
} else {
for (AffineExpr origUpperBoundExpr : origUbMap.getResults())
boundExprs.push_back(
origLowerBoundExpr +
(origUpperBoundExpr - origLowerBoundExpr).ceilDiv(tileParameter));
}
AffineMap ubMap =
AffineMap::get(origUbMap.getNumDims(), origUbMap.getNumSymbols() + 1,
boundExprs, b.getContext());
newLoop.setUpperBound(ubOperands, ubMap);
newLoop.setStep(origLoop.getStepAsInt());
}
static void constructParametricallyTiledIndexSetHyperRect(
MutableArrayRef<AffineForOp> origLoops,
MutableArrayRef<AffineForOp> newLoops, ArrayRef<Value> tileSizes) {
assert(!origLoops.empty() && "expected atleast one loop in band");
assert(origLoops.size() == tileSizes.size() &&
"expected tiling parameter for each loop in band.");
OpBuilder b(origLoops[0].getOperation());
unsigned width = origLoops.size();
for (unsigned i = 0; i < width; ++i) {
setInterTileBoundsParametric(b, origLoops[i], newLoops[i], tileSizes[i]);
}
for (unsigned i = 0; i < width; ++i) {
setIntraTileBoundsParametric(b, origLoops[i], newLoops[i],
newLoops[i + width], tileSizes[i]);
}
}
static void
constructTiledIndexSetHyperRect(MutableArrayRef<AffineForOp> origLoops,
MutableArrayRef<AffineForOp> newLoops,
ArrayRef<unsigned> tileSizes) {
assert(!origLoops.empty());
assert(origLoops.size() == tileSizes.size());
OpBuilder b(origLoops[0].getOperation());
unsigned width = origLoops.size();
for (unsigned i = 0; i < width; i++) {
OperandRange newLbOperands = origLoops[i].getLowerBoundOperands();
OperandRange newUbOperands = origLoops[i].getUpperBoundOperands();
newLoops[i].setLowerBound(newLbOperands, origLoops[i].getLowerBoundMap());
newLoops[i].setUpperBound(newUbOperands, origLoops[i].getUpperBoundMap());
newLoops[i].setStep(tileSizes[i] * origLoops[i].getStepAsInt());
}
for (unsigned i = 0; i < width; i++) {
int64_t largestDiv = getLargestDivisorOfTripCount(origLoops[i]);
std::optional<uint64_t> mayBeConstantCount =
getConstantTripCount(origLoops[i]);
AffineMap lbMap = b.getDimIdentityMap();
newLoops[width + i].setLowerBound(
newLoops[i].getInductionVar(), lbMap);
newLoops[width + i].setStep(origLoops[i].getStepAsInt());
if (mayBeConstantCount && *mayBeConstantCount < tileSizes[i]) {
AffineMap ubMap = b.getSingleDimShiftAffineMap(
*mayBeConstantCount * origLoops[i].getStepAsInt());
newLoops[width + i].setUpperBound(
newLoops[i].getInductionVar(), ubMap);
} else if (largestDiv % tileSizes[i] != 0) {
SmallVector<Value, 4> ubOperands;
AffineBound ub = origLoops[i].getUpperBound();
ubOperands.reserve(ub.getNumOperands() + 1);
AffineMap origUbMap = ub.getMap();
for (unsigned j = 0, e = origUbMap.getNumDims(); j < e; ++j)
ubOperands.push_back(ub.getOperand(j));
ubOperands.push_back(newLoops[i].getInductionVar());
for (unsigned j = 0, e = origUbMap.getNumSymbols(); j < e; ++j)
ubOperands.push_back(ub.getOperand(origUbMap.getNumDims() + j));
SmallVector<AffineExpr, 4> boundExprs;
boundExprs.reserve(1 + origUbMap.getNumResults());
AffineExpr dim = b.getAffineDimExpr(origUbMap.getNumDims());
boundExprs.push_back(dim + tileSizes[i] * origLoops[i].getStepAsInt());
boundExprs.append(origUbMap.getResults().begin(),
origUbMap.getResults().end());
AffineMap ubMap =
AffineMap::get(origUbMap.getNumDims() + 1, origUbMap.getNumSymbols(),
boundExprs, b.getContext());
newLoops[width + i].setUpperBound(ubOperands, ubMap);
} else {
AffineExpr dim = b.getAffineDimExpr(0);
AffineMap ubMap = AffineMap::get(
1, 0, dim + tileSizes[i] * origLoops[i].getStepAsInt());
newLoops[width + i].setUpperBound(newLoops[i].getInductionVar(), ubMap);
}
}
}
LogicalResult
mlir::affine::tilePerfectlyNested(MutableArrayRef<AffineForOp> input,
ArrayRef<unsigned> tileSizes,
SmallVectorImpl<AffineForOp> *tiledNest) {
if (input.empty())
return success();
if (failed(performPreTilingChecks(input, tileSizes)))
return failure();
MutableArrayRef<AffineForOp> origLoops = input;
AffineForOp rootAffineForOp = origLoops[0];
unsigned width = input.size();
SmallVector<AffineForOp, 6> tiledLoops(2 * width);
constructTiledLoopNest(origLoops, rootAffineForOp, width, tiledLoops);
SmallVector<Value, 8> origLoopIVs;
extractForInductionVars(input, &origLoopIVs);
constructTiledIndexSetHyperRect(origLoops, tiledLoops, tileSizes);
for (unsigned i = 0; i < width; i++)
origLoopIVs[i].replaceAllUsesWith(tiledLoops[i + width].getInductionVar());
rootAffineForOp.erase();
if (tiledNest)
*tiledNest = std::move(tiledLoops);
return success();
}
LogicalResult mlir::affine::tilePerfectlyNestedParametric(
MutableArrayRef<AffineForOp> input, ArrayRef<Value> tileSizes,
SmallVectorImpl<AffineForOp> *tiledNest) {
if (input.empty())
return success();
if (failed(performPreTilingChecks(input, tileSizes)))
return failure();
MutableArrayRef<AffineForOp> origLoops = input;
AffineForOp rootAffineForOp = origLoops[0];
unsigned width = input.size();
SmallVector<AffineForOp, 6> tiledLoops(2 * width);
constructTiledLoopNest(origLoops, rootAffineForOp, width, tiledLoops);
SmallVector<Value, 8> origLoopIVs;
extractForInductionVars(input, &origLoopIVs);
constructParametricallyTiledIndexSetHyperRect(origLoops, tiledLoops,
tileSizes);
for (unsigned i = 0; i < width; i++)
origLoopIVs[i].replaceAllUsesWith(tiledLoops[i + width].getInductionVar());
rootAffineForOp.erase();
if (tiledNest)
*tiledNest = std::move(tiledLoops);
return success();
}
void mlir::affine::getPerfectlyNestedLoops(
SmallVectorImpl<AffineForOp> &nestedLoops, AffineForOp root) {
for (unsigned i = 0; i < std::numeric_limits<unsigned>::max(); ++i) {
nestedLoops.push_back(root);
Block &body = root.getRegion().front();
if (body.begin() != std::prev(body.end(), 2))
return;
root = dyn_cast<AffineForOp>(&body.front());
if (!root)
return;
}
}
void mlir::affine::getTileableBands(
func::FuncOp f, std::vector<SmallVector<AffineForOp, 6>> *bands) {
for (AffineForOp forOp : f.getOps<AffineForOp>()) {
SmallVector<AffineForOp, 6> band;
getPerfectlyNestedLoops(band, forOp);
bands->push_back(band);
}
}
LogicalResult mlir::affine::loopUnrollFull(AffineForOp forOp) {
std::optional<uint64_t> mayBeConstantTripCount = getConstantTripCount(forOp);
if (mayBeConstantTripCount.has_value()) {
uint64_t tripCount = *mayBeConstantTripCount;
if (tripCount == 0)
return success();
if (tripCount == 1)
return promoteIfSingleIteration(forOp);
return loopUnrollByFactor(forOp, tripCount);
}
return failure();
}
LogicalResult mlir::affine::loopUnrollUpToFactor(AffineForOp forOp,
uint64_t unrollFactor) {
std::optional<uint64_t> mayBeConstantTripCount = getConstantTripCount(forOp);
if (mayBeConstantTripCount.has_value() &&
*mayBeConstantTripCount < unrollFactor)
return loopUnrollByFactor(forOp, *mayBeConstantTripCount);
return loopUnrollByFactor(forOp, unrollFactor);
}
static void generateUnrolledLoop(
Block *loopBodyBlock, Value forOpIV, uint64_t unrollFactor,
function_ref<Value(unsigned, Value, OpBuilder)> ivRemapFn,
function_ref<void(unsigned, Operation *, OpBuilder)> annotateFn,
ValueRange iterArgs, ValueRange yieldedValues) {
auto builder = OpBuilder::atBlockTerminator(loopBodyBlock);
if (!annotateFn)
annotateFn = [](unsigned, Operation *, OpBuilder) {};
Block::iterator srcBlockEnd = std::prev(loopBodyBlock->end(), 2);
SmallVector<Value, 4> lastYielded(yieldedValues);
for (unsigned i = 1; i < unrollFactor; i++) {
IRMapping operandMap;
operandMap.map(iterArgs, lastYielded);
if (!forOpIV.use_empty()) {
Value ivUnroll = ivRemapFn(i, forOpIV, builder);
operandMap.map(forOpIV, ivUnroll);
}
for (auto it = loopBodyBlock->begin(); it != std::next(srcBlockEnd); it++) {
Operation *clonedOp = builder.clone(*it, operandMap);
annotateFn(i, clonedOp, builder);
}
for (unsigned i = 0, e = lastYielded.size(); i < e; i++) {
Operation *defOp = yieldedValues[i].getDefiningOp();
if (defOp && defOp->getBlock() == loopBodyBlock)
lastYielded[i] = operandMap.lookup(yieldedValues[i]);
}
}
for (auto it = loopBodyBlock->begin(); it != std::next(srcBlockEnd); it++)
annotateFn(0, &*it, builder);
loopBodyBlock->getTerminator()->setOperands(lastYielded);
}
static LogicalResult generateCleanupLoopForUnroll(AffineForOp forOp,
uint64_t unrollFactor) {
OpBuilder builder(forOp->getBlock(), std::next(Block::iterator(forOp)));
auto cleanupForOp = cast<AffineForOp>(builder.clone(*forOp));
auto results = forOp.getResults();
auto cleanupResults = cleanupForOp.getResults();
auto cleanupIterOperands = cleanupForOp.getInits();
for (auto e : llvm::zip(results, cleanupResults, cleanupIterOperands)) {
std::get<0>(e).replaceAllUsesWith(std::get<1>(e));
cleanupForOp->replaceUsesOfWith(std::get<2>(e), std::get<0>(e));
}
AffineMap cleanupMap;
SmallVector<Value, 4> cleanupOperands;
getCleanupLoopLowerBound(forOp, unrollFactor, cleanupMap, cleanupOperands);
if (!cleanupMap)
return failure();
cleanupForOp.setLowerBound(cleanupOperands, cleanupMap);
(void)promoteIfSingleIteration(cleanupForOp);
forOp.setUpperBound(cleanupOperands, cleanupMap);
return success();
}
LogicalResult mlir::affine::loopUnrollByFactor(
AffineForOp forOp, uint64_t unrollFactor,
function_ref<void(unsigned, Operation *, OpBuilder)> annotateFn,
bool cleanUpUnroll) {
assert(unrollFactor > 0 && "unroll factor should be positive");
std::optional<uint64_t> mayBeConstantTripCount = getConstantTripCount(forOp);
if (unrollFactor == 1) {
if (mayBeConstantTripCount && *mayBeConstantTripCount == 1 &&
failed(promoteIfSingleIteration(forOp)))
return failure();
return success();
}
if (llvm::hasSingleElement(forOp.getBody()->getOperations()))
return success();
if (mayBeConstantTripCount && *mayBeConstantTripCount < unrollFactor) {
if (cleanUpUnroll) {
return loopUnrollFull(forOp);
}
return failure();
}
if (getLargestDivisorOfTripCount(forOp) % unrollFactor != 0) {
if (forOp.getLowerBoundMap().getNumResults() != 1 ||
forOp.getUpperBoundMap().getNumResults() != 1)
return failure();
if (cleanUpUnroll)
return loopUnrollFull(forOp);
if (failed(generateCleanupLoopForUnroll(forOp, unrollFactor)))
assert(false && "cleanup loop lower bound map for single result lower "
"and upper bound maps can always be determined");
}
ValueRange iterArgs(forOp.getRegionIterArgs());
auto yieldedValues = forOp.getBody()->getTerminator()->getOperands();
int64_t step = forOp.getStepAsInt();
forOp.setStep(step * unrollFactor);
generateUnrolledLoop(
forOp.getBody(), forOp.getInductionVar(), unrollFactor,
[&](unsigned i, Value iv, OpBuilder b) {
auto d0 = b.getAffineDimExpr(0);
auto bumpMap = AffineMap::get(1, 0, d0 + i * step);
return b.create<AffineApplyOp>(forOp.getLoc(), bumpMap, iv);
},
annotateFn,
iterArgs, yieldedValues);
(void)promoteIfSingleIteration(forOp);
return success();
}
LogicalResult mlir::affine::loopUnrollJamUpToFactor(AffineForOp forOp,
uint64_t unrollJamFactor) {
std::optional<uint64_t> mayBeConstantTripCount = getConstantTripCount(forOp);
if (mayBeConstantTripCount.has_value() &&
*mayBeConstantTripCount < unrollJamFactor)
return loopUnrollJamByFactor(forOp, *mayBeConstantTripCount);
return loopUnrollJamByFactor(forOp, unrollJamFactor);
}
static bool areInnerBoundsInvariant(AffineForOp forOp) {
auto walkResult = forOp.walk([&](AffineForOp aForOp) {
for (auto controlOperand : aForOp.getControlOperands()) {
if (!forOp.isDefinedOutsideOfLoop(controlOperand))
return WalkResult::interrupt();
}
return WalkResult::advance();
});
return !walkResult.wasInterrupted();
}
LogicalResult mlir::affine::loopUnrollJamByFactor(AffineForOp forOp,
uint64_t unrollJamFactor) {
assert(unrollJamFactor > 0 && "unroll jam factor should be positive");
std::optional<uint64_t> mayBeConstantTripCount = getConstantTripCount(forOp);
if (unrollJamFactor == 1) {
if (mayBeConstantTripCount && *mayBeConstantTripCount == 1 &&
failed(promoteIfSingleIteration(forOp)))
return failure();
return success();
}
if (llvm::hasSingleElement(forOp.getBody()->getOperations()))
return success();
if (mayBeConstantTripCount && *mayBeConstantTripCount < unrollJamFactor) {
LLVM_DEBUG(llvm::dbgs() << "[failed] trip count < unroll-jam factor\n");
return failure();
}
if (!areInnerBoundsInvariant(forOp))
return failure();
JamBlockGatherer<AffineForOp> jbg;
jbg.walk(forOp);
auto &subBlocks = jbg.subBlocks;
SmallVector<AffineForOp, 4> loopsWithIterArgs;
forOp.walk([&](AffineForOp aForOp) {
if (aForOp.getNumIterOperands() > 0)
loopsWithIterArgs.push_back(aForOp);
});
SmallVector<LoopReduction> reductions;
if (forOp.getNumIterOperands() > 0)
getSupportedReductions(forOp, reductions);
if (getLargestDivisorOfTripCount(forOp) % unrollJamFactor != 0) {
if (forOp.getLowerBoundMap().getNumResults() != 1 ||
forOp.getUpperBoundMap().getNumResults() != 1)
return failure();
if (failed(generateCleanupLoopForUnroll(forOp, unrollJamFactor)))
assert(false && "cleanup loop lower bound map for single result lower "
"and upper bound maps can always be determined");
}
SmallVector<IRMapping, 4> operandMaps(unrollJamFactor - 1);
SmallVector<AffineForOp, 4> newLoopsWithIterArgs;
IRRewriter rewriter(forOp.getContext());
for (AffineForOp oldForOp : loopsWithIterArgs) {
SmallVector<Value> dupIterOperands, dupYieldOperands;
ValueRange oldIterOperands = oldForOp.getInits();
ValueRange oldIterArgs = oldForOp.getRegionIterArgs();
ValueRange oldYieldOperands =
cast<AffineYieldOp>(oldForOp.getBody()->getTerminator()).getOperands();
for (unsigned i = unrollJamFactor - 1; i >= 1; --i) {
dupIterOperands.append(oldIterOperands.begin(), oldIterOperands.end());
dupYieldOperands.append(oldYieldOperands.begin(), oldYieldOperands.end());
}
bool forOpReplaced = oldForOp == forOp;
AffineForOp newForOp =
cast<AffineForOp>(*oldForOp.replaceWithAdditionalYields(
rewriter, dupIterOperands, false,
[&](OpBuilder &b, Location loc, ArrayRef<BlockArgument> newBbArgs) {
return dupYieldOperands;
}));
newLoopsWithIterArgs.push_back(newForOp);
if (forOpReplaced)
forOp = newForOp;
ValueRange newIterArgs = newForOp.getRegionIterArgs();
unsigned oldNumIterArgs = oldIterArgs.size();
ValueRange newResults = newForOp.getResults();
unsigned oldNumResults = newResults.size() / unrollJamFactor;
assert(oldNumIterArgs == oldNumResults &&
"oldNumIterArgs must be the same as oldNumResults");
for (unsigned i = unrollJamFactor - 1; i >= 1; --i) {
for (unsigned j = 0; j < oldNumIterArgs; ++j) {
operandMaps[i - 1].map(newIterArgs[j],
newIterArgs[i * oldNumIterArgs + j]);
operandMaps[i - 1].map(newResults[j],
newResults[i * oldNumResults + j]);
}
}
}
int64_t step = forOp.getStepAsInt();
forOp.setStep(step * unrollJamFactor);
auto forOpIV = forOp.getInductionVar();
for (unsigned i = unrollJamFactor - 1; i >= 1; --i) {
for (auto &subBlock : subBlocks) {
OpBuilder builder(subBlock.first->getBlock(), std::next(subBlock.second));
if (!forOpIV.use_empty()) {
auto d0 = builder.getAffineDimExpr(0);
auto bumpMap = AffineMap::get(1, 0, d0 + i * step);
auto ivUnroll =
builder.create<AffineApplyOp>(forOp.getLoc(), bumpMap, forOpIV);
operandMaps[i - 1].map(forOpIV, ivUnroll);
}
for (auto it = subBlock.first; it != std::next(subBlock.second); ++it)
builder.clone(*it, operandMaps[i - 1]);
}
for (auto newForOp : newLoopsWithIterArgs) {
unsigned oldNumIterOperands =
newForOp.getNumIterOperands() / unrollJamFactor;
unsigned numControlOperands = newForOp.getNumControlOperands();
auto yieldOp = cast<AffineYieldOp>(newForOp.getBody()->getTerminator());
unsigned oldNumYieldOperands = yieldOp.getNumOperands() / unrollJamFactor;
assert(oldNumIterOperands == oldNumYieldOperands &&
"oldNumIterOperands must be the same as oldNumYieldOperands");
for (unsigned j = 0; j < oldNumIterOperands; ++j) {
newForOp.setOperand(numControlOperands + i * oldNumIterOperands + j,
operandMaps[i - 1].lookupOrDefault(
newForOp.getOperand(numControlOperands + j)));
yieldOp.setOperand(
i * oldNumYieldOperands + j,
operandMaps[i - 1].lookupOrDefault(yieldOp.getOperand(j)));
}
}
}
if (forOp.getNumResults() > 0) {
rewriter.setInsertionPointAfter(forOp);
auto loc = forOp.getLoc();
unsigned oldNumResults = forOp.getNumResults() / unrollJamFactor;
for (LoopReduction &reduction : reductions) {
unsigned pos = reduction.iterArgPosition;
Value lhs = forOp.getResult(pos);
Value rhs;
SmallPtrSet<Operation *, 4> newOps;
for (unsigned i = unrollJamFactor - 1; i >= 1; --i) {
rhs = forOp.getResult(i * oldNumResults + pos);
lhs = arith::getReductionOp(reduction.kind, rewriter, loc, lhs, rhs);
if (!lhs)
return failure();
Operation *op = lhs.getDefiningOp();
assert(op && "Reduction op should have been created");
newOps.insert(op);
}
forOp.getResult(pos).replaceAllUsesExcept(lhs, newOps);
}
}
(void)promoteIfSingleIteration(forOp);
return success();
}
void mlir::affine::interchangeLoops(AffineForOp forOpA, AffineForOp forOpB) {
assert(&*forOpA.getBody()->begin() == forOpB.getOperation());
auto &forOpABody = forOpA.getBody()->getOperations();
auto &forOpBBody = forOpB.getBody()->getOperations();
forOpA->getBlock()->getOperations().splice(Block::iterator(forOpA),
forOpABody, forOpABody.begin(),
std::prev(forOpABody.end()));
forOpABody.splice(forOpABody.begin(), forOpBBody, forOpBBody.begin(),
std::prev(forOpBBody.end()));
forOpBBody.splice(forOpBBody.begin(), forOpA->getBlock()->getOperations(),
Block::iterator(forOpA));
}
static bool checkLoopInterchangeDependences(
const std::vector<SmallVector<DependenceComponent, 2>> &depCompsVec,
ArrayRef<AffineForOp> loops, ArrayRef<unsigned> loopPermMap) {
unsigned maxLoopDepth = loops.size();
SmallVector<unsigned, 4> loopPermMapInv;
loopPermMapInv.resize(maxLoopDepth);
for (unsigned i = 0; i < maxLoopDepth; ++i)
loopPermMapInv[loopPermMap[i]] = i;
for (const auto &depComps : depCompsVec) {
assert(depComps.size() >= maxLoopDepth);
for (unsigned j = 0; j < maxLoopDepth; ++j) {
unsigned permIndex = loopPermMapInv[j];
assert(depComps[permIndex].lb);
int64_t depCompLb = *depComps[permIndex].lb;
if (depCompLb > 0)
break;
if (depCompLb < 0)
return false;
}
}
return true;
}
bool mlir::affine::isValidLoopInterchangePermutation(
ArrayRef<AffineForOp> loops, ArrayRef<unsigned> loopPermMap) {
assert(loopPermMap.size() == loops.size());
unsigned maxLoopDepth = loops.size();
std::vector<SmallVector<DependenceComponent, 2>> depCompsVec;
getDependenceComponents(loops[0], maxLoopDepth, &depCompsVec);
return checkLoopInterchangeDependences(depCompsVec, loops, loopPermMap);
}
bool LLVM_ATTRIBUTE_UNUSED
mlir::affine::isPerfectlyNested(ArrayRef<AffineForOp> loops) {
assert(!loops.empty() && "no loops provided");
auto hasTwoElements = [](Block *block) {
auto secondOpIt = std::next(block->begin());
return secondOpIt != block->end() && &*secondOpIt == &block->back();
};
auto enclosingLoop = loops.front();
for (auto loop : loops.drop_front()) {
auto parentForOp = dyn_cast<AffineForOp>(loop->getParentOp());
if (parentForOp != enclosingLoop || !hasTwoElements(parentForOp.getBody()))
return false;
enclosingLoop = loop;
}
return true;
}
unsigned mlir::affine::permuteLoops(MutableArrayRef<AffineForOp> input,
ArrayRef<unsigned> permMap) {
assert(input.size() == permMap.size() && "invalid permutation map size");
SmallVector<unsigned, 4> checkPermMap(permMap.begin(), permMap.end());
llvm::sort(checkPermMap);
if (llvm::any_of(llvm::enumerate(checkPermMap),
[](const auto &en) { return en.value() != en.index(); }))
assert(false && "invalid permutation map");
if (input.size() < 2)
return 0;
assert(isPerfectlyNested(input) && "input not perfectly nested");
SmallVector<std::pair<unsigned, unsigned>, 4> invPermMap;
for (unsigned i = 0, e = input.size(); i < e; ++i)
invPermMap.push_back({permMap[i], i});
llvm::sort(invPermMap);
if (permMap.back() != input.size() - 1) {
auto *destBody = input[invPermMap.back().second].getBody();
auto *srcBody = input.back().getBody();
destBody->getOperations().splice(destBody->begin(),
srcBody->getOperations(), srcBody->begin(),
std::prev(srcBody->end()));
}
for (int i = input.size() - 1; i >= 0; --i) {
if (permMap[i] == 0) {
if (i == 0)
continue;
auto *parentBlock = input[0]->getBlock();
parentBlock->getOperations().splice(Block::iterator(input[0]),
input[i]->getBlock()->getOperations(),
Block::iterator(input[i]));
continue;
}
unsigned parentPosInInput = invPermMap[permMap[i] - 1].second;
if (i > 0 && static_cast<unsigned>(i - 1) == parentPosInInput)
continue;
auto *destBody = input[parentPosInInput].getBody();
destBody->getOperations().splice(destBody->begin(),
input[i]->getBlock()->getOperations(),
Block::iterator(input[i]));
}
return invPermMap[0].second;
}
AffineForOp mlir::affine::sinkSequentialLoops(AffineForOp forOp) {
SmallVector<AffineForOp, 4> loops;
getPerfectlyNestedLoops(loops, forOp);
if (loops.size() < 2)
return forOp;
unsigned maxLoopDepth = loops.size();
std::vector<SmallVector<DependenceComponent, 2>> depCompsVec;
getDependenceComponents(loops[0], maxLoopDepth, &depCompsVec);
SmallVector<bool, 8> isParallelLoop(maxLoopDepth, true);
for (auto &depComps : depCompsVec) {
assert(depComps.size() >= maxLoopDepth);
for (unsigned j = 0; j < maxLoopDepth; ++j) {
DependenceComponent &depComp = depComps[j];
assert(depComp.lb.has_value() && depComp.ub.has_value());
if (*depComp.lb != 0 || *depComp.ub != 0)
isParallelLoop[j] = false;
}
}
unsigned numParallelLoops = llvm::count(isParallelLoop, true);
SmallVector<unsigned, 4> loopPermMap(maxLoopDepth);
unsigned nextSequentialLoop = numParallelLoops;
unsigned nextParallelLoop = 0;
for (unsigned i = 0; i < maxLoopDepth; ++i) {
if (isParallelLoop[i]) {
loopPermMap[i] = nextParallelLoop++;
} else {
loopPermMap[i] = nextSequentialLoop++;
}
}
if (!checkLoopInterchangeDependences(depCompsVec, loops, loopPermMap))
return forOp;
unsigned loopNestRootIndex = permuteLoops(loops, loopPermMap);
return loops[loopNestRootIndex];
}
static void augmentMapAndBounds(OpBuilder &b, Value iv, AffineMap *map,
SmallVector<Value, 4> *operands,
int64_t offset = 0) {
auto bounds = llvm::to_vector<4>(map->getResults());
bounds.push_back(b.getAffineDimExpr(map->getNumDims()) + offset);
operands->insert(operands->begin() + map->getNumDims(), iv);
*map = AffineMap::get(map->getNumDims() + 1, map->getNumSymbols(), bounds,
b.getContext());
canonicalizeMapAndOperands(map, operands);
}
static SmallVector<AffineForOp, 8>
stripmineSink(AffineForOp forOp, uint64_t factor,
ArrayRef<AffineForOp> targets) {
auto originalStep = forOp.getStepAsInt();
auto scaledStep = originalStep * factor;
forOp.setStep(scaledStep);
OpBuilder b(forOp->getBlock(), std::next(Block::iterator(forOp)));
auto lbMap = forOp.getLowerBoundMap();
SmallVector<Value, 4> lbOperands(forOp.getLowerBoundOperands());
augmentMapAndBounds(b, forOp.getInductionVar(), &lbMap, &lbOperands);
auto ubMap = forOp.getUpperBoundMap();
SmallVector<Value, 4> ubOperands(forOp.getUpperBoundOperands());
augmentMapAndBounds(b, forOp.getInductionVar(), &ubMap, &ubOperands,
scaledStep);
auto iv = forOp.getInductionVar();
SmallVector<AffineForOp, 8> innerLoops;
for (auto t : targets) {
auto b = OpBuilder::atBlockTerminator(t.getBody());
auto newForOp = b.create<AffineForOp>(t.getLoc(), lbOperands, lbMap,
ubOperands, ubMap, originalStep);
auto begin = t.getBody()->begin();
auto nOps = t.getBody()->getOperations().size() - 2;
newForOp.getBody()->getOperations().splice(
newForOp.getBody()->getOperations().begin(),
t.getBody()->getOperations(), begin, std::next(begin, nOps));
replaceAllUsesInRegionWith(iv, newForOp.getInductionVar(),
newForOp.getRegion());
innerLoops.push_back(newForOp);
}
return innerLoops;
}
template <typename SizeType>
static AffineForOp stripmineSink(AffineForOp forOp, SizeType factor,
AffineForOp target) {
auto res = stripmineSink(forOp, factor, ArrayRef<AffineForOp>(target));
assert(res.size() == 1 && "Expected 1 inner forOp");
return res[0];
}
SmallVector<SmallVector<AffineForOp, 8>, 8>
mlir::affine::tile(ArrayRef<AffineForOp> forOps, ArrayRef<uint64_t> sizes,
ArrayRef<AffineForOp> targets) {
SmallVector<SmallVector<AffineForOp, 8>, 8> res;
SmallVector<AffineForOp, 8> currentTargets(targets.begin(), targets.end());
for (auto it : llvm::zip(forOps, sizes)) {
auto step = stripmineSink(std::get<0>(it), std::get<1>(it), currentTargets);
res.push_back(step);
currentTargets = step;
}
return res;
}
SmallVector<AffineForOp, 8> mlir::affine::tile(ArrayRef<AffineForOp> forOps,
ArrayRef<uint64_t> sizes,
AffineForOp target) {
SmallVector<AffineForOp, 8> res;
for (auto loops : tile(forOps, sizes, ArrayRef<AffineForOp>(target))) {
assert(loops.size() == 1);
res.push_back(loops[0]);
}
return res;
}
LogicalResult mlir::affine::coalesceLoops(MutableArrayRef<AffineForOp> loops) {
if (loops.size() < 2)
return success();
AffineForOp innermost = loops.back();
AffineForOp outermost = loops.front();
AffineBound ub = outermost.getUpperBound();
AffineMap origUbMap = ub.getMap();
Location loc = outermost.getLoc();
OpBuilder builder(outermost);
for (AffineForOp loop : loops) {
if (loop.getStepAsInt() != 1 || !loop.hasConstantLowerBound() ||
loop.getConstantLowerBound() != 0)
return failure();
}
SmallVector<Value, 4> upperBoundSymbols;
SmallVector<Value, 4> ubOperands(ub.getOperands().begin(),
ub.getOperands().end());
Value prev;
if (!llvm::hasSingleElement(origUbMap.getResults()))
prev = builder.create<AffineMinOp>(loc, origUbMap, ubOperands);
else
prev = builder.create<AffineApplyOp>(loc, origUbMap, ubOperands);
upperBoundSymbols.push_back(prev);
for (AffineForOp loop : loops.drop_front()) {
ub = loop.getUpperBound();
origUbMap = ub.getMap();
ubOperands = ub.getOperands();
Value upperBound;
if (!llvm::hasSingleElement(origUbMap.getResults()))
upperBound = builder.create<AffineMinOp>(loc, origUbMap, ubOperands);
else
upperBound = builder.create<AffineApplyOp>(loc, origUbMap, ubOperands);
upperBoundSymbols.push_back(upperBound);
SmallVector<Value, 4> operands;
operands.push_back(prev);
operands.push_back(upperBound);
prev = builder.create<AffineApplyOp>(
loc,
AffineMap::get(1,
1,
builder.getAffineDimExpr(0) *
builder.getAffineSymbolExpr(0)),
operands);
}
AffineMap newUbMap = AffineMap::get(
0,
1, builder.getAffineSymbolExpr(0), builder.getContext());
outermost.setUpperBound(prev, newUbMap);
builder.setInsertionPointToStart(outermost.getBody());
Value previous = outermost.getInductionVar();
for (unsigned idx = loops.size(); idx > 0; --idx) {
if (idx != loops.size()) {
SmallVector<Value, 4> operands;
operands.push_back(previous);
operands.push_back(upperBoundSymbols[idx]);
previous = builder.create<AffineApplyOp>(
loc,
AffineMap::get(
1, 1,
builder.getAffineDimExpr(0).floorDiv(
builder.getAffineSymbolExpr(0))),
operands);
}
Value inductionVariable;
if (idx == 1) {
inductionVariable = previous;
} else {
SmallVector<Value, 4> applyOperands;
applyOperands.push_back(previous);
applyOperands.push_back(upperBoundSymbols[idx - 1]);
inductionVariable = builder.create<AffineApplyOp>(
loc,
AffineMap::get(
1, 1,
builder.getAffineDimExpr(0) % builder.getAffineSymbolExpr(0)),
applyOperands);
}
replaceAllUsesInRegionWith(loops[idx - 1].getInductionVar(),
inductionVariable, loops.back().getRegion());
}
AffineForOp secondOutermostLoop = loops[1];
innermost.getBody()->back().erase();
outermost.getBody()->getOperations().splice(
Block::iterator(secondOutermostLoop.getOperation()),
innermost.getBody()->getOperations());
secondOutermostLoop.erase();
return success();
}
void mlir::affine::mapLoopToProcessorIds(scf::ForOp forOp,
ArrayRef<Value> processorId,
ArrayRef<Value> numProcessors) {
assert(processorId.size() == numProcessors.size());
if (processorId.empty())
return;
OpBuilder b(forOp);
Location loc(forOp.getLoc());
AffineExpr lhs, rhs;
bindSymbols(forOp.getContext(), lhs, rhs);
auto mulMap = AffineMap::get(0, 2, lhs * rhs);
auto addMap = AffineMap::get(0, 2, lhs + rhs);
Value linearIndex = processorId.front();
for (unsigned i = 1, e = processorId.size(); i < e; ++i) {
auto mulApplyOp = b.create<AffineApplyOp>(
loc, mulMap, ValueRange{linearIndex, numProcessors[i]});
linearIndex = b.create<AffineApplyOp>(
loc, addMap, ValueRange{mulApplyOp, processorId[i]});
}
auto mulApplyOp = b.create<AffineApplyOp>(
loc, mulMap, ValueRange{linearIndex, forOp.getStep()});
Value lb = b.create<AffineApplyOp>(
loc, addMap, ValueRange{mulApplyOp, forOp.getLowerBound()});
forOp.setLowerBound(lb);
Value step = forOp.getStep();
for (auto numProcs : numProcessors)
step = b.create<AffineApplyOp>(loc, mulMap, ValueRange{numProcs, step});
forOp.setStep(step);
}
static void
findHighestBlockForPlacement(const MemRefRegion ®ion, Block &block,
Block::iterator &begin, Block::iterator &end,
Block **copyPlacementBlock,
Block::iterator *copyInPlacementStart,
Block::iterator *copyOutPlacementStart) {
const auto *cst = region.getConstraints();
SmallVector<Value, 4> symbols;
cst->getValues(cst->getNumDimVars(), cst->getNumDimAndSymbolVars(), &symbols);
SmallVector<AffineForOp, 4> enclosingFors;
getAffineForIVs(*block.begin(), &enclosingFors);
auto it = enclosingFors.rbegin();
for (auto e = enclosingFors.rend(); it != e; ++it) {
if (llvm::is_contained(symbols, it->getInductionVar()))
break;
}
if (it != enclosingFors.rbegin()) {
auto lastInvariantIV = *std::prev(it);
*copyInPlacementStart = Block::iterator(lastInvariantIV.getOperation());
*copyOutPlacementStart = std::next(*copyInPlacementStart);
*copyPlacementBlock = lastInvariantIV->getBlock();
} else {
*copyInPlacementStart = begin;
*copyOutPlacementStart = end;
*copyPlacementBlock = █
}
}
struct StrideInfo {
int64_t stride;
int64_t numEltPerStride;
};
static void getMultiLevelStrides(const MemRefRegion ®ion,
ArrayRef<int64_t> bufferShape,
SmallVectorImpl<StrideInfo> *strideInfos) {
if (bufferShape.size() <= 1)
return;
int64_t numEltPerStride = 1;
int64_t stride = 1;
for (int d = bufferShape.size() - 1; d >= 1; d--) {
int64_t dimSize = cast<MemRefType>(region.memref.getType()).getDimSize(d);
stride *= dimSize;
numEltPerStride *= bufferShape[d];
if (bufferShape[d] < dimSize && bufferShape[d - 1] > 1) {
strideInfos->push_back({stride, numEltPerStride});
}
}
}
static AffineForOp
generatePointWiseCopy(Location loc, Value memref, Value fastMemRef,
ArrayRef<AffineMap> lbMaps, ArrayRef<Value> lbOperands,
ArrayRef<AffineMap> ubMaps, ArrayRef<Value> ubOperands,
ArrayRef<AffineExpr> fastBufOffsets, bool isCopyOut,
OpBuilder b) {
assert(llvm::all_of(lbMaps, [&](AffineMap lbMap) {
return lbMap.getNumInputs() == lbOperands.size();
}));
assert(llvm::all_of(ubMaps, [&](AffineMap ubMap) {
return ubMap.getNumInputs() == ubOperands.size();
}));
unsigned rank = cast<MemRefType>(memref.getType()).getRank();
assert(lbMaps.size() == rank && "wrong number of lb maps");
assert(ubMaps.size() == rank && "wrong number of ub maps");
SmallVector<Value, 4> memIndices;
SmallVector<AffineExpr, 4> fastBufExprs;
SmallVector<Value, 4> fastBufMapOperands;
AffineForOp copyNestRoot;
SmallVector<AffineApplyOp, 4> mayBeDeadApplys;
for (unsigned d = 0; d < rank; ++d) {
auto forOp = createCanonicalizedAffineForOp(b, loc, lbOperands, lbMaps[d],
ubOperands, ubMaps[d]);
if (d == 0)
copyNestRoot = forOp;
b = OpBuilder::atBlockTerminator(forOp.getBody());
auto fastBufOffsetMap =
AffineMap::get(lbOperands.size(), 0, fastBufOffsets[d]);
auto offset = b.create<AffineApplyOp>(loc, fastBufOffsetMap, lbOperands);
fastBufExprs.push_back(b.getAffineDimExpr(2 * d + 1) -
b.getAffineDimExpr(2 * d));
fastBufMapOperands.push_back(offset);
fastBufMapOperands.push_back(forOp.getInductionVar());
mayBeDeadApplys.push_back(offset);
memIndices.push_back(forOp.getInductionVar());
}
auto fastBufMap =
AffineMap::get(2 * rank, 0, fastBufExprs, b.getContext());
fullyComposeAffineMapAndOperands(&fastBufMap, &fastBufMapOperands);
fastBufMap = simplifyAffineMap(fastBufMap);
canonicalizeMapAndOperands(&fastBufMap, &fastBufMapOperands);
for (auto applyOp : mayBeDeadApplys)
if (applyOp.use_empty())
applyOp.erase();
if (!isCopyOut) {
auto load = b.create<AffineLoadOp>(loc, memref, memIndices);
b.create<AffineStoreOp>(loc, load, fastMemRef, fastBufMap,
fastBufMapOperands);
return copyNestRoot;
}
auto load =
b.create<AffineLoadOp>(loc, fastMemRef, fastBufMap, fastBufMapOperands);
b.create<AffineStoreOp>(loc, load, memref, memIndices);
return copyNestRoot;
}
static InFlightDiagnostic LLVM_ATTRIBUTE_UNUSED
emitRemarkForBlock(Block &block) {
return block.getParentOp()->emitRemark();
}
static LogicalResult generateCopy(
const MemRefRegion ®ion, Block *block, Block::iterator begin,
Block::iterator end, Block *copyPlacementBlock,
Block::iterator copyInPlacementStart, Block::iterator copyOutPlacementStart,
const AffineCopyOptions ©Options, DenseMap<Value, Value> &fastBufferMap,
DenseSet<Operation *> ©Nests, uint64_t *sizeInBytes,
Block::iterator *nBegin, Block::iterator *nEnd) {
*nBegin = begin;
*nEnd = end;
func::FuncOp f = begin->getParentOfType<func::FuncOp>();
OpBuilder topBuilder(f.getBody());
Value zeroIndex = topBuilder.create<arith::ConstantIndexOp>(f.getLoc(), 0);
*sizeInBytes = 0;
if (begin == end)
return success();
bool isCopyOutAtEndOfBlock = (end == copyOutPlacementStart);
OpBuilder prologue(copyPlacementBlock, copyInPlacementStart);
OpBuilder epilogue(copyPlacementBlock, copyOutPlacementStart);
OpBuilder &b = region.isWrite() ? epilogue : prologue;
auto func = copyPlacementBlock->getParent()->getParentOfType<func::FuncOp>();
OpBuilder top(func.getBody());
auto loc = region.loc;
auto memref = region.memref;
auto memRefType = cast<MemRefType>(memref.getType());
if (!memRefType.getLayout().isIdentity()) {
LLVM_DEBUG(llvm::dbgs() << "Non-identity layout map not yet supported\n");
return failure();
}
SmallVector<Value, 4> memIndices;
SmallVector<Value, 4> bufIndices;
unsigned rank = memRefType.getRank();
SmallVector<int64_t, 4> fastBufferShape;
std::vector<SmallVector<int64_t, 4>> lbs;
SmallVector<int64_t, 8> lbDivisors;
lbs.reserve(rank);
std::optional<int64_t> numElements = region.getConstantBoundingSizeAndShape(
&fastBufferShape, &lbs, &lbDivisors);
if (!numElements) {
LLVM_DEBUG(llvm::dbgs() << "Non-constant region size not supported\n");
return failure();
}
if (*numElements == 0) {
LLVM_DEBUG(llvm::dbgs() << "Nothing to copy\n");
return success();
}
SmallVector<AffineMap, 4> lbMaps(rank), ubMaps(rank);
for (unsigned i = 0; i < rank; ++i)
region.getLowerAndUpperBound(i, lbMaps[i], ubMaps[i]);
const FlatAffineValueConstraints *cst = region.getConstraints();
SmallVector<Value, 8> regionSymbols;
cst->getValues(rank, cst->getNumVars(), ®ionSymbols);
SmallVector<AffineExpr, 4> fastBufOffsets;
fastBufOffsets.reserve(rank);
for (unsigned d = 0; d < rank; d++) {
assert(lbs[d].size() == cst->getNumCols() - rank && "incorrect bound size");
AffineExpr offset = top.getAffineConstantExpr(0);
for (unsigned j = 0, e = cst->getNumCols() - rank - 1; j < e; j++)
offset = offset + lbs[d][j] * top.getAffineDimExpr(j);
assert(lbDivisors[d] > 0);
offset =
(offset + lbs[d][cst->getNumCols() - 1 - rank]).floorDiv(lbDivisors[d]);
if (auto caf = dyn_cast<AffineConstantExpr>(offset)) {
auto indexVal = caf.getValue();
if (indexVal == 0) {
memIndices.push_back(zeroIndex);
} else {
memIndices.push_back(
top.create<arith::ConstantIndexOp>(loc, indexVal).getResult());
}
} else {
auto map = AffineMap::get(
cst->getNumDimVars() + cst->getNumSymbolVars() - rank, 0, offset);
memIndices.push_back(b.create<AffineApplyOp>(loc, map, regionSymbols));
}
bufIndices.push_back(zeroIndex);
fastBufOffsets.push_back(offset);
}
Value fastMemRef;
bool existingBuf = fastBufferMap.count(memref) > 0;
if (!existingBuf) {
AffineMap fastBufferLayout = b.getMultiDimIdentityMap(rank);
auto fastMemRefType =
MemRefType::get(fastBufferShape, memRefType.getElementType(),
fastBufferLayout, copyOptions.fastMemorySpace);
fastMemRef =
prologue.create<memref::AllocOp>(loc, fastMemRefType).getResult();
fastBufferMap[memref] = fastMemRef;
auto maySizeInBytes = getIntOrFloatMemRefSizeInBytes(fastMemRefType);
*sizeInBytes = maySizeInBytes ? *maySizeInBytes : 0;
LLVM_DEBUG(emitRemarkForBlock(*block)
<< "Creating fast buffer of type " << fastMemRefType
<< " and size " << llvm::divideCeil(*sizeInBytes, 1024)
<< " KiB\n");
} else {
fastMemRef = fastBufferMap[memref];
}
auto numElementsSSA = top.create<arith::ConstantIndexOp>(loc, *numElements);
Value dmaStride;
Value numEltPerDmaStride;
if (copyOptions.generateDma) {
SmallVector<StrideInfo, 4> dmaStrideInfos;
getMultiLevelStrides(region, fastBufferShape, &dmaStrideInfos);
if (dmaStrideInfos.size() > 1) {
LLVM_DEBUG(llvm::dbgs() << "Only up to one level of stride supported\n");
return failure();
}
if (!dmaStrideInfos.empty()) {
dmaStride =
top.create<arith::ConstantIndexOp>(loc, dmaStrideInfos[0].stride);
numEltPerDmaStride = top.create<arith::ConstantIndexOp>(
loc, dmaStrideInfos[0].numEltPerStride);
}
}
auto postDomFilter = std::prev(end);
auto memAffineMap = b.getMultiDimIdentityMap(memIndices.size());
fullyComposeAffineMapAndOperands(&memAffineMap, &memIndices);
auto bufAffineMap = b.getMultiDimIdentityMap(bufIndices.size());
fullyComposeAffineMapAndOperands(&bufAffineMap, &bufIndices);
if (!copyOptions.generateDma) {
auto copyNest =
generatePointWiseCopy(loc, memref, fastMemRef, lbMaps,
regionSymbols, ubMaps,
regionSymbols, fastBufOffsets,
region.isWrite(), b);
copyNests.insert(copyNest);
if (region.isWrite() && isCopyOutAtEndOfBlock)
*nEnd = Block::iterator(copyNest.getOperation());
} else {
auto tagMemRefType = MemRefType::get({1}, top.getIntegerType(32), {},
copyOptions.tagMemorySpace);
auto tagMemRef = prologue.create<memref::AllocOp>(loc, tagMemRefType);
SmallVector<Value, 4> tagIndices({zeroIndex});
auto tagAffineMap = b.getMultiDimIdentityMap(tagIndices.size());
fullyComposeAffineMapAndOperands(&tagAffineMap, &tagIndices);
if (!region.isWrite()) {
b.create<AffineDmaStartOp>(loc, memref, memAffineMap, memIndices,
fastMemRef, bufAffineMap, bufIndices,
tagMemRef, tagAffineMap, tagIndices,
numElementsSSA, dmaStride, numEltPerDmaStride);
} else {
auto op = b.create<AffineDmaStartOp>(
loc, fastMemRef, bufAffineMap, bufIndices, memref, memAffineMap,
memIndices, tagMemRef, tagAffineMap, tagIndices, numElementsSSA,
dmaStride, numEltPerDmaStride);
if (isCopyOutAtEndOfBlock)
*nEnd = Block::iterator(op.getOperation());
}
b.create<AffineDmaWaitOp>(loc, tagMemRef, tagAffineMap, zeroIndex,
numElementsSSA);
auto tagDeallocOp = epilogue.create<memref::DeallocOp>(loc, tagMemRef);
if (*nEnd == end && isCopyOutAtEndOfBlock)
*nEnd = Block::iterator(tagDeallocOp.getOperation());
}
if (!existingBuf) {
auto bufDeallocOp = epilogue.create<memref::DeallocOp>(loc, fastMemRef);
if (!copyOptions.generateDma && *nEnd == end && isCopyOutAtEndOfBlock)
*nEnd = Block::iterator(bufDeallocOp.getOperation());
}
SmallVector<AffineExpr, 4> remapExprs;
remapExprs.reserve(rank);
for (unsigned i = 0; i < rank; i++) {
auto dimExpr = b.getAffineDimExpr(regionSymbols.size() + i);
remapExprs.push_back(dimExpr - fastBufOffsets[i]);
}
auto indexRemap = AffineMap::get(regionSymbols.size() + rank, 0, remapExprs,
b.getContext());
Block::iterator prevOfBegin;
bool isBeginAtStartOfBlock = (begin == block->begin());
if (!isBeginAtStartOfBlock)
prevOfBegin = std::prev(begin);
(void)replaceAllMemRefUsesWith(memref, fastMemRef,
{}, indexRemap,
regionSymbols,
{},
&*begin,
&*postDomFilter);
*nBegin = isBeginAtStartOfBlock ? block->begin() : std::next(prevOfBegin);
return success();
}
static bool getFullMemRefAsRegion(Operation *op, unsigned numParamLoopIVs,
MemRefRegion *region) {
unsigned rank;
if (auto loadOp = dyn_cast<AffineLoadOp>(op)) {
rank = loadOp.getMemRefType().getRank();
region->memref = loadOp.getMemRef();
region->setWrite(false);
} else if (auto storeOp = dyn_cast<AffineStoreOp>(op)) {
rank = storeOp.getMemRefType().getRank();
region->memref = storeOp.getMemRef();
region->setWrite(true);
} else {
assert(false && "expected load or store op");
return false;
}
auto memRefType = cast<MemRefType>(region->memref.getType());
if (!memRefType.hasStaticShape())
return false;
auto *regionCst = region->getConstraints();
SmallVector<AffineForOp, 4> ivs;
getAffineForIVs(*op, &ivs);
ivs.resize(numParamLoopIVs);
SmallVector<Value, 4> symbols;
extractForInductionVars(ivs, &symbols);
*regionCst = FlatAffineValueConstraints(rank, numParamLoopIVs, 0);
regionCst->setValues(rank, rank + numParamLoopIVs, symbols);
for (unsigned d = 0; d < rank; d++) {
auto dimSize = memRefType.getDimSize(d);
assert(dimSize > 0 && "filtered dynamic shapes above");
regionCst->addBound(BoundType::LB, d, 0);
regionCst->addBound(BoundType::UB, d, dimSize - 1);
}
return true;
}
LogicalResult
mlir::affine::affineDataCopyGenerate(Block::iterator begin, Block::iterator end,
const AffineCopyOptions ©Options,
std::optional<Value> filterMemRef,
DenseSet<Operation *> ©Nests) {
if (begin == end)
return success();
assert(begin->getBlock() == std::prev(end)->getBlock() &&
"Inconsistent block begin/end args");
assert(end != end->getBlock()->end() && "end can't be the block terminator");
Block *block = begin->getBlock();
unsigned copyDepth = getNestingDepth(&*begin);
LLVM_DEBUG(llvm::dbgs() << "Generating copies at depth " << copyDepth
<< "\n");
LLVM_DEBUG(llvm::dbgs() << "from begin: " << *begin << "\n");
LLVM_DEBUG(llvm::dbgs() << "to inclusive end: " << *std::prev(end) << "\n");
SmallMapVector<Value, std::unique_ptr<MemRefRegion>, 4> readRegions;
SmallMapVector<Value, std::unique_ptr<MemRefRegion>, 4> writeRegions;
DenseMap<Value, Value> fastBufferMap;
bool error = false;
block->walk(begin, end, [&](Operation *opInst) {
if (auto loadOp = dyn_cast<AffineLoadOp>(opInst)) {
if ((filterMemRef.has_value() && filterMemRef != loadOp.getMemRef()) ||
(loadOp.getMemRefType().getMemorySpaceAsInt() !=
copyOptions.slowMemorySpace))
return;
} else if (auto storeOp = dyn_cast<AffineStoreOp>(opInst)) {
if ((filterMemRef.has_value() && filterMemRef != storeOp.getMemRef()) ||
storeOp.getMemRefType().getMemorySpaceAsInt() !=
copyOptions.slowMemorySpace)
return;
} else {
return;
}
auto region = std::make_unique<MemRefRegion>(opInst->getLoc());
if (failed(region->compute(opInst, copyDepth, nullptr,
false))) {
LLVM_DEBUG(llvm::dbgs()
<< "Error obtaining memory region: semi-affine maps?\n");
LLVM_DEBUG(llvm::dbgs() << "over-approximating to the entire memref\n");
if (!getFullMemRefAsRegion(opInst, copyDepth, region.get())) {
LLVM_DEBUG(
opInst->emitError("non-constant memref sizes not yet supported"));
error = true;
return;
}
}
auto updateRegion =
[&](const SmallMapVector<Value, std::unique_ptr<MemRefRegion>, 4>
&targetRegions) {
const auto *const it = targetRegions.find(region->memref);
if (it == targetRegions.end())
return false;
if (failed(it->second->unionBoundingBox(*region))) {
LLVM_DEBUG(llvm::dbgs()
<< "Memory region bounding box failed; "
"over-approximating to the entire memref\n");
if (!getFullMemRefAsRegion(opInst, copyDepth, region.get())) {
LLVM_DEBUG(opInst->emitError(
"non-constant memref sizes not yet supported"));
error = true;
return true;
}
it->second->getConstraints()->clearAndCopyFrom(
*region->getConstraints());
} else {
region->getConstraints()->clearAndCopyFrom(
*it->second->getConstraints());
}
return true;
};
bool existsInRead = updateRegion(readRegions);
if (error)
return;
bool existsInWrite = updateRegion(writeRegions);
if (error)
return;
if (region->isWrite() && !existsInWrite) {
writeRegions[region->memref] = std::move(region);
} else if (!region->isWrite() && !existsInRead) {
readRegions[region->memref] = std::move(region);
}
});
if (error) {
LLVM_DEBUG(begin->emitError(
"copy generation failed for one or more memref's in this block\n"));
return failure();
}
uint64_t totalCopyBuffersSizeInBytes = 0;
bool ret = true;
auto processRegions =
[&](const SmallMapVector<Value, std::unique_ptr<MemRefRegion>, 4>
®ions) {
for (const auto ®ionEntry : regions) {
Block::iterator copyInPlacementStart, copyOutPlacementStart;
Block *copyPlacementBlock;
findHighestBlockForPlacement(
*regionEntry.second, *block, begin, end, ©PlacementBlock,
©InPlacementStart, ©OutPlacementStart);
uint64_t sizeInBytes;
Block::iterator nBegin, nEnd;
LogicalResult iRet = generateCopy(
*regionEntry.second, block, begin, end, copyPlacementBlock,
copyInPlacementStart, copyOutPlacementStart, copyOptions,
fastBufferMap, copyNests, &sizeInBytes, &nBegin, &nEnd);
if (succeeded(iRet)) {
begin = nBegin;
end = nEnd;
totalCopyBuffersSizeInBytes += sizeInBytes;
}
ret = ret & succeeded(iRet);
}
};
processRegions(readRegions);
processRegions(writeRegions);
if (!ret) {
LLVM_DEBUG(begin->emitError(
"copy generation failed for one or more memref's in this block\n"));
return failure();
}
AffineForOp forOp;
if (llvm::DebugFlag && (forOp = dyn_cast<AffineForOp>(&*begin))) {
LLVM_DEBUG(forOp.emitRemark()
<< llvm::divideCeil(totalCopyBuffersSizeInBytes, 1024)
<< " KiB of copy buffers in fast memory space for this block");
}
if (totalCopyBuffersSizeInBytes > copyOptions.fastMemCapacityBytes) {
block->getParentOp()->emitWarning(
"total size of all copy buffers' for this block exceeds fast memory "
"capacity");
}
return success();
}
LogicalResult mlir::affine::affineDataCopyGenerate(
AffineForOp forOp, const AffineCopyOptions ©Options,
std::optional<Value> filterMemRef, DenseSet<Operation *> ©Nests) {
return affineDataCopyGenerate(forOp.getBody()->begin(),
std::prev(forOp.getBody()->end()), copyOptions,
filterMemRef, copyNests);
}
LogicalResult mlir::affine::generateCopyForMemRegion(
const MemRefRegion &memrefRegion, Operation *analyzedOp,
const AffineCopyOptions ©Options, CopyGenerateResult &result) {
Block *block = analyzedOp->getBlock();
auto begin = analyzedOp->getIterator();
auto end = std::next(begin);
DenseMap<Value, Value> fastBufferMap;
DenseSet<Operation *> copyNests;
auto err = generateCopy(memrefRegion, block, begin, end, block, begin, end,
copyOptions, fastBufferMap, copyNests,
&result.sizeInBytes, &begin, &end);
if (failed(err))
return err;
const auto &en = fastBufferMap.find(memrefRegion.memref);
if (en == fastBufferMap.end())
return failure();
result.alloc = en->second.getDefiningOp();
assert(result.alloc && "fast buffer expected to be locally allocated");
assert(copyNests.size() <= 1 && "At most one copy nest is expected.");
result.copyNest = copyNests.empty() ? nullptr : *copyNests.begin();
return success();
}
static void
gatherLoopsInBlock(Block *block, unsigned currLoopDepth,
std::vector<SmallVector<AffineForOp, 2>> &depthToLoops) {
assert(currLoopDepth <= depthToLoops.size() && "Unexpected currLoopDepth");
if (currLoopDepth == depthToLoops.size())
depthToLoops.emplace_back();
for (auto &op : *block) {
if (auto forOp = dyn_cast<AffineForOp>(op)) {
depthToLoops[currLoopDepth].push_back(forOp);
gatherLoopsInBlock(forOp.getBody(), currLoopDepth + 1, depthToLoops);
}
}
}
void mlir::affine::gatherLoops(
func::FuncOp func, std::vector<SmallVector<AffineForOp, 2>> &depthToLoops) {
for (auto &block : func)
gatherLoopsInBlock(&block, 0, depthToLoops);
if (!depthToLoops.empty()) {
assert(depthToLoops.back().empty() && "Last loop level is not empty?");
depthToLoops.pop_back();
}
}
AffineForOp mlir::affine::createCanonicalizedAffineForOp(
OpBuilder b, Location loc, ValueRange lbOperands, AffineMap lbMap,
ValueRange ubOperands, AffineMap ubMap, int64_t step) {
SmallVector<Value, 4> lowerOperands(lbOperands);
SmallVector<Value, 4> upperOperands(ubOperands);
fullyComposeAffineMapAndOperands(&lbMap, &lowerOperands);
canonicalizeMapAndOperands(&lbMap, &lowerOperands);
lbMap = removeDuplicateExprs(lbMap);
fullyComposeAffineMapAndOperands(&ubMap, &upperOperands);
canonicalizeMapAndOperands(&ubMap, &upperOperands);
ubMap = removeDuplicateExprs(ubMap);
return b.create<AffineForOp>(loc, lowerOperands, lbMap, upperOperands, ubMap,
step);
}
static AffineIfOp createSeparationCondition(MutableArrayRef<AffineForOp> loops,
OpBuilder b) {
if (loops.empty())
return nullptr;
auto *context = loops[0].getContext();
FlatAffineValueConstraints cst;
SmallVector<Operation *, 8> ops;
llvm::append_range(ops, loops);
(void)getIndexSet(ops, &cst);
cst.removeIndependentConstraints(0, loops.size());
SmallVector<int64_t, 8> fullTileLb, fullTileUb;
for (auto loop : loops) {
(void)loop;
assert(loop.getStepAsInt() == 1 && "point loop step expected to be one");
cst.setDimSymbolSeparation(cst.getNumDimAndSymbolVars() -
1);
unsigned fullTileLbPos, fullTileUbPos;
if (!cst.getConstantBoundOnDimSize(0, nullptr,
nullptr,
nullptr, &fullTileLbPos,
&fullTileUbPos)) {
LLVM_DEBUG(llvm::dbgs() << "Can't get constant diff pair for a loop\n");
return nullptr;
}
SmallVector<unsigned, 4> lbIndices, ubIndices;
cst.getLowerAndUpperBoundIndices(0, &lbIndices, &ubIndices);
auto fLb = cst.getInequality(fullTileLbPos);
auto fUb = cst.getInequality(fullTileUbPos);
fullTileLb.assign(fLb.begin(), fLb.end());
fullTileUb.assign(fUb.begin(), fUb.end());
for (auto lbIndex : lbIndices)
for (unsigned i = 0, e = cst.getNumCols(); i < e; ++i)
cst.atIneq(lbIndex, i) = fullTileLb[i] - cst.atIneq(lbIndex, i);
for (auto ubIndex : ubIndices)
for (unsigned i = 0, e = cst.getNumCols(); i < e; ++i)
cst.atIneq(ubIndex, i) -= fullTileUb[i];
cst.removeVar(0);
}
cst.removeTrivialRedundancy();
cst.setDimSymbolSeparation(0);
IntegerSet ifCondSet = cst.getAsIntegerSet(context);
if (!ifCondSet)
return nullptr;
SmallVector<Value, 4> setOperands;
cst.getValues(0, cst.getNumDimAndSymbolVars(), &setOperands);
canonicalizeSetAndOperands(&ifCondSet, &setOperands);
return b.create<AffineIfOp>(loops[0].getLoc(), ifCondSet, setOperands,
true);
}
static LogicalResult
createFullTiles(MutableArrayRef<AffineForOp> inputNest,
SmallVectorImpl<AffineForOp> &fullTileLoops, OpBuilder b) {
fullTileLoops.reserve(inputNest.size());
FlatAffineValueConstraints cst;
for (auto loop : inputNest) {
if (loop.getStepAsInt() != 1) {
LLVM_DEBUG(llvm::dbgs()
<< "[tile separation] non-unit stride not implemented\n");
return failure();
}
SmallVector<Operation *, 1> loopOp{loop.getOperation()};
(void)getIndexSet(loopOp, &cst);
cst.setDimSymbolSeparation(cst.getNumDimAndSymbolVars() - 1);
unsigned lbPos, ubPos;
if (!cst.getConstantBoundOnDimSize(0, nullptr,
nullptr,
nullptr, &lbPos, &ubPos) ||
lbPos == ubPos) {
LLVM_DEBUG(llvm::dbgs() << "[tile separation] Can't get constant diff / "
"equalities not yet handled\n");
return failure();
}
cst.setDimSymbolSeparation(0);
AffineValueMap lbVmap, ubVmap;
cst.getIneqAsAffineValueMap(0, lbPos, lbVmap, b.getContext());
cst.getIneqAsAffineValueMap(0, ubPos, ubVmap, b.getContext());
AffineForOp fullTileLoop = createCanonicalizedAffineForOp(
b, loop.getLoc(), lbVmap.getOperands(), lbVmap.getAffineMap(),
ubVmap.getOperands(), ubVmap.getAffineMap());
b = OpBuilder::atBlockTerminator(fullTileLoop.getBody());
fullTileLoops.push_back(fullTileLoop);
}
IRMapping operandMap;
for (const auto &loopEn : llvm::enumerate(inputNest))
operandMap.map(loopEn.value().getInductionVar(),
fullTileLoops[loopEn.index()].getInductionVar());
b = OpBuilder::atBlockTerminator(fullTileLoops.back().getBody());
for (auto &op : inputNest.back().getBody()->without_terminator())
b.clone(op, operandMap);
return success();
}
LogicalResult
mlir::affine::separateFullTiles(MutableArrayRef<AffineForOp> inputNest,
SmallVectorImpl<AffineForOp> *fullTileNest) {
if (inputNest.empty())
return success();
auto firstLoop = inputNest[0];
auto prevLoop = firstLoop;
for (auto loop : inputNest.drop_front(1)) {
assert(loop->getParentOp() == prevLoop && "input not contiguously nested");
prevLoop = loop;
}
SmallVector<AffineForOp, 4> fullTileLoops;
OpBuilder b(firstLoop);
if (failed(createFullTiles(inputNest, fullTileLoops, b))) {
if (!fullTileLoops.empty())
fullTileLoops.front().erase();
return failure();
}
b = OpBuilder(firstLoop);
AffineIfOp ifOp = createSeparationCondition(inputNest, b);
if (!ifOp) {
fullTileLoops.front().erase();
LLVM_DEBUG(llvm::dbgs() << "All tiles are full tiles, or failure creating "
"separation condition\n");
return failure();
}
Block *thenBlock = ifOp.getThenBlock();
AffineForOp outermostFullTileLoop = fullTileLoops[0];
thenBlock->getOperations().splice(
std::prev(thenBlock->end()),
outermostFullTileLoop->getBlock()->getOperations(),
Block::iterator(outermostFullTileLoop));
Block *elseBlock = ifOp.getElseBlock();
elseBlock->getOperations().splice(std::prev(elseBlock->end()),
firstLoop->getBlock()->getOperations(),
Block::iterator(firstLoop));
if (fullTileNest)
*fullTileNest = std::move(fullTileLoops);
return success();
}
LogicalResult affine::coalescePerfectlyNestedAffineLoops(AffineForOp op) {
LogicalResult result(failure());
SmallVector<AffineForOp> loops;
getPerfectlyNestedLoops(loops, op);
if (loops.size() <= 1)
return success();
SmallVector<unsigned> operandsDefinedAbove(loops.size());
for (unsigned i = 0, e = loops.size(); i < e; ++i) {
operandsDefinedAbove[i] = i;
for (unsigned j = 0; j < i; ++j) {
if (areValuesDefinedAbove(loops[i].getOperands(), loops[j].getRegion())) {
operandsDefinedAbove[i] = j;
break;
}
}
}
for (unsigned end = loops.size(); end > 0; --end) {
unsigned start = 0;
for (; start < end - 1; ++start) {
auto maxPos =
*std::max_element(std::next(operandsDefinedAbove.begin(), start),
std::next(operandsDefinedAbove.begin(), end));
if (maxPos > start)
continue;
assert(maxPos == start &&
"expected loop bounds to be known at the start of the band");
auto band = llvm::MutableArrayRef(loops.data() + start, end - start);
if (succeeded(coalesceLoops(band)))
result = success();
break;
}
if (start != end - 1)
end = start + 1;
}
return result;
}