[mlir] Move the Buffer related source files out of Transforms/

Transforms/ should only contain dialect-independent transformations,
and these files are a much better fit for the bufferization dialect anyways.

Differential Revision: https://reviews.llvm.org/D117839
This commit is contained in:
River Riddle 2022-01-20 14:30:47 -08:00
parent e01e4c9115
commit 0e9a4a3b65
15 changed files with 175 additions and 172 deletions

View File

@ -11,13 +11,12 @@
//
//===----------------------------------------------------------------------===//
#ifndef MLIR_TRANSFORMS_BUFFERUTILS_H
#define MLIR_TRANSFORMS_BUFFERUTILS_H
#ifndef MLIR_DIALECT_BUFFERIZATION_TRANSFORMS_BUFFERUTILS_H
#define MLIR_DIALECT_BUFFERIZATION_TRANSFORMS_BUFFERUTILS_H
#include "mlir/Analysis/BufferViewFlowAnalysis.h"
#include "mlir/Analysis/Liveness.h"
#include "mlir/Dialect/Arithmetic/IR/Arithmetic.h"
#include "mlir/Dialect/StandardOps/IR/Ops.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/Dominance.h"
@ -25,6 +24,11 @@
#include "mlir/Transforms/DialectConversion.h"
namespace mlir {
namespace memref {
class GlobalOp;
} // namespace memref
namespace bufferization {
/// A simple analysis that detects allocation operations.
class BufferPlacementAllocs {
@ -117,10 +121,6 @@ protected:
Liveness liveness;
};
namespace memref {
class GlobalOp;
} // namespace memref
// Support class to create global ops for tensor-valued constants in the
// program. Globals are created lazily at the top of the `moduleOp` with pretty
// names. Duplicates are avoided.
@ -137,6 +137,7 @@ private:
// dependence to the memref dialect for this.
DenseMap<Attribute, Operation *> globals;
};
} // namespace bufferization
} // namespace mlir
#endif // MLIR_TRANSFORMS_BUFFERUTILS_H
#endif // MLIR_DIALECT_BUFFERIZATION_TRANSFORMS_BUFFERUTILS_H

View File

