Skip to content

[mlir][bufferization] Ownership-based deallocation: Allow manual (de)allocs #68648

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions mlir/include/mlir/Dialect/Bufferization/IR/BufferizationBase.td
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,17 @@ def Bufferization_Dialect : Dialect {
/// arguments during One-Shot Module Bufferize.
constexpr const static ::llvm::StringLiteral
kBufferLayoutAttrName = "bufferization.buffer_layout";

/// An attribute that can be attached to ops with an allocation and/or
/// deallocation side effect. It indicates that the op is under a "manual
/// deallocation" scheme. In the case of an allocation op, the returned
/// value is *not* an automatically managed allocation and assigned an
/// ownership of "false". Furthermore, only deallocation ops that are
/// guaranteed to deallocate a buffer under "manual deallocation" are
/// allowed to have this attribute. (Deallocation ops without this
/// attribute are rejected by the ownership-based buffer deallocation pass.)
constexpr const static ::llvm::StringLiteral
kManualDeallocation = "bufferization.manual_deallocation";
}];
let hasOperationAttrVerify = 1;
}
Expand Down
20 changes: 20 additions & 0 deletions mlir/lib/Dialect/Bufferization/IR/BufferizationDialect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ constexpr const ::llvm::StringLiteral BufferizationDialect::kWritableAttrName;
constexpr const ::llvm::StringLiteral
BufferizationDialect::kBufferLayoutAttrName;

/// An attribute that can be attached to ops with an allocation and/or
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want to repeat the doc in both td and cpp ?

/// deallocation side effect. It indicates that the op is under a "manual
/// deallocation" scheme. In the case of an allocation op, the returned
/// value is *not* an automatically managed allocation and assigned an
/// ownership of "false". Furthermore, only deallocation ops that are
/// guaranteed to deallocate a buffer under "manual deallocation" are
/// allowed to have this attribute. (Deallocation ops without this
/// attribute are rejected by the ownership-based buffer deallocation pass.)
constexpr const ::llvm::StringLiteral BufferizationDialect::kManualDeallocation;

