Skip to content

Commit 7026a8c

Browse files
committed
[mlir][bufferization] Don't clone on unknown ownership and verify function boundary ABI
Inserting clones requires a lot of assumptions to hold on the input IR, e.g., all writes to a buffer need to dominate all reads. This is not guaranteed by one-shot bufferization and isn't easy to verify, thus it could quickly lead to incorrect results that are hard to debug. This commit changes the mechanism of how an ownership indicator is materialized when there is not already a unique ownership present. Additionally, we don't create copies of returned memrefs anymore when we don't have ownership. Instead, we insert assert operations to make sure we have ownership at runtime, or otherwise report to the user that correctness could not be guaranteed.
1 parent cd7f171 commit 7026a8c

24 files changed

+289
-212
lines changed

mlir/include/mlir/Dialect/Bufferization/IR/BufferDeallocationOpInterface.h

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,17 @@ struct DeallocationOptions {
9595
// A pass option indicating whether private functions should be modified to
9696
// pass the ownership of MemRef values instead of adhering to the function
9797
// boundary ABI.
98-
bool privateFuncDynamicOwnership = false;
98+
bool privateFuncDynamicOwnership = true;
99+
100+
/// Inserts `cf.assert` operations to verify the function boundary ABI at
101+
/// runtime. Currently, it is only checked that the ownership of returned
102+
/// MemRefs is 'true'. This makes sure that ownership is yielded and the
103+
/// returned MemRef does not originate from the same allocation as a function
104+
/// argument. TODO: check that returned MemRefs don't alias each other.
105+
/// If it can be determined statically that the ABI is not adhered
106+
/// to, an error will already be emitted at compile time. This cannot be
107+
/// changed with this option.
108+
bool verifyFunctionBoundaryABI = true;
99109
};
100110

101111
/// This class collects all the state that we need to perform the buffer
@@ -138,12 +148,12 @@ class DeallocationState {
138148
void getLiveMemrefsIn(Block *block, SmallVectorImpl<Value> &memrefs);
139149

140150
/// Given an SSA value of MemRef type, this function queries the ownership and
141-
/// if it is not already in the 'Unique' state, potentially inserts IR to get
142-
/// a new SSA value, returned as the first element of the pair, which has
143-
/// 'Unique' ownership and can be used instead of the passed Value with the
144-
/// the ownership indicator returned as the second element of the pair.
145-
std::pair<Value, Value>
146-
getMemrefWithUniqueOwnership(OpBuilder &builder, Value memref, Block *block);
151+
/// if it is not already in the 'Unique' state, potentially inserts IR to
152+
/// determine the ownership (which might involve expensive aliasing checks at
153+
/// runtime).
154+
Value getMemrefWithUniqueOwnership(const DeallocationOptions &options,
155+
OpBuilder &builder, Value memref,
156+
Block *block);
147157

148158
/// Given two basic blocks and the values passed via block arguments to the
149159
/// destination block, compute the list of MemRefs that have to be retained in

mlir/include/mlir/Dialect/Bufferization/IR/BufferDeallocationOpInterface.td

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,38 @@ def BufferDeallocationOpInterface :
5555
ownership indicator when needed, it should be implemented using this
5656
method (which is especially important if operations are created that
5757
cannot be easily canonicalized away anymore).
58+
Ownership indicators have to be materialized when
59+
* needed for the condition operands of a `bufferization.dealloc` op
60+
* passed along MemRefs to successor blocks via additional forwarded
61+
operands of terminator ops
62+
* passing them as additional operands to nested regions (e.g.,
63+
init_args of `scf.for`)
64+
* passing them as additional operands to a call operation when
65+
`private-function-dynamic-ownership` is enabled
66+
* a copy is made conditionally on the current ownership, etc.
67+
68+
In the following example, the deallocation pass would add an
69+
additional block argument to `^bb1` for passing the ownership of `%0`
70+
along and thus the ownership indicator has to be materialized before
71+
the `cf.br` operation and added as a forwarded operand.
72+
```mlir
73+
%0 = arith.select %cond, %m1, %m2 : memref<f32>
74+
cf.br ^bb1(%0 : memref<f32>)
75+
^bb1(%arg0: memref<f32>)
76+
...
77+
```
78+
The `arith.select` operation could implement this interface method to
79+
materialize another `arith.select` operation to select the
80+
corresponding ownership indicator.
81+
```mlir
82+
%0 = arith.select %cond, %m1, %m2 : memref<f32>
83+
%0_ownership = arith.select %cond, %m1_ownership, %m2_ownership : i1
84+
cf.br ^bb1(%0, %0_ownership : memref<f32>, i1)
85+
^bb1(%arg0: memref<f32>, %arg1: i1)
86+
...
87+
```
5888
}],
59-
/*retType=*/"std::pair<Value, Value>",
89+
/*retType=*/"Value",
6090
/*methodName=*/"materializeUniqueOwnershipForMemref",
6191
/*args=*/(ins "DeallocationState &":$state,
6292
"const DeallocationOptions &":$options,
@@ -65,7 +95,7 @@ def BufferDeallocationOpInterface :
6595
/*methodBody=*/[{}],
6696
/*defaultImplementation=*/[{
6797
return state.getMemrefWithUniqueOwnership(
68-
builder, memref, memref.getParentBlock());
98+
options, builder, memref, memref.getParentBlock());
6999
}]>,
70100
];
71101
}

