1// RUN: mlir-opt %s -allow-unregistered-dialect -one-shot-bufferize="bufferize-function-boundaries=1" -split-input-file -verify-diagnostics
2
3func.func private @foo() -> tensor<?xf32>
4
5func.func @bar() -> tensor<?xf32> {
6  %foo = constant @foo : () -> (tensor<?xf32>)
7// expected-error @+1 {{expected a CallOp}}
8  %res = call_indirect %foo() : () -> (tensor<?xf32>)
9  return %res : tensor<?xf32>
10}
11
12// -----
13
14// expected-error @+2 {{cannot bufferize bodiless function that returns a tensor}}
15// expected-error @+1 {{failed to bufferize op}}
16func.func private @foo() -> tensor<?xf32>
17
18// -----
19
20// expected-error @+1 {{cannot bufferize a FuncOp with tensors and without a unique ReturnOp}}
21func.func @swappy(%cond1 : i1, %cond2 : i1, %t1 : tensor<f32>, %t2 : tensor<f32>)
22    -> (tensor<f32>, tensor<f32>)
23{
24  cf.cond_br %cond1, ^bb1, ^bb2
25
26  ^bb1:
27    %T:2 = scf.if %cond2 -> (tensor<f32>, tensor<f32>) {
28      scf.yield %t1, %t2 : tensor<f32>, tensor<f32>
29    } else {
30      scf.yield %t2, %t1 : tensor<f32>, tensor<f32>
31    }
32    return %T#0, %T#1 : tensor<f32>, tensor<f32>
33  ^bb2:
34    return %t2, %t1 : tensor<f32>, tensor<f32>
35}
36
37// -----
38
39func.func @scf_if_not_equivalent(
40    %cond: i1, %t1: tensor<?xf32> {bufferization.writable = true},
41    %idx: index) -> tensor<?xf32> {
42  %r = scf.if %cond -> (tensor<?xf32>) {
43    scf.yield %t1 : tensor<?xf32>
44  } else {
45    // This buffer aliases, but it is not equivalent.
46    %t2 = tensor.extract_slice %t1 [%idx] [%idx] [1] : tensor<?xf32> to tensor<?xf32>
47    // expected-error @+1 {{operand #0 of ReturnLike op does not satisfy destination passing style}}
48    scf.yield %t2 : tensor<?xf32>
49  }
50  // expected-error @+1 {{operand #0 of ReturnLike op does not satisfy destination passing style}}
51  return %r : tensor<?xf32>
52}
53
54// -----
55
56func.func @scf_if_not_aliasing(
57    %cond: i1, %t1: tensor<?xf32> {bufferization.writable = true},
58    %idx: index) -> f32 {
59  %r = scf.if %cond -> (tensor<?xf32>) {
60    scf.yield %t1 : tensor<?xf32>
61  } else {
62    // This buffer aliases.
63    %t2 = bufferization.alloc_tensor(%idx) : tensor<?xf32>
64    // expected-error @+1 {{operand #0 of ReturnLike op does not satisfy destination passing style}}
65    scf.yield %t2 : tensor<?xf32>
66  }
67  %f = tensor.extract %r[%idx] : tensor<?xf32>
68  return %f : f32
69}
70
71// -----
72
73// expected-error @-3 {{expected callgraph to be free of circular dependencies}}
74
75func.func @foo() {
76  call @bar() : () -> ()
77  return
78}
79
80func.func @bar() {
81  call @foo() : () -> ()
82  return
83}
84
85// -----
86
87func.func @scf_for(%A : tensor<?xf32>,
88              %B : tensor<?xf32> {bufferization.writable = true},
89              %C : tensor<4xf32>,
90              %lb : index, %ub : index, %step : index)
91  -> (f32, f32)
92{
93  %r0:2 = scf.for %i = %lb to %ub step %step iter_args(%tA = %A, %tB = %B)
94      -> (tensor<?xf32>, tensor<?xf32>)
95  {
96    %ttA = tensor.insert_slice %C into %tA[0][4][1] : tensor<4xf32> into tensor<?xf32>
97    %ttB = tensor.insert_slice %C into %tB[0][4][1] : tensor<4xf32> into tensor<?xf32>
98
99    // Throw a wrench in the system by swapping yielded values: this result in a
100    // ping-pong of values at each iteration on which we currently want to fail.
101
102    // expected-error @+1 {{Yield operand #0 is not equivalent to the corresponding iter bbArg}}
103    scf.yield %ttB, %ttA : tensor<?xf32>, tensor<?xf32>
104  }
105
106  %f0 = tensor.extract %r0#0[%step] : tensor<?xf32>
107  %f1 = tensor.extract %r0#1[%step] : tensor<?xf32>
108  return %f0, %f1: f32, f32
109}
110
111// -----
112
113func.func @scf_while_non_equiv_condition(%arg0: tensor<5xi1>,
114                                         %arg1: tensor<5xi1>,
115                                         %idx: index) -> (i1, i1)
116{
117  %r0, %r1 = scf.while (%w0 = %arg0, %w1 = %arg1)
118      : (tensor<5xi1>, tensor<5xi1>) -> (tensor<5xi1>, tensor<5xi1>) {
119    %condition = tensor.extract %w0[%idx] : tensor<5xi1>
120    // expected-error @+1 {{Condition arg #0 is not equivalent to the corresponding iter bbArg}}
121    scf.condition(%condition) %w1, %w0 : tensor<5xi1>, tensor<5xi1>
122  } do {
123  ^bb0(%b0: tensor<5xi1>, %b1: tensor<5xi1>):
124    %pos = "dummy.some_op"() : () -> (index)
125    %val = "dummy.another_op"() : () -> (i1)
126    %1 = tensor.insert %val into %b0[%pos] : tensor<5xi1>
127    scf.yield %1, %b1 : tensor<5xi1>, tensor<5xi1>
128  }
129
130  %v0 = tensor.extract %r0[%idx] : tensor<5xi1>
131  %v1 = tensor.extract %r1[%idx] : tensor<5xi1>
132  return %v0, %v1 : i1, i1
133}
134
135// -----
136
137func.func @scf_while_non_equiv_yield(%arg0: tensor<5xi1>,
138                                     %arg1: tensor<5xi1>,
139                                     %idx: index) -> (i1, i1)
140{
141  %r0, %r1 = scf.while (%w0 = %arg0, %w1 = %arg1)
142      : (tensor<5xi1>, tensor<5xi1>) -> (tensor<5xi1>, tensor<5xi1>) {
143    %condition = tensor.extract %w0[%idx] : tensor<5xi1>
144    scf.condition(%condition) %w0, %w1 : tensor<5xi1>, tensor<5xi1>
145  } do {
146  ^bb0(%b0: tensor<5xi1>, %b1: tensor<5xi1>):
147    %pos = "dummy.some_op"() : () -> (index)
148    %val = "dummy.another_op"() : () -> (i1)
149    %1 = tensor.insert %val into %b0[%pos] : tensor<5xi1>
150    // expected-error @+1 {{Yield operand #0 is not equivalent to the corresponding iter bbArg}}
151    scf.yield %b1, %1 : tensor<5xi1>, tensor<5xi1>
152  }
153
154  %v0 = tensor.extract %r0[%idx] : tensor<5xi1>
155  %v1 = tensor.extract %r1[%idx] : tensor<5xi1>
156  return %v0, %v1 : i1, i1
157}
158
159// -----
160
161func.func private @fun_with_side_effects(%A: tensor<?xf32> {bufferization.writable = true})
162
163func.func @foo(%A: tensor<?xf32> {bufferization.writable = true}) -> (tensor<?xf32>) {
164  call @fun_with_side_effects(%A) : (tensor<?xf32>) -> ()
165  return %A: tensor<?xf32>
166}
167
168func.func @scf_yield_needs_copy(%A : tensor<?xf32> {bufferization.writable = true}, %iters : index) {
169  %c0 = arith.constant 0 : index
170  %c1 = arith.constant 1 : index
171  %res = scf.for %arg0 = %c0 to %iters step %c1 iter_args(%bbarg = %A) -> (tensor<?xf32>) {
172    %r = func.call @foo(%A) : (tensor<?xf32>) -> (tensor<?xf32>)
173    // expected-error @+1 {{Yield operand #0 is not equivalent to the corresponding iter bbArg}}
174    scf.yield %r : tensor<?xf32>
175  }
176  call @fun_with_side_effects(%res) : (tensor<?xf32>) -> ()
177  return
178}
179
180// -----
181
182func.func @extract_slice_fun(%A : tensor<?xf32> {bufferization.writable = true})
183  ->  tensor<4xf32>
184{
185  // This bufferizes to a pattern that the cross-function boundary pass needs to
186  // convert into a new memref argument at all call site; this may be either:
187  //   - an externally created aliasing subview (if we want to allow aliasing
188  //     function arguments).
189  //   - a new alloc + copy (more expensive but does not create new function
190  //     argument aliasing).
191  %r0 = tensor.extract_slice %A[0][4][1] : tensor<?xf32> to tensor<4xf32>
192
193  // expected-error @+1 {{operand #0 of ReturnLike op does not satisfy destination passing style}}
194  return %r0: tensor<4xf32>
195}
196
197// -----
198
199func.func @scf_yield(%b : i1, %A : tensor<4xf32>, %B : tensor<4xf32>) -> tensor<4xf32>
200{
201  %r = scf.if %b -> (tensor<4xf32>) {
202    scf.yield %A : tensor<4xf32>
203  } else {
204    scf.yield %B : tensor<4xf32>
205  }
206  // expected-error @+1 {{operand #0 of ReturnLike op does not satisfy destination passing style}}
207  return %r: tensor<4xf32>
208}
209
210// -----
211
212func.func @unknown_op(%A : tensor<4xf32>) -> tensor<4xf32>
213{
214  // expected-error: @+1 {{op was not bufferized}}
215  %r = "marklar"(%A) : (tensor<4xf32>) -> (tensor<4xf32>)
216  // expected-error @+1 {{operand #0 of ReturnLike op does not satisfy destination passing style}}
217  return %r: tensor<4xf32>
218}
219
220// -----
221
222func.func @mini_test_case1() -> tensor<10x20xf32> {
223  %f0 = arith.constant 0.0 : f32
224  %t = bufferization.alloc_tensor() : tensor<10x20xf32>
225  %r = linalg.fill ins(%f0 : f32) outs(%t : tensor<10x20xf32>) -> tensor<10x20xf32>
226  // expected-error @+1 {{operand #0 of ReturnLike op does not satisfy destination passing style}}
227  return %r : tensor<10x20xf32>
228}
229
230// -----
231
232func.func @main() -> tensor<4xi32> {
233  %r = scf.execute_region -> tensor<4xi32> {
234    %A = arith.constant dense<[1, 2, 3, 4]> : tensor<4xi32>
235    // expected-error @+1 {{operand #0 of ReturnLike op does not satisfy destination passing style}}
236    scf.yield %A: tensor<4xi32>
237  }
238
239  // expected-error @+1 {{operand #0 of ReturnLike op does not satisfy destination passing style}}
240  return %r: tensor<4xi32>
241}
242
243// -----
244
245func.func @to_memref_op_is_writing(
246    %t1: tensor<?xf32> {bufferization.writable = true}, %idx1: index,
247    %idx2: index, %idx3: index, %v1: vector<5xf32>) -> (vector<5xf32>, vector<5xf32>) {
248  // This is a RaW conflict because to_memref is an inplace write and %t1 is
249  // read further down. This will likely have to change with partial
250  // bufferization.
251
252  // expected-error @+1 {{input IR has RaW conflict}}
253  %0 = bufferization.to_memref %t1 : memref<?xf32>
254
255  // Read from both.
256  %cst = arith.constant 0.0 : f32
257  %r1 = vector.transfer_read %t1[%idx3], %cst : tensor<?xf32>, vector<5xf32>
258  %r2 = vector.transfer_read %0[%idx3], %cst : memref<?xf32>, vector<5xf32>
259
260  return %r1, %r2 : vector<5xf32>, vector<5xf32>
261}
262
263// -----
264
265// expected-error @+2 {{failed to bufferize op}}
266// expected-error @+1 {{cannot bufferize bodiless function that returns a tensor}}
267func.func private @foo(%t : tensor<?xf32>) -> (f32, tensor<?xf32>, f32)
268
269func.func @call_to_unknown_tensor_returning_func(%t : tensor<?xf32>) {
270  call @foo(%t) : (tensor<?xf32>) -> (f32, tensor<?xf32>, f32)
271  return
272}
273
274// -----
275
276func.func @foo(%t : tensor<5xf32>) -> (tensor<5xf32>) {
277  %0 = bufferization.alloc_tensor() : tensor<5xf32>
278  // expected-error @+1 {{operand #0 of ReturnLike op does not satisfy destination passing style}}
279  return %0 : tensor<5xf32>
280}
281
282// Note: This function is not analyzed because there was an error in the
283// previous one.
284func.func @call_to_func_returning_non_equiv_tensor(%t : tensor<5xf32>) {
285  call @foo(%t) : (tensor<5xf32>) -> (tensor<5xf32>)
286  return
287}
288
289// -----
290
291func.func @destination_passing_style_dominance_test_1(%cst : f32, %idx : index,
292                                                 %idx2 : index) -> f32 {
293  %0 = scf.execute_region -> tensor<?xf32> {
294    %1 = bufferization.alloc_tensor(%idx) : tensor<?xf32>
295    // expected-error @+1 {{operand #0 of ReturnLike op does not satisfy destination passing style}}
296    scf.yield %1 : tensor<?xf32>
297  }
298  %2 = tensor.insert %cst into %0[%idx] : tensor<?xf32>
299  %r = tensor.extract %2[%idx2] : tensor<?xf32>
300  return %r : f32
301}
302
303// -----
304
305func.func @destination_passing_style_dominance_test_2(%cst : f32, %idx : index,
306                                                 %idx2 : index) -> f32 {
307  %1 = bufferization.alloc_tensor(%idx) : tensor<?xf32>
308
309  %0 = scf.execute_region -> tensor<?xf32> {
310    // This YieldOp is in destination-passing style, thus no error.
311    scf.yield %1 : tensor<?xf32>
312  }
313  %2 = tensor.insert %cst into %0[%idx] : tensor<?xf32>
314  %r = tensor.extract %2[%idx2] : tensor<?xf32>
315  return %r : f32
316}
317