//===----------------------------------------------------------------------===//
// Bufferization Dialect Interfaces
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -105,6 +115,16 @@ BufferizationDialect::verifyOperationAttribute(Operation *op,
NamedAttribute attr) {
using bufferization::BufferizableOpInterface;

if (attr.getName() == kManualDeallocation) {
if (!mlir::hasEffect<MemoryEffects::Allocate>(op) &&
!mlir::hasEffect<MemoryEffects::Free>(op))
return op->emitOpError("attribute '")
<< kManualDeallocation
<< "' can be used only on ops that have an allocation and/or free "
"side effect";
return success();
}

return op->emitError()
<< "attribute '" << attr.getName()
<< "' not supported as an op attribute by the bufferization dialect";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "mlir/Dialect/Bufferization/IR/BufferDeallocationOpInterface.h"
#include "mlir/Dialect/Bufferization/IR/Bufferization.h"
#include "mlir/Dialect/Bufferization/Transforms/Passes.h"
#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/Dialect/SCF/IR/SCF.h"
Expand Down Expand Up @@ -856,13 +857,32 @@ FailureOr<Operation *> BufferDeallocation::handleInterface(CallOpInterface op) {
FailureOr<Operation *>
BufferDeallocation::handleInterface(MemoryEffectOpInterface op) {
auto *block = op->getBlock();
OpBuilder builder = OpBuilder::atBlockBegin(block);

for (auto operand : llvm::make_filter_range(op->getOperands(), isMemref))
if (op.getEffectOnValue<MemoryEffects::Free>(operand).has_value())
return op->emitError(
"memory free side-effect on MemRef value not supported!");
for (auto operand : llvm::make_filter_range(op->getOperands(), isMemref)) {
if (op.getEffectOnValue<MemoryEffects::Free>(operand).has_value()) {
if (!op->hasAttr(BufferizationDialect::kManualDeallocation))
return op->emitError(
"memory free side-effect on MemRef value not supported!");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite get this message here? It does not seems to relate to the conditions that lead here, or am I missing something?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is one of the things that confused me in #72289.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The buffer deallocation pass fails if there are any "free" side effects (memory deallocations) in the input IR. We cannot handle such input programs at the moment.

There is one exception: Existing buffer deallocation ops are allowed if they are annotated with bufferization.manual_deallocation. The corresponding allocation must then also be annotated with that attribute. The buffer deallocation pass can completely ignore such buffers. (Internally, they are assigned an ownership indicator of "false".)

In essence, bufferization.manual_deallocation is a way exclude parts of the input IR from the buffer deallocation pass.

The code here checks if the op has a free side effect. If that is the case and there is no manual_deallocation attribute on the op, we reject the IR.


// Buffers that were allocated under "manual deallocation" may be
// manually deallocated. We insert a runtime assertion to cover certain
// cases of invalid IR where an automatically managed buffer allocation
// is manually deallocated. This is not a bulletproof check!
OpBuilder::InsertionGuard g(builder);
builder.setInsertionPoint(op);
Ownership ownership = state.getOwnership(operand, block);
if (ownership.isUnique()) {
Value ownershipInverted = builder.create<arith::XOrIOp>(
op.getLoc(), ownership.getIndicator(),
buildBoolValue(builder, op.getLoc(), true));
builder.create<cf::AssertOp>(
op.getLoc(), ownershipInverted,
"expected that the block does not have ownership");
}
}
}

OpBuilder builder = OpBuilder::atBlockBegin(block);
for (auto res : llvm::make_filter_range(op->getResults(), isMemref)) {
auto allocEffect = op.getEffectOnValue<MemoryEffects::Allocate>(res);
if (allocEffect.has_value()) {
Expand All @@ -880,6 +900,15 @@ BufferDeallocation::handleInterface(MemoryEffectOpInterface op) {
continue;
}

if (op->hasAttr(BufferizationDialect::kManualDeallocation)) {
// This allocation will be deallocated manually. Assign an ownership of
// "false", so that it will never be deallocated by the buffer
// deallocation pass.
state.resetOwnerships(res, block);
state.updateOwnership(res, buildBoolValue(builder, op.getLoc(), false));
continue;
}

state.updateOwnership(res, buildBoolValue(builder, op.getLoc(), true));
state.addMemrefToDeallocate(res, block);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,35 @@ func.func @op_without_aliasing_and_allocation() -> memref<4xf32> {
// CHECK: [[CLONE:%.+]] = bufferization.clone [[GLOBAL]]
// CHECK: scf.yield [[CLONE]] :
// CHECK: return [[RES]] :

// -----

// Allocations with "bufferization.manual_deallocation" are assigned an
// ownership of "false".

func.func @manual_deallocation(%c: i1, %f: f32, %idx: index) -> f32 {
%0 = memref.alloc() {bufferization.manual_deallocation} : memref<5xf32>
linalg.fill ins(%f : f32) outs(%0 : memref<5xf32>)
%1 = memref.alloc() : memref<5xf32>
linalg.fill ins(%f : f32) outs(%1 : memref<5xf32>)
%2 = arith.select %c, %0, %1 : memref<5xf32>
%3 = memref.load %2[%idx] : memref<5xf32>

// Only buffers that are under "manual deallocation" are allowed to be
// deallocated with memref.dealloc. For consistency reasons, the
// manual_deallocation attribute must also be specified. A runtime insertion
// is inserted to ensure that we do not have ownership. (This is not a
// bulletproof check, but covers some cases of invalid IR.)
memref.dealloc %0 {bufferization.manual_deallocation} : memref<5xf32>

return %3 : f32
}

// CHECK-LABEL: func @manual_deallocation(
// CHECK: %[[true:.*]] = arith.constant true
// CHECK: %[[manual_alloc:.*]] = memref.alloc() {bufferization.manual_deallocation} : memref<5xf32>
// CHECK: %[[managed_alloc:.*]] = memref.alloc() : memref<5xf32>
// CHECK: %[[selected:.*]] = arith.select
// CHECK: cf.assert %[[true]], "expected that the block does not have ownership"
// CHECK: memref.dealloc %[[manual_alloc]]
// CHECK: bufferization.dealloc (%[[managed_alloc]] : memref<5xf32>) if (%[[true]])
7 changes: 7 additions & 0 deletions mlir/test/Dialect/Bufferization/invalid.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,10 @@ func.func @invalid_dealloc_wrong_number_of_results(%arg0: memref<2xf32>, %arg1:
%0:3 = "bufferization.dealloc"(%arg0, %arg1, %arg2, %arg2, %arg1) <{operandSegmentSizes = array<i32: 2, 2, 1>}> : (memref<2xf32>, memref<4xi32>, i1, i1, memref<4xi32>) -> (i1, i1, i1)
return %0#0 : i1
}

// -----

func.func @invalid_manual_deallocation() {
// expected-error @below{{op attribute 'bufferization.manual_deallocation' can be used only on ops that have an allocation and/or free side effect}}
arith.constant {bufferization.manual_deallocation} 0 : index
}