#include "mlir/Dialect/Affine/Utils.h"
#include "mlir/Dialect/Affine/Analysis/Utils.h"
#include "mlir/Dialect/Affine/IR/AffineOps.h"
#include "mlir/Dialect/Affine/IR/AffineValueMap.h"
#include "mlir/Dialect/Affine/LoopUtils.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/IR/AffineExprVisitor.h"
#include "mlir/IR/BlockAndValueMapping.h"
#include "mlir/IR/Dominance.h"
#include "mlir/IR/IntegerSet.h"
#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
#define DEBUG_TYPE "affine-utils"
using namespace mlir;
using namespace presburger;
namespace {
class AffineApplyExpander
: public AffineExprVisitor<AffineApplyExpander, Value> {
public:
AffineApplyExpander(OpBuilder &builder, ValueRange dimValues,
ValueRange symbolValues, Location loc)
: builder(builder), dimValues(dimValues), symbolValues(symbolValues),
loc(loc) {}
template <typename OpTy>
Value buildBinaryExpr(AffineBinaryOpExpr expr) {
auto lhs = visit(expr.getLHS());
auto rhs = visit(expr.getRHS());
if (!lhs || !rhs)
return nullptr;
auto op = builder.create<OpTy>(loc, lhs, rhs);
return op.getResult();
}
Value visitAddExpr(AffineBinaryOpExpr expr) {
return buildBinaryExpr<arith::AddIOp>(expr);
}
Value visitMulExpr(AffineBinaryOpExpr expr) {
return buildBinaryExpr<arith::MulIOp>(expr);
}
Value visitModExpr(AffineBinaryOpExpr expr) {
auto rhsConst = expr.getRHS().dyn_cast<AffineConstantExpr>();
if (!rhsConst) {
emitError(
loc,
"semi-affine expressions (modulo by non-const) are not supported");
return nullptr;
}
if (rhsConst.getValue() <= 0) {
emitError(loc, "modulo by non-positive value is not supported");
return nullptr;
}
auto lhs = visit(expr.getLHS());
auto rhs = visit(expr.getRHS());
assert(lhs && rhs && "unexpected affine expr lowering failure");
Value remainder = builder.create<arith::RemSIOp>(loc, lhs, rhs);
Value zeroCst = builder.create<arith::ConstantIndexOp>(loc, 0);
Value isRemainderNegative = builder.create<arith::CmpIOp>(
loc, arith::CmpIPredicate::slt, remainder, zeroCst);
Value correctedRemainder =
builder.create<arith::AddIOp>(loc, remainder, rhs);
Value result = builder.create<arith::SelectOp>(
loc, isRemainderNegative, correctedRemainder, remainder);
return result;
}
Value visitFloorDivExpr(AffineBinaryOpExpr expr) {
auto rhsConst = expr.getRHS().dyn_cast<AffineConstantExpr>();
if (!rhsConst) {
emitError(
loc,
"semi-affine expressions (division by non-const) are not supported");
return nullptr;
}
if (rhsConst.getValue() <= 0) {
emitError(loc, "division by non-positive value is not supported");
return nullptr;
}
auto lhs = visit(expr.getLHS());
auto rhs = visit(expr.getRHS());
assert(lhs && rhs && "unexpected affine expr lowering failure");
Value zeroCst = builder.create<arith::ConstantIndexOp>(loc, 0);
Value noneCst = builder.create<arith::ConstantIndexOp>(loc, -1);
Value negative = builder.create<arith::CmpIOp>(
loc, arith::CmpIPredicate::slt, lhs, zeroCst);
Value negatedDecremented = builder.create<arith::SubIOp>(loc, noneCst, lhs);
Value dividend =
builder.create<arith::SelectOp>(loc, negative, negatedDecremented, lhs);
Value quotient = builder.create<arith::DivSIOp>(loc, dividend, rhs);
Value correctedQuotient =
builder.create<arith::SubIOp>(loc, noneCst, quotient);
Value result = builder.create<arith::SelectOp>(loc, negative,
correctedQuotient, quotient);
return result;
}
Value visitCeilDivExpr(AffineBinaryOpExpr expr) {
auto rhsConst = expr.getRHS().dyn_cast<AffineConstantExpr>();
if (!rhsConst) {
emitError(loc) << "semi-affine expressions (division by non-const) are "
"not supported";
return nullptr;
}
if (rhsConst.getValue() <= 0) {
emitError(loc, "division by non-positive value is not supported");
return nullptr;
}
auto lhs = visit(expr.getLHS());
auto rhs = visit(expr.getRHS());
assert(lhs && rhs && "unexpected affine expr lowering failure");
Value zeroCst = builder.create<arith::ConstantIndexOp>(loc, 0);
Value oneCst = builder.create<arith::ConstantIndexOp>(loc, 1);
Value nonPositive = builder.create<arith::CmpIOp>(
loc, arith::CmpIPredicate::sle, lhs, zeroCst);
Value negated = builder.create<arith::SubIOp>(loc, zeroCst, lhs);
Value decremented = builder.create<arith::SubIOp>(loc, lhs, oneCst);
Value dividend =
builder.create<arith::SelectOp>(loc, nonPositive, negated, decremented);
Value quotient = builder.create<arith::DivSIOp>(loc, dividend, rhs);
Value negatedQuotient =
builder.create<arith::SubIOp>(loc, zeroCst, quotient);
Value incrementedQuotient =
builder.create<arith::AddIOp>(loc, quotient, oneCst);
Value result = builder.create<arith::SelectOp>(
loc, nonPositive, negatedQuotient, incrementedQuotient);
return result;
}
Value visitConstantExpr(AffineConstantExpr expr) {
auto op = builder.create<arith::ConstantIndexOp>(loc, expr.getValue());
return op.getResult();
}
Value visitDimExpr(AffineDimExpr expr) {
assert(expr.getPosition() < dimValues.size() &&
"affine dim position out of range");
return dimValues[expr.getPosition()];
}
Value visitSymbolExpr(AffineSymbolExpr expr) {
assert(expr.getPosition() < symbolValues.size() &&
"symbol dim position out of range");
return symbolValues[expr.getPosition()];
}
private:
OpBuilder &builder;
ValueRange dimValues;
ValueRange symbolValues;
Location loc;
};
}
mlir::Value mlir::expandAffineExpr(OpBuilder &builder, Location loc,
AffineExpr expr, ValueRange dimValues,
ValueRange symbolValues) {
return AffineApplyExpander(builder, dimValues, symbolValues, loc).visit(expr);
}
Optional<SmallVector<Value, 8>> mlir::expandAffineMap(OpBuilder &builder,
Location loc,
AffineMap affineMap,
ValueRange operands) {
auto numDims = affineMap.getNumDims();
auto expanded = llvm::to_vector<8>(
llvm::map_range(affineMap.getResults(),
[numDims, &builder, loc, operands](AffineExpr expr) {
return expandAffineExpr(builder, loc, expr,
operands.take_front(numDims),
operands.drop_front(numDims));
}));
if (llvm::all_of(expanded, [](Value v) { return v; }))
return expanded;
return None;
}
static void promoteIfBlock(AffineIfOp ifOp, bool elseBlock) {
if (elseBlock)
assert(ifOp.hasElse() && "else block expected");
Block *destBlock = ifOp->getBlock();
Block *srcBlock = elseBlock ? ifOp.getElseBlock() : ifOp.getThenBlock();
destBlock->getOperations().splice(
Block::iterator(ifOp), srcBlock->getOperations(), srcBlock->begin(),
std::prev(srcBlock->end()));
ifOp.erase();
}
static Operation *getOutermostInvariantForOp(AffineIfOp ifOp) {
auto ifOperands = ifOp.getOperands();
auto *res = ifOp.getOperation();
while (!isa<func::FuncOp>(res->getParentOp())) {
auto *parentOp = res->getParentOp();
if (auto forOp = dyn_cast<AffineForOp>(parentOp)) {
if (llvm::is_contained(ifOperands, forOp.getInductionVar()))
break;
} else if (auto parallelOp = dyn_cast<AffineParallelOp>(parentOp)) {
for (auto iv : parallelOp.getIVs())
if (llvm::is_contained(ifOperands, iv))
break;
} else if (!isa<AffineIfOp>(parentOp)) {
break;
}
res = parentOp;
}
return res;
}
static AffineIfOp hoistAffineIfOp(AffineIfOp ifOp, Operation *hoistOverOp) {
if (hoistOverOp == ifOp)
return ifOp;
BlockAndValueMapping operandMap;
OpBuilder b(hoistOverOp);
auto hoistedIfOp = b.create<AffineIfOp>(ifOp.getLoc(), ifOp.getIntegerSet(),
ifOp.getOperands(),
true);
Operation *hoistOverOpClone = nullptr;
StringAttr idForIfOp = b.getStringAttr("__mlir_if_hoisting");
operandMap.clear();
b.setInsertionPointAfter(hoistOverOp);
ifOp->setAttr(idForIfOp, b.getBoolAttr(true));
hoistOverOpClone = b.clone(*hoistOverOp, operandMap);
promoteIfBlock(ifOp, false);
auto *thenBlock = hoistedIfOp.getThenBlock();
thenBlock->getOperations().splice(thenBlock->begin(),
hoistOverOp->getBlock()->getOperations(),
Block::iterator(hoistOverOp));
AffineIfOp ifCloneInElse;
hoistOverOpClone->walk([&](AffineIfOp ifClone) {
if (!ifClone->getAttr(idForIfOp))
return WalkResult::advance();
ifCloneInElse = ifClone;
return WalkResult::interrupt();
});
assert(ifCloneInElse && "if op clone should exist");
if (!ifCloneInElse.hasElse())
ifCloneInElse.erase();
else
promoteIfBlock(ifCloneInElse, true);
auto *elseBlock = hoistedIfOp.getElseBlock();
elseBlock->getOperations().splice(
elseBlock->begin(), hoistOverOpClone->getBlock()->getOperations(),
Block::iterator(hoistOverOpClone));
return hoistedIfOp;
}
LogicalResult
mlir::affineParallelize(AffineForOp forOp,
ArrayRef<LoopReduction> parallelReductions) {
unsigned numReductions = parallelReductions.size();
if (numReductions != forOp.getNumIterOperands())
return failure();
Location loc = forOp.getLoc();
OpBuilder outsideBuilder(forOp);
AffineMap lowerBoundMap = forOp.getLowerBoundMap();
ValueRange lowerBoundOperands = forOp.getLowerBoundOperands();
AffineMap upperBoundMap = forOp.getUpperBoundMap();
ValueRange upperBoundOperands = forOp.getUpperBoundOperands();
auto reducedValues = llvm::to_vector<4>(llvm::map_range(
parallelReductions, [](const LoopReduction &red) { return red.value; }));
auto reductionKinds = llvm::to_vector<4>(llvm::map_range(
parallelReductions, [](const LoopReduction &red) { return red.kind; }));
AffineParallelOp newPloop = outsideBuilder.create<AffineParallelOp>(
loc, ValueRange(reducedValues).getTypes(), reductionKinds,
llvm::makeArrayRef(lowerBoundMap), lowerBoundOperands,
llvm::makeArrayRef(upperBoundMap), upperBoundOperands,
llvm::makeArrayRef(forOp.getStep()));
newPloop.getRegion().takeBody(forOp.getRegion());
Operation *yieldOp = &newPloop.getBody()->back();
SmallVector<Value> newResults;
newResults.reserve(numReductions);
for (unsigned i = 0; i < numReductions; ++i) {
Value init = forOp.getIterOperands()[i];
Operation *reductionOp = yieldOp->getOperand(i).getDefiningOp();
assert(reductionOp && "yielded value is expected to be produced by an op");
outsideBuilder.getInsertionBlock()->getOperations().splice(
outsideBuilder.getInsertionPoint(), newPloop.getBody()->getOperations(),
reductionOp);
reductionOp->setOperands({init, newPloop->getResult(i)});
forOp->getResult(i).replaceAllUsesWith(reductionOp->getResult(0));
}
unsigned numIVs = 1;
yieldOp->setOperands(reducedValues);
newPloop.getBody()->eraseArguments(
llvm::to_vector<4>(llvm::seq<unsigned>(numIVs, numReductions + numIVs)));
forOp.erase();
return success();
}
LogicalResult mlir::hoistAffineIfOp(AffineIfOp ifOp, bool *folded) {
if (ifOp.getNumResults() != 0)
return failure();
RewritePatternSet patterns(ifOp.getContext());
AffineIfOp::getCanonicalizationPatterns(patterns, ifOp.getContext());
bool erased;
FrozenRewritePatternSet frozenPatterns(std::move(patterns));
(void)applyOpPatternsAndFold(ifOp, frozenPatterns, &erased);
if (erased) {
if (folded)
*folded = true;
return failure();
}
if (folded)
*folded = false;
assert(llvm::all_of(ifOp.getOperands(),
[](Value v) {
return isTopLevelValue(v) || isForInductionVar(v);
}) &&
"operands not composed");
auto *hoistOverOp = getOutermostInvariantForOp(ifOp);
AffineIfOp hoistedIfOp = ::hoistAffineIfOp(ifOp, hoistOverOp);
if (hoistedIfOp == ifOp)
return failure();
(void)applyPatternsAndFoldGreedily(
hoistedIfOp->getParentWithTrait<OpTrait::IsIsolatedFromAbove>(),
frozenPatterns);
return success();
}
AffineExpr mlir::substWithMin(AffineExpr e, AffineExpr dim, AffineExpr min,
AffineExpr max, bool positivePath) {
if (e == dim)
return positivePath ? min : max;
if (auto bin = e.dyn_cast<AffineBinaryOpExpr>()) {
AffineExpr lhs = bin.getLHS();
AffineExpr rhs = bin.getRHS();
if (bin.getKind() == mlir::AffineExprKind::Add)
return substWithMin(lhs, dim, min, max, positivePath) +
substWithMin(rhs, dim, min, max, positivePath);
auto c1 = bin.getLHS().dyn_cast<AffineConstantExpr>();
auto c2 = bin.getRHS().dyn_cast<AffineConstantExpr>();
if (c1 && c1.getValue() < 0)
return getAffineBinaryOpExpr(
bin.getKind(), c1, substWithMin(rhs, dim, min, max, !positivePath));
if (c2 && c2.getValue() < 0)
return getAffineBinaryOpExpr(
bin.getKind(), substWithMin(lhs, dim, min, max, !positivePath), c2);
return getAffineBinaryOpExpr(
bin.getKind(), substWithMin(lhs, dim, min, max, positivePath),
substWithMin(rhs, dim, min, max, positivePath));
}
return e;
}
void mlir::normalizeAffineParallel(AffineParallelOp op) {
if (op.hasMinMaxBounds())
return;
AffineMap lbMap = op.getLowerBoundsMap();
SmallVector<int64_t, 8> steps = op.getSteps();
bool isAlreadyNormalized =
llvm::all_of(llvm::zip(steps, lbMap.getResults()), [](auto tuple) {
int64_t step = std::get<0>(tuple);
auto lbExpr =
std::get<1>(tuple).template dyn_cast<AffineConstantExpr>();
return lbExpr && lbExpr.getValue() == 0 && step == 1;
});
if (isAlreadyNormalized)
return;
AffineValueMap ranges;
AffineValueMap::difference(op.getUpperBoundsValueMap(),
op.getLowerBoundsValueMap(), &ranges);
auto builder = OpBuilder::atBlockBegin(op.getBody());
auto zeroExpr = builder.getAffineConstantExpr(0);
SmallVector<AffineExpr, 8> lbExprs;
SmallVector<AffineExpr, 8> ubExprs;
for (unsigned i = 0, e = steps.size(); i < e; ++i) {
int64_t step = steps[i];
lbExprs.push_back(zeroExpr);
AffineExpr ubExpr = ranges.getResult(i).ceilDiv(step);
ubExprs.push_back(ubExpr);
BlockArgument iv = op.getBody()->getArgument(i);
AffineExpr lbExpr = lbMap.getResult(i);
unsigned nDims = lbMap.getNumDims();
auto expr = lbExpr + builder.getAffineDimExpr(nDims) * step;
auto map = AffineMap::get(nDims + 1,
lbMap.getNumSymbols(), expr);
OperandRange lbOperands = op.getLowerBoundsOperands();
OperandRange dimOperands = lbOperands.take_front(nDims);
OperandRange symbolOperands = lbOperands.drop_front(nDims);
SmallVector<Value, 8> applyOperands{dimOperands};
applyOperands.push_back(iv);
applyOperands.append(symbolOperands.begin(), symbolOperands.end());
auto apply = builder.create<AffineApplyOp>(op.getLoc(), map, applyOperands);
iv.replaceAllUsesExcept(apply, apply);
}
SmallVector<int64_t, 8> newSteps(op.getNumDims(), 1);
op.setSteps(newSteps);
auto newLowerMap = AffineMap::get(
0, 0, lbExprs, op.getContext());
op.setLowerBounds({}, newLowerMap);
auto newUpperMap = AffineMap::get(ranges.getNumDims(), ranges.getNumSymbols(),
ubExprs, op.getContext());
op.setUpperBounds(ranges.getOperands(), newUpperMap);
}
LogicalResult mlir::normalizeAffineFor(AffineForOp op) {
if (succeeded(promoteIfSingleIteration(op)))
return success();
if (op.hasConstantLowerBound() && (op.getConstantLowerBound() == 0) &&
(op.getStep() == 1))
return success();
if (op.getLowerBoundMap().getNumResults() != 1)
return failure();
Location loc = op.getLoc();
OpBuilder opBuilder(op);
int64_t origLoopStep = op.getStep();
SmallVector<Value, 4> ubOperands;
AffineBound lb = op.getLowerBound();
AffineBound ub = op.getUpperBound();
ubOperands.reserve(ub.getNumOperands() + lb.getNumOperands());
AffineMap origLbMap = lb.getMap();
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 = origLbMap.getNumDims(); j < e; ++j)
ubOperands.push_back(lb.getOperand(j));
for (unsigned j = 0, e = origUbMap.getNumSymbols(); j < e; ++j)
ubOperands.push_back(ub.getOperand(origUbMap.getNumDims() + j));
for (unsigned j = 0, e = origLbMap.getNumSymbols(); j < e; ++j)
ubOperands.push_back(lb.getOperand(origLbMap.getNumDims() + j));
SmallVector<AffineExpr, 1> origLbExprs(origLbMap.getResults().begin(),
origLbMap.getResults().end());
SmallVector<AffineExpr, 2> origUbExprs(origUbMap.getResults().begin(),
origUbMap.getResults().end());
SmallVector<AffineExpr, 4> newUbExprs;
for (unsigned i = 0, e = origUbExprs.size(); i < e; ++i) {
newUbExprs.push_back(
(origUbExprs[i] - origLbExprs[0]).ceilDiv(origLoopStep));
}
AffineMap newUbMap =
AffineMap::get(origLbMap.getNumDims() + origUbMap.getNumDims(),
origLbMap.getNumSymbols() + origUbMap.getNumSymbols(),
newUbExprs, opBuilder.getContext());
canonicalizeMapAndOperands(&newUbMap, &ubOperands);
SmallVector<Value, 4> lbOperands(lb.getOperands().begin(),
lb.getOperands().begin() +
lb.getMap().getNumDims());
op.setUpperBound(ubOperands, newUbMap);
op.setLowerBound({}, opBuilder.getConstantAffineMap(0));
op.setStep(1);
opBuilder.setInsertionPointToStart(op.getBody());
lbOperands.push_back(op.getInductionVar());
for (unsigned j = 0, e = origLbMap.getNumSymbols(); j < e; ++j)
lbOperands.push_back(lb.getOperand(origLbMap.getNumDims() + j));
AffineExpr origIVExpr = opBuilder.getAffineDimExpr(lb.getMap().getNumDims());
AffineExpr newIVExpr = origIVExpr * origLoopStep + origLbMap.getResult(0);
AffineMap ivMap = AffineMap::get(origLbMap.getNumDims() + 1,
origLbMap.getNumSymbols(), newIVExpr);
canonicalizeMapAndOperands(&ivMap, &lbOperands);
Operation *newIV = opBuilder.create<AffineApplyOp>(loc, ivMap, lbOperands);
op.getInductionVar().replaceAllUsesExcept(newIV->getResult(0), newIV);
return success();
}
template <typename EffectType, typename T>
static bool hasNoInterveningEffect(Operation *start, T memOp) {
Value memref = memOp.getMemRef();
bool isOriginalAllocation = memref.getDefiningOp<memref::AllocaOp>() ||
memref.getDefiningOp<memref::AllocOp>();
bool hasSideEffect = false;
std::function<void(Operation *)> checkOperation = [&](Operation *op) {
if (hasSideEffect)
return;
if (auto memEffect = dyn_cast<MemoryEffectOpInterface>(op)) {
SmallVector<MemoryEffects::EffectInstance, 1> effects;
memEffect.getEffects(effects);
bool opMayHaveEffect = false;
for (auto effect : effects) {
if (isa<EffectType>(effect.getEffect())) {
if (isOriginalAllocation && effect.getValue() &&
(effect.getValue().getDefiningOp<memref::AllocaOp>() ||
effect.getValue().getDefiningOp<memref::AllocOp>())) {
if (effect.getValue() != memref)
continue;
}
opMayHaveEffect = true;
break;
}
}
if (!opMayHaveEffect)
return;
if (isa<AffineReadOpInterface, AffineWriteOpInterface>(op)) {
MemRefAccess srcAccess(op);
MemRefAccess destAccess(memOp);
if (srcAccess.memref == destAccess.memref) {
FlatAffineValueConstraints dependenceConstraints;
unsigned minSurroundingLoops =
getNumCommonSurroundingLoops(*start, *memOp);
unsigned nsLoops = getNumCommonSurroundingLoops(*op, *memOp);
unsigned d;
for (d = nsLoops + 1; d > minSurroundingLoops; d--) {
DependenceResult result = checkMemrefAccessDependence(
srcAccess, destAccess, d, &dependenceConstraints,
nullptr);
if (hasDependence(result)) {
hasSideEffect = true;
return;
}
}
return;
}
}
hasSideEffect = true;
return;
}
if (op->hasTrait<OpTrait::HasRecursiveSideEffects>()) {
for (Region ®ion : op->getRegions())
for (Block &block : region)
for (Operation &op : block)
checkOperation(&op);
return;
}
hasSideEffect = true;
};
auto until = [&](Operation *parent, Operation *to) {
assert(parent->isAncestor(to));
checkOperation(parent);
};
std::function<void(Operation *, Operation *)> recur =
[&](Operation *from, Operation *untilOp) {
assert(
from->getParentRegion()->isAncestor(untilOp->getParentRegion()) &&
"Checking for side effect between two operations without a common "
"ancestor");
if (from->getParentRegion() != untilOp->getParentRegion()) {
recur(from, untilOp->getParentOp());
until(untilOp->getParentOp(), untilOp);
return;
}
SmallVector<Block *, 2> todoBlocks;
{
for (auto iter = ++from->getIterator(), end = from->getBlock()->end();
iter != end && &*iter != untilOp; ++iter) {
checkOperation(&*iter);
}
if (untilOp->getBlock() != from->getBlock())
for (Block *succ : from->getBlock()->getSuccessors())
todoBlocks.push_back(succ);
}
SmallPtrSet<Block *, 4> done;
while (!todoBlocks.empty()) {
Block *blk = todoBlocks.pop_back_val();
if (done.count(blk))
continue;
done.insert(blk);
for (auto &op : *blk) {
if (&op == untilOp)
break;
checkOperation(&op);
if (&op == blk->getTerminator())
for (Block *succ : blk->getSuccessors())
todoBlocks.push_back(succ);
}
}
};
recur(start, memOp);
return !hasSideEffect;
}
static LogicalResult forwardStoreToLoad(
AffineReadOpInterface loadOp, SmallVectorImpl<Operation *> &loadOpsToErase,
SmallPtrSetImpl<Value> &memrefsToErase, DominanceInfo &domInfo) {
Operation *lastWriteStoreOp = nullptr;
for (auto *user : loadOp.getMemRef().getUsers()) {
auto storeOp = dyn_cast<AffineWriteOpInterface>(user);
if (!storeOp)
continue;
MemRefAccess srcAccess(storeOp);
MemRefAccess destAccess(loadOp);
if (srcAccess != destAccess)
continue;
if (!domInfo.dominates(storeOp, loadOp))
continue;
if (!hasNoInterveningEffect<MemoryEffects::Write>(storeOp, loadOp))
continue;
assert(lastWriteStoreOp == nullptr &&
"multiple simulataneous replacement stores");
lastWriteStoreOp = storeOp;
}
if (!lastWriteStoreOp)
return failure();
Value storeVal =
cast<AffineWriteOpInterface>(lastWriteStoreOp).getValueToStore();
if (storeVal.getType() != loadOp.getValue().getType())
return failure();
loadOp.getValue().replaceAllUsesWith(storeVal);
memrefsToErase.insert(loadOp.getMemRef());
loadOpsToErase.push_back(loadOp);
return success();
}
static void findUnusedStore(AffineWriteOpInterface writeA,
SmallVectorImpl<Operation *> &opsToErase,
PostDominanceInfo &postDominanceInfo) {
for (Operation *user : writeA.getMemRef().getUsers()) {
auto writeB = dyn_cast<AffineWriteOpInterface>(user);
if (!writeB)
continue;
if (writeB == writeA)
continue;
if (writeB->getParentRegion() != writeA->getParentRegion())
continue;
MemRefAccess srcAccess(writeB);
MemRefAccess destAccess(writeA);
if (srcAccess != destAccess)
continue;
if (!postDominanceInfo.postDominates(writeB, writeA))
continue;
if (!hasNoInterveningEffect<MemoryEffects::Read>(writeA, writeB))
continue;
opsToErase.push_back(writeA);
break;
}
}
static void loadCSE(AffineReadOpInterface loadA,
SmallVectorImpl<Operation *> &loadOpsToErase,
DominanceInfo &domInfo) {
SmallVector<AffineReadOpInterface, 4> loadCandidates;
for (auto *user : loadA.getMemRef().getUsers()) {
auto loadB = dyn_cast<AffineReadOpInterface>(user);
if (!loadB || loadB == loadA)
continue;
MemRefAccess srcAccess(loadB);
MemRefAccess destAccess(loadA);
if (srcAccess != destAccess) {
continue;
}
if (!domInfo.dominates(loadB, loadA))
continue;
if (!hasNoInterveningEffect<MemoryEffects::Write>(loadB.getOperation(),
loadA))
continue;
if (loadB.getValue().getType() != loadA.getValue().getType())
continue;
loadCandidates.push_back(loadB);
}
Value loadB;
for (AffineReadOpInterface option : loadCandidates) {
if (llvm::all_of(loadCandidates, [&](AffineReadOpInterface depStore) {
return depStore == option ||
domInfo.dominates(option.getOperation(),
depStore.getOperation());
})) {
loadB = option.getValue();
break;
}
}
if (loadB) {
loadA.getValue().replaceAllUsesWith(loadB);
loadOpsToErase.push_back(loadA);
}
}
void mlir::affineScalarReplace(func::FuncOp f, DominanceInfo &domInfo,
PostDominanceInfo &postDomInfo) {
SmallVector<Operation *, 8> opsToErase;
SmallPtrSet<Value, 4> memrefsToErase;
f.walk([&](AffineReadOpInterface loadOp) {
if (failed(
forwardStoreToLoad(loadOp, opsToErase, memrefsToErase, domInfo))) {
loadCSE(loadOp, opsToErase, domInfo);
}
});
for (auto *op : opsToErase)
op->erase();
opsToErase.clear();
f.walk([&](AffineWriteOpInterface storeOp) {
findUnusedStore(storeOp, opsToErase, postDomInfo);
});
for (auto *op : opsToErase)
op->erase();
for (auto memref : memrefsToErase) {
Operation *defOp = memref.getDefiningOp();
if (!defOp || !isa<memref::AllocOp>(defOp))
continue;
if (llvm::any_of(memref.getUsers(), [&](Operation *ownerOp) {
return !isa<AffineWriteOpInterface, memref::DeallocOp>(ownerOp);
}))
continue;
for (auto *user : llvm::make_early_inc_range(memref.getUsers()))
user->erase();
defOp->erase();
}
}
LogicalResult mlir::replaceAllMemRefUsesWith(Value oldMemRef, Value newMemRef,
Operation *op,
ArrayRef<Value> extraIndices,
AffineMap indexRemap,
ArrayRef<Value> extraOperands,
ArrayRef<Value> symbolOperands,
bool allowNonDereferencingOps) {
unsigned newMemRefRank = newMemRef.getType().cast<MemRefType>().getRank();
(void)newMemRefRank;
unsigned oldMemRefRank = oldMemRef.getType().cast<MemRefType>().getRank();
(void)oldMemRefRank;
if (indexRemap) {
assert(indexRemap.getNumSymbols() == symbolOperands.size() &&
"symbolic operand count mismatch");
assert(indexRemap.getNumInputs() ==
extraOperands.size() + oldMemRefRank + symbolOperands.size());
assert(indexRemap.getNumResults() + extraIndices.size() == newMemRefRank);
} else {
assert(oldMemRefRank + extraIndices.size() == newMemRefRank);
}
assert(oldMemRef.getType().cast<MemRefType>().getElementType() ==
newMemRef.getType().cast<MemRefType>().getElementType());
SmallVector<unsigned, 2> usePositions;
for (const auto &opEntry : llvm::enumerate(op->getOperands())) {
if (opEntry.value() == oldMemRef)
usePositions.push_back(opEntry.index());
}
if (usePositions.empty())
return success();
if (usePositions.size() > 1) {
assert(false && "multiple dereferencing uses in a single op not supported");
return failure();
}
unsigned memRefOperandPos = usePositions.front();
OpBuilder builder(op);
auto affMapAccInterface = dyn_cast<AffineMapAccessInterface>(op);
if (!affMapAccInterface) {
if (!allowNonDereferencingOps) {
return failure();
}
op->setOperand(memRefOperandPos, newMemRef);
return success();
}
NamedAttribute oldMapAttrPair =
affMapAccInterface.getAffineMapAttrForMemRef(oldMemRef);
AffineMap oldMap = oldMapAttrPair.getValue().cast<AffineMapAttr>().getValue();
unsigned oldMapNumInputs = oldMap.getNumInputs();
SmallVector<Value, 4> oldMapOperands(
op->operand_begin() + memRefOperandPos + 1,
op->operand_begin() + memRefOperandPos + 1 + oldMapNumInputs);
SmallVector<Value, 4> oldMemRefOperands;
SmallVector<Value, 4> affineApplyOps;
oldMemRefOperands.reserve(oldMemRefRank);
if (oldMap != builder.getMultiDimIdentityMap(oldMap.getNumDims())) {
for (auto resultExpr : oldMap.getResults()) {
auto singleResMap = AffineMap::get(oldMap.getNumDims(),
oldMap.getNumSymbols(), resultExpr);
auto afOp = builder.create<AffineApplyOp>(op->getLoc(), singleResMap,
oldMapOperands);
oldMemRefOperands.push_back(afOp);
affineApplyOps.push_back(afOp);
}
} else {
oldMemRefOperands.assign(oldMapOperands.begin(), oldMapOperands.end());
}
SmallVector<Value, 4> remapOperands;
remapOperands.reserve(extraOperands.size() + oldMemRefRank +
symbolOperands.size());
remapOperands.append(extraOperands.begin(), extraOperands.end());
remapOperands.append(oldMemRefOperands.begin(), oldMemRefOperands.end());
remapOperands.append(symbolOperands.begin(), symbolOperands.end());
SmallVector<Value, 4> remapOutputs;
remapOutputs.reserve(oldMemRefRank);
if (indexRemap &&
indexRemap != builder.getMultiDimIdentityMap(indexRemap.getNumDims())) {
for (auto resultExpr : indexRemap.getResults()) {
auto singleResMap = AffineMap::get(
indexRemap.getNumDims(), indexRemap.getNumSymbols(), resultExpr);
auto afOp = builder.create<AffineApplyOp>(op->getLoc(), singleResMap,
remapOperands);
remapOutputs.push_back(afOp);
affineApplyOps.push_back(afOp);
}
} else {
remapOutputs.assign(remapOperands.begin(), remapOperands.end());
}
SmallVector<Value, 4> newMapOperands;
newMapOperands.reserve(newMemRefRank);
for (Value extraIndex : extraIndices) {
assert(extraIndex.getDefiningOp()->getNumResults() == 1 &&
"single result op's expected to generate these indices");
assert((isValidDim(extraIndex) || isValidSymbol(extraIndex)) &&
"invalid memory op index");
newMapOperands.push_back(extraIndex);
}
newMapOperands.append(remapOutputs.begin(), remapOutputs.end());
assert(newMapOperands.size() == newMemRefRank);
auto newMap = builder.getMultiDimIdentityMap(newMemRefRank);
fullyComposeAffineMapAndOperands(&newMap, &newMapOperands);
newMap = simplifyAffineMap(newMap);
canonicalizeMapAndOperands(&newMap, &newMapOperands);
for (Value value : affineApplyOps)
if (value.use_empty())
value.getDefiningOp()->erase();
OperationState state(op->getLoc(), op->getName());
state.operands.reserve(op->getNumOperands() + extraIndices.size());
state.operands.append(op->operand_begin(),
op->operand_begin() + memRefOperandPos);
state.operands.push_back(newMemRef);
state.operands.append(newMapOperands.begin(), newMapOperands.end());
state.operands.append(op->operand_begin() + memRefOperandPos + 1 +
oldMapNumInputs,
op->operand_end());
state.types.reserve(op->getNumResults());
for (auto result : op->getResults())
state.types.push_back(result.getType());
auto newMapAttr = AffineMapAttr::get(newMap);
for (auto namedAttr : op->getAttrs()) {
if (namedAttr.getName() == oldMapAttrPair.getName())
state.attributes.push_back({namedAttr.getName(), newMapAttr});
else
state.attributes.push_back(namedAttr);
}
auto *repOp = builder.create(state);
op->replaceAllUsesWith(repOp);
op->erase();
return success();
}
LogicalResult mlir::replaceAllMemRefUsesWith(
Value oldMemRef, Value newMemRef, ArrayRef<Value> extraIndices,
AffineMap indexRemap, ArrayRef<Value> extraOperands,
ArrayRef<Value> symbolOperands, Operation *domOpFilter,
Operation *postDomOpFilter, bool allowNonDereferencingOps,
bool replaceInDeallocOp) {
unsigned newMemRefRank = newMemRef.getType().cast<MemRefType>().getRank();
(void)newMemRefRank;
unsigned oldMemRefRank = oldMemRef.getType().cast<MemRefType>().getRank();
(void)oldMemRefRank;
if (indexRemap) {
assert(indexRemap.getNumSymbols() == symbolOperands.size() &&
"symbol operand count mismatch");
assert(indexRemap.getNumInputs() ==
extraOperands.size() + oldMemRefRank + symbolOperands.size());
assert(indexRemap.getNumResults() + extraIndices.size() == newMemRefRank);
} else {
assert(oldMemRefRank + extraIndices.size() == newMemRefRank);
}
assert(oldMemRef.getType().cast<MemRefType>().getElementType() ==
newMemRef.getType().cast<MemRefType>().getElementType());
std::unique_ptr<DominanceInfo> domInfo;
std::unique_ptr<PostDominanceInfo> postDomInfo;
if (domOpFilter)
domInfo = std::make_unique<DominanceInfo>(
domOpFilter->getParentOfType<func::FuncOp>());
if (postDomOpFilter)
postDomInfo = std::make_unique<PostDominanceInfo>(
postDomOpFilter->getParentOfType<func::FuncOp>());
DenseSet<Operation *> opsToReplace;
for (auto *op : oldMemRef.getUsers()) {
if (domOpFilter && !domInfo->dominates(domOpFilter, op))
continue;
if (postDomOpFilter && !postDomInfo->postDominates(postDomOpFilter, op))
continue;
if (isa<memref::DeallocOp>(op) && !replaceInDeallocOp)
continue;
if (!isa<AffineMapAccessInterface>(*op)) {
if (!allowNonDereferencingOps) {
LLVM_DEBUG(llvm::dbgs()
<< "Memref replacement failed: non-deferencing memref op: \n"
<< *op << '\n');
return failure();
}
if (!op->hasTrait<OpTrait::MemRefsNormalizable>()) {
LLVM_DEBUG(llvm::dbgs() << "Memref replacement failed: use without a "
"memrefs normalizable trait: \n"
<< *op << '\n');
return failure();
}
}
opsToReplace.insert(op);
}
for (auto *op : opsToReplace) {
if (failed(replaceAllMemRefUsesWith(
oldMemRef, newMemRef, op, extraIndices, indexRemap, extraOperands,
symbolOperands, allowNonDereferencingOps)))
llvm_unreachable("memref replacement guaranteed to succeed here");
}
return success();
}
void mlir::createAffineComputationSlice(
Operation *opInst, SmallVectorImpl<AffineApplyOp> *sliceOps) {
SmallVector<Value, 4> subOperands;
subOperands.reserve(opInst->getNumOperands());
for (auto operand : opInst->getOperands())
if (isa_and_nonnull<AffineApplyOp>(operand.getDefiningOp()))
subOperands.push_back(operand);
SmallVector<Operation *, 4> affineApplyOps;
getReachableAffineApplyOps(subOperands, affineApplyOps);
if (affineApplyOps.empty())
return;
bool localized = true;
for (auto *op : affineApplyOps) {
for (auto result : op->getResults()) {
for (auto *user : result.getUsers()) {
if (user != opInst) {
localized = false;
break;
}
}
}
}
if (localized)
return;
OpBuilder builder(opInst);
SmallVector<Value, 4> composedOpOperands(subOperands);
auto composedMap = builder.getMultiDimIdentityMap(composedOpOperands.size());
fullyComposeAffineMapAndOperands(&composedMap, &composedOpOperands);
sliceOps->reserve(composedMap.getNumResults());
for (auto resultExpr : composedMap.getResults()) {
auto singleResMap = AffineMap::get(composedMap.getNumDims(),
composedMap.getNumSymbols(), resultExpr);
sliceOps->push_back(builder.create<AffineApplyOp>(
opInst->getLoc(), singleResMap, composedOpOperands));
}
SmallVector<Value, 4> newOperands(opInst->getOperands());
for (unsigned i = 0, e = newOperands.size(); i < e; i++) {
unsigned j, f;
for (j = 0, f = subOperands.size(); j < f; j++) {
if (newOperands[i] == subOperands[j])
break;
}
if (j < subOperands.size()) {
newOperands[i] = (*sliceOps)[j];
}
}
for (unsigned idx = 0, e = newOperands.size(); idx < e; idx++) {
opInst->setOperand(idx, newOperands[idx]);
}
}
enum TileExprPattern { TileFloorDiv, TileMod, TileNone };
static LogicalResult getTileSizePos(
AffineMap map,
SmallVectorImpl<std::tuple<AffineExpr, unsigned, unsigned>> &tileSizePos) {
SmallVector<std::tuple<AffineExpr, AffineExpr, unsigned>, 4> floordivExprs;
unsigned pos = 0;
for (AffineExpr expr : map.getResults()) {
if (expr.getKind() == AffineExprKind::FloorDiv) {
AffineBinaryOpExpr binaryExpr = expr.cast<AffineBinaryOpExpr>();
if (binaryExpr.getRHS().isa<AffineConstantExpr>())
floordivExprs.emplace_back(
std::make_tuple(binaryExpr.getLHS(), binaryExpr.getRHS(), pos));
}
pos++;
}
if (floordivExprs.empty()) {
tileSizePos = SmallVector<std::tuple<AffineExpr, unsigned, unsigned>>{};
return success();
}
for (std::tuple<AffineExpr, AffineExpr, unsigned> fexpr : floordivExprs) {
AffineExpr floordivExprLHS = std::get<0>(fexpr);
AffineExpr floordivExprRHS = std::get<1>(fexpr);
unsigned floordivPos = std::get<2>(fexpr);
bool found = false;
pos = 0;
for (AffineExpr expr : map.getResults()) {
bool notTiled = false;
if (pos != floordivPos) {
expr.walk([&](AffineExpr e) {
if (e == floordivExprLHS) {
if (expr.getKind() == AffineExprKind::Mod) {
AffineBinaryOpExpr binaryExpr = expr.cast<AffineBinaryOpExpr>();
if (floordivExprLHS == binaryExpr.getLHS() &&
floordivExprRHS == binaryExpr.getRHS()) {
if (!found) {
tileSizePos.emplace_back(
std::make_tuple(binaryExpr.getRHS(), floordivPos, pos));
found = true;
} else {
notTiled = true;
}
} else {
notTiled = true;
}
} else {
notTiled = true;
}
}
});
}
if (notTiled) {
tileSizePos = SmallVector<std::tuple<AffineExpr, unsigned, unsigned>>{};
return success();
}
pos++;
}
}
return success();
}
static bool
isNormalizedMemRefDynamicDim(unsigned dim, AffineMap layoutMap,
SmallVectorImpl<unsigned> &inMemrefTypeDynDims,
MLIRContext *context) {
bool isDynamicDim = false;
AffineExpr expr = layoutMap.getResults()[dim];
expr.walk([&inMemrefTypeDynDims, &isDynamicDim, &context](AffineExpr e) {
if (e.isa<AffineDimExpr>()) {
for (unsigned dm : inMemrefTypeDynDims) {
if (e == getAffineDimExpr(dm, context)) {
isDynamicDim = true;
}
}
}
});
return isDynamicDim;
}
static AffineExpr createDimSizeExprForTiledLayout(AffineExpr oldMapOutput,
TileExprPattern pat) {
AffineExpr newMapOutput;
AffineBinaryOpExpr binaryExpr = nullptr;
switch (pat) {
case TileExprPattern::TileMod:
binaryExpr = oldMapOutput.cast<AffineBinaryOpExpr>();
newMapOutput = binaryExpr.getRHS();
break;
case TileExprPattern::TileFloorDiv:
binaryExpr = oldMapOutput.cast<AffineBinaryOpExpr>();
newMapOutput = getAffineBinaryOpExpr(
AffineExprKind::CeilDiv, binaryExpr.getLHS(), binaryExpr.getRHS());
break;
default:
newMapOutput = oldMapOutput;
}
return newMapOutput;
}
static void createNewDynamicSizes(MemRefType oldMemRefType,
MemRefType newMemRefType, AffineMap map,
memref::AllocOp *allocOp, OpBuilder b,
SmallVectorImpl<Value> &newDynamicSizes) {
SmallVector<Value, 4> inAffineApply;
ArrayRef<int64_t> oldMemRefShape = oldMemRefType.getShape();
unsigned dynIdx = 0;
for (unsigned d = 0; d < oldMemRefType.getRank(); ++d) {
if (oldMemRefShape[d] < 0) {
inAffineApply.emplace_back(allocOp->getDynamicSizes()[dynIdx]);
dynIdx++;
} else {
Attribute constantAttr =
b.getIntegerAttr(b.getIndexType(), oldMemRefShape[d]);
inAffineApply.emplace_back(
b.create<arith::ConstantOp>(allocOp->getLoc(), constantAttr));
}
}
unsigned newDimIdx = 0;
ArrayRef<int64_t> newMemRefShape = newMemRefType.getShape();
SmallVector<std::tuple<AffineExpr, unsigned, unsigned>> tileSizePos;
(void)getTileSizePos(map, tileSizePos);
for (AffineExpr expr : map.getResults()) {
if (newMemRefShape[newDimIdx] < 0) {
enum TileExprPattern pat = TileExprPattern::TileNone;
for (auto pos : tileSizePos) {
if (newDimIdx == std::get<1>(pos))
pat = TileExprPattern::TileFloorDiv;
else if (newDimIdx == std::get<2>(pos))
pat = TileExprPattern::TileMod;
}
AffineExpr newMapOutput = createDimSizeExprForTiledLayout(expr, pat);
AffineMap newMap =
AffineMap::get(map.getNumInputs(), map.getNumSymbols(), newMapOutput);
Value affineApp =
b.create<AffineApplyOp>(allocOp->getLoc(), newMap, inAffineApply);
newDynamicSizes.emplace_back(affineApp);
}
newDimIdx++;
}
}
LogicalResult mlir::normalizeMemRef(memref::AllocOp *allocOp) {
MemRefType memrefType = allocOp->getType();
OpBuilder b(*allocOp);
MemRefType newMemRefType =
normalizeMemRefType(memrefType, b, allocOp->getSymbolOperands().size());
if (newMemRefType == memrefType)
return failure();
Value oldMemRef = allocOp->getResult();
SmallVector<Value, 4> symbolOperands(allocOp->getSymbolOperands());
AffineMap layoutMap = memrefType.getLayout().getAffineMap();
memref::AllocOp newAlloc;
SmallVector<std::tuple<AffineExpr, unsigned, unsigned>> tileSizePos;
(void)getTileSizePos(layoutMap, tileSizePos);
if (newMemRefType.getNumDynamicDims() > 0 && !tileSizePos.empty()) {
MemRefType oldMemRefType = oldMemRef.getType().cast<MemRefType>();
SmallVector<Value, 4> newDynamicSizes;
createNewDynamicSizes(oldMemRefType, newMemRefType, layoutMap, allocOp, b,
newDynamicSizes);
newAlloc =
b.create<memref::AllocOp>(allocOp->getLoc(), newMemRefType,
newDynamicSizes, allocOp->getAlignmentAttr());
} else {
newAlloc = b.create<memref::AllocOp>(allocOp->getLoc(), newMemRefType,
allocOp->getAlignmentAttr());
}
if (failed(replaceAllMemRefUsesWith(oldMemRef, newAlloc,
{},
layoutMap,
{},
symbolOperands,
nullptr,
nullptr,
true))) {
newAlloc.erase();
return failure();
}
assert(llvm::all_of(oldMemRef.getUsers(), [](Operation *op) {
return isa<memref::DeallocOp>(op);
}));
oldMemRef.replaceAllUsesWith(newAlloc);
allocOp->erase();
return success();
}
MemRefType mlir::normalizeMemRefType(MemRefType memrefType, OpBuilder b,
unsigned numSymbolicOperands) {
unsigned rank = memrefType.getRank();
if (rank == 0)
return memrefType;
if (memrefType.getLayout().isIdentity()) {
return memrefType;
}
AffineMap layoutMap = memrefType.getLayout().getAffineMap();
SmallVector<std::tuple<AffineExpr, unsigned, unsigned>> tileSizePos;
(void)getTileSizePos(layoutMap, tileSizePos);
if (memrefType.getNumDynamicDims() > 0 && tileSizePos.empty())
return memrefType;
ArrayRef<int64_t> shape = memrefType.getShape();
FlatAffineValueConstraints fac(rank, numSymbolicOperands);
SmallVector<unsigned, 4> memrefTypeDynDims;
for (unsigned d = 0; d < rank; ++d) {
if (shape[d] > 0) {
fac.addBound(IntegerPolyhedron::LB, d, 0);
fac.addBound(IntegerPolyhedron::UB, d, shape[d] - 1);
} else {
memrefTypeDynDims.emplace_back(d);
}
}
unsigned newRank = layoutMap.getNumResults();
if (failed(fac.composeMatchingMap(layoutMap)))
return memrefType;
fac.projectOut(newRank, fac.getNumVars() - newRank - fac.getNumLocalVars());
SmallVector<int64_t, 4> newShape(newRank);
for (unsigned d = 0; d < newRank; ++d) {
bool isDynDim = isNormalizedMemRefDynamicDim(
d, layoutMap, memrefTypeDynDims, b.getContext());
if (isDynDim) {
newShape[d] = -1;
} else {
auto ubConst = fac.getConstantBound(IntegerPolyhedron::UB, d);
assert(ubConst && "should always have an upper bound");
if (ubConst.value() < 0)
return memrefType;
newShape[d] = ubConst.value() + 1;
}
}
MemRefType newMemRefType =
MemRefType::Builder(memrefType)
.setShape(newShape)
.setLayout(AffineMapAttr::get(b.getMultiDimIdentityMap(newRank)));
return newMemRefType;
}