1 // RUN: mlir-opt -promote-buffers-to-stack -split-input-file %s | FileCheck %s --check-prefix=CHECK --check-prefix DEFINDEX
2 // RUN: mlir-opt -promote-buffers-to-stack="max-alloc-size-in-bytes=64" -split-input-file %s | FileCheck %s --check-prefix=CHECK --check-prefix LOWLIMIT
3 // RUN: mlir-opt -promote-buffers-to-stack="max-rank-of-allocated-memref=2" -split-input-file %s | FileCheck %s --check-prefix=CHECK --check-prefix RANK
5 // This file checks the behavior of PromoteBuffersToStack pass for converting
6 // AllocOps into AllocaOps, if possible.
11 // bb1 bb2 <- Initial position of AllocOp
14 // PromoteBuffersToStack expected behavior: It should convert %0 into an
17 // CHECK-LABEL: func @condBranch
18 func.func @condBranch(%arg0: i1, %arg1: memref<2xf32>, %arg2: memref<2xf32>) {
19 cf.cond_br %arg0, ^bb1, ^bb2
21 cf.br ^bb3(%arg1 : memref<2xf32>)
23 %0 = memref.alloc() : memref<2xf32>
24 test.buffer_based in(%arg1: memref<2xf32>) out(%0: memref<2xf32>)
25 cf.br ^bb3(%0 : memref<2xf32>)
26 ^bb3(%1: memref<2xf32>):
27 test.copy(%1, %arg2) : (memref<2xf32>, memref<2xf32>)
31 // CHECK-NEXT: cf.cond_br {{.*}}
33 // CHECK-NEXT: %[[ALLOCA:.*]] = memref.alloca()
42 // bb1 bb2 <- Initial position of AllocOp
45 // PromoteBuffersToStack expected behavior:
46 // Since the alloc has dynamic type, it is not converted into an alloca.
48 // CHECK-LABEL: func @condBranchDynamicType
49 func.func @condBranchDynamicType(
54 cf.cond_br %arg0, ^bb1, ^bb2(%arg3: index)
56 cf.br ^bb3(%arg1 : memref<?xf32>)
58 %1 = memref.alloc(%0) : memref<?xf32>
59 test.buffer_based in(%arg1: memref<?xf32>) out(%1: memref<?xf32>)
60 cf.br ^bb3(%1 : memref<?xf32>)
61 ^bb3(%2: memref<?xf32>):
62 test.copy(%2, %arg2) : (memref<?xf32>, memref<?xf32>)
66 // CHECK-NEXT: cf.cond_br
68 // CHECK: ^bb2(%[[IDX:.*]]:{{.*}})
69 // CHECK-NEXT: %[[ALLOC0:.*]] = memref.alloc(%[[IDX]])
70 // CHECK-NEXT: test.buffer_based
72 // CHECK-NEXT: ^bb3(%[[ALLOC0:.*]]:{{.*}})
73 // CHECK: test.copy(%[[ALLOC0]],
78 // CHECK-LABEL: func @dynamicRanked
79 func.func @dynamicRanked(%memref: memref<*xf32>) {
80 %0 = memref.rank %memref : memref<*xf32>
81 %1 = memref.alloc(%0) : memref<?xindex>
85 // CHECK-NEXT: %[[RANK:.*]] = memref.rank %{{.*}} : memref<*xf32>
86 // CHECK-NEXT: %[[ALLOCA:.*]] = memref.alloca(%[[RANK]])
90 // CHECK-LABEL: func @dynamicRanked2D
91 func.func @dynamicRanked2D(%memref: memref<*xf32>) {
92 %0 = memref.rank %memref : memref<*xf32>
93 %1 = memref.alloc(%0, %0) : memref<?x?xindex>
97 // CHECK-NEXT: %[[RANK:.*]] = memref.rank %{{.*}} : memref<*xf32>
98 // RANK-NEXT: %[[ALLOC:.*]] = memref.alloca(%[[RANK]], %[[RANK]])
99 // DEFINDEX-NEXT: %[[ALLOC:.*]] = memref.alloc(%[[RANK]], %[[RANK]])
103 // CHECK-LABEL: func @dynamicNoRank
104 func.func @dynamicNoRank(%arg0: index) {
105 %0 = memref.alloc(%arg0) : memref<?xindex>
109 // CHECK-NEXT: %[[ALLOC:.*]] = memref.alloc
113 // Test Case: Existing AllocOp with no users.
114 // PromoteBuffersToStack expected behavior: It should convert it to an
117 // CHECK-LABEL: func @emptyUsesValue
118 func.func @emptyUsesValue(%arg0: memref<4xf32>) {
119 %0 = memref.alloc() : memref<4xf32>
122 // CHECK-NEXT: %[[ALLOCA:.*]] = memref.alloca()
123 // CHECK-NEXT: return
130 // | bb1 <- Initial position of AllocOp
133 // PromoteBuffersToStack expected behavior: It should convert it into an
136 // CHECK-LABEL: func @criticalEdge
137 func.func @criticalEdge(%arg0: i1, %arg1: memref<2xf32>, %arg2: memref<2xf32>) {
138 cf.cond_br %arg0, ^bb1, ^bb2(%arg1 : memref<2xf32>)
140 %0 = memref.alloc() : memref<2xf32>
141 test.buffer_based in(%arg1: memref<2xf32>) out(%0: memref<2xf32>)
142 cf.br ^bb2(%0 : memref<2xf32>)
143 ^bb2(%1: memref<2xf32>):
144 test.copy(%1, %arg2) : (memref<2xf32>, memref<2xf32>)
148 // CHECK-NEXT: cf.cond_br {{.*}}
150 // CHECK-NEXT: %[[ALLOCA:.*]] = memref.alloca()
152 // CHECK-NEXT: return
157 // bb0 <- Initial position of AllocOp
162 // PromoteBuffersToStack expected behavior: It converts the alloc in an alloca.
164 // CHECK-LABEL: func @invCriticalEdge
165 func.func @invCriticalEdge(%arg0: i1, %arg1: memref<2xf32>, %arg2: memref<2xf32>) {
166 %0 = memref.alloc() : memref<2xf32>
167 test.buffer_based in(%arg1: memref<2xf32>) out(%0: memref<2xf32>)
168 cf.cond_br %arg0, ^bb1, ^bb2(%arg1 : memref<2xf32>)
170 cf.br ^bb2(%0 : memref<2xf32>)
171 ^bb2(%1: memref<2xf32>):
172 test.copy(%1, %arg2) : (memref<2xf32>, memref<2xf32>)
176 // CHECK-NEXT: %[[ALLOCA:.*]] = memref.alloca()
179 // CHECK-NEXT: return
184 // bb0 <- Initial position of the first AllocOp
188 // bb3 <- Initial position of the second AllocOp
189 // PromoteBuffersToStack expected behavior: It converts the allocs into allocas.
191 // CHECK-LABEL: func @ifElse
192 func.func @ifElse(%arg0: i1, %arg1: memref<2xf32>, %arg2: memref<2xf32>) {
193 %0 = memref.alloc() : memref<2xf32>
194 test.buffer_based in(%arg1: memref<2xf32>) out(%0: memref<2xf32>)
196 ^bb1(%arg1, %0 : memref<2xf32>, memref<2xf32>),
197 ^bb2(%0, %arg1 : memref<2xf32>, memref<2xf32>)
198 ^bb1(%1: memref<2xf32>, %2: memref<2xf32>):
199 cf.br ^bb3(%1, %2 : memref<2xf32>, memref<2xf32>)
200 ^bb2(%3: memref<2xf32>, %4: memref<2xf32>):
201 cf.br ^bb3(%3, %4 : memref<2xf32>, memref<2xf32>)
202 ^bb3(%5: memref<2xf32>, %6: memref<2xf32>):
203 %7 = memref.alloc() : memref<2xf32>
204 test.buffer_based in(%5: memref<2xf32>) out(%7: memref<2xf32>)
205 test.copy(%7, %arg2) : (memref<2xf32>, memref<2xf32>)
209 // CHECK-NEXT: %[[ALLOCA0:.*]] = memref.alloca()
210 // CHECK-NEXT: test.buffer_based
211 // CHECK: %[[ALLOCA1:.*]] = memref.alloca()
212 // CHECK: test.buffer_based
213 // CHECK: test.copy(%[[ALLOCA1]]
214 // CHECK-NEXT: return
218 // Test Case: No users for buffer in if-else CFG
219 // bb0 <- Initial position of AllocOp
224 // PromoteBuffersToStack expected behavior: It converts the alloc into alloca.
226 // CHECK-LABEL: func @ifElseNoUsers
227 func.func @ifElseNoUsers(%arg0: i1, %arg1: memref<2xf32>, %arg2: memref<2xf32>) {
228 %0 = memref.alloc() : memref<2xf32>
229 test.buffer_based in(%arg1: memref<2xf32>) out(%0: memref<2xf32>)
231 ^bb1(%arg1, %0 : memref<2xf32>, memref<2xf32>),
232 ^bb2(%0, %arg1 : memref<2xf32>, memref<2xf32>)
233 ^bb1(%1: memref<2xf32>, %2: memref<2xf32>):
234 cf.br ^bb3(%1, %2 : memref<2xf32>, memref<2xf32>)
235 ^bb2(%3: memref<2xf32>, %4: memref<2xf32>):
236 cf.br ^bb3(%3, %4 : memref<2xf32>, memref<2xf32>)
237 ^bb3(%5: memref<2xf32>, %6: memref<2xf32>):
238 test.copy(%arg1, %arg2) : (memref<2xf32>, memref<2xf32>)
242 // CHECK-NEXT: %[[ALLOCA:.*]] = memref.alloca()
248 // bb0 <- Initial position of the first AllocOp
255 // bb5 <- Initial position of the second AllocOp
256 // PromoteBuffersToStack expected behavior: The two allocs should be converted
259 // CHECK-LABEL: func @ifElseNested
260 func.func @ifElseNested(%arg0: i1, %arg1: memref<2xf32>, %arg2: memref<2xf32>) {
261 %0 = memref.alloc() : memref<2xf32>
262 test.buffer_based in(%arg1: memref<2xf32>) out(%0: memref<2xf32>)
264 ^bb1(%arg1, %0 : memref<2xf32>, memref<2xf32>),
265 ^bb2(%0, %arg1 : memref<2xf32>, memref<2xf32>)
266 ^bb1(%1: memref<2xf32>, %2: memref<2xf32>):
267 cf.br ^bb5(%1, %2 : memref<2xf32>, memref<2xf32>)
268 ^bb2(%3: memref<2xf32>, %4: memref<2xf32>):
269 cf.cond_br %arg0, ^bb3(%3 : memref<2xf32>), ^bb4(%4 : memref<2xf32>)
270 ^bb3(%5: memref<2xf32>):
271 cf.br ^bb5(%5, %3 : memref<2xf32>, memref<2xf32>)
272 ^bb4(%6: memref<2xf32>):
273 cf.br ^bb5(%3, %6 : memref<2xf32>, memref<2xf32>)
274 ^bb5(%7: memref<2xf32>, %8: memref<2xf32>):
275 %9 = memref.alloc() : memref<2xf32>
276 test.buffer_based in(%7: memref<2xf32>) out(%9: memref<2xf32>)
277 test.copy(%9, %arg2) : (memref<2xf32>, memref<2xf32>)
281 // CHECK-NEXT: %[[ALLOCA0:.*]] = memref.alloca()
282 // CHECK-NEXT: test.buffer_based
283 // CHECK: %[[ALLOCA1:.*]] = memref.alloca()
284 // CHECK: test.buffer_based
285 // CHECK: test.copy(%[[ALLOCA1]]
286 // CHECK-NEXT: return
290 // Test Case: Dead operations in a single block.
291 // PromoteBuffersToStack expected behavior: It converts the two AllocOps into
294 // CHECK-LABEL: func @redundantOperations
295 func.func @redundantOperations(%arg0: memref<2xf32>) {
296 %0 = memref.alloc() : memref<2xf32>
297 test.buffer_based in(%arg0: memref<2xf32>) out(%0: memref<2xf32>)
298 %1 = memref.alloc() : memref<2xf32>
299 test.buffer_based in(%0: memref<2xf32>) out(%1: memref<2xf32>)
303 // CHECK: (%[[ARG0:.*]]: {{.*}})
304 // CHECK-NEXT: %[[ALLOCA0:.*]] = memref.alloca()
305 // CHECK-NEXT: test.buffer_based in(%[[ARG0]]{{.*}} out(%[[ALLOCA0]]
306 // CHECK: %[[ALLOCA1:.*]] = memref.alloca()
307 // CHECK-NEXT: test.buffer_based in(%[[ALLOCA0]]{{.*}} out(%[[ALLOCA1]]
315 // Initial pos of the 1st AllocOp -> bb1 bb2 <- Initial pos of the 2nd AllocOp
318 // PromoteBuffersToStack expected behavior: Both AllocOps are converted into
321 // CHECK-LABEL: func @moving_alloc_and_inserting_missing_dealloc
322 func.func @moving_alloc_and_inserting_missing_dealloc(
324 %arg0: memref<2xf32>,
325 %arg1: memref<2xf32>) {
326 cf.cond_br %cond, ^bb1, ^bb2
328 %0 = memref.alloc() : memref<2xf32>
329 test.buffer_based in(%arg0: memref<2xf32>) out(%0: memref<2xf32>)
330 cf.br ^exit(%0 : memref<2xf32>)
332 %1 = memref.alloc() : memref<2xf32>
333 test.buffer_based in(%arg0: memref<2xf32>) out(%1: memref<2xf32>)
334 cf.br ^exit(%1 : memref<2xf32>)
335 ^exit(%arg2: memref<2xf32>):
336 test.copy(%arg2, %arg1) : (memref<2xf32>, memref<2xf32>)
340 // CHECK-NEXT: cf.cond_br {{.*}}
342 // CHECK-NEXT: %{{.*}} = memref.alloca()
344 // CHECK-NEXT: %{{.*}} = memref.alloca()
346 // CHECK-NEXT: return
350 // Test Case: Nested regions - This test defines a BufferBasedOp inside the
351 // region of a RegionBufferBasedOp.
352 // PromoteBuffersToStack expected behavior: The AllocOps are converted into
355 // CHECK-LABEL: func @nested_regions_and_cond_branch
356 func.func @nested_regions_and_cond_branch(
358 %arg1: memref<2xf32>,
359 %arg2: memref<2xf32>) {
360 cf.cond_br %arg0, ^bb1, ^bb2
362 cf.br ^bb3(%arg1 : memref<2xf32>)
364 %0 = memref.alloc() : memref<2xf32>
365 test.region_buffer_based in(%arg1: memref<2xf32>) out(%0: memref<2xf32>) {
366 ^bb0(%gen1_arg0: f32, %gen1_arg1: f32):
367 %1 = memref.alloc() : memref<2xf32>
368 test.buffer_based in(%arg1: memref<2xf32>) out(%1: memref<2xf32>)
369 %tmp1 = math.exp %gen1_arg0 : f32
370 test.region_yield %tmp1 : f32
372 cf.br ^bb3(%0 : memref<2xf32>)
373 ^bb3(%1: memref<2xf32>):
374 test.copy(%1, %arg2) : (memref<2xf32>, memref<2xf32>)
378 // CHECK-NEXT: cf.cond_br {{.*}}
380 // CHECK-NEXT: %[[ALLOCA0:.*]] = memref.alloca()
382 // CHECK-NEXT: %[[ALLOCA1:.*]] = memref.alloc()
386 // Test Case: buffer deallocation escaping
387 // PromoteBuffersToStack expected behavior: The first alloc is returned, so
388 // there is no conversion allowed. The second alloc is converted, since it
389 // only remains in the scope of the function.
391 // CHECK-LABEL: func @memref_in_function_results
392 func.func @memref_in_function_results(
393 %arg0: memref<5xf32>,
394 %arg1: memref<10xf32>,
395 %arg2: memref<5xf32>) -> (memref<10xf32>, memref<15xf32>) {
396 %x = memref.alloc() : memref<15xf32>
397 %y = memref.alloc() : memref<5xf32>
398 test.buffer_based in(%arg0: memref<5xf32>) out(%y: memref<5xf32>)
399 test.copy(%y, %arg2) : (memref<5xf32>, memref<5xf32>)
400 return %arg1, %x : memref<10xf32>, memref<15xf32>
402 // CHECK: (%[[ARG0:.*]]: memref<5xf32>, %[[ARG1:.*]]: memref<10xf32>,
403 // CHECK-SAME: %[[RESULT:.*]]: memref<5xf32>)
404 // CHECK: %[[ALLOC:.*]] = memref.alloc()
405 // CHECK: %[[ALLOCA:.*]] = memref.alloca()
407 // CHECK: return %[[ARG1]], %[[ALLOC]]
411 // Test Case: nested region control flow
412 // The allocation in the nested if branch cannot be converted to an alloca
413 // due to its dynamic memory allocation behavior.
415 // CHECK-LABEL: func @nested_region_control_flow
416 func.func @nested_region_control_flow(
418 %arg1 : index) -> memref<?x?xf32> {
419 %0 = arith.cmpi eq, %arg0, %arg1 : index
420 %1 = memref.alloc(%arg0, %arg0) : memref<?x?xf32>
421 %2 = scf.if %0 -> (memref<?x?xf32>) {
422 scf.yield %1 : memref<?x?xf32>
424 %3 = memref.alloc(%arg0, %arg1) : memref<?x?xf32>
425 scf.yield %1 : memref<?x?xf32>
427 return %2 : memref<?x?xf32>
430 // CHECK: %[[ALLOC0:.*]] = memref.alloc(%arg0, %arg0)
431 // CHECK-NEXT: %[[ALLOC1:.*]] = scf.if
432 // CHECK: scf.yield %[[ALLOC0]]
433 // CHECK: %[[ALLOC2:.*]] = memref.alloc(%arg0, %arg1)
434 // CHECK-NEXT: scf.yield %[[ALLOC0]]
435 // CHECK: return %[[ALLOC1]]
439 // Test Case: nested region control flow within a region interface.
440 // The alloc %0 does not need to be converted in this case since the
441 // allocation finally escapes the method.
443 // CHECK-LABEL: func @inner_region_control_flow
444 func.func @inner_region_control_flow(%arg0 : index) -> memref<2x2xf32> {
445 %0 = memref.alloc() : memref<2x2xf32>
446 %1 = test.region_if %0 : memref<2x2xf32> -> (memref<2x2xf32>) then {
447 ^bb0(%arg1 : memref<2x2xf32>):
448 test.region_if_yield %arg1 : memref<2x2xf32>
450 ^bb0(%arg1 : memref<2x2xf32>):
451 test.region_if_yield %arg1 : memref<2x2xf32>
453 ^bb0(%arg1 : memref<2x2xf32>):
454 test.region_if_yield %arg1 : memref<2x2xf32>
456 return %1 : memref<2x2xf32>
459 // CHECK: %[[ALLOC0:.*]] = memref.alloc()
460 // CHECK-NEXT: %[[ALLOC1:.*]] = test.region_if
461 // CHECK-NEXT: ^bb0(%[[ALLOC2:.*]]:{{.*}}):
462 // CHECK-NEXT: test.region_if_yield %[[ALLOC2]]
463 // CHECK: ^bb0(%[[ALLOC3:.*]]:{{.*}}):
464 // CHECK-NEXT: test.region_if_yield %[[ALLOC3]]
465 // CHECK: ^bb0(%[[ALLOC4:.*]]:{{.*}}):
466 // CHECK-NEXT: test.region_if_yield %[[ALLOC4]]
467 // CHECK: return %[[ALLOC1]]
471 // Test Case: structured control-flow loop using a nested alloc.
472 // Alloc %0 will be converted to an alloca. %3 is not transformed.
474 // CHECK-LABEL: func @loop_alloc
475 func.func @loop_alloc(
480 %res: memref<2xf32>) {
481 %0 = memref.alloc() : memref<2xf32>
482 %1 = scf.for %i = %lb to %ub step %step
483 iter_args(%iterBuf = %buf) -> memref<2xf32> {
484 %2 = arith.cmpi eq, %i, %ub : index
485 %3 = memref.alloc() : memref<2xf32>
486 scf.yield %3 : memref<2xf32>
488 test.copy(%1, %res) : (memref<2xf32>, memref<2xf32>)
492 // CHECK-NEXT: %[[ALLOCA:.*]] = memref.alloca()
493 // CHECK-NEXT: scf.for
494 // CHECK: %[[ALLOC:.*]] = memref.alloc()
498 // Test Case: structured control-flow loop with a nested if operation.
499 // The loop yields buffers that have been defined outside of the loop and the
500 // backedges only use the iteration arguments (or one of its aliases).
501 // Therefore, we do not have to (and are not allowed to) free any buffers
502 // that are passed via the backedges. The alloc is converted to an AllocaOp.
504 // CHECK-LABEL: func @loop_nested_if_no_alloc
505 func.func @loop_nested_if_no_alloc(
510 %res: memref<2xf32>) {
511 %0 = memref.alloc() : memref<2xf32>
512 %1 = scf.for %i = %lb to %ub step %step
513 iter_args(%iterBuf = %buf) -> memref<2xf32> {
514 %2 = arith.cmpi eq, %i, %ub : index
515 %3 = scf.if %2 -> (memref<2xf32>) {
516 scf.yield %0 : memref<2xf32>
518 scf.yield %iterBuf : memref<2xf32>
520 scf.yield %3 : memref<2xf32>
522 test.copy(%1, %res) : (memref<2xf32>, memref<2xf32>)
526 // CHECK: %[[ALLOCA0:.*]] = memref.alloca()
527 // CHECK-NEXT: %[[ALLOCA1:.*]] = scf.for {{.*}} iter_args(%[[IALLOCA:.*]] =
528 // CHECK: %[[ALLOCA2:.*]] = scf.if
529 // CHECK: scf.yield %[[ALLOCA0]]
530 // CHECK: scf.yield %[[IALLOCA]]
531 // CHECK: scf.yield %[[ALLOCA2]]
532 // CHECK: test.copy(%[[ALLOCA1]], %arg4)
536 // Test Case: structured control-flow loop with a nested if operation using
537 // a deeply nested buffer allocation.
538 // The allocs are not converted in this case.
540 // CHECK-LABEL: func @loop_nested_if_alloc
541 func.func @loop_nested_if_alloc(
545 %buf: memref<2xf32>) -> memref<2xf32> {
546 %0 = memref.alloc() : memref<2xf32>
547 %1 = scf.for %i = %lb to %ub step %step
548 iter_args(%iterBuf = %buf) -> memref<2xf32> {
549 %2 = arith.cmpi eq, %i, %ub : index
550 %3 = scf.if %2 -> (memref<2xf32>) {
551 %4 = memref.alloc() : memref<2xf32>
552 scf.yield %4 : memref<2xf32>
554 scf.yield %0 : memref<2xf32>
556 scf.yield %3 : memref<2xf32>
558 return %1 : memref<2xf32>
561 // CHECK: %[[ALLOC0:.*]] = memref.alloc()
562 // CHECK-NEXT: %[[ALLOC1:.*]] = scf.for {{.*}}
563 // CHECK: %[[ALLOC2:.*]] = scf.if
564 // CHECK: %[[ALLOC3:.*]] = memref.alloc()
565 // CHECK-NEXT: scf.yield %[[ALLOC3]]
566 // CHECK: scf.yield %[[ALLOC0]]
567 // CHECK: scf.yield %[[ALLOC2]]
568 // CHECK: return %[[ALLOC1]]
572 // Test Case: The allocated buffer is too large and, hence, it is not
573 // converted. In the actual implementation the largest size is 1KB.
575 // CHECK-LABEL: func @large_buffer_allocation
576 func.func @large_buffer_allocation(%arg0: memref<2048xf32>) {
577 %0 = memref.alloc() : memref<2048xf32>
578 test.copy(%0, %arg0) : (memref<2048xf32>, memref<2048xf32>)
582 // CHECK-NEXT: %[[ALLOC:.*]] = memref.alloc()
583 // CHECK-NEXT: test.copy
587 // Test Case: AllocOp with element type index.
588 // PromoteBuffersToStack expected behavior: It should convert it to an
591 // CHECK-LABEL: func @indexElementType
592 func.func @indexElementType() {
593 %0 = memref.alloc() : memref<4xindex>
596 // DEFINDEX-NEXT: memref.alloca()
597 // LOWLIMIT-NEXT: memref.alloca()
598 // RANK-NEXT: memref.alloca()
599 // CHECK-NEXT: return
603 // CHECK-LABEL: func @bigIndexElementType
604 module attributes { dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<index, 256>>} {
605 func.func @bigIndexElementType() {
606 %0 = memref.alloc() {alignment = 64 : i64, custom_attr} : memref<4xindex>
610 // DEFINDEX-NEXT: memref.alloca() {alignment = 64 : i64, custom_attr}
611 // LOWLIMIT-NEXT: memref.alloc() {alignment = 64 : i64, custom_attr}
612 // RANK-NEXT: memref.alloca() {alignment = 64 : i64, custom_attr}
613 // CHECK-NEXT: return