#include "mlir/Dialect/Affine/Passes.h"
#include "mlir/Dialect/Affine/Analysis/AffineAnalysis.h"
#include "mlir/Dialect/Affine/Analysis/AffineStructures.h"
#include "mlir/Dialect/Affine/Analysis/LoopAnalysis.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/Affine/Utils.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/IRMapping.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"
#include <optional>
namespace mlir {
namespace affine {
#define GEN_PASS_DEF_AFFINELOOPTILING
#include "mlir/Dialect/Affine/Passes.h.inc"
}
}
using namespace mlir;
using namespace mlir::affine;
#define DEBUG_TYPE "affine-loop-tile"
namespace {
struct LoopTiling : public affine::impl::AffineLoopTilingBase<LoopTiling> {
LoopTiling() = default;
explicit LoopTiling(uint64_t cacheSizeBytes, bool avoidMaxMinBounds = true)
: avoidMaxMinBounds(avoidMaxMinBounds) {
this->cacheSizeInKiB = cacheSizeBytes / 1024;
}
void runOnOperation() override;
void getTileSizes(ArrayRef<AffineForOp> band,
SmallVectorImpl<unsigned> *tileSizes);
constexpr static unsigned kDefaultTileSize = 4;
bool avoidMaxMinBounds = true;
};
}
std::unique_ptr<OperationPass<func::FuncOp>>
mlir::affine::createLoopTilingPass(uint64_t cacheSizeBytes) {
return std::make_unique<LoopTiling>(cacheSizeBytes);
}
std::unique_ptr<OperationPass<func::FuncOp>>
mlir::affine::createLoopTilingPass() {
return std::make_unique<LoopTiling>();
}
static void adjustToDivisorsOfTripCounts(ArrayRef<AffineForOp> band,
SmallVectorImpl<unsigned> *tileSizes) {
assert(band.size() == tileSizes->size() && "invalid tile size count");
for (unsigned i = 0, e = band.size(); i < e; i++) {
unsigned &tSizeAdjusted = (*tileSizes)[i];
std::optional<uint64_t> mayConst = getConstantTripCount(band[i]);
if (!mayConst)
continue;
uint64_t constTripCount = *mayConst;
if (constTripCount > 1 && tSizeAdjusted > constTripCount / 2)
tSizeAdjusted = constTripCount / 2;
while (constTripCount % tSizeAdjusted != 0)
tSizeAdjusted--;
}
}
void LoopTiling::getTileSizes(ArrayRef<AffineForOp> band,
SmallVectorImpl<unsigned> *tileSizes) {
if (band.empty())
return;
if (tileSize) {
tileSizes->assign(band.size(), tileSize);
return;
}
if (!this->tileSizes.empty()) {
tileSizes->assign(this->tileSizes.begin(), this->tileSizes.end());
tileSizes->resize(band.size(), kDefaultTileSize);
return;
}
tileSizes->resize(band.size());
AffineForOp rootForOp = band[0];
(void)rootForOp;
std::optional<int64_t> fp = getMemoryFootprintBytes(band[0], 0);
if (!fp) {
std::fill(tileSizes->begin(), tileSizes->end(),
LoopTiling::kDefaultTileSize);
if (avoidMaxMinBounds)
adjustToDivisorsOfTripCounts(band, tileSizes);
LLVM_DEBUG(
rootForOp.emitWarning("memory footprint unknown: using default tile "
"sizes adjusted to trip count divisors"));
return;
}
uint64_t cacheSizeBytes = cacheSizeInKiB * 1024;
uint64_t excessFactor = llvm::divideCeil(*fp, cacheSizeBytes);
if (excessFactor <= 1) {
std::fill(tileSizes->begin(), tileSizes->end(), 1);
return;
}
unsigned tSize =
static_cast<unsigned>(floorl(std::pow(excessFactor, 1.0 / band.size())));
unsigned cumulProductOfTileSizes = 1;
for (unsigned i = 0, e = band.size(); i < e; i++) {
if (i < e - 1)
(*tileSizes)[i] = tSize;
else
(*tileSizes)[i] = std::max(
1U, static_cast<unsigned>(excessFactor / cumulProductOfTileSizes));
cumulProductOfTileSizes *= (*tileSizes)[i];
}
if (avoidMaxMinBounds)
adjustToDivisorsOfTripCounts(band, tileSizes);
}
void LoopTiling::runOnOperation() {
std::vector<SmallVector<AffineForOp, 6>> bands;
getTileableBands(getOperation(), &bands);
for (auto &band : bands) {
if (!isTilingValid(band)) {
band.front().emitRemark("tiling nest is invalid due to dependences");
continue;
}
SmallVector<unsigned, 6> tileSizes;
getTileSizes(band, &tileSizes);
if (llvm::DebugFlag) {
auto diag = band[0].emitRemark("using tile sizes [");
for (unsigned tSize : tileSizes)
diag << tSize << ' ';
diag << "]\n";
}
SmallVector<AffineForOp, 6> tiledNest;
if (failed(tilePerfectlyNested(band, tileSizes, &tiledNest))) {
assert(!band.empty() && "guaranteed to succeed on empty bands");
LLVM_DEBUG(band.front()->emitRemark("loop tiling failed!\n"));
continue;
}
if (separate) {
auto intraTileLoops =
MutableArrayRef<AffineForOp>(tiledNest).drop_front(band.size());
if (failed(separateFullTiles(intraTileLoops))) {
assert(!intraTileLoops.empty() &&
"guaranteed to succeed on empty bands");
LLVM_DEBUG(intraTileLoops.front()->emitRemark(
"separation post tiling failed!\n"));
}
}
}
}
constexpr unsigned LoopTiling::kDefaultTileSize;