#include "bishengir/Transforms/Passes.h"
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/Linalg/IR/Linalg.h"
#include "mlir/Dialect/SCF/IR/SCF.h"
#include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/IR/IRMapping.h"
#include "mlir/Pass/Pass.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/Support/Debug.h"
#define DEBUG_TYPE "fuse-reduction-into-loop"
#define DBGS() (llvm::dbgs() << '[' << DEBUG_TYPE << "] ")
#define LDBG(X) LLVM_DEBUG(DBGS() << X << "\n")
namespace bishengir {
using namespace mlir;
#define GEN_PASS_DEF_FUSEREDUCTIONINTOLOOP
#include "bishengir/Transforms/Passes.h.inc"
namespace {
struct ReduceCandidate {
unsigned resultIdx;
linalg::ReduceOp reduceOp;
arith::AddFOp addfOp;
Value value2D;
};
static bool isAdditiveReduction(linalg::ReduceOp reduceOp) {
Region &combiner = reduceOp.getCombiner();
if (!combiner.hasOneBlock())
return false;
Block &block = combiner.front();
if (block.getOperations().size() != 2)
return false;
auto addfOp = dyn_cast<arith::AddFOp>(&block.front());
if (!addfOp)
return false;
auto yieldOp = dyn_cast<linalg::YieldOp>(block.getTerminator());
if (!yieldOp || yieldOp.getNumOperands() != 1)
return false;
return yieldOp.getOperand(0) == addfOp.getResult();
}
static std::optional<std::pair<arith::AddFOp, Value>>
matchAccumulationPattern(scf::ForOp forOp, unsigned iterArgIdx) {
auto yieldOp = cast<scf::YieldOp>(forOp.getBody()->getTerminator());
Value yieldedValue = yieldOp.getOperand(iterArgIdx);
auto addfOp = yieldedValue.getDefiningOp<arith::AddFOp>();
if (!addfOp)
return std::nullopt;
BlockArgument iterArg = forOp.getRegionIterArg(iterArgIdx);
Value other;
if (addfOp.getLhs() == iterArg)
other = addfOp.getRhs();
else if (addfOp.getRhs() == iterArg)
other = addfOp.getLhs();
else
return std::nullopt;
return std::make_pair(addfOp, other);
}
static SmallVector<ReduceCandidate>
findReduceCandidates(scf::ForOp forOp) {
SmallVector<ReduceCandidate> candidates;
for (OpResult result : forOp.getResults()) {
unsigned idx = result.getResultNumber();
if (!result.hasOneUse())
continue;
auto reduceOp = dyn_cast<linalg::ReduceOp>(*result.getUsers().begin());
if (!reduceOp)
continue;
if (reduceOp.getInputs().size() != 1 || reduceOp.getInputs()[0] != result)
continue;
if (!isAdditiveReduction(reduceOp))
continue;
auto match = matchAccumulationPattern(forOp, idx);
if (!match)
continue;
auto [addfOp, value2D] = *match;
BlockArgument iterArg = forOp.getRegionIterArg(idx);
if (!iterArg.hasOneUse())
continue;
auto outType =
dyn_cast<RankedTensorType>(reduceOp.getResults()[0].getType());
if (!outType || !outType.hasStaticShape())
continue;
candidates.push_back({idx, reduceOp, addfOp, value2D});
}
return candidates;
}
struct FuseReductionIntoLoop
: public impl::FuseReductionIntoLoopBase<FuseReductionIntoLoop> {
explicit FuseReductionIntoLoop() : FuseReductionIntoLoopBase() {}
void runOnOperation() override {
func::FuncOp funcOp = getOperation();
SmallVector<scf::ForOp> forOps;
funcOp.walk([&](scf::ForOp forOp) { forOps.push_back(forOp); });
for (scf::ForOp forOp : forOps)
transformForOp(forOp);
}
private:
void transformForOp(scf::ForOp forOp) {
auto candidates = findReduceCandidates(forOp);
if (candidates.empty())
return;
LDBG("Found " << candidates.size()
<< " reduction candidate(s) to fuse into loop");
OpBuilder builder(forOp);
Location loc = forOp.getLoc();
DenseMap<Operation *, ReduceCandidate *> addfToCand;
for (auto &c : candidates)
addfToCand[c.addfOp.getOperation()] = &c;
SmallVector<Value> newInitArgs(forOp.getInitArgs());
DenseMap<unsigned, RankedTensorType> candOutTypes;
DenseMap<unsigned, Value> candZeroInits;
for (auto &cand : candidates) {
auto outType =
cast<RankedTensorType>(cand.reduceOp.getResults()[0].getType());
candOutTypes[cand.resultIdx] = outType;
Value emptyTensor = builder.create<tensor::EmptyOp>(
loc, outType.getShape(), outType.getElementType());
Value zero = builder.create<arith::ConstantOp>(
loc, builder.getZeroAttr(outType.getElementType()));
Value zeroFilled =
builder.create<linalg::FillOp>(loc, zero, emptyTensor)
.getResult(0);
newInitArgs[cand.resultIdx] = zeroFilled;
candZeroInits[cand.resultIdx] = zeroFilled;
}
auto newForOp = builder.create<scf::ForOp>(
loc, forOp.getLowerBound(), forOp.getUpperBound(), forOp.getStep(),
newInitArgs,
[](OpBuilder &b, Location loc, Value , ValueRange iterArgs) {
b.create<scf::YieldOp>(loc, iterArgs);
});
Block *newBody = newForOp.getBody();
Block *oldBody = forOp.getBody();
Operation *autoYield = newBody->getTerminator();
IRMapping mapping;
mapping.map(oldBody->getArgument(0), newBody->getArgument(0));
for (unsigned i = 0; i < forOp.getNumRegionIterArgs(); ++i) {
if (!candOutTypes.count(i))
mapping.map(oldBody->getArgument(i + 1), newBody->getArgument(i + 1));
}
builder.setInsertionPoint(autoYield);
for (Operation &op : oldBody->without_terminator()) {
auto it = addfToCand.find(&op);
if (it != addfToCand.end()) {
auto &cand = *it->second;
Value mapped2D = mapping.lookupOrDefault(cand.value2D);
Value reduceInit = candZeroInits[cand.resultIdx];
SmallVector<int64_t> dims(cand.reduceOp.getDimensions());
auto newReduce = builder.create<linalg::ReduceOp>(
cand.reduceOp.getLoc(),
ValueRange{mapped2D},
ValueRange{reduceInit}, dims,
[](OpBuilder &b, Location bodyLoc, ValueRange args) {
Value sum = b.create<arith::AddFOp>(bodyLoc, args[0], args[1]);
b.create<linalg::YieldOp>(bodyLoc, sum);
});
Value iterArg1D = newBody->getArgument(cand.resultIdx + 1);
Value newAddf = builder.create<arith::AddFOp>(
cand.addfOp.getLoc(), iterArg1D, newReduce.getResults()[0]);
mapping.map(cand.addfOp.getResult(), newAddf);
} else {
builder.clone(op, mapping);
}
}
auto oldYield = cast<scf::YieldOp>(oldBody->getTerminator());
SmallVector<Value> newYieldOperands;
for (unsigned i = 0; i < oldYield.getNumOperands(); ++i)
newYieldOperands.push_back(
mapping.lookupOrDefault(oldYield.getOperand(i)));
autoYield->setOperands(newYieldOperands);
for (unsigned i = 0; i < forOp.getNumResults(); ++i) {
if (!candOutTypes.count(i))
forOp.getResult(i).replaceAllUsesWith(newForOp.getResult(i));
}
for (auto &cand : candidates) {
cand.reduceOp.getResults()[0].replaceAllUsesWith(
newForOp.getResult(cand.resultIdx));
cand.reduceOp->erase();
}
forOp->erase();
LDBG("Successfully fused reductions into loop");
}
};
}
std::unique_ptr<mlir::Pass> createFuseReductionIntoLoopPass() {
return std::make_unique<FuseReductionIntoLoop>();
}
}