[mlir][linalg][bufferize] CallOps do not bufferize to memory writes

However, since CallOps have no aliasing OpResults, their OpOperands always bufferize out-of-place.

This change removes `bufferizesToMemoryWrite` from `CallOpInterface`. This method was called, but its return value did not matter.

Differential Revision: https://reviews.llvm.org/D114616
This commit is contained in:
Matthias Springer 2021-12-01 18:45:27 +09:00
parent ec97e1206a
commit 2fd0ea960c
5 changed files with 49 additions and 11 deletions

View File

@ -42,9 +42,16 @@ def BufferizableOpInterface : OpInterface<"BufferizableOpInterface"> {
InterfaceMethod<
/*desc=*/[{
Return `true` if the given OpOperand bufferizes to a memory write.
This method will never be called on OpOperands that do not have a
tensor type.
This method will never be called on OpOperands that do not have an
aliasing OpResult. Intuitively, it does not make sense for an
OpOperand to bufferize to a memory write without returning an aliasing
tensor, because the write would have no visible effect outside of the
op.
Note: It is always safe to consider an OpOperand as a memory write,
even if it does actually not write; however, this can introduce
unnecessary out-of-place bufferization decisions. The analysis of
@ -57,6 +64,8 @@ def BufferizableOpInterface : OpInterface<"BufferizableOpInterface"> {
/*methodBody=*/"",
/*defaultImplementation=*/[{
// Does not have to be implemented for ops without tensor OpOperands.
// Does not have to be implemented for OpOperands that do not have an
// aliasing OpResult.
llvm_unreachable("bufferizesToMemoryWrite not implemented");
}]
>,

View File

@ -239,11 +239,15 @@ static std::string printValueInfo(Value value, bool prefix) {
/// Return true if opOperand has been decided to bufferize in-place.
static bool isInplaceMemoryWrite(OpOperand &opOperand,
const BufferizationAliasInfo &aliasInfo) {
// Ops that do not bufferize to a memory write, cannot be write in-place.
// OpOperands without an aliasing OpResult do not write.
OpResult opResult = getAliasingOpResult(opOperand);
if (!opResult)
return false;
// OpOperands that do not bufferize to a memory write do not write in-place.
if (!bufferizesToMemoryWrite(opOperand))
return false;
OpResult opResult = getAliasingOpResult(opOperand);
return opResult && aliasInfo.isInPlace(opResult);
// Check current bufferization decisions.
return aliasInfo.isInPlace(opResult);
}
/// Return true if, under current bufferization decisions, the buffer of `value`

View File

@ -447,14 +447,6 @@ struct CallOpInterface
return true;
}
bool bufferizesToMemoryWrite(Operation *op, OpOperand &opOperand) const {
// CallOpInterface alone doesn't bufferize to a memory write, one of the
// uses of the matching bbArg may. It is the responsibility of the caller to
// inspect bbArgs. In the absence of a BufferizationAliasInfo, we need to be
// conservative.
return true;
}
SmallVector<OpOperand *> getAliasingOpOperand(Operation *op,
OpResult opResult) const {
// TODO: Can we do better?

View File

@ -44,6 +44,17 @@ struct ExecuteRegionOpInterface
return true;
}
// TODO: For better bufferization results, this could return `true` only if
// there is a memory write in the region.
bool isMemoryWrite(Operation *op, OpResult opResult) const {
// Similar to scf.if, results of this op are always considered memory writes
// in the analysis. This is a useful pattern for all ops that have tensor
// OpResults but no tensor OpOperands. By default, `isMemoryWrite` is
// implemented in terms of `bufferizesToMemoryWrite`, which does not work on
// ops without OpOperands.
return true;
}
LogicalResult bufferize(Operation *op, OpBuilder &b,
BufferizationState &state) const {
// TODO: Add bufferization support when needed. scf.execute_region should be

View File

@ -1470,3 +1470,25 @@ func @scf_if_out_of_place3(%t1: tensor<?xf32> {linalg.inplaceable = true},
return %r, %v2 : tensor<?xf32>, vector<10xf32>
}
// -----
// CHECK-LABEL: func @some_use
func @some_use(%A : tensor<?xf32> {linalg.inplaceable = true},
%v : vector<5xf32>) -> (tensor<?xf32>) {
%idx = arith.constant 0 : index
// CHECK: vector.transfer_write
// CHECK-SAME: {__inplace_results_attr__ = ["true"]
%0 = vector.transfer_write %v, %A[%idx] : vector<5xf32>, tensor<?xf32>
return %0 : tensor<?xf32>
}
// CHECK-LABEL: func @main_func
func @main_func(%A : tensor<?xf32> {linalg.inplaceable = true},
%v : vector<5xf32>) -> (tensor<?xf32>) {
// Function calls always bufferize out-of-place at the moment.
// CHECK: call
// CHECK-SAME: {__inplace_results_attr__ = ["false"]
%0 = call @some_use(%A, %v) : (tensor<?xf32>, vector<5xf32>) -> (tensor<?xf32>)
return %0 : tensor<?xf32>
}