#include "mlir/Dialect/Bufferization/Transforms/Passes.h"
#include "mlir/Dialect/Bufferization/IR/BufferizableOpInterface.h"
#include "mlir/Dialect/Bufferization/IR/Bufferization.h"
#include "mlir/Dialect/Bufferization/Transforms/Bufferize.h"
#include "mlir/Dialect/Bufferization/Transforms/OneShotAnalysis.h"
#include "mlir/Dialect/Bufferization/Transforms/OneShotModuleBufferize.h"
#include "mlir/Dialect/Bufferization/Transforms/Transforms.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/IR/Diagnostics.h"
#include "mlir/IR/Operation.h"
#include "mlir/Interfaces/ControlFlowInterfaces.h"
#include "mlir/Interfaces/SideEffectInterfaces.h"
#include "mlir/Pass/PassManager.h"
#include "llvm/Support/DebugLog.h"
#include <optional>
namespace mlir {
namespace bufferization {
#define GEN_PASS_DEF_ONESHOTBUFFERIZEPASS
#include "mlir/Dialect/Bufferization/Transforms/Passes.h.inc"
}
}
#define DEBUG_TYPE "bufferize"
using namespace mlir;
using namespace mlir::bufferization;
namespace {
static OneShotBufferizationOptions::AnalysisHeuristic
parseHeuristicOption(const std::string &s) {
if (s == "bottom-up")
return OneShotBufferizationOptions::AnalysisHeuristic::BottomUp;
if (s == "top-down")
return OneShotBufferizationOptions::AnalysisHeuristic::TopDown;
if (s == "bottom-up-from-terminators")
return OneShotBufferizationOptions::AnalysisHeuristic::
BottomUpFromTerminators;
if (s == "fuzzer")
return OneShotBufferizationOptions::AnalysisHeuristic::Fuzzer;
llvm_unreachable("invalid analysisheuristic option");
}
struct OneShotBufferizePass
: public bufferization::impl::OneShotBufferizePassBase<
OneShotBufferizePass> {
using Base::Base;
void runOnOperation() override {
OneShotBufferizationOptions opt;
if (!options) {
opt.allowReturnAllocsFromLoops = allowReturnAllocsFromLoops;
opt.allowUnknownOps = allowUnknownOps;
opt.analysisFuzzerSeed = analysisFuzzerSeed;
opt.analysisHeuristic = parseHeuristicOption(analysisHeuristic);
opt.copyBeforeWrite = copyBeforeWrite;
opt.dumpAliasSets = dumpAliasSets;
opt.setFunctionBoundaryTypeConversion(functionBoundaryTypeConversion);
if (mustInferMemorySpace && useEncodingForMemorySpace) {
emitError(getOperation()->getLoc())
<< "only one of 'must-infer-memory-space' and "
"'use-encoding-for-memory-space' are allowed in "
<< getArgument();
return signalPassFailure();
}
if (mustInferMemorySpace) {
opt.defaultMemorySpaceFn =
[](TensorType t) -> std::optional<Attribute> {
return std::nullopt;
};
}
if (useEncodingForMemorySpace) {
opt.defaultMemorySpaceFn =
[](TensorType t) -> std::optional<Attribute> {
if (auto rtt = dyn_cast<RankedTensorType>(t))
return rtt.getEncoding();
return std::nullopt;
};
}
opt.printConflicts = printConflicts;
opt.bufferAlignment = bufferAlignment;
opt.testAnalysisOnly = testAnalysisOnly;
opt.bufferizeFunctionBoundaries = bufferizeFunctionBoundaries;
opt.checkParallelRegions = checkParallelRegions;
opt.noAnalysisFuncFilter = noAnalysisFuncFilter;
LayoutMapOption unknownTypeConversionOption = unknownTypeConversion;
if (unknownTypeConversionOption == LayoutMapOption::InferLayoutMap) {
emitError(UnknownLoc::get(&getContext()),
"Invalid option: 'infer-layout-map' is not a valid value for "
"'unknown-type-conversion'");
return signalPassFailure();
}
opt.unknownTypeConverterFn = [=](TensorType tensorType,
Attribute memorySpace,
const BufferizationOptions &options) {
if (unknownTypeConversionOption == LayoutMapOption::IdentityLayoutMap)
return bufferization::getMemRefTypeWithStaticIdentityLayout(
tensorType, memorySpace);
assert(unknownTypeConversionOption ==
LayoutMapOption::FullyDynamicLayoutMap &&
"invalid layout map option");
return bufferization::getMemRefTypeWithFullyDynamicLayout(tensorType,
memorySpace);
};
OpFilter::Entry::FilterFn filterFn = [&](Operation *op) {
if (this->dialectFilter.hasValue() && !(*this->dialectFilter).empty())
return llvm::is_contained(this->dialectFilter,
op->getDialect()->getNamespace());
return true;
};
opt.opFilter.allowOperation(filterFn);
} else {
opt = *options;
}
if (opt.copyBeforeWrite && opt.testAnalysisOnly) {
emitError(UnknownLoc::get(&getContext()),
"Invalid option: 'copy-before-write' cannot be used with "
"'test-analysis-only'");
return signalPassFailure();
}
if (opt.printConflicts && !opt.testAnalysisOnly) {
emitError(
UnknownLoc::get(&getContext()),
"Invalid option: 'print-conflicts' requires 'test-analysis-only'");
return signalPassFailure();
}
if (opt.dumpAliasSets && !opt.testAnalysisOnly) {
emitError(
UnknownLoc::get(&getContext()),
"Invalid option: 'dump-alias-sets' requires 'test-analysis-only'");
return signalPassFailure();
}
BufferizationState state;
BufferizationStatistics statistics;
ModuleOp moduleOp = getOperation();
if (opt.bufferizeFunctionBoundaries) {
if (failed(
runOneShotModuleBufferize(moduleOp, opt, state, &statistics))) {
signalPassFailure();
return;
}
} else {
if (!opt.noAnalysisFuncFilter.empty()) {
emitError(UnknownLoc::get(&getContext()),
"Invalid option: 'no-analysis-func-filter' requires "
"'bufferize-function-boundaries'");
return signalPassFailure();
}
if (failed(runOneShotBufferize(moduleOp, opt, state, &statistics))) {
signalPassFailure();
return;
}
}
this->numBufferAlloc = statistics.numBufferAlloc;
this->numTensorInPlace = statistics.numTensorInPlace;
this->numTensorOutOfPlace = statistics.numTensorOutOfPlace;
}
private:
std::optional<OneShotBufferizationOptions> options;
};
}
namespace {
class BufferizationRewriter : public IRRewriter, public RewriterBase::Listener {
public:
BufferizationRewriter(MLIRContext *ctx, DenseSet<Operation *> &erasedOps,
DenseSet<Operation *> &toBufferOps,
SmallVector<Operation *> &worklist,
const BufferizationOptions &options,
BufferizationStatistics *statistics)
: IRRewriter(ctx), erasedOps(erasedOps), toBufferOps(toBufferOps),
worklist(worklist), analysisState(options), statistics(statistics) {
setListener(this);
}
protected:
void notifyOperationErased(Operation *op) override {
erasedOps.insert(op);
toBufferOps.erase(op);
}
void notifyOperationInserted(Operation *op, InsertPoint previous) override {
if (previous.isSet())
return;
erasedOps.erase(op);
if (statistics) {
if (auto sideEffectingOp = dyn_cast<MemoryEffectOpInterface>(op))
statistics->numBufferAlloc += static_cast<int64_t>(
sideEffectingOp.hasEffect<MemoryEffects::Allocate>());
}
if (isa<ToBufferOp>(op)) {
toBufferOps.insert(op);
return;
}
if (isa<ToTensorOp>(op))
return;
if (!hasTensorSemantics(op))
return;
auto const &options = analysisState.getOptions();
if (!options.isOpAllowed(op))
return;
worklist.push_back(op);
}
private:
DenseSet<Operation *> &erasedOps;
DenseSet<Operation *> &toBufferOps;
SmallVector<Operation *> &worklist;
const AnalysisState analysisState;
BufferizationStatistics *statistics;
};
}
LogicalResult bufferization::bufferizeOp(Operation *op,
const BufferizationOptions &options,
BufferizationState &bufferizationState,
BufferizationStatistics *statistics) {
if (options.copyBeforeWrite) {
AnalysisState analysisState(options);
if (failed(insertTensorCopies(op, analysisState, bufferizationState)))
return failure();
}
DenseSet<Operation *> toBufferOps;
op->walk([&](ToBufferOp toBufferOp) { toBufferOps.insert(toBufferOp); });
SmallVector<Operation *> worklist;
op->walk<WalkOrder::PostOrder>([&](Operation *op) {
if (options.isOpAllowed(op) && hasTensorSemantics(op))
worklist.push_back(op);
});
DenseSet<Operation *> erasedOps;
BufferizationRewriter rewriter(op->getContext(), erasedOps, toBufferOps,
worklist, options, statistics);
for (unsigned i = 0; i < worklist.size(); ++i) {
Operation *nextOp = worklist[i];
if (erasedOps.contains(nextOp))
continue;
auto bufferizableOp = options.dynCastBufferizableOp(nextOp);
if (!bufferizableOp)
continue;
if (!hasTensorSemantics(nextOp))
continue;
if (!bufferizableOp.supportsUnstructuredControlFlow())
for (Region &r : nextOp->getRegions())
if (r.getBlocks().size() > 1)
return nextOp->emitOpError(
"op or BufferizableOpInterface implementation does not support "
"unstructured control flow, but at least one region has multiple "
"blocks");
LDBG(3) << "//===-------------------------------------------===//\n"
<< "IR after bufferizing: " << nextOp->getName();
rewriter.setInsertionPoint(nextOp);
if (failed(
bufferizableOp.bufferize(rewriter, options, bufferizationState))) {
LDBG(2) << "failed to bufferize\n"
<< "//===-------------------------------------------===//";
return nextOp->emitError("failed to bufferize op");
}
LDBG(3) << *op << "\n//===-------------------------------------------===//";
}
if (erasedOps.contains(op))
return success();
for (Operation *op : toBufferOps) {
rewriter.setInsertionPoint(op);
(void)bufferization::foldToBufferToTensorPair(
rewriter, cast<ToBufferOp>(op), options);
}
op->walk<WalkOrder::PostOrder>([&](ToTensorOp toTensorOp) {
if (toTensorOp->getUses().empty()) {
rewriter.eraseOp(toTensorOp);
return WalkResult::skip();
}
return WalkResult::advance();
});
if (options.allowUnknownOps)
return success();
for (Operation *op : worklist) {
if (erasedOps.contains(op))
continue;
if (!hasTensorSemantics(op))
continue;
if (!options.isOpAllowed(op))
continue;
if (op->getUses().empty() && isMemoryEffectFree(op))
continue;
if (isa<ToTensorOp, ToBufferOp>(op))
continue;
return op->emitError("op was not bufferized");
}
return success();
}
LogicalResult
bufferization::bufferizeBlockSignature(Block *block, RewriterBase &rewriter,
const BufferizationOptions &options,
BufferizationState &state) {
OpBuilder::InsertionGuard g(rewriter);
auto bufferizableOp = options.dynCastBufferizableOp(block->getParentOp());
if (!bufferizableOp)
return failure();
SmallVector<Type> newTypes;
for (BlockArgument &bbArg : block->getArguments()) {
auto tensorType = dyn_cast<TensorLikeType>(bbArg.getType());
if (!tensorType) {
newTypes.push_back(bbArg.getType());
continue;
}
FailureOr<BufferLikeType> bufferType =
bufferization::getBufferType(bbArg, options, state);
if (failed(bufferType))
return failure();
newTypes.push_back(*bufferType);
}
for (auto [bbArg, type] : llvm::zip(block->getArguments(), newTypes)) {
if (bbArg.getType() == type)
continue;
SmallVector<OpOperand *> bbArgUses;
for (OpOperand &use : bbArg.getUses())
bbArgUses.push_back(&use);
Type tensorType = bbArg.getType();
bbArg.setType(type);
rewriter.setInsertionPointToStart(block);
if (!bbArgUses.empty()) {
Value toTensorOp = bufferization::ToTensorOp::create(
rewriter, bbArg.getLoc(), tensorType, bbArg);
for (OpOperand *use : bbArgUses)
use->set(toTensorOp);
}
}
for (Operation *op : block->getUsers()) {
auto branchOp = dyn_cast<BranchOpInterface>(op);
if (!branchOp)
return op->emitOpError("cannot bufferize ops with block references that "
"do not implement BranchOpInterface");
auto it = llvm::find(op->getSuccessors(), block);
assert(it != op->getSuccessors().end() && "could find successor");
int64_t successorIdx = std::distance(op->getSuccessors().begin(), it);
SuccessorOperands operands = branchOp.getSuccessorOperands(successorIdx);
SmallVector<Value> newOperands;
for (auto [operand, type] :
llvm::zip(operands.getForwardedOperands(), newTypes)) {
if (operand.getType() == type) {
newOperands.push_back(operand);
continue;
}
FailureOr<BufferLikeType> operandBufferType =
bufferization::getBufferType(operand, options, state);
if (failed(operandBufferType))
return failure();
rewriter.setInsertionPointAfterValue(operand);
Value bufferizedOperand = bufferization::ToBufferOp::create(
rewriter, operand.getLoc(), *operandBufferType, operand);
if (type != *operandBufferType)
bufferizedOperand = memref::CastOp::create(rewriter, operand.getLoc(),
type, bufferizedOperand);
newOperands.push_back(bufferizedOperand);
}
operands.getMutableForwardedOperands().assign(newOperands);
}
return success();
}