Files
clang-p2996/mlir/lib/Dialect/Affine/Transforms/AffineExpandIndexOps.cpp
Krzysztof Drewniak 9e8200c718 [mlir][Affine] Expand affine.[de]linearize_index without affine maps (#116703)
As the documentation for -affine-expand-index-ops says,
affine.delinearize_index and affine.linearize_index don't need to be
expanded into the affine dialect.

Expanding these operations into affine.apply operations can introduce
unwanted "simplifications", mainly translations of `(dN mod C + ...)` to
`(dN + ... - (dN floordiv C) * C)` and similar, which create worse
generated code. This commit resolves this issue by expanding out
affine.delanierize_index directly.

In addition, the lowering of affine.linearize_index now sorts the
operands by loop-independence, allowing an increased amount of
loop-invariant code motion after lowering.

The old behavior is preserved as -expand-affine-index-ops-as-affine but
is no longer the default
2024-11-21 11:36:32 -06:00

218 lines
8.0 KiB
C++

//===- AffineExpandIndexOps.cpp - Affine expand index ops pass ------------===//
//
// 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 expand affine index ops into one or more more
// fundamental operations.
//===----------------------------------------------------------------------===//
#include "mlir/Dialect/Affine/LoopUtils.h"
#include "mlir/Dialect/Affine/Passes.h"
#include "mlir/Dialect/Affine/IR/AffineOps.h"
#include "mlir/Dialect/Affine/Transforms/Transforms.h"
#include "mlir/Dialect/Affine/Utils.h"
#include "mlir/Dialect/Arith/Utils/Utils.h"
#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
namespace mlir {
namespace affine {
#define GEN_PASS_DEF_AFFINEEXPANDINDEXOPS
#include "mlir/Dialect/Affine/Passes.h.inc"
} // namespace affine
} // namespace mlir
using namespace mlir;
using namespace mlir::affine;
/// Given a basis (in static and dynamic components), return the sequence of
/// suffix products of the basis, including the product of the entire basis,
/// which must **not** contain an outer bound.
///
/// If excess dynamic values are provided, the values at the beginning
/// will be ignored. This allows for dropping the outer bound without
/// needing to manipulate the dynamic value array.
static SmallVector<Value> computeStrides(Location loc, RewriterBase &rewriter,
ValueRange dynamicBasis,
ArrayRef<int64_t> staticBasis) {
if (staticBasis.empty())
return {};
SmallVector<Value> result;
result.reserve(staticBasis.size());
size_t dynamicIndex = dynamicBasis.size();
Value dynamicPart = nullptr;
int64_t staticPart = 1;
for (int64_t elem : llvm::reverse(staticBasis)) {
if (ShapedType::isDynamic(elem)) {
if (dynamicPart)
dynamicPart = rewriter.create<arith::MulIOp>(
loc, dynamicPart, dynamicBasis[dynamicIndex - 1]);
else
dynamicPart = dynamicBasis[dynamicIndex - 1];
--dynamicIndex;
} else {
staticPart *= elem;
}
if (dynamicPart && staticPart == 1) {
result.push_back(dynamicPart);
} else {
Value stride =
rewriter.createOrFold<arith::ConstantIndexOp>(loc, staticPart);
if (dynamicPart)
stride = rewriter.create<arith::MulIOp>(loc, dynamicPart, stride);
result.push_back(stride);
}
}
std::reverse(result.begin(), result.end());
return result;
}
namespace {
/// Lowers `affine.delinearize_index` into a sequence of division and remainder
/// operations.
struct LowerDelinearizeIndexOps
: public OpRewritePattern<AffineDelinearizeIndexOp> {
using OpRewritePattern<AffineDelinearizeIndexOp>::OpRewritePattern;
LogicalResult matchAndRewrite(AffineDelinearizeIndexOp op,
PatternRewriter &rewriter) const override {
Location loc = op.getLoc();
Value linearIdx = op.getLinearIndex();
unsigned numResults = op.getNumResults();
ArrayRef<int64_t> staticBasis = op.getStaticBasis();
if (numResults == staticBasis.size())
staticBasis = staticBasis.drop_front();
if (numResults == 1) {
rewriter.replaceOp(op, linearIdx);
return success();
}
SmallVector<Value> results;
results.reserve(numResults);
SmallVector<Value> strides =
computeStrides(loc, rewriter, op.getDynamicBasis(), staticBasis);
Value zero = rewriter.createOrFold<arith::ConstantIndexOp>(loc, 0);
Value initialPart =
rewriter.create<arith::FloorDivSIOp>(loc, linearIdx, strides.front());
results.push_back(initialPart);
auto emitModTerm = [&](Value stride) -> Value {
Value remainder = rewriter.create<arith::RemSIOp>(loc, linearIdx, stride);
Value remainderNegative = rewriter.create<arith::CmpIOp>(
loc, arith::CmpIPredicate::slt, remainder, zero);
Value corrected = rewriter.create<arith::AddIOp>(loc, remainder, stride);
Value mod = rewriter.create<arith::SelectOp>(loc, remainderNegative,
corrected, remainder);
return mod;
};
// Generate all the intermediate parts
for (size_t i = 0, e = strides.size() - 1; i < e; ++i) {
Value thisStride = strides[i];
Value nextStride = strides[i + 1];
Value modulus = emitModTerm(thisStride);
// We know both inputs are positive, so floorDiv == div.
// This could potentially be a divui, but it's not clear if that would
// cause issues.
Value divided = rewriter.create<arith::DivSIOp>(loc, modulus, nextStride);
results.push_back(divided);
}
results.push_back(emitModTerm(strides.back()));
rewriter.replaceOp(op, results);
return success();
}
};
/// Lowers `affine.linearize_index` into a sequence of multiplications and
/// additions. Make a best effort to sort the input indices so that
/// the most loop-invariant terms are at the left of the additions
/// to enable loop-invariant code motion.
struct LowerLinearizeIndexOps final : OpRewritePattern<AffineLinearizeIndexOp> {
using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(AffineLinearizeIndexOp op,
PatternRewriter &rewriter) const override {
// Should be folded away, included here for safety.
if (op.getMultiIndex().empty()) {
rewriter.replaceOpWithNewOp<arith::ConstantIndexOp>(op, 0);
return success();
}
Location loc = op.getLoc();
ValueRange multiIndex = op.getMultiIndex();
size_t numIndexes = multiIndex.size();
ArrayRef<int64_t> staticBasis = op.getStaticBasis();
if (numIndexes == staticBasis.size())
staticBasis = staticBasis.drop_front();
SmallVector<Value> strides =
computeStrides(loc, rewriter, op.getDynamicBasis(), staticBasis);
SmallVector<std::pair<Value, int64_t>> scaledValues;
scaledValues.reserve(numIndexes);
// Note: strides doesn't contain a value for the final element (stride 1)
// and everything else lines up. We use the "mutable" accessor so we can get
// our hands on an `OpOperand&` for the loop invariant counting function.
for (auto [stride, idxOp] :
llvm::zip_equal(strides, llvm::drop_end(op.getMultiIndexMutable()))) {
Value scaledIdx =
rewriter.create<arith::MulIOp>(loc, idxOp.get(), stride);
int64_t numHoistableLoops = numEnclosingInvariantLoops(idxOp);
scaledValues.emplace_back(scaledIdx, numHoistableLoops);
}
scaledValues.emplace_back(
multiIndex.back(),
numEnclosingInvariantLoops(op.getMultiIndexMutable()[numIndexes - 1]));
// Sort by how many enclosing loops there are, ties implicitly broken by
// size of the stride.
llvm::stable_sort(scaledValues,
[&](auto l, auto r) { return l.second > r.second; });
Value result = scaledValues.front().first;
for (auto [scaledValue, numHoistableLoops] :
llvm::drop_begin(scaledValues)) {
std::ignore = numHoistableLoops;
result = rewriter.create<arith::AddIOp>(loc, result, scaledValue);
}
rewriter.replaceOp(op, result);
return success();
}
};
class ExpandAffineIndexOpsPass
: public affine::impl::AffineExpandIndexOpsBase<ExpandAffineIndexOpsPass> {
public:
ExpandAffineIndexOpsPass() = default;
void runOnOperation() override {
MLIRContext *context = &getContext();
RewritePatternSet patterns(context);
populateAffineExpandIndexOpsPatterns(patterns);
if (failed(
applyPatternsAndFoldGreedily(getOperation(), std::move(patterns))))
return signalPassFailure();
}
};
} // namespace
void mlir::affine::populateAffineExpandIndexOpsPatterns(
RewritePatternSet &patterns) {
patterns.insert<LowerDelinearizeIndexOps, LowerLinearizeIndexOps>(
patterns.getContext());
}
std::unique_ptr<Pass> mlir::affine::createAffineExpandIndexOpsPass() {
return std::make_unique<ExpandAffineIndexOpsPass>();
}