Skip to content

Commit 6a651c7

Browse files
committed
Revert "[mlir][bufferization] Don't clone on unknown ownership and verify function boundary ABI (#66626)"
This reverts commit aa9eb47. It introduced a double free in a test case. Reverting to have some time for fixing this and relanding later.
1 parent 3214f7b commit 6a651c7

24 files changed

+243
-321
lines changed

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

Lines changed: 7 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -95,20 +95,7 @@ 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 = 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 also ensures that the returned memref does not
103-
/// originate from the same allocation as a function argument.
104-
/// Note: The function boundary ABI is disabled for non-external private
105-
/// functions if `privateFuncDynamicOwnership` is enabled and thus this option
106-
/// does not apply to them.
107-
/// TODO: check that returned MemRefs don't alias each other.
108-
/// If it can be determined statically that the ABI is not adhered
109-
/// to, an error will already be emitted at compile time. This cannot be
110-
/// changed with this option.
111-
bool verifyFunctionBoundaryABI = true;
98+
bool privateFuncDynamicOwnership = false;
11299
};
113100

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

153140
/// Given an SSA value of MemRef type, this function queries the ownership and
154-
/// if it is not already in the 'Unique' state, potentially inserts IR to
155-
/// determine the ownership (which might involve expensive aliasing checks at
156-
/// runtime).
157-
Value materializeMemRefOwnership(const DeallocationOptions &options,
158-
OpBuilder &builder, Value memref,
159-
Block *block);
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);
160147

161148
/// Given two basic blocks and the values passed via block arguments to the
162149
/// destination block, compute the list of MemRefs that have to be retained in
@@ -233,16 +220,6 @@ FailureOr<Operation *>
233220
insertDeallocOpForReturnLike(DeallocationState &state, Operation *op,
234221
ValueRange operands,
235222
SmallVectorImpl<Value> &updatedOperandOwnerships);
236-
237-
/// Materializes IR that extracts the allocated pointers of the MemRef operands
238-
/// of the defining operation of `memref` as indices and compares them. The
239-
/// ownership of the first one that matches is returned and intended to be
240-
/// assigned to `memref`.
241-
Value defaultComputeMemRefOwnership(const DeallocationOptions &options,
242-
DeallocationState &state,
243-
OpBuilder &builder, Value memref,
244-
Block *block);
245-
246223
} // namespace deallocation_impl
247224

248225
} // namespace bufferization

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

Lines changed: 3 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -55,47 +55,17 @@ 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-
* passing them as additional operands to nested regions (e.g.,
61-
init_args of `scf.for`)
62-
* passing them as additional operands to a call operation when
63-
`private-function-dynamic-ownership` is enabled
64-
65-
In the following example, the deallocation pass would add an
66-
additional block argument to `^bb1` for passing the ownership of `%0`
67-
along and thus the ownership indicator has to be materialized before
68-
the `cf.br` operation and added as a forwarded operand.
69-
```mlir
70-
%0 = arith.select %cond, %m1, %m2 : memref<f32>
71-
cf.br ^bb1(%0 : memref<f32>)
72-
^bb1(%arg0: memref<f32>)
73-
...
74-
```
75-
The `arith.select` operation could implement this interface method to
76-
materialize another `arith.select` operation to select the
77-
corresponding ownership indicator.
78-
```mlir
79-
%0 = arith.select %cond, %m1, %m2 : memref<f32>
80-
%0_ownership = arith.select %cond, %m1_ownership, %m2_ownership : i1
81-
cf.br ^bb1(%0, %0_ownership : memref<f32>, i1)
82-
^bb1(%arg0: memref<f32>, %arg1: i1)
83-
...
84-
```
85-
86-
The default implementation assumes that all MemRef operands already
87-
have 'Unique' ownership.
8858
}],
89-
/*retType=*/"Value",
59+
/*retType=*/"std::pair<Value, Value>",
9060
/*methodName=*/"materializeUniqueOwnershipForMemref",
9161
/*args=*/(ins "DeallocationState &":$state,
9262
"const DeallocationOptions &":$options,
9363
"OpBuilder &":$builder,
9464
"Value":$memref),
9565
/*methodBody=*/[{}],
9666
/*defaultImplementation=*/[{
97-
return deallocation_impl::defaultComputeMemRefOwnership(
98-
options, state, builder, memref, memref.getParentBlock());
67+
return state.getMemrefWithUniqueOwnership(
68+
builder, memref, memref.getParentBlock());
9969
}]>,
10070
];
10171
}

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

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

