blob: 488aae4c714c5cffb408d36d4551663e81fd8388 [file] [edit]
//===- SCFToAffine.cpp - SCF to Affine conversion -------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file implements a pass to raise scf ops to affine ops.
//
//===----------------------------------------------------------------------===//
#include "mlir/Conversion/SCFToAffine/SCFToAffine.h"
#include "mlir/Dialect/Affine/IR/AffineOps.h"
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/SCF/IR/SCF.h"
#include "mlir/IR/AffineExpr.h"
#include "mlir/IR/AffineMap.h"
#include "mlir/IR/Value.h"
#include "mlir/Interfaces/DataLayoutInterfaces.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Support/LLVM.h"
#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
#include "llvm/ADT/SmallVector.h"
namespace mlir {
#define GEN_PASS_DEF_RAISESCFTOAFFINEPASS
#include "mlir/Conversion/Passes.h.inc"
} // namespace mlir
using namespace mlir;
namespace {
//===----------------------------------------------------------------------===//
// SCFToAffinePass
//===----------------------------------------------------------------------===//
struct SCFToAffinePass
: public impl::RaiseSCFToAffinePassBase<SCFToAffinePass> {
void runOnOperation() override;
};
//===----------------------------------------------------------------------===//
// ForOpRewrite
//===----------------------------------------------------------------------===//
/// Raise an `scf.for` to an equivalent `affine.for` if lb, ub and step satisfy
/// certain constraints making this possible.
struct ForOpRewrite : public OpRewritePattern<scf::ForOp> {
using OpRewritePattern<scf::ForOp>::OpRewritePattern;
LogicalResult matchAndRewrite(scf::ForOp op,
PatternRewriter &rewriter) const override;
private:
/// Definitively decide whether we are going to raise or not.
///
/// An `scf.for` can trivially be raised if lb, ub are dimensions and step is
/// a constant. With some more work one can raise under relaxed constraints as
/// expressed by this function.
bool canRaiseToAffine(scf::ForOp op) const;
/// Cast lb, ub, step and the induction variable of an integer-typed `op` to
/// `index`, in place. The bound and step casts are placed at the top level of
/// the affine scope so they are valid affine symbols; the induction variable
/// is cast back to its original type at the start of the body so the body is
/// left unchanged. Assumes `canRaiseToAffine(op) == true`.
void castBoundsToIndex(scf::ForOp op, PatternRewriter &rewriter) const;
/// Returns an equivalent `affine.for` skeleton and the *old* induction
/// variable for use by the body that is inlined later. The affine loop body
/// is left empty except for an operation computing the old induction variable
/// from the new one *iff* it differs from the new one.
///
/// Assumes `canRaiseToAffine(op) == true` and index casts were performed (if
/// necessary).
///
/// There are two cases:
///
/// 1. step is constant
/// 2. step is dynamic (not constant)
///
/// In case (1) and if lb, ub are (valid) dimensions `scf.for` is trivially
/// raised (leaving lb, ub, iv as is). If lb is an `affine.max` we "inline" it
/// into the loop's lower bound map. Similarly if ub is an `affine.min`.
///
/// In case (2) we *normalize* the loop to run from 0 with step 1: the new
/// upper bound is `ceil((ub - lb) / step)` and the original induction
/// variable is recovered in the body as `lb + step * new_iv`. Here we require
/// lb to be a dimension; ub may still be an `affine.min`, which is rescaled
/// accordingly.
std::pair<affine::AffineForOp, Value>
createAffineFor(scf::ForOp op, PatternRewriter &rewriter) const;
std::pair<affine::AffineForOp, Value>
createAffineForWithConstantStep(scf::ForOp op, int64_t step,
PatternRewriter &rewriter) const;
std::pair<affine::AffineForOp, Value>
createAffineForWithDynamicStep(scf::ForOp op,
PatternRewriter &rewriter) const;
};
bool indexBoundsRaisable(scf::ForOp op) {
Value lb = op.getLowerBound();
Value ub = op.getUpperBound();
IntegerAttr constAttr;
// The asymmetry between lb and ub comes from the fact that the step
// normalization (for non-constant (dynamic) steps) does not work with
// multiple *lower* bounds (max).
bool lbOK = affine::isValidDim(lb) ||
(isa_and_present<affine::AffineMaxOp>(lb.getDefiningOp()) &&
matchPattern(op.getStep(), m_Constant(&constAttr)));
bool ubOK = affine::isValidDim(ub) ||
isa_and_present<affine::AffineMinOp>(ub.getDefiningOp());
bool stepOK = affine::isValidSymbol(op.getStep());
return lbOK && ubOK && stepOK;
}
/// Decide whether an integer-typed loop can be raised by first casting its
/// bounds (lb, ub, step) to `index`. Requires the cast to be lossless under
/// affine's *signed* `index` interpretation, and every bound to be available at
/// the top level of the affine scope (so the inserted casts are valid symbols).
bool intBoundsRaisable(scf::ForOp op, IntegerType intType) {
uint64_t indexWidth = DataLayout::closest(op)
.getTypeSizeInBits(IndexType::get(op.getContext()))
.getFixedValue();
// Lossless under signed index: sign-extend needs width <= indexWidth;
// zero-extend (unsigned) needs a spare sign bit, i.e. width < indexWidth.
uint64_t requiredWidth = intType.getWidth() + (op.getUnsignedCmp() ? 1 : 0);
if (requiredWidth > indexWidth)
return false;
Region *scope = affine::getAffineScope(op);
if (!scope)
return false;
// Being top-level implies the value is a symbol once it is casted to index.
return affine::isTopLevelValue(op.getLowerBound(), scope) &&
affine::isTopLevelValue(op.getUpperBound(), scope) &&
affine::isTopLevelValue(op.getStep(), scope);
}
bool ForOpRewrite::canRaiseToAffine(scf::ForOp op) const {
Type type = op.getInductionVar().getType();
if (isa<IndexType>(type))
return indexBoundsRaisable(op);
if (auto intType = dyn_cast<IntegerType>(type))
return intBoundsRaisable(op, intType);
return false;
}
LogicalResult ForOpRewrite::matchAndRewrite(scf::ForOp op,
PatternRewriter &rewriter) const {
if (!canRaiseToAffine(op)) {
return rewriter.notifyMatchFailure(op, "cannot raise scf op to affine");
}
if (!isa<IndexType>(op.getInductionVar().getType()))
castBoundsToIndex(op, rewriter);
auto [affineFor, oldIV] = createAffineFor(op, rewriter);
Block *affineBody = affineFor.getBody();
if (affineBody->mightHaveTerminator()) {
// No unregistered ops in the body, so this is definitive.
Operation *terminator = affineBody->getTerminator();
assert(isa<affine::AffineYieldOp>(terminator) &&
"expected affine.yield if there *might* be terminator");
rewriter.eraseOp(terminator);
}
SmallVector<Value> argValues;
argValues.push_back(oldIV);
llvm::append_range(argValues, affineFor.getRegionIterArgs());
rewriter.inlineBlockBefore(op.getBody(), affineBody, affineBody->end(),
argValues);
auto scfYieldOp = cast<scf::YieldOp>(affineBody->getTerminator());
rewriter.setInsertionPointToEnd(affineBody);
rewriter.replaceOpWithNewOp<affine::AffineYieldOp>(scfYieldOp,
scfYieldOp->getOperands());
rewriter.replaceOp(op, affineFor);
return success();
}
std::pair<affine::AffineForOp, Value>
ForOpRewrite::createAffineFor(scf::ForOp op, PatternRewriter &rewriter) const {
IntegerAttr constAttr;
if (matchPattern(op.getStep(), m_Constant(&constAttr))) {
int64_t step = constAttr.getInt();
assert(step > 0 && "scf.for has positive step");
return createAffineForWithConstantStep(op, step, rewriter);
}
return createAffineForWithDynamicStep(op, rewriter);
}
std::pair<affine::AffineForOp, Value>
ForOpRewrite::createAffineForWithConstantStep(scf::ForOp op, int64_t step,
PatternRewriter &rewriter) const {
Value lb = op.getLowerBound();
Value ub = op.getUpperBound();
auto lbOperands = ValueRange(lb);
auto ubOperands = ValueRange(ub);
auto lbMap = AffineMap::getMultiDimIdentityMap(1, rewriter.getContext());
auto ubMap = AffineMap::getMultiDimIdentityMap(1, rewriter.getContext());
if (auto ubMinOp = ub.getDefiningOp<affine::AffineMinOp>()) {
ubOperands = ubMinOp->getOperands();
ubMap = ubMinOp.getAffineMap();
}
if (auto lbMaxOp = lb.getDefiningOp<affine::AffineMaxOp>()) {
lbOperands = lbMaxOp->getOperands();
lbMap = lbMaxOp.getAffineMap();
}
auto affineFor =
affine::AffineForOp::create(rewriter, op.getLoc(), lbOperands, lbMap,
ubOperands, ubMap, step, op.getInits());
return std::make_pair(affineFor, affineFor.getInductionVar());
}
std::pair<affine::AffineForOp, Value>
ForOpRewrite::createAffineForWithDynamicStep(scf::ForOp op,
PatternRewriter &rewriter) const {
Value lb = op.getLowerBound();
Value ub = op.getUpperBound();
Value step = op.getStep();
assert(affine::isValidDim(lb) &&
"dynamic-step lower bound must be a valid affine dim");
AffineExpr d0 = rewriter.getAffineDimExpr(0);
AffineExpr d1 = rewriter.getAffineDimExpr(1);
AffineExpr s0 = rewriter.getAffineSymbolExpr(0);
AffineMap zeroMap = rewriter.getConstantAffineMap(0);
llvm::SmallVector<Value, 3> ubOperands = {lb, ub, step};
// ub is transformed with (x - lb + step - 1) floorDiv step where x ranges
// over all ub_i. lb is transformed to zero.
AffineMap ubMap = AffineMap::get(2, 1, (d1 - d0 + s0 - 1).floorDiv(s0));
if (auto ubMinOp = ub.getDefiningOp<affine::AffineMinOp>()) {
AffineMap origUbMap = ubMinOp.getAffineMap();
unsigned ubDims = origUbMap.getNumDims();
unsigned ubSyms = origUbMap.getNumSymbols();
AffineExpr lbDim = rewriter.getAffineDimExpr(ubDims);
AffineExpr stepSym = rewriter.getAffineSymbolExpr(ubSyms);
SmallVector<AffineExpr> ubExprs;
ubExprs.reserve(origUbMap.getNumResults());
for (AffineExpr ubI : origUbMap.getResults()) {
ubExprs.push_back((ubI - lbDim + stepSym - 1).floorDiv(stepSym));
}
// Combined space: dims = [ub dims, lb]
// syms = [ub syms, step]
ubMap =
AffineMap::get(ubDims + 1, ubSyms + 1, ubExprs, rewriter.getContext());
// Operand order consistent with "combined space" above:
ValueRange ubOps = ubMinOp->getOperands();
SmallVector<Value> combined;
combined.append(ubOps.begin(), ubOps.begin() + ubDims); // ub dims
combined.push_back(lb); // lb (single dim)
combined.append(ubOps.begin() + ubDims, ubOps.end()); // ub syms
combined.push_back(op.getStep()); // step (single sym)
ubOperands = std::move(combined);
}
auto affineFor = affine::AffineForOp::create(
rewriter, op.getLoc(), {}, zeroMap, ubOperands, ubMap, 1, op.getInits());
// old_iv = old_lb + new_iv * step
AffineMap ivMap = AffineMap::get(2, 1, d0 + d1 * s0);
llvm::SmallVector<Value, 3> ivOperands = {lb, affineFor.getInductionVar(),
step};
rewriter.setInsertionPointToStart(affineFor.getBody());
auto oldIV =
affine::AffineApplyOp::create(rewriter, op.getLoc(), ivMap, ivOperands);
return std::make_pair(affineFor, oldIV);
}
void ForOpRewrite::castBoundsToIndex(scf::ForOp loop,
PatternRewriter &rewriter) const {
OpBuilder::InsertionGuard guard(rewriter);
Value lb = loop.getLowerBound();
Value ub = loop.getUpperBound();
Value step = loop.getStep();
Type originalType = step.getType();
assert(lb.getType() == originalType && ub.getType() == originalType &&
"expected lb, ub, and step to have the same type");
auto createIndexCast = [&](Type out, Value in) -> Value {
Location loc = loop.getLoc();
if (loop.getUnsignedCmp())
return arith::IndexCastUIOp::create(rewriter, loc, out, in);
return arith::IndexCastOp::create(rewriter, loc, out, in);
};
// We place the bound casts at the top level of the affine scope so that they
// are identified as valid affine symbols.
Region *scope = affine::getAffineScope(loop);
Operation *anchor = loop;
while (anchor->getParentRegion() != scope)
anchor = anchor->getParentOp();
rewriter.setInsertionPoint(anchor);
Value newLb = createIndexCast(rewriter.getIndexType(), lb);
Value newUb = createIndexCast(rewriter.getIndexType(), ub);
Value newStep = createIndexCast(rewriter.getIndexType(), step);
rewriter.modifyOpInPlace(loop, [&] {
loop.setLowerBound(newLb);
loop.setUpperBound(newUb);
loop.setStep(newStep);
Value originalIV = loop.getInductionVar();
Value iv = loop.getBody()->insertArgument(
(unsigned)0, rewriter.getIndexType(), loop.getLoc());
rewriter.setInsertionPointToStart(loop.getBody());
Value castIV = createIndexCast(originalType, iv);
rewriter.replaceAllUsesWith(originalIV, castIV);
// Original induction var is now at index 1.
loop.getBody()->eraseArgument(1);
});
}
//===----------------------------------------------------------------------===//
// Pass implementation
//===----------------------------------------------------------------------===//
void SCFToAffinePass::runOnOperation() {
MLIRContext &ctx = getContext();
RewritePatternSet patterns(&ctx);
populateSCFToAffineConversionPatterns(patterns);
(void)applyPatternsGreedily(getOperation(), std::move(patterns));
}
} // namespace
//===----------------------------------------------------------------------===//
// API
//===----------------------------------------------------------------------===//
void mlir::populateSCFToAffineConversionPatterns(RewritePatternSet &patterns) {
patterns.add<ForOpRewrite>(patterns.getContext());
}