@ -14,10 +14,34 @@ namespace bufferization {
/// buffers.
std::unique_ptr<Pass> createBufferDeallocationPass();
/// Creates a pass that moves allocations upwards to reduce the number of
/// required copies that are inserted during the BufferDeallocation pass.
std::unique_ptr<Pass> createBufferHoistingPass();
/// Creates a pass that moves allocations upwards out of loops. This avoids
/// reallocations inside of loops.
std::unique_ptr<Pass> createBufferLoopHoistingPass();
/// Creates a pass that converts memref function results to out-params.
std::unique_ptr<Pass> createBufferResultsToOutParamsPass();
/// Creates a pass that finalizes a partial bufferization by removing remaining
/// bufferization.to_tensor and bufferization.to_memref operations.
std::unique_ptr<OperationPass<FuncOp>> createFinalizingBufferizePass();
/// Creates a pass that promotes heap-based allocations to stack-based ones.
/// Only buffers smaller than the provided size are promoted.
/// Dynamic shaped buffers are promoted up to the given rank.
std::unique_ptr<Pass>
createPromoteBuffersToStackPass(unsigned maxAllocSizeInBytes = 1024,
unsigned bitwidthOfIndexType = 64,
unsigned maxRankOfAllocatedMemRef = 1);
/// Creates a pass that promotes heap-based allocations to stack-based ones.
/// Only buffers smaller with `isSmallAlloc(alloc) == true` are promoted.
std::unique_ptr<Pass>
createPromoteBuffersToStackPass(std::function<bool(Value)> isSmallAlloc);
//===----------------------------------------------------------------------===//
// Registration
//===----------------------------------------------------------------------===//

View File

@ -88,6 +88,51 @@ def BufferDeallocation : Pass<"buffer-deallocation", "FuncOp"> {
let constructor = "mlir::bufferization::createBufferDeallocationPass()";
}
def BufferHoisting : Pass<"buffer-hoisting", "FuncOp"> {
let summary = "Optimizes placement of allocation operations by moving them "
"into common dominators and out of nested regions";
let description = [{
This pass implements an approach to aggressively move allocations upwards
into common dominators and out of nested regions.
}];
let constructor = "mlir::bufferization::createBufferHoistingPass()";
}
def BufferLoopHoisting : Pass<"buffer-loop-hoisting", "FuncOp"> {
let summary = "Optimizes placement of allocation operations by moving them "
"out of loop nests";
let description = [{
This pass implements an approach to aggressively move allocations upwards
out of loop nests. It does not move allocations into common dominators.
}];
let constructor = "mlir::bufferization::createBufferLoopHoistingPass()";
}
def BufferResultsToOutParams : Pass<"buffer-results-to-out-params", "ModuleOp"> {
let summary = "Converts memref-typed function results to out-params";
let description = [{
Some calling conventions prefer to pass output memrefs as "out params". The
conversion to this calling convention must be done as an atomic
transformation of the entire program (hence this is a module pass).
For example, if a call is rewritten, the callee needs to be rewritten
otherwise the IR will end up invalid. Thus, this transformation
require an atomic change to the entire program (e.g. the whole module).
This pass is expected to run immediately after bufferization is finished.
At that point, tensor-typed results will have been converted to memref-typed
results, and can be consistently converted to out params.
All memref-typed results are appended to the function argument list.
The main issue with this pass (and the out-param calling convention) is that
buffers for results need to be allocated in the caller. This currently only
works for static shaped memrefs.
}];
let constructor = "mlir::bufferization::createBufferResultsToOutParamsPass()";
let dependentDialects = ["memref::MemRefDialect"];
}
def FinalizingBufferize : Pass<"finalizing-bufferize", "FuncOp"> {
let summary = "Finalize a partial bufferization";
let description = [{
@ -104,4 +149,28 @@ def FinalizingBufferize : Pass<"finalizing-bufferize", "FuncOp"> {
let constructor = "mlir::bufferization::createFinalizingBufferizePass()";
}
def PromoteBuffersToStack : Pass<"promote-buffers-to-stack", "FuncOp"> {
let summary = "Promotes heap-based allocations to automatically managed "
"stack-based allocations";
let description = [{
This pass implements a simple algorithm to convert heap-based memory
allocations to stack-based ones. It uses a built-in heuristic to decide
whether it makes sense to convert an allocation. Furthermore, dynamic
shaped buffers that are limited by the rank of the tensor can be
converted. They are only transformed if they are considered to be small.
}];
let constructor = "mlir::bufferization::createPromoteBuffersToStackPass()";
let options = [
Option<"maxAllocSizeInBytes", "max-alloc-size-in-bytes", "unsigned",
/*default=*/"1024",
"Maximal size in bytes to promote allocations to stack.">,
Option<"bitwidthOfIndexType", "bitwidth-of-index-type", "unsigned",
/*default=*/"64",
"Bitwidth of the index type. Used for size estimation.">,
Option<"maxRankOfAllocatedMemRef", "max-rank-of-allocated-memref", "unsigned",
/*default=*/"1",
"Maximal memref rank to promote dynamic buffers.">,
];
}
#endif // MLIR_DIALECT_BUFFERIZATION_TRANSFORMS_PASSES

View File

@ -19,9 +19,9 @@
namespace mlir {
namespace bufferization {
class BufferizeTypeConverter;
class GlobalCreator;
} // namespace bufferization
class GlobalCreator;
class RewritePatternSet;
using OwningRewritePatternList = RewritePatternSet;
@ -38,7 +38,7 @@ std::unique_ptr<Pass> createFuncBufferizePass();
/// Add patterns to bufferize tensor constants into global memrefs to the given
/// pattern list.
void populateTensorConstantBufferizePatterns(
GlobalCreator &globalCreator,
bufferization::GlobalCreator &globalCreator,
bufferization::BufferizeTypeConverter &typeConverter,
RewritePatternSet &patterns);

View File

@ -33,30 +33,6 @@ enum FusionMode { Greedy, ProducerConsumer, Sibling };
// Passes
//===----------------------------------------------------------------------===//
/// Creates a pass that moves allocations upwards to reduce the number of
/// required copies that are inserted during the BufferDeallocation pass.
std::unique_ptr<Pass> createBufferHoistingPass();
/// Creates a pass that moves allocations upwards out of loops. This avoids
/// reallocations inside of loops.
std::unique_ptr<Pass> createBufferLoopHoistingPass();
/// Creates a pass that promotes heap-based allocations to stack-based ones.
/// Only buffers smaller than the provided size are promoted.
/// Dynamic shaped buffers are promoted up to the given rank.
std::unique_ptr<Pass>
createPromoteBuffersToStackPass(unsigned maxAllocSizeInBytes = 1024,
unsigned bitwidthOfIndexType = 64,
unsigned maxRankOfAllocatedMemRef = 1);
/// Creates a pass that promotes heap-based allocations to stack-based ones.
/// Only buffers smaller with `isSmallAlloc(alloc) == true` are promoted.
std::unique_ptr<Pass>
createPromoteBuffersToStackPass(std::function<bool(Value)> isSmallAlloc);
/// Creates a pass that converts memref function results to out-params.
std::unique_ptr<Pass> createBufferResultsToOutParamsPass();
/// Creates an instance of the Canonicalizer pass, configured with default
/// settings (which can be overridden by pass options on the command line).
std::unique_ptr<Pass> createCanonicalizerPass();

View File

@ -217,75 +217,6 @@ def AffinePipelineDataTransfer
let constructor = "mlir::createPipelineDataTransferPass()";
}
def BufferHoisting : Pass<"buffer-hoisting", "FuncOp"> {
let summary = "Optimizes placement of allocation operations by moving them "
"into common dominators and out of nested regions";
let description = [{
This pass implements an approach to aggressively move allocations upwards
into common dominators and out of nested regions.
}];
let constructor = "mlir::createBufferHoistingPass()";
}
def BufferLoopHoisting : Pass<"buffer-loop-hoisting", "FuncOp"> {
let summary = "Optimizes placement of allocation operations by moving them "
"out of loop nests";
let description = [{
This pass implements an approach to aggressively move allocations upwards
out of loop nests. It does not move allocations into common dominators.
}];
let constructor = "mlir::createBufferLoopHoistingPass()";
}
def PromoteBuffersToStack : Pass<"promote-buffers-to-stack", "FuncOp"> {
let summary = "Promotes heap-based allocations to automatically managed "
"stack-based allocations";
let description = [{
This pass implements a simple algorithm to convert heap-based memory
allocations to stack-based ones. It uses a built-in heuristic to decide
whether it makes sense to convert an allocation. Furthermore, dynamic
shaped buffers that are limited by the rank of the tensor can be
converted. They are only transformed if they are considered to be small.
}];
let constructor = "mlir::createPromoteBuffersToStackPass()";
let options = [
Option<"maxAllocSizeInBytes", "max-alloc-size-in-bytes", "unsigned",
/*default=*/"1024",
"Maximal size in bytes to promote allocations to stack.">,
Option<"bitwidthOfIndexType", "bitwidth-of-index-type", "unsigned",
/*default=*/"64",
"Bitwidth of the index type. Used for size estimation.">,
Option<"maxRankOfAllocatedMemRef", "max-rank-of-allocated-memref", "unsigned",
/*default=*/"1",
"Maximal memref rank to promote dynamic buffers.">,
];
}
def BufferResultsToOutParams : Pass<"buffer-results-to-out-params", "ModuleOp"> {
let summary = "Converts memref-typed function results to out-params";
let description = [{
Some calling conventions prefer to pass output memrefs as "out params". The
conversion to this calling convention must be done as an atomic
transformation of the entire program (hence this is a module pass).
For example, if a call is rewritten, the callee needs to be rewritten
otherwise the IR will end up invalid. Thus, this transformation
require an atomic change to the entire program (e.g. the whole module).
This pass is expected to run immediately after bufferization is finished.
At that point, tensor-typed results will have been converted to memref-typed
results, and can be consistently converted to out params.
All memref-typed results are appended to the function argument list.
The main issue with this pass (and the out-param calling convention) is that
buffers for results need to be allocated in the caller. This currently only
works for static shaped memrefs.
}];
let constructor = "mlir::createBufferResultsToOutParamsPass()";
let dependentDialects = ["memref::MemRefDialect"];
}
def Canonicalizer : Pass<"canonicalize"> {
let summary = "Canonicalize operations";
let description = [{

View File

@ -54,12 +54,13 @@
#include "mlir/Dialect/Bufferization/IR/AllocationOpInterface.h"
#include "mlir/Dialect/Bufferization/IR/Bufferization.h"
#include "mlir/Dialect/Bufferization/Transforms/BufferUtils.h"
#include "mlir/Dialect/Bufferization/Transforms/Passes.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/Transforms/BufferUtils.h"
#include "llvm/ADT/SetOperations.h"
using namespace mlir;
using namespace mlir::bufferization;
/// Walks over all immediate return-like terminators in the given region.
static LogicalResult

View File

@ -12,14 +12,15 @@
// convert heap-based allocations to stack-based allocations, if possible.
#include "PassDetail.h"
#include "mlir/Dialect/Bufferization/Transforms/BufferUtils.h"
#include "mlir/Dialect/Bufferization/Transforms/Passes.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/IR/Operation.h"
#include "mlir/Interfaces/LoopLikeInterface.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Transforms/BufferUtils.h"
#include "mlir/Transforms/Passes.h"
using namespace mlir;
using namespace mlir::bufferization;
/// Returns true if the given operation implements a known high-level region-
/// based control-flow interface.
@ -422,23 +423,22 @@ private:
} // namespace
std::unique_ptr<Pass> mlir::createBufferHoistingPass() {
std::unique_ptr<Pass> mlir::bufferization::createBufferHoistingPass() {
return std::make_unique<BufferHoistingPass>();
}
std::unique_ptr<Pass> mlir::createBufferLoopHoistingPass() {
std::unique_ptr<Pass> mlir::bufferization::createBufferLoopHoistingPass() {
return std::make_unique<BufferLoopHoistingPass>();
}
std::unique_ptr<Pass>
mlir::createPromoteBuffersToStackPass(unsigned maxAllocSizeInBytes,
unsigned bitwidthOfIndexType,
unsigned maxRankOfAllocatedMemRef) {
std::unique_ptr<Pass> mlir::bufferization::createPromoteBuffersToStackPass(
unsigned maxAllocSizeInBytes, unsigned bitwidthOfIndexType,
unsigned maxRankOfAllocatedMemRef) {
return std::make_unique<PromoteBuffersToStackPass>(
maxAllocSizeInBytes, bitwidthOfIndexType, maxRankOfAllocatedMemRef);
}
std::unique_ptr<Pass>
mlir::createPromoteBuffersToStackPass(std::function<bool(Value)> isSmallAlloc) {
std::unique_ptr<Pass> mlir::bufferization::createPromoteBuffersToStackPass(
std::function<bool(Value)> isSmallAlloc) {
return std::make_unique<PromoteBuffersToStackPass>(std::move(isSmallAlloc));
}

View File

@ -7,11 +7,11 @@
//===----------------------------------------------------------------------===//
#include "PassDetail.h"
#include "mlir/Dialect/Bufferization/Transforms/Passes.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/Dialect/StandardOps/IR/Ops.h"
#include "mlir/IR/Operation.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Transforms/Passes.h"
using namespace mlir;
@ -139,6 +139,7 @@ struct BufferResultsToOutParamsPass
};
} // namespace
std::unique_ptr<Pass> mlir::createBufferResultsToOutParamsPass() {
std::unique_ptr<Pass>
mlir::bufferization::createBufferResultsToOutParamsPass() {
return std::make_unique<BufferResultsToOutParamsPass>();
}

View File

@ -10,18 +10,18 @@
//
//===----------------------------------------------------------------------===//
#include "mlir/Transforms/BufferUtils.h"
#include "PassDetail.h"
#include "mlir/Dialect/Bufferization/Transforms/BufferUtils.h"
#include "mlir/Dialect/Bufferization/Transforms/Bufferize.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/Dialect/MemRef/Utils/MemRefUtils.h"
#include "mlir/Dialect/StandardOps/IR/Ops.h"
#include "mlir/IR/Operation.h"
#include "mlir/Interfaces/ControlFlowInterfaces.h"
#include "mlir/Interfaces/LoopLikeInterface.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Transforms/Passes.h"
#include "llvm/ADT/SetOperations.h"
using namespace mlir;
using namespace mlir::bufferization;
//===----------------------------------------------------------------------===//
// BufferPlacementAllocs
@ -139,3 +139,49 @@ bool BufferPlacementTransformationBase::isLoop(Operation *op) {
return false;
}
//===----------------------------------------------------------------------===//
// BufferPlacementTransformationBase
//===----------------------------------------------------------------------===//
memref::GlobalOp GlobalCreator::getGlobalFor(arith::ConstantOp constantOp) {
auto type = constantOp.getType().cast<RankedTensorType>();
BufferizeTypeConverter typeConverter;
// If we already have a global for this constant value, no need to do
// anything else.
auto it = globals.find(constantOp.getValue());
if (it != globals.end())
return cast<memref::GlobalOp>(it->second);
// Create a builder without an insertion point. We will insert using the
// symbol table to guarantee unique names.
OpBuilder globalBuilder(moduleOp.getContext());
SymbolTable symbolTable(moduleOp);
// Create a pretty name.
SmallString<64> buf;
llvm::raw_svector_ostream os(buf);
interleave(type.getShape(), os, "x");
os << "x" << type.getElementType();
// Add an optional alignment to the global memref.
IntegerAttr memrefAlignment =
alignment > 0 ? IntegerAttr::get(globalBuilder.getI64Type(), alignment)
: IntegerAttr();
auto global = globalBuilder.create<memref::GlobalOp>(
constantOp.getLoc(), (Twine("__constant_") + os.str()).str(),
/*sym_visibility=*/globalBuilder.getStringAttr("private"),
/*type=*/typeConverter.convertType(type).cast<MemRefType>(),
/*initial_value=*/constantOp.getValue().cast<ElementsAttr>(),
/*constant=*/true,
/*alignment=*/memrefAlignment);
symbolTable.insert(global);
// The symbol table inserts at the end of the module, but globals are a bit
// nicer if they are at the beginning.
global->moveBefore(&moduleOp.front());
globals[constantOp.getValue()] = global;
return global;
}

View File

@ -1,6 +1,9 @@
add_mlir_dialect_library(MLIRBufferizationTransforms
Bufferize.cpp
BufferDeallocation.cpp
BufferOptimizations.cpp
BufferResultsToOutParams.cpp
BufferUtils.cpp
OneShotAnalysis.cpp
ADDITIONAL_HEADER_DIRS

View File

@ -10,10 +10,10 @@
#include "mlir/Dialect/Arithmetic/IR/Arithmetic.h"
#include "mlir/Dialect/Bufferization/IR/BufferizableOpInterface.h"
#include "mlir/Dialect/Bufferization/Transforms/BufferUtils.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/IR/Dialect.h"
#include "mlir/IR/Operation.h"
#include "mlir/Transforms/BufferUtils.h"
using namespace mlir::bufferization;

View File

@ -12,57 +12,16 @@
#include "PassDetail.h"
#include "mlir/Dialect/Bufferization/IR/Bufferization.h"
#include "mlir/Dialect/Bufferization/Transforms/BufferUtils.h"
#include "mlir/Dialect/Bufferization/Transforms/Bufferize.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/Dialect/StandardOps/IR/Ops.h"
#include "mlir/Dialect/StandardOps/Transforms/Passes.h"
#include "mlir/IR/BlockAndValueMapping.h"
#include "mlir/Transforms/BufferUtils.h"
#include "mlir/Transforms/DialectConversion.h"
using namespace mlir;
memref::GlobalOp GlobalCreator::getGlobalFor(arith::ConstantOp constantOp) {
auto type = constantOp.getType().cast<RankedTensorType>();
bufferization::BufferizeTypeConverter typeConverter;
// If we already have a global for this constant value, no need to do
// anything else.
auto it = globals.find(constantOp.getValue());
if (it != globals.end())
return cast<memref::GlobalOp>(it->second);
// Create a builder without an insertion point. We will insert using the
// symbol table to guarantee unique names.
OpBuilder globalBuilder(moduleOp.getContext());
SymbolTable symbolTable(moduleOp);
// Create a pretty name.
SmallString<64> buf;
llvm::raw_svector_ostream os(buf);
interleave(type.getShape(), os, "x");
os << "x" << type.getElementType();
// Add an optional alignment to the global memref.
IntegerAttr memrefAlignment =
alignment > 0 ? IntegerAttr::get(globalBuilder.getI64Type(), alignment)
: IntegerAttr();
auto global = globalBuilder.create<memref::GlobalOp>(
constantOp.getLoc(), (Twine("__constant_") + os.str()).str(),
/*sym_visibility=*/globalBuilder.getStringAttr("private"),
/*type=*/typeConverter.convertType(type).cast<MemRefType>(),
/*initial_value=*/constantOp.getValue().cast<ElementsAttr>(),
/*constant=*/true,
/*alignment=*/memrefAlignment);
symbolTable.insert(global);
// The symbol table inserts at the end of the module, but globals are a bit
// nicer if they are at the beginning.
global->moveBefore(&moduleOp.front());
globals[constantOp.getValue()] = global;
return global;
}
using namespace mlir::bufferization;
namespace {
class BufferizeTensorConstantOp

View File

@ -1,9 +1,6 @@
add_subdirectory(Utils)
add_mlir_library(MLIRTransforms
BufferOptimizations.cpp
BufferResultsToOutParams.cpp
BufferUtils.cpp
Canonicalizer.cpp
ControlFlowSink.cpp
CSE.cpp
@ -31,7 +28,6 @@ add_mlir_library(MLIRTransforms
LINK_LIBS PUBLIC
MLIRAffine
MLIRAnalysis
MLIRBufferization
MLIRCopyOpInterface
MLIRLoopLikeInterface
MLIRMemRef

View File

@ -27,10 +27,6 @@ namespace memref {
class MemRefDialect;
} // namespace memref
namespace bufferization {
class BufferizationDialect;
} // namespace bufferization
#define GEN_PASS_CLASSES
#include "mlir/Transforms/Passes.h.inc"