Files
clang-p2996/mlir/test/Dialect/Bufferization/Transforms/one-shot-module-bufferize-invalid.mlir
Matthias Springer 4002eaaa01 [mlir][bufferize] Improve analysis of external functions
External functions have no body, so they cannot be analyzed. Assume conservatively that each tensor bbArg may be aliasing with each tensor result. Furthermore, assume that each function arg is read and written-to after bufferization. This default behavior can be controlled with `bufferization.access` (similar to `bufferization.memory_layout`) in test cases.

Also fix a bug in the dialect attribute verifier, which did not run for region argument attributes.

Differential Revision: https://reviews.llvm.org/D139517
2022-12-09 14:36:33 +01:00

318 lines
10 KiB
MLIR

// RUN: mlir-opt %s -allow-unregistered-dialect -one-shot-bufferize="bufferize-function-boundaries=1" -split-input-file -verify-diagnostics
func.func private @foo() -> tensor<?xf32>
func.func @bar() -> tensor<?xf32> {
%foo = constant @foo : () -> (tensor<?xf32>)
// expected-error @+1 {{expected a CallOp}}
%res = call_indirect %foo() : () -> (tensor<?xf32>)
return %res : tensor<?xf32>
}
// -----
// expected-error @+2 {{cannot bufferize bodiless function that returns a tensor}}
// expected-error @+1 {{failed to bufferize op}}
func.func private @foo() -> tensor<?xf32>
// -----
// expected-error @+1 {{cannot bufferize a FuncOp with tensors and without a unique ReturnOp}}
func.func @swappy(%cond1 : i1, %cond2 : i1, %t1 : tensor<f32>, %t2 : tensor<f32>)
-> (tensor<f32>, tensor<f32>)
{
cf.cond_br %cond1, ^bb1, ^bb2
^bb1:
%T:2 = scf.if %cond2 -> (tensor<f32>, tensor<f32>) {
scf.yield %t1, %t2 : tensor<f32>, tensor<f32>
} else {
scf.yield %t2, %t1 : tensor<f32>, tensor<f32>
}
return %T#0, %T#1 : tensor<f32>, tensor<f32>
^bb2:
return %t2, %t1 : tensor<f32>, tensor<f32>
}
// -----
func.func @scf_if_not_equivalent(
%cond: i1, %t1: tensor<?xf32> {bufferization.writable = true},
%idx: index) -> tensor<?xf32> {
%r = scf.if %cond -> (tensor<?xf32>) {
scf.yield %t1 : tensor<?xf32>
} else {
// This buffer aliases, but it is not equivalent.
%t2 = tensor.extract_slice %t1 [%idx] [%idx] [1] : tensor<?xf32> to tensor<?xf32>
// expected-error @+1 {{operand #0 may return/yield a new buffer allocation}}
scf.yield %t2 : tensor<?xf32>
}
// expected-error @+1 {{operand #0 may return/yield a new buffer allocation}}
return %r : tensor<?xf32>
}
// -----
func.func @scf_if_not_aliasing(
%cond: i1, %t1: tensor<?xf32> {bufferization.writable = true},
%idx: index) -> f32 {
%r = scf.if %cond -> (tensor<?xf32>) {
scf.yield %t1 : tensor<?xf32>
} else {
// This buffer aliases.
%t2 = bufferization.alloc_tensor(%idx) : tensor<?xf32>
// expected-error @+1 {{operand #0 may return/yield a new buffer allocation}}
scf.yield %t2 : tensor<?xf32>
}
%f = tensor.extract %r[%idx] : tensor<?xf32>
return %f : f32
}
// -----
// expected-error @-3 {{expected callgraph to be free of circular dependencies}}
func.func @foo() {
call @bar() : () -> ()
return
}
func.func @bar() {
call @foo() : () -> ()
return
}
// -----
func.func @scf_for(%A : tensor<?xf32>,
%B : tensor<?xf32> {bufferization.writable = true},
%C : tensor<4xf32>,
%lb : index, %ub : index, %step : index)
-> (f32, f32)
{
%r0:2 = scf.for %i = %lb to %ub step %step iter_args(%tA = %A, %tB = %B)
-> (tensor<?xf32>, tensor<?xf32>)
{
%ttA = tensor.insert_slice %C into %tA[0][4][1] : tensor<4xf32> into tensor<?xf32>
%ttB = tensor.insert_slice %C into %tB[0][4][1] : tensor<4xf32> into tensor<?xf32>
// Throw a wrench in the system by swapping yielded values: this result in a
// ping-pong of values at each iteration on which we currently want to fail.
// expected-error @+1 {{Yield operand #0 is not equivalent to the corresponding iter bbArg}}
scf.yield %ttB, %ttA : tensor<?xf32>, tensor<?xf32>
}
%f0 = tensor.extract %r0#0[%step] : tensor<?xf32>
%f1 = tensor.extract %r0#1[%step] : tensor<?xf32>
return %f0, %f1: f32, f32
}
// -----
func.func @scf_while_non_equiv_condition(%arg0: tensor<5xi1>,
%arg1: tensor<5xi1>,
%idx: index) -> (i1, i1)
{
%r0, %r1 = scf.while (%w0 = %arg0, %w1 = %arg1)
: (tensor<5xi1>, tensor<5xi1>) -> (tensor<5xi1>, tensor<5xi1>) {
%condition = tensor.extract %w0[%idx] : tensor<5xi1>
// expected-error @+1 {{Condition arg #0 is not equivalent to the corresponding iter bbArg}}
scf.condition(%condition) %w1, %w0 : tensor<5xi1>, tensor<5xi1>
} do {
^bb0(%b0: tensor<5xi1>, %b1: tensor<5xi1>):
%pos = "dummy.some_op"() : () -> (index)
%val = "dummy.another_op"() : () -> (i1)
%1 = tensor.insert %val into %b0[%pos] : tensor<5xi1>
scf.yield %1, %b1 : tensor<5xi1>, tensor<5xi1>
}
%v0 = tensor.extract %r0[%idx] : tensor<5xi1>
%v1 = tensor.extract %r1[%idx] : tensor<5xi1>
return %v0, %v1 : i1, i1
}
// -----
func.func @scf_while_non_equiv_yield(%arg0: tensor<5xi1>,
%arg1: tensor<5xi1>,
%idx: index) -> (i1, i1)
{
%r0, %r1 = scf.while (%w0 = %arg0, %w1 = %arg1)
: (tensor<5xi1>, tensor<5xi1>) -> (tensor<5xi1>, tensor<5xi1>) {
%condition = tensor.extract %w0[%idx] : tensor<5xi1>
scf.condition(%condition) %w0, %w1 : tensor<5xi1>, tensor<5xi1>
} do {
^bb0(%b0: tensor<5xi1>, %b1: tensor<5xi1>):
%pos = "dummy.some_op"() : () -> (index)
%val = "dummy.another_op"() : () -> (i1)
%1 = tensor.insert %val into %b0[%pos] : tensor<5xi1>
// expected-error @+1 {{Yield operand #0 is not equivalent to the corresponding iter bbArg}}
scf.yield %b1, %1 : tensor<5xi1>, tensor<5xi1>
}
%v0 = tensor.extract %r0[%idx] : tensor<5xi1>
%v1 = tensor.extract %r1[%idx] : tensor<5xi1>
return %v0, %v1 : i1, i1
}
// -----
func.func private @fun_with_side_effects(%A: tensor<?xf32>)
func.func @foo(%A: tensor<?xf32> {bufferization.writable = true}) -> (tensor<?xf32>) {
call @fun_with_side_effects(%A) : (tensor<?xf32>) -> ()
return %A: tensor<?xf32>
}
func.func @scf_yield_needs_copy(%A : tensor<?xf32> {bufferization.writable = true}, %iters : index) {
%c0 = arith.constant 0 : index
%c1 = arith.constant 1 : index
%res = scf.for %arg0 = %c0 to %iters step %c1 iter_args(%bbarg = %A) -> (tensor<?xf32>) {
%r = func.call @foo(%A) : (tensor<?xf32>) -> (tensor<?xf32>)
// expected-error @+1 {{Yield operand #0 is not equivalent to the corresponding iter bbArg}}
scf.yield %r : tensor<?xf32>
}
call @fun_with_side_effects(%res) : (tensor<?xf32>) -> ()
return
}
// -----
func.func @extract_slice_fun(%A : tensor<?xf32> {bufferization.writable = true})
-> tensor<4xf32>
{
// This bufferizes to a pattern that the cross-function boundary pass needs to
// convert into a new memref argument at all call site; this may be either:
// - an externally created aliasing subview (if we want to allow aliasing
// function arguments).
// - a new alloc + copy (more expensive but does not create new function
// argument aliasing).
%r0 = tensor.extract_slice %A[0][4][1] : tensor<?xf32> to tensor<4xf32>
// expected-error @+1 {{operand #0 may return/yield a new buffer allocation}}
return %r0: tensor<4xf32>
}
// -----
func.func @scf_yield(%b : i1, %A : tensor<4xf32>, %B : tensor<4xf32>) -> tensor<4xf32>
{
%r = scf.if %b -> (tensor<4xf32>) {
scf.yield %A : tensor<4xf32>
} else {
scf.yield %B : tensor<4xf32>
}
// expected-error @+1 {{operand #0 may return/yield a new buffer allocation}}
return %r: tensor<4xf32>
}
// -----
func.func @unknown_op(%A : tensor<4xf32>) -> tensor<4xf32>
{
// expected-error: @+1 {{op was not bufferized}}
%r = "marklar"(%A) : (tensor<4xf32>) -> (tensor<4xf32>)
// expected-error @+1 {{operand #0 may return/yield a new buffer allocation}}
return %r: tensor<4xf32>
}
// -----
func.func @mini_test_case1() -> tensor<10x20xf32> {
%f0 = arith.constant 0.0 : f32
%t = bufferization.alloc_tensor() : tensor<10x20xf32>
%r = linalg.fill ins(%f0 : f32) outs(%t : tensor<10x20xf32>) -> tensor<10x20xf32>
// expected-error @+1 {{operand #0 may return/yield a new buffer allocation}}
return %r : tensor<10x20xf32>
}
// -----
func.func @main() -> tensor<4xi32> {
%r = scf.execute_region -> tensor<4xi32> {
%A = arith.constant dense<[1, 2, 3, 4]> : tensor<4xi32>
// expected-error @+1 {{operand #0 may return/yield a new buffer allocation}}
scf.yield %A: tensor<4xi32>
}
// expected-error @+1 {{operand #0 may return/yield a new buffer allocation}}
return %r: tensor<4xi32>
}
// -----
func.func @to_memref_op_is_writing(
%t1: tensor<?xf32> {bufferization.writable = true}, %idx1: index,
%idx2: index, %idx3: index, %v1: vector<5xf32>) -> (vector<5xf32>, vector<5xf32>) {
// This is a RaW conflict because to_memref is an inplace write and %t1 is
// read further down. This will likely have to change with partial
// bufferization.
// expected-error @+1 {{to_memref ops not supported during One-Shot Analysis}}
%0 = bufferization.to_memref %t1 : memref<?xf32>
// Read from both.
%cst = arith.constant 0.0 : f32
%r1 = vector.transfer_read %t1[%idx3], %cst : tensor<?xf32>, vector<5xf32>
%r2 = vector.transfer_read %0[%idx3], %cst : memref<?xf32>, vector<5xf32>
return %r1, %r2 : vector<5xf32>, vector<5xf32>
}
// -----
// expected-error @+2 {{failed to bufferize op}}
// expected-error @+1 {{cannot bufferize bodiless function that returns a tensor}}
func.func private @foo(%t : tensor<?xf32>) -> (f32, tensor<?xf32>, f32)
func.func @call_to_unknown_tensor_returning_func(%t : tensor<?xf32>) {
call @foo(%t) : (tensor<?xf32>) -> (f32, tensor<?xf32>, f32)
return
}
// -----
func.func @foo(%t : tensor<5xf32>) -> (tensor<5xf32>) {
%0 = bufferization.alloc_tensor() : tensor<5xf32>
// expected-error @+1 {{operand #0 may return/yield a new buffer allocation}}
return %0 : tensor<5xf32>
}
// Note: This function is not analyzed because there was an error in the
// previous one.
func.func @call_to_func_returning_non_equiv_tensor(%t : tensor<5xf32>) {
call @foo(%t) : (tensor<5xf32>) -> (tensor<5xf32>)
return
}
// -----
func.func @yield_alloc_dominance_test_1(%cst : f32, %idx : index,
%idx2 : index) -> f32 {
%0 = scf.execute_region -> tensor<?xf32> {
%1 = bufferization.alloc_tensor(%idx) : tensor<?xf32>
// expected-error @+1 {{operand #0 may return/yield a new buffer allocation}}
scf.yield %1 : tensor<?xf32>
}
%2 = tensor.insert %cst into %0[%idx] : tensor<?xf32>
%r = tensor.extract %2[%idx2] : tensor<?xf32>
return %r : f32
}
// -----
func.func @yield_alloc_dominance_test_2(%cst : f32, %idx : index,
%idx2 : index) -> f32 {
%1 = bufferization.alloc_tensor(%idx) : tensor<?xf32>
%0 = scf.execute_region -> tensor<?xf32> {
// This YieldOp returns a value that is defined in a parent block, thus
// no error.
scf.yield %1 : tensor<?xf32>
}
%2 = tensor.insert %cst into %0[%idx] : tensor<?xf32>
%r = tensor.extract %2[%idx2] : tensor<?xf32>
return %r : f32
}