This revision is needed to support bufferization of `cf.br`/`cf.cond_br`. It will also be useful for better analysis of loop ops. This revision generalizes `getAliasingOpResults` to `getAliasingValues`. An OpOperand can now not only alias with OpResults but also with BlockArguments. In the case of `cf.br` (will be added in a later revision): a `cf.br` operand will alias with the corresponding argument of the destination block. If an op does not implement the `BufferizableOpInterface`, the analysis in conservative. It previously assumed that an OpOperand may alias with each OpResult. It now assumes that an OpOperand may alias with each OpResult and each BlockArgument of the entry block. Differential Revision: https://reviews.llvm.org/D157957
161 lines
5.9 KiB
MLIR
161 lines
5.9 KiB
MLIR
// RUN: mlir-opt %s -one-shot-bufferize="test-analysis-only" \
|
|
// RUN: -allow-unregistered-dialect -split-input-file | FileCheck %s
|
|
|
|
// RUN: mlir-opt %s -one-shot-bufferize="test-analysis-only dump-alias-sets" \
|
|
// RUN: -allow-unregistered-dialect -split-input-file | \
|
|
// RUN: FileCheck %s --check-prefix=CHECK-ALIAS-SETS
|
|
|
|
// CHECK-LABEL: func @unknown_op_aliasing(
|
|
// CHECK-ALIAS-SETS-LABEL: func @unknown_op_aliasing(
|
|
func.func @unknown_op_aliasing(%f: f32, %f2: f32, %pos: index) -> f32 {
|
|
// CHECK-ALIAS-SETS: %[[empty:.*]] = tensor.empty
|
|
|
|
%0 = tensor.empty() : tensor<10xf32>
|
|
// CHECK: linalg.fill {__inplace_operands_attr__ = ["none", "true"]}
|
|
// CHECK-ALIAS-SETS: %[[fill1:.*]] = linalg.fill
|
|
%1 = linalg.fill ins(%f : f32) outs(%0 : tensor<10xf32>) -> tensor<10xf32>
|
|
|
|
// Something must bufferize out-of-place because the op may return an alias
|
|
// of %1.
|
|
// CHECK: "dummy.dummy_op"(%{{.*}}) {__inplace_operands_attr__ = ["false"]}
|
|
%alias = "dummy.dummy_op"(%1) : (tensor<10xf32>) -> (tensor<10xf32>)
|
|
|
|
// CHECK: linalg.fill {__inplace_operands_attr__ = ["none", "true"]}
|
|
// CHECK-ALIAS-SETS: %[[fill2:.*]] = linalg.fill
|
|
// CHECK-ALIAS-SETS-SAME: __opresult_alias_set_attr__ = [{{\[}}"%[[fill2]]", "%[[fill1]]", "%[[empty]]"]]
|
|
%2 = linalg.fill ins(%f2 : f32) outs(%1 : tensor<10xf32>) -> tensor<10xf32>
|
|
%3 = tensor.extract %alias[%pos] : tensor<10xf32>
|
|
return %3 : f32
|
|
}
|
|
|
|
// -----
|
|
|
|
// CHECK-LABEL: func @unknown_op_bbarg_aliasing(
|
|
// CHECK-ALIAS-SETS-LABEL: func @unknown_op_bbarg_aliasing(
|
|
func.func @unknown_op_bbarg_aliasing() {
|
|
%0 = tensor.empty() : tensor<7xf32>
|
|
|
|
// %arg0 is not aliasing with %0 because it bufferizes out-of-place.
|
|
// CHECK-ALIAS-SETS: "dummy.dummy_op"
|
|
// CHECK-ALIAS-SETS-NEXT: ^{{.*}}(%[[arg:.*]]: tensor<7xf32>):
|
|
// CHECK-ALIAS-SETS-NEXT: }) {__bbarg_alias_set_attr__ = [{{\[}}[{{\[}}"%[[arg]]"]]]], __inplace_operands_attr__ = ["false"]} : (tensor<7xf32>) -> ()
|
|
"dummy.dummy_op"(%0) ({
|
|
^bb0(%arg1: tensor<7xf32>):
|
|
}) : (tensor<7xf32>) -> ()
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
// CHECK-LABEL: func @unknown_op_writing(
|
|
func.func @unknown_op_writing(%f: f32, %f2: f32, %pos: index) -> f32 {
|
|
%0 = tensor.empty() : tensor<10xf32>
|
|
// CHECK: linalg.fill {__inplace_operands_attr__ = ["none", "true"]}
|
|
%1 = linalg.fill ins(%f : f32) outs(%0 : tensor<10xf32>) -> tensor<10xf32>
|
|
|
|
// The op may bufferize to a memory write, so it must bufferize out-of-place.
|
|
// CHECK: "dummy.dummy_op"(%{{.*}}) {__inplace_operands_attr__ = ["false"]}
|
|
"dummy.dummy_op"(%1) : (tensor<10xf32>) -> ()
|
|
|
|
%3 = tensor.extract %1[%pos] : tensor<10xf32>
|
|
return %3 : f32
|
|
}
|
|
|
|
// -----
|
|
|
|
// CHECK-LABEL: func @read_of_undef_is_not_a_conflict(
|
|
func.func @read_of_undef_is_not_a_conflict(%f: f32, %idx: index) -> f32 {
|
|
%0 = tensor.empty() : tensor<10xf32>
|
|
// This can be in-place because the read below does reads undefined data.
|
|
// CHECK: tensor.insert {{.*}} {__inplace_operands_attr__ = ["none", "true", "none"]}
|
|
%1 = tensor.insert %f into %0[%idx] : tensor<10xf32>
|
|
%2 = tensor.extract %0[%idx] : tensor<10xf32>
|
|
return %2 : f32
|
|
}
|
|
|
|
// -----
|
|
|
|
// CHECK-LABEL: func @read_of_alloc_tensor_is_not_a_conflict(
|
|
func.func @read_of_alloc_tensor_is_not_a_conflict(%f: f32, %idx: index) -> f32 {
|
|
%0 = bufferization.alloc_tensor() : tensor<10xf32>
|
|
// This can be in-place because the read below does reads undefined data.
|
|
// CHECK: tensor.insert {{.*}} {__inplace_operands_attr__ = ["none", "true", "none"]}
|
|
%1 = tensor.insert %f into %0[%idx] : tensor<10xf32>
|
|
%2 = tensor.extract %0[%idx] : tensor<10xf32>
|
|
return %2 : f32
|
|
}
|
|
|
|
// -----
|
|
|
|
// CHECK-LABEL: func @to_memref_not_read_only(
|
|
func.func @to_memref_not_read_only(%idx : index, %f: f32) -> f32 {
|
|
%t = tensor.generate {
|
|
^bb0(%i : index):
|
|
tensor.yield %f : f32
|
|
} : tensor<5xf32>
|
|
// Some op may write into the result of to_memref later.
|
|
// CHECK: bufferization.to_memref
|
|
// CHECK-SAME: {__inplace_operands_attr__ = ["false"]}
|
|
%m = bufferization.to_memref %t : memref<5xf32>
|
|
%2 = tensor.extract %t[%idx] : tensor<5xf32>
|
|
return %2 : f32
|
|
}
|
|
|
|
// -----
|
|
|
|
// CHECK-LABEL: func @to_memref_read_only(
|
|
func.func @to_memref_read_only(%idx : index, %f: f32) -> f32 {
|
|
%t = tensor.generate {
|
|
^bb0(%i : index):
|
|
tensor.yield %f : f32
|
|
} : tensor<5xf32>
|
|
// Some op may write into the result of to_memref later.
|
|
// CHECK: bufferization.to_memref
|
|
// CHECK-SAME: {__inplace_operands_attr__ = ["true"]}
|
|
%m = bufferization.to_memref %t {read_only} : memref<5xf32>
|
|
%2 = tensor.extract %t[%idx] : tensor<5xf32>
|
|
return %2 : f32
|
|
}
|
|
|
|
// -----
|
|
|
|
// CHECK-LABEL: func @bbarg_of_unknown_op(
|
|
func.func @bbarg_of_unknown_op(%f: f32) {
|
|
%0 = tensor.empty() : tensor<10xf32>
|
|
// CHECK: linalg.fill {__inplace_operands_attr__ = ["none", "true"]}
|
|
%1 = linalg.fill ins(%f : f32) outs(%0 : tensor<10xf32>) -> tensor<10xf32>
|
|
|
|
// The op is not bufferizable because %1 is assumed to alias with %arg1.
|
|
// BlockArguments are considered "not writable" by default. So %1 is also
|
|
// considered "not writable".
|
|
|
|
// CHECK: "dummy.dummy_op"
|
|
// CHECK: {__inplace_operands_attr__ = ["false"]} : (tensor<10xf32>) -> ()
|
|
"dummy.dummy_op"(%1) ({
|
|
^bb0(%arg1: tensor<10xf32>):
|
|
}) : (tensor<10xf32>) -> ()
|
|
return
|
|
}
|
|
|
|
// -----
|
|
|
|
// CHECK-LABEL: func @bbarg_of_unknown_op_2(
|
|
func.func @bbarg_of_unknown_op_2(%f: f32) {
|
|
%0 = tensor.empty() : tensor<10xf32>
|
|
// CHECK: linalg.fill {__inplace_operands_attr__ = ["none", "true"]}
|
|
%1 = linalg.fill ins(%f : f32) outs(%0 : tensor<10xf32>) -> tensor<10xf32>
|
|
|
|
// The op is not bufferizable because %1 is assumed to alias with %arg1.
|
|
// BlockArguments are considered "not writable" by default. So %1 is also
|
|
// considered "not writable".
|
|
|
|
// CHECK: "dummy.dummy_op"
|
|
"dummy.dummy_op"(%1) ({
|
|
^bb0(%arg1: tensor<10xf32>):
|
|
// CHECK: "dummy.another_op"(%{{.*}}) {__inplace_operands_attr__ = ["false"]}
|
|
"dummy.another_op"(%arg1) : (tensor<10xf32>) -> ()
|
|
}) : (tensor<10xf32>) -> ()
|
|
// CHECK: {__inplace_operands_attr__ = ["false"]} : (tensor<10xf32>) -> ()
|
|
return
|
|
}
|