mlir/include/mlir/Dialect/Bufferization/Pipelines/Passes.h

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
namespace mlir {
1919
namespace bufferization {
20+
struct DeallocationOptions;
2021

2122
/// Options for the buffer deallocation pipeline.
2223
struct BufferDeallocationPipelineOptions
@@ -27,7 +28,23 @@ struct BufferDeallocationPipelineOptions
2728
"Allows to add additional arguments to private functions to "
2829
"dynamically pass ownership of memrefs to callees. This can enable "
2930
"earlier deallocations."),
30-
llvm::cl::init(false)};
31+
llvm::cl::init(true)};
32+
PassOptions::Option<bool> verifyFunctionBoundaryABI{
33+
*this, "verify-function-boundary-abi",
34+
llvm::cl::desc(
35+
"Inserts `cf.assert` operations to verify the function boundary ABI "
36+
"at runtime. Currently, it is only checked that the ownership of "
37+
"returned MemRefs is 'true'. This makes sure that ownership is "
38+
"yielded and the returned MemRef does not originate from the same "
39+
"allocation as a function argument. If it can be determined "
40+
"statically that the ABI is not adhered to, an error will already be "
41+
"emitted at compile time. This cannot be changed with this option."),
42+
llvm::cl::init(true)};
43+
44+
/// Convert this BufferDeallocationPipelineOptions struct to a
45+
/// DeallocationOptions struct to be passed to the
46+
/// OwnershipBasedBufferDeallocationPass.
47+
DeallocationOptions asDeallocationOptions() const;
3148
};
3249

3350
//===----------------------------------------------------------------------===//

mlir/include/mlir/Dialect/Bufferization/Transforms/Passes.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#ifndef MLIR_DIALECT_BUFFERIZATION_TRANSFORMS_PASSES_H
22
#define MLIR_DIALECT_BUFFERIZATION_TRANSFORMS_PASSES_H
33

4+
#include "mlir/Dialect/Bufferization/IR/BufferDeallocationOpInterface.h"
45
#include "mlir/Pass/Pass.h"
56

67
namespace mlir {
@@ -31,7 +32,7 @@ std::unique_ptr<Pass> createBufferDeallocationPass();
3132
/// Creates an instance of the OwnershipBasedBufferDeallocation pass to free all
3233
/// allocated buffers.
3334
std::unique_ptr<Pass> createOwnershipBasedBufferDeallocationPass(
34-
bool privateFuncDynamicOwnership = false);
35+
const DeallocationOptions &options = DeallocationOptions());
3536

3637
/// Creates a pass that optimizes `bufferization.dealloc` operations. For
3738
/// example, it reduces the number of alias checks needed at runtime using
@@ -134,8 +135,9 @@ func::FuncOp buildDeallocationLibraryFunction(OpBuilder &builder, Location loc,
134135
LogicalResult deallocateBuffers(Operation *op);
135136

136137
/// Run ownership basedbuffer deallocation.
137-
LogicalResult deallocateBuffersOwnershipBased(FunctionOpInterface op,
138-
bool privateFuncDynamicOwnership);
138+
LogicalResult deallocateBuffersOwnershipBased(
139+
FunctionOpInterface op,
140+
const DeallocationOptions &options = DeallocationOptions());
139141

140142
/// Creates a pass that moves allocations upwards to reduce the number of
141143
/// required copies that are inserted during the BufferDeallocation pass.

mlir/include/mlir/Dialect/Bufferization/Transforms/Passes.td

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,10 +219,20 @@ def OwnershipBasedBufferDeallocation : Pass<
219219
}];
220220
let options = [
221221
Option<"privateFuncDynamicOwnership", "private-function-dynamic-ownership",
222-
"bool", /*default=*/"false",
222+
"bool", /*default=*/"true",
223223
"Allows to add additional arguments to private functions to "
224224
"dynamically pass ownership of memrefs to callees. This can enable "
225225
"earlier deallocations.">,
226+
Option<"verifyFunctionBoundaryABI", "verify-function-boundary-abi",
227+
"bool", /*default=*/"true",
228+
"Inserts `cf.assert` operations to verify the function boundary ABI "
229+
"at runtime. Currently, it is only checked that the ownership of "
230+
"returned MemRefs is 'true'. This makes sure that ownership is "
231+
"yielded and the returned MemRef does not originate from the same "
232+
"allocation as a function argument. "
233+
"If it can be determined statically that the ABI is not adhered "
234+
"to, an error will already be emitted at compile time. This cannot "
235+
"be changed with this option.">,
226236
];
227237
let constructor = "mlir::bufferization::createOwnershipBasedBufferDeallocationPass()";
228238

mlir/lib/Dialect/Arith/Transforms/BufferDeallocationOpInterfaceImpl.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,25 +53,26 @@ struct SelectOpInterface
5353
return op; // nothing to do
5454
}
5555

56-
std::pair<Value, Value>
57-
materializeUniqueOwnershipForMemref(Operation *op, DeallocationState &state,
58-
const DeallocationOptions &options,
59-
OpBuilder &builder, Value value) const {
56+
Value materializeUniqueOwnershipForMemref(Operation *op,
57+
DeallocationState &state,
58+
const DeallocationOptions &options,
59+
OpBuilder &builder,
60+
Value value) const {
6061
auto selectOp = cast<arith::SelectOp>(op);
6162
assert(value == selectOp.getResult() &&
6263
"Value not defined by this operation");
6364

6465
Block *block = value.getParentBlock();
6566
if (!state.getOwnership(selectOp.getTrueValue(), block).isUnique() ||
6667
!state.getOwnership(selectOp.getFalseValue(), block).isUnique())
67-
return state.getMemrefWithUniqueOwnership(builder, value,
68+
return state.getMemrefWithUniqueOwnership(options, builder, value,
6869
value.getParentBlock());
6970

7071
Value ownership = builder.create<arith::SelectOp>(
7172
op->getLoc(), selectOp.getCondition(),
7273
state.getOwnership(selectOp.getTrueValue(), block).getIndicator(),
7374
state.getOwnership(selectOp.getFalseValue(), block).getIndicator());
74-
return {selectOp.getResult(), ownership};
75+
return ownership;
7576
}
7677
};
7778

mlir/lib/Dialect/Bufferization/IR/BufferDeallocationOpInterface.cpp

Lines changed: 73 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -132,30 +132,79 @@ void DeallocationState::getLiveMemrefsIn(Block *block,
132132
memrefs.append(liveMemrefs);
133133
}
134134

135-
std::pair<Value, Value>
136-
DeallocationState::getMemrefWithUniqueOwnership(OpBuilder &builder,
137-
Value memref, Block *block) {
138-
auto iter = ownershipMap.find({memref, block});
139-
assert(iter != ownershipMap.end() &&
140-
"Value must already have been registered in the ownership map");
141-
142-
Ownership ownership = iter->second;
143-
if (ownership.isUnique())
144-
return {memref, ownership.getIndicator()};
145-
146-
// Instead of inserting a clone operation we could also insert a dealloc
147-
// operation earlier in the block and use the updated ownerships returned by
148-
// the op for the retained values. Alternatively, we could insert code to
149-
// check aliasing at runtime and use this information to combine two unique
150-
// ownerships more intelligently to not end up with an 'Unknown' ownership in
151-
// the first place.
152-
auto cloneOp =
153-
builder.create<bufferization::CloneOp>(memref.getLoc(), memref);
154-
Value condition = buildBoolValue(builder, memref.getLoc(), true);
155-
Value newMemref = cloneOp.getResult();
156-
updateOwnership(newMemref, condition);
157-
memrefsToDeallocatePerBlock[newMemref.getParentBlock()].push_back(newMemref);
158-
return {newMemref, condition};
135+
Value DeallocationState::getMemrefWithUniqueOwnership(
136+
const DeallocationOptions &options, OpBuilder &builder, Value memref,
137+
Block *block) {
138+
// NOTE: * if none of the operands have the same allocated pointer, a new
139+
// memref was allocated and thus the operation should have the allocate
140+
// side-effect defined on that result value and thus the correct unique
141+
// ownership is pre-populated by the ownership pass (unless an interface
142+
// implementation is incorrect).
143+
// * if exactly one operand has the same allocated pointer, this retunes
144+
// the ownership of exactly that operand
145+
// * if multiple operands match the allocated pointer of the result, the
146+
// ownership indicators of all of them always have to evaluate to the
147+
// same value because no dealloc operations may be present and because
148+
// of the rules they are passed to nested regions and successor blocks.
149+
// This could be verified at runtime by inserting `cf.assert`
150+
// operations, but would require O(|operands|^2) additional operations
151+
// to check and is thus not implemented yet (would need to insert a
152+
// library function to avoid code-size explosion which would make the
153+
// deallocation pass a module pass)
154+
auto ipSave = builder.saveInsertionPoint();
155+
SmallVector<Value> worklist;
156+
worklist.push_back(memref);
157+
158+
while (!worklist.empty()) {
159+
Value curr = worklist.back();
160+
Ownership ownership = getOwnership(curr, block);
161+
if (ownership.isUnique()) {
162+
worklist.pop_back();
163+
continue;
164+
}
165+
166+
Operation *defOp = curr.getDefiningOp();
167+
assert(defOp &&
168+
"the ownership-based deallocation pass should be written in a way "
169+
"that pre-populates ownership for block arguments");
170+
171+
bool allKnown = true;
172+
for (Value val : llvm::make_filter_range(defOp->getOperands(), isMemref)) {
173+
Ownership ownership = getOwnership(val, block);
174+
if (ownership.isUnique())
175+
continue;
176+
177+
worklist.push_back(val);
178+
allKnown = false;
179+
}
180+
181+
if (allKnown) {
182+
builder.setInsertionPointAfter(defOp);
183+
SmallVector<Value> operands(
184+
llvm::make_filter_range(defOp->getOperands(), isMemref));
185+
Value resultPtr = builder.create<memref::ExtractAlignedPointerAsIndexOp>(
186+
defOp->getLoc(), curr);
187+
Value ownership = getOwnership(operands.front(), block).getIndicator();
188+
189+
for (Value val : ArrayRef(operands).drop_front()) {
190+
Value operandPtr =
191+
builder.create<memref::ExtractAlignedPointerAsIndexOp>(
192+
defOp->getLoc(), val);
193+
Value isSameBuffer = builder.create<arith::CmpIOp>(
194+
defOp->getLoc(), arith::CmpIPredicate::eq, resultPtr, operandPtr);
195+
Value newOwnership = getOwnership(val, block).getIndicator();
196+
ownership = builder.create<arith::SelectOp>(
197+
defOp->getLoc(), isSameBuffer, newOwnership, ownership);
198+
}
199+
// Ownership is already 'Unknown', so we need to override instead of
200+
// joining.
201+
resetOwnerships(curr, block);
202+
updateOwnership(curr, ownership, block);
203+
}
204+
}
205+
206+
builder.restoreInsertionPoint(ipSave);
207+
return getOwnership(memref, block).getIndicator();
159208
}
160209

161210
void DeallocationState::getMemrefsToRetain(

mlir/lib/Dialect/Bufferization/Pipelines/BufferizationPipelines.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,34 @@
88

99
#include "mlir/Dialect/Bufferization/Pipelines/Passes.h"
1010

11+
#include "mlir/Dialect/Bufferization/IR/BufferDeallocationOpInterface.h"
1112
#include "mlir/Dialect/Bufferization/Transforms/Passes.h"
1213
#include "mlir/Dialect/Func/IR/FuncOps.h"
1314
#include "mlir/Dialect/MemRef/Transforms/Passes.h"
1415
#include "mlir/Pass/PassManager.h"
1516
#include "mlir/Transforms/Passes.h"
1617

18+
using namespace mlir;
19+
using namespace bufferization;
20+
1721
//===----------------------------------------------------------------------===//
1822
// Pipeline implementation.
1923
//===----------------------------------------------------------------------===//
2024

25+
DeallocationOptions
26+
BufferDeallocationPipelineOptions::asDeallocationOptions() const {
27+
DeallocationOptions opts;
28+
opts.privateFuncDynamicOwnership = privateFunctionDynamicOwnership.getValue();
29+
opts.verifyFunctionBoundaryABI = verifyFunctionBoundaryABI.getValue();
30+
return opts;
31+
}
32+
2133
void mlir::bufferization::buildBufferDeallocationPipeline(
2234
OpPassManager &pm, const BufferDeallocationPipelineOptions &options) {
2335
pm.addPass(memref::createExpandReallocPass(/*emitDeallocs=*/false));
2436
pm.addPass(createCanonicalizerPass());
2537
pm.addPass(createOwnershipBasedBufferDeallocationPass(
26-
options.privateFunctionDynamicOwnership.getValue()));
38+
options.asDeallocationOptions()));
2739
pm.addPass(createCanonicalizerPass());
2840
pm.addPass(createBufferDeallocationSimplificationPass());
2941
pm.addPass(createLowerDeallocationsPass());

mlir/lib/Dialect/Bufferization/Pipelines/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ add_mlir_dialect_library(MLIRBufferizationPipelines
55
${MLIR_MAIN_INCLUDE_DIR}/mlir/Dialect/Bufferization
66

77
LINK_LIBS PUBLIC
8+
MLIRBufferizationDialect
89
MLIRBufferizationTransforms
910
MLIRMemRefTransforms
1011
MLIRFuncDialect

mlir/lib/Dialect/Bufferization/Transforms/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ add_mlir_dialect_library(MLIRBufferizationTransforms
2626
LINK_LIBS PUBLIC
2727
MLIRArithDialect
2828
MLIRBufferizationDialect
29+
MLIRControlFlowDialect
2930
MLIRControlFlowInterfaces
3031
MLIRFuncDialect
3132
MLIRFunctionInterfaces

0 commit comments

Comments
 (0)