1818
namespace mlir {
1919
namespace bufferization {
20-
struct DeallocationOptions;
2120

2221
/// Options for the buffer deallocation pipeline.
2322
struct BufferDeallocationPipelineOptions
@@ -28,23 +27,7 @@ struct BufferDeallocationPipelineOptions
2827
"Allows to add additional arguments to private functions to "
2928
"dynamically pass ownership of memrefs to callees. This can enable "
3029
"earlier deallocations."),
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;
30+
llvm::cl::init(false)};
4831
};
4932

5033
//===----------------------------------------------------------------------===//

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
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"
54
#include "mlir/Pass/Pass.h"
65

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

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

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

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

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

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -219,20 +219,10 @@ def OwnershipBasedBufferDeallocation : Pass<
219219
}];
220220
let options = [
221221
Option<"privateFuncDynamicOwnership", "private-function-dynamic-ownership",
222-
"bool", /*default=*/"true",
222+
"bool", /*default=*/"false",
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.">,
236226
];
237227
let constructor = "mlir::bufferization::createOwnershipBasedBufferDeallocationPass()";
238228

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

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

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

6564
Block *block = value.getParentBlock();
65+
if (!state.getOwnership(selectOp.getTrueValue(), block).isUnique() ||
66+
!state.getOwnership(selectOp.getFalseValue(), block).isUnique())
67+
return state.getMemrefWithUniqueOwnership(builder, value,
68+
value.getParentBlock());
69+
6670
Value ownership = builder.create<arith::SelectOp>(
6771
op->getLoc(), selectOp.getCondition(),
6872
state.getOwnership(selectOp.getTrueValue(), block).getIndicator(),
6973
state.getOwnership(selectOp.getFalseValue(), block).getIndicator());
70-
return ownership;
74+
return {selectOp.getResult(), ownership};
7175
}
7276
};
7377

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

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

135-
Value DeallocationState::materializeMemRefOwnership(
136-
const DeallocationOptions &options, OpBuilder &builder, Value memref,
137-
Block *block) {
138-
// NOTE: Starts at the operation defining `memref` and performs a DFS along
139-
// the reverse def/use chain until MemRef values with 'Unique' ownership are
140-
// found. For the operation being currently processed:
141-
// * if none of the operands have the same allocated pointer (i.e., originate
142-
// from the same allocation), a new memref was allocated and thus the
143-
// operation should have the allocate side-effect defined on that result
144-
// value and thus the correct unique ownership is pre-populated by the
145-
// ownership pass (unless an interface implementation is incorrect). Note
146-
// that this is problematic for operations of unregistered dialects because
147-
// the allocation side-effect cannot be represented in the assembly format.
148-
// * if exactly one operand has the same allocated pointer, this returnes the
149-
// ownership of exactly that operand
150-
// * if multiple operands match the allocated pointer of the result, the
151-
// ownership indicators of all of them always have to evaluate to the same
152-
// value because no dealloc operations may be present and because of the
153-
// rules they are passed to nested regions and successor blocks. This could
154-
// be verified at runtime by inserting `cf.assert` operations, but would
155-
// require O(|operands|^2) additional operations to check and is thus not
156-
// implemented yet (would need to insert a library function to avoid
157-
// code-size explosion which would make the deallocation pass a module pass)
158-
auto ipSave = builder.saveInsertionPoint();
159-
SmallVector<Value> worklist;
160-
worklist.push_back(memref);
161-
162-
while (!worklist.empty()) {
163-
Value curr = worklist.back();
164-
165-
// If the value already has unique ownership, we don't have to process it
166-
// anymore.
167-
Ownership ownership = getOwnership(curr, block);
168-
if (ownership.isUnique()) {
169-
worklist.pop_back();
170-
continue;
171-
}
172-
173-
// Check if all operands of MemRef type have unique ownership.
174-
Operation *defOp = curr.getDefiningOp();
175-
assert(defOp &&
176-
"the ownership-based deallocation pass should be written in a way "
177-
"that pre-populates ownership for block arguments");
178-
179-
bool allKnown = true;
180-
for (Value val : llvm::make_filter_range(defOp->getOperands(), isMemref)) {
181-
Ownership ownership = getOwnership(val, block);
182-
if (ownership.isUnique())
183-
continue;
184-
185-
worklist.push_back(val);
186-
allKnown = false;
187-
}
188-
189-
// If all MemRef operands have unique ownership, we can check if the op
190-
// implements the BufferDeallocationOpInterface and call that or, otherwise,
191-
// we call the generic implementation manually here.
192-
if (allKnown) {
193-
builder.setInsertionPointAfter(defOp);
194-
if (auto deallocInterface =
195-
dyn_cast<BufferDeallocationOpInterface>(defOp);
196-
deallocInterface && curr.getParentBlock() == block)
197-
ownership = deallocInterface.materializeUniqueOwnershipForMemref(
198-
*this, options, builder, curr);
199-
else
200-
ownership = deallocation_impl::defaultComputeMemRefOwnership(
201-
options, *this, builder, curr, block);
202-
203-
// Ownership is already 'Unknown', so we need to override instead of
204-
// joining.
205-
resetOwnerships(curr, block);
206-
updateOwnership(curr, ownership, block);
207-
}
208-
}
209-
210-
builder.restoreInsertionPoint(ipSave);
211-
return getOwnership(memref, block).getIndicator();
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};
212159
}
213160

214161
void DeallocationState::getMemrefsToRetain(
@@ -366,25 +313,3 @@ FailureOr<Operation *> deallocation_impl::insertDeallocOpForReturnLike(
366313

367314
return op;
368315
}
369-
370-
Value deallocation_impl::defaultComputeMemRefOwnership(
371-
const DeallocationOptions &options, DeallocationState &state,
372-
OpBuilder &builder, Value memref, Block *block) {
373-
Operation *defOp = memref.getDefiningOp();
374-
SmallVector<Value> operands(
375-
llvm::make_filter_range(defOp->getOperands(), isMemref));
376-
Value resultPtr = builder.create<memref::ExtractAlignedPointerAsIndexOp>(
377-
defOp->getLoc(), memref);
378-
Value ownership = state.getOwnership(operands.front(), block).getIndicator();
379-
380-
for (Value val : ArrayRef(operands).drop_front()) {
381-
Value operandPtr = builder.create<memref::ExtractAlignedPointerAsIndexOp>(
382-
defOp->getLoc(), val);
383-
Value isSameBuffer = builder.create<arith::CmpIOp>(
384-
defOp->getLoc(), arith::CmpIPredicate::eq, resultPtr, operandPtr);
385-
Value newOwnership = state.getOwnership(val, block).getIndicator();
386-
ownership = builder.create<arith::SelectOp>(defOp->getLoc(), isSameBuffer,
387-
newOwnership, ownership);
388-
}
389-
return ownership;
390-
}

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

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

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

11-
#include "mlir/Dialect/Bufferization/IR/BufferDeallocationOpInterface.h"
1211
#include "mlir/Dialect/Bufferization/Transforms/Passes.h"
1312
#include "mlir/Dialect/Func/IR/FuncOps.h"
1413
#include "mlir/Dialect/MemRef/Transforms/Passes.h"
1514
#include "mlir/Pass/PassManager.h"
1615
#include "mlir/Transforms/Passes.h"
1716

18-
using namespace mlir;
19-
using namespace bufferization;
20-
2117
//===----------------------------------------------------------------------===//
2218
// Pipeline implementation.
2319
//===----------------------------------------------------------------------===//
2420

25-
DeallocationOptions
26-
BufferDeallocationPipelineOptions::asDeallocationOptions() const {
27-
DeallocationOptions opts;
28-
opts.privateFuncDynamicOwnership = privateFunctionDynamicOwnership.getValue();
29-
opts.verifyFunctionBoundaryABI = verifyFunctionBoundaryABI.getValue();
30-
return opts;
31-
}
32-
3321
void mlir::bufferization::buildBufferDeallocationPipeline(
3422
OpPassManager &pm, const BufferDeallocationPipelineOptions &options) {
3523
pm.addPass(memref::createExpandReallocPass(/*emitDeallocs=*/false));
3624
pm.addPass(createCanonicalizerPass());
3725
pm.addPass(createOwnershipBasedBufferDeallocationPass(
38-
options.asDeallocationOptions()));
26+
options.privateFunctionDynamicOwnership.getValue()));
3927
pm.addPass(createCanonicalizerPass());
4028
pm.addPass(createBufferDeallocationSimplificationPass());
4129
pm.addPass(createLowerDeallocationsPass());

0 commit comments

Comments
 (0)