// RUN: mlir-opt %s -linalg-comprehensive-module-bufferize -split-input-file | FileCheck %s // CHECK-LABEL: func @transfer_read(%{{.*}}: memref) -> vector<4xf32> { func @transfer_read(%A : tensor) -> (vector<4xf32>) { %c0 = constant 0 : index %f0 = constant 0.0 : f32 // CHECK: %[[RES:.*]] = vector.transfer_read {{.*}} : memref, vector<4xf32> %0 = vector.transfer_read %A[%c0], %f0 : tensor, vector<4xf32> // CHECK: return %[[RES]] : vector<4xf32> return %0 : vector<4xf32> } // ----- // CHECK-DAG: #[[$map_1d_dyn:.*]] = affine_map<(d0)[s0, s1] -> (d0 * s1 + s0)> // CHECK-LABEL: func @fill_inplace( // CHECK-SAME: %[[A:[a-zA-Z0-9]*]]: memref func @fill_inplace(%A : tensor {linalg.inplaceable = true}) -> tensor { // CHECK: %[[F0:.*]] = constant 0.000000e+00 : f32 %f0 = constant 0.0 : f32 /// Inplaceable, no alloc // CHECK-NOT: alloc // CHECK: linalg.fill(%[[F0]], %[[A]]) : f32, memref %r = linalg.fill(%f0, %A) : f32, tensor -> tensor // CHECK: return // CHECK-NOT: tensor return %r: tensor } // ----- // CHECK-LABEL: func @tensor_extract(%{{.*}}: memref) -> f32 { func @tensor_extract(%A : tensor) -> (f32) { %c0 = constant 0 : index // CHECK: %[[RES:.*]] = memref.load {{.*}} : memref %0 = tensor.extract %A[%c0] : tensor // CHECK: return %[[RES]] : f32 return %0 : f32 } // ----- // CHECK-DAG: #[[$map_1d_dyn:.*]] = affine_map<(d0)[s0, s1] -> (d0 * s1 + s0)> /// No linalg.inplaceable flag, must allocate. // CHECK-LABEL: func @not_inplace( // CHECK-SAME: %[[A:[a-zA-Z0-9]*]]: memref) -> memref { func @not_inplace(%A : tensor) -> tensor { // CHECK: %[[F0:.*]] = constant 0.000000e+00 : f32 %f0 = constant 0.0 : f32 // CHECK: %[[D0:.*]] = memref.dim %[[A]], {{.*}} : memref // CHECK: %[[ALLOC:.*]] = memref.alloc(%[[D0]]) : memref // CHECK: linalg.fill(%[[F0]], %[[ALLOC]]) : f32, memref %r = linalg.fill(%f0, %A) : f32, tensor -> tensor // CHECK: dealloc %[[ALLOC]] : memref // CHECK: return %[[ALLOC]] : memref return %r: tensor } // ----- // CHECK-DAG: #[[$map_2d_dyn:.*]] = affine_map<(d0, d1)[s0, s1, s2] -> (d0 * s1 + s0 + d1 * s2)> // CHECK-LABEL: func @not_inplace // CHECK-SAME: %[[A:[a-zA-Z0-9]*]]: memref) { func @not_inplace(%A : tensor {linalg.inplaceable = true}) -> tensor { %f0 = constant 0.0 : f32 /// Cross-op multiple uses of %A, the first op which has interfering reads must alloc. // CHECK: %[[ALLOC:.*]] = memref.alloc // CHECK: linalg.fill({{.*}}, %[[ALLOC]] %f = linalg.fill(%f0, %A) : f32, tensor -> tensor /// The second op has no interfering reads and can reuse. // CHECK-NOT: alloc // CHECK: linalg.matmul ins(%[[ALLOC]], %[[ALLOC]]{{.*}}) outs(%[[A]] %r = linalg.matmul ins(%f, %f: tensor, tensor) outs(%A: tensor) -> tensor // CHECK: return // CHECK-NOT: tensor return %r: tensor } // ----- // CHECK-LABEL: func @not_inplace func @not_inplace(%A : tensor {linalg.inplaceable = true}) -> tensor { /// Within op multiple uses of %A, must alloc. // CHECK: alloc %r = linalg.matmul ins(%A, %A: tensor, tensor) outs(%A: tensor) -> tensor return %r: tensor } // ----- // CHECK-LABEL: func @vec_inplace func @vec_inplace(%A : tensor {linalg.inplaceable = true}, %vec : vector<4xf32>) -> tensor { %c0 = constant 0 : index // CHECK-NOT: alloc %r = vector.transfer_write %vec, %A[%c0] : vector<4xf32>, tensor // CHECK: return // CHECK-NOT: tensor return %r: tensor } // ----- // CHECK-DAG: #[[$map_1d_dyn:.*]] = affine_map<(d0)[s0, s1] -> (d0 * s1 + s0)> // CHECK-LABEL: func @vec_not_inplace // CHECK-SAME: %[[A:[a-zA-Z0-9]*]]: memref func @vec_not_inplace(%A : tensor {linalg.inplaceable = true}, %vec : vector<4xf32>) -> (tensor, tensor) { %c0 = constant 0 : index %c1 = constant 1 : index /// Cross-op multiple uses of %A, the first vector.transfer which has interfering reads must alloc. // CHECK: %[[ALLOC:.*]] = memref.alloc // CHECK-NEXT: vector.transfer_write {{.*}}, %[[ALLOC]] %r0 = vector.transfer_write %vec, %A[%c0] : vector<4xf32>, tensor /// The second vector.transfer has no interfering reads and can reuse the buffer. // CHECK-NOT: alloc // CHECK-NEXT: vector.transfer_write {{.*}}, %[[A]] %r1 = vector.transfer_write %vec, %A[%c1] : vector<4xf32>, tensor // CHECK: return // CHECK-NOT: tensor return %r0, %r1: tensor, tensor } // ----- // CHECK-DAG: #[[$map_1d_dyn:.*]] = affine_map<(d0)[s0, s1] -> (d0 * s1 + s0)> // CHECK-LABEL: func @insert_slice_fun // CHECK-SAME: %[[A0:[a-zA-Z0-9]*]]: memref, // CHECK-SAME: %[[A1:[a-zA-Z0-9]*]]: memref, // CHECK-SAME: %[[t0:[a-zA-Z0-9]*]]: memref<4xf32, #[[$map_1d_dyn]]>, // CHECK-SAME: %[[t1:[a-zA-Z0-9]*]]: memref<4xf32, #[[$map_1d_dyn]]> func @insert_slice_fun(%A0 : tensor, %A1 : tensor {linalg.inplaceable = true}, %t0 : tensor<4xf32>, %t1 : tensor<4xf32> {linalg.inplaceable = true}) -> (tensor, tensor, tensor, tensor) { // Alloc and copy the whole result tensor. Copy the tensor.extract_slice. // CHECK: %[[REALLOC_A0:.*]] = memref.alloc // CHECK: linalg.copy(%[[A0]], %[[REALLOC_A0]] // CHECK: %[[SV_A0:.*]] = memref.subview %[[REALLOC_A0]] // CHECK: linalg.copy(%[[t0]], %[[SV_A0]]) %r0 = tensor.insert_slice %t0 into %A0[0][4][1] : tensor<4xf32> into tensor // Alloc and copy the whole result tensor. Copy the tensor.extract_slice. // CHECK: %[[REALLOC_A0_2:.*]] = memref.alloc // CHECK: linalg.copy(%[[A0]] // CHECK: %[[SV_A0_2:.*]] = memref.subview %[[REALLOC_A0_2]] // CHECK: linalg.copy(%[[t1]], %[[SV_A0_2]]) %r1 = tensor.insert_slice %t1 into %A0[0][4][1] : tensor<4xf32> into tensor // Still alloc the large tensor because %A1 is read after. Copy the tensor.extract_slice. // CHECK: %[[REALLOC_A1:.*]] = memref.alloc // CHECK: linalg.copy(%[[A1]] // CHECK: %[[SV_A1:.*]] = memref.subview %[[REALLOC_A1]] // CHECK: linalg.copy(%[[t0]], %[[SV_A1]]) %r2 = tensor.insert_slice %t0 into %A1[0][4][1] : tensor<4xf32> into tensor // Do not realloc the large tensor. Copy the tensor.extract_slice. // CHECK-NOT: alloc // CHECK: %[[SV_A1_2:.*]] = memref.subview %[[A1]] // CHECK: linalg.copy(%[[t1]], %[[SV_A1_2]]) %r3 = tensor.insert_slice %t1 into %A1[0][4][1] : tensor<4xf32> into tensor // CHECK: return %[[REALLOC_A0]], %[[REALLOC_A0_2]], %[[REALLOC_A1]] : // CHECK-SAME: memref, memref, memref return %r0, %r1, %r2, %r3: tensor, tensor, tensor, tensor } // ----- // CHECK-DAG: #[[$map_1d_dyn:.*]] = affine_map<(d0)[s0, s1] -> (d0 * s1 + s0)> // CHECK-LABEL: func @insert_slice_fun // CHECK-SAME: %[[A:[a-zA-Z0-9]*]]: memref // CHECK-SAME: %[[t:[a-zA-Z0-9]*]]: memref<4xf32, #[[$map_1d_dyn]]> func @insert_slice_fun(%A : tensor {linalg.inplaceable = true}, %t : tensor<4xf32>) -> tensor { %f0 = constant 0.0 : f32 // CHECK-NOT: alloc // CHECK: %[[SV_A:.*]] = memref.subview %[[A]] // CHECK: linalg.copy(%[[t]], %[[SV_A]]) %r0 = tensor.insert_slice %t into %A[0][4][1] : tensor<4xf32> into tensor /// Overwrite A inplace. // CHECK: linalg.fill({{.*}}, %[[A]] %r1 = linalg.fill(%f0, %r0) : f32, tensor -> tensor // CHECK: return // CHECK-NOT: tensor return %r1: tensor } // ----- // CHECK-DAG: #[[$map_1d_dyn:.*]] = affine_map<(d0)[s0, s1] -> (d0 * s1 + s0)> // CHECK-LABEL: func @insert_slice_fun // CHECK-SAME: %[[A:[a-zA-Z0-9]*]]: memref // CHECK-SAME: %[[t:[a-zA-Z0-9]*]]: memref<4xf32, #[[$map_1d_dyn]]> func @insert_slice_fun(%A : tensor {linalg.inplaceable = true}, %t : tensor<4xf32>) -> tensor { %f0 = constant 0.0 : f32 // CHECK: linalg.fill({{.*}}, %[[A]] %r0 = linalg.fill(%f0, %A) : f32, tensor -> tensor // CHECK-NOT: alloc // CHECK: %[[SV_A:.*]] = memref.subview %[[A]] /// Overwrite A inplace by copying into the subview. // CHECK: linalg.copy(%[[t]], %[[SV_A]]) %r1 = tensor.insert_slice %t into %r0[0][4][1] : tensor<4xf32> into tensor // CHECK: return // CHECK-NOT: tensor return %r1: tensor } // ----- // CHECK-DAG: #[[$map_1d_dyn:.*]] = affine_map<(d0)[s0, s1] -> (d0 * s1 + s0)> // CHECK-LABEL: func @insert_slice_fun_not_inplace // CHECK-SAME: %[[A:[a-zA-Z0-9]*]]: memref // CHECK-SAME: %[[t:[a-zA-Z0-9]*]]: memref<4xf32, #[[$map_1d_dyn]]> func @insert_slice_fun_not_inplace(%A : tensor, %t : tensor<4xf32>) -> tensor { // CHECK: %[[ALLOC:.*]] = memref.alloc(%{{.*}}) : memref // CHECK: linalg.copy(%[[A]], %[[ALLOC]]) : memref // CHECK: %[[SV:.*]] = memref.subview %[[ALLOC]][0] [4] [1] : memref to memref<4xf32> // CHECK: linalg.copy(%[[t]], %[[SV]]) : memref<4xf32, #map>, memref<4xf32> // CHECK: memref.dealloc %[[ALLOC]] : memref %r0 = tensor.insert_slice %t into %A[0][4][1] : tensor<4xf32> into tensor // CHECK: return %{{.*}} : memref return %r0: tensor } // ----- // CHECK-DAG: #[[$map_1d_dyn:.*]] = affine_map<(d0)[s0, s1] -> (d0 * s1 + s0)> // CHECK-LABEL: func @insert_slice_fun_not_inplace // CHECK-SAME: %[[A:[a-zA-Z0-9]*]]: memref // CHECK-SAME: %[[t:[a-zA-Z0-9]*]]: memref<4xf32, #[[$map_1d_dyn]]> func @insert_slice_fun_not_inplace(%A : tensor {linalg.inplaceable = true}, %t : tensor<4xf32>) -> (tensor, tensor) { %f0 = constant 0.0 : f32 // tensor.insert_slice is bufferized first, %A is inplaceable so we can make this inplace // CHECK-DAG: %[[SV_A:.*]] = memref.subview %[[A]][0] [4] [1] : memref to memref<4xf32, {{.*}}> // CHECK-DAG: linalg.copy(%[[t]], %[[SV_A]]) : memref<4xf32, {{.*}}>, memref<4xf32, {{.*}}> %r0 = tensor.insert_slice %t into %A[0][4][1] : tensor<4xf32> into tensor // fill would interfere with %r0 that is also being returned. // So we need to bufferize it out of place and make a new alloc. // CHECK-DAG: %[[ALLOC:.*]] = memref.alloc({{.*}}) : memref // CHECK: linalg.fill(%{{.*}}, %[[ALLOC]] %r1 = linalg.fill(%f0, %A) : f32, tensor -> tensor // CHECK: memref.dealloc %[[ALLOC]] : memref // CHECK: return %[[ALLOC]] : memref return %r1, %r0: tensor, tensor } //===----------------------------------------------------------------------===// // Simple loop cases //===----------------------------------------------------------------------===// // ----- // CHECK-DAG: #[[$map_1d_dyn:.*]] = affine_map<(d0)[s0, s1] -> (d0 * s1 + s0)> // CHECK-LABEL: func @scf_for_yield_only // CHECK-SAME: %[[A:[a-zA-Z0-9]*]]: memref // CHECK-SAME: %[[t:[a-zA-Z0-9]*]]: memref func @scf_for_yield_only(%A : tensor, %B : tensor {linalg.inplaceable = true}, %lb : index, %ub : index, %step : index) -> (tensor, tensor) { // CHECK: %[[ALLOC_FOR_A:.*]] = memref.alloc // CHECK: linalg.copy(%[[A]], %[[ALLOC_FOR_A]]) // The first scf.for remains but just turns into dead code. %r0 = scf.for %i = %lb to %ub step %step iter_args(%t = %A) -> (tensor) { scf.yield %t : tensor } // The second scf.for remains but just turns into dead code. %r1 = scf.for %i = %lb to %ub step %step iter_args(%t = %B) -> (tensor) { scf.yield %t : tensor } // CHECK: memref.dealloc %[[ALLOC_FOR_A]] : memref // CHECK: return %[[ALLOC_FOR_A]] : memref return %r0, %r1: tensor, tensor } // ----- // CHECK-DAG: #[[$map_1d_dyn:.*]] = affine_map<(d0)[s0, s1] -> (d0 * s1 + s0)> // CHECK-LABEL: func @scf_for_with_tensor.insert_slice // CHECK-SAME: %[[A:[a-zA-Z0-9]*]]: memref // CHECK-SAME: %[[B:[a-zA-Z0-9]*]]: memref // CHECK-SAME: %[[C:[a-zA-Z0-9]*]]: memref<4xf32, #[[$map_1d_dyn]]> func @scf_for_with_tensor.insert_slice( %A : tensor, %B : tensor {linalg.inplaceable = true}, %C : tensor<4xf32>, %lb : index, %ub : index, %step : index) -> (tensor, tensor) { // CHECK: %[[ALLOC_FOR_A:.*]] = memref.alloc // CHECK: linalg.copy(%[[A]], %[[ALLOC_FOR_A]]) // CHECK: %[[svA:.*]] = memref.subview %[[ALLOC_FOR_A]][0] [4] [1] // CHECK: %[[svB:.*]] = memref.subview %[[B]][0] [4] [1] // CHECK: scf.for {{.*}} // CHECK-NOT: iter_args %r0:2 = scf.for %i = %lb to %ub step %step iter_args(%tA = %A, %tB = %B) -> (tensor, tensor) { // %ttA bufferizes to direct copy of %BUFFER_CAST_C into %svA // CHECK: linalg.copy(%[[C]], %[[svA]]) %ttA = tensor.insert_slice %C into %tA[0][4][1] : tensor<4xf32> into tensor // %ttB bufferizes to direct copy of %BUFFER_CAST_C into %BUFFER_CAST_B // CHECK: linalg.copy(%[[C]], %[[svB]]) %ttB = tensor.insert_slice %C into %tB[0][4][1] : tensor<4xf32> into tensor // CHECK-NOT: scf.yield scf.yield %ttA, %ttB : tensor, tensor } // CHECK: memref.dealloc %[[ALLOC_FOR_A]] : memref // CHECK: return %[[ALLOC_FOR_A]] : memref return %r0#0, %r0#1: tensor, tensor } // ----- //===----------------------------------------------------------------------===// // Cross function boundary cases. //===----------------------------------------------------------------------===// // CHECK: #[[$DYN_1D_MAP:.*]] = affine_map<(d0)[s0, s1] -> (d0 * s1 + s0)> // CHECK: memref.global "private" constant @__constant_4xi32 : memref<4xi32> = dense<[1, 2, 3, 4]> // CHECK: func private @some_external_func(memref<4xi32, #[[$DYN_1D_MAP]]>) func private @some_external_func(tensor<4xi32>) // CHECK: func @main() func @main() { // CHECK: %[[A:.*]] = memref.get_global @__constant_4xi32 : memref<4xi32> %A = constant dense<[1, 2, 3, 4]> : tensor<4xi32> // CHECK: %[[B:.*]] = memref.cast %[[A]] : memref<4xi32> to memref<4xi32, #[[$DYN_1D_MAP]]> // CHECK: call @some_external_func(%[[B]]) : (memref<4xi32, #[[$DYN_1D_MAP]]>) -> () call @some_external_func(%A) : (tensor<4xi32>) -> () return } // ----- // CHECK: #[[$DYN_1D_MAP:.*]] = affine_map<(d0)[s0, s1] -> (d0 * s1 + s0)> // CHECK: func private @some_external_func(memref) func private @some_external_func(tensor) // CHECK: func @scf_for_with_tensor_insert_slice( // CHECK-SAME: %[[A:[a-zA-Z0-9]*]]: memref // CHECK-SAME: %[[B:[a-zA-Z0-9]*]]: memref // CHECK-SAME: %[[C:[a-zA-Z0-9]*]]: memref<4xf32, #[[$DYN_1D_MAP]]> func @scf_for_with_tensor_insert_slice( %A : tensor, %B : tensor, %C : tensor<4xf32>, %lb : index, %ub : index, %step : index) -> (tensor, tensor) { // CHECK-NEXT: scf.for %r0:2 = scf.for %i = %lb to %ub step %step iter_args(%tA = %A, %tB = %B) -> (tensor, tensor) { // CHECK-NEXT: %[[SVA:.*]] = memref.subview %[[A]] // CHECK-NEXT: linalg.copy(%[[C]], %[[SVA]]) : memref<4xf32, #[[$DYN_1D_MAP]]>, memref<4xf32, #[[$DYN_1D_MAP]]> %ttA = tensor.insert_slice %C into %tA[%i][4][1] : tensor<4xf32> into tensor // CHECK-NEXT: %[[SVB:.*]] = memref.subview %[[B]] // CHECK-NEXT: linalg.copy(%[[C]], %[[SVB]]) : memref<4xf32, #[[$DYN_1D_MAP]]>, memref<4xf32, #[[$DYN_1D_MAP]]> %ttB = tensor.insert_slice %C into %tB[%i][4][1] : tensor<4xf32> into tensor // scf.yield is empty and is elided // CHECK-NOT: scf.yield scf.yield %ttA, %ttB : tensor, tensor } // Swaparoo requires bufferizing the whole function to figure out who's who. return %r0#1, %r0#0: tensor, tensor } // CHECK: func @bar( // CHECK-SAME: %[[A:[a-zA-Z0-9]*]]: memref // CHECK-SAME: %[[B:[a-zA-Z0-9]*]]: memref // CHECK-SAME: %[[C:[a-zA-Z0-9]*]]: memref<4xf32, #[[$DYN_1D_MAP]]> func @bar( %A : tensor {linalg.inplaceable = true}, %B : tensor {linalg.inplaceable = true}, %C : tensor<4xf32> {linalg.inplaceable = true}, %lb : index, %ub : index, %step : index) -> (tensor, tensor) { // CHECK-NEXT: call @scf_for_with_tensor_insert_slice(%[[A]], %[[B]], %[[C]] %r0:2 = call @scf_for_with_tensor_insert_slice(%A, %B, %C, %lb, %ub, %step) : (tensor, tensor, tensor<4xf32>, index, index, index) -> (tensor, tensor) // %r0#0 is actually %B after inplaceable results are swapped in the callee. // CHECK-NEXT: call @some_external_func(%[[B]]) : (memref) -> () call @some_external_func(%r0#0) : (tensor) -> () // CHECK-NEXT: return return %r0#0, %r0#1: tensor, tensor } // ----- // CHECK-DAG: #[[$DYN_0D_MAP:.*]] = affine_map<()[s0] -> (s0)> // CHECK-DAG: #[[$DYN_1D_MAP:.*]] = affine_map<(d0)[s0, s1] -> (d0 * s1 + s0)> // CHECK: func @init_and_dot( // CHECK-SAME: %[[A:[a-zA-Z0-9]*]]: memref<64xf32, #[[$DYN_1D_MAP]]> // CHECK-SAME: %[[B:[a-zA-Z0-9]*]]: memref<64xf32, #[[$DYN_1D_MAP]]> // CHECK-SAME: %[[C:[a-zA-Z0-9]*]]: memref func @init_and_dot(%a: tensor<64xf32>, %b: tensor<64xf32>, %c: tensor) -> tensor { // CHECK-NEXT: %[[C0:.*]] = constant 0{{.*}} : f32 %v0 = constant 0.0 : f32 // CHECK-NEXT: linalg.fill(%[[C0]], %[[C]]) : f32, memref %d = linalg.fill(%v0, %c) : f32, tensor -> tensor // CHECK-NEXT: linalg.dot ins(%[[A]], %[[B]] : memref<64xf32, #[[$DYN_1D_MAP]]>, memref<64xf32, #[[$DYN_1D_MAP]]>) outs(%[[C]] : memref) %e = linalg.dot ins(%a, %b : tensor<64xf32>,tensor<64xf32>) outs(%d: tensor) -> tensor // CHECK-NEXT: return return %e : tensor } // CHECK: func @main() func @main() { // CHECK-DAG: %[[C0:.*]] = constant 0{{.*}} : f32 // CHECK-DAG: %[[C1:.*]] = constant 1{{.*}} : f32 // CHECK-DAG: %[[C2:.*]] = constant 2{{.*}} : f32 %v0 = constant 0.0 : f32 %v1 = constant 1.0 : f32 %v2 = constant 2.0 : f32 // CHECK-NEXT: %[[A:.*]] = memref.alloc() : memref<64xf32> // CHECK-NEXT: %[[B:.*]] = memref.alloc() : memref<64xf32> // CHECK-NEXT: %[[C:.*]] = memref.alloc() : memref %A = linalg.init_tensor [64] : tensor<64xf32> %B = linalg.init_tensor [64] : tensor<64xf32> %C = linalg.init_tensor [] : tensor // CHECK-NEXT: linalg.fill(%[[C1]], %[[A]]) : f32, memref<64xf32> // CHECK-NEXT: linalg.fill(%[[C2]], %[[B]]) : f32, memref<64xf32> // CHECK-NEXT: linalg.fill(%[[C0]], %[[C]]) : f32, memref %AA = linalg.fill(%v1, %A) : f32, tensor<64xf32> -> tensor<64xf32> %BB = linalg.fill(%v2, %B) : f32, tensor<64xf32> -> tensor<64xf32> %CC = linalg.fill(%v0, %C) : f32, tensor -> tensor // CHECK-NEXT: %[[cA:.*]] = memref.cast %[[A]] : memref<64xf32> to memref<64xf32, #[[$DYN_1D_MAP]]> // CHECK-NEXT: %[[cB:.*]] = memref.cast %[[B]] : memref<64xf32> to memref<64xf32, #[[$DYN_1D_MAP]]> // CHECK-NEXT: %[[cC:.*]] = memref.cast %[[C]] : memref to memref // CHECK-NEXT: call @init_and_dot(%[[cA]], %[[cB]], %[[cC]]) %res = call @init_and_dot(%AA, %BB, %CC) : (tensor<64xf32>, tensor<64xf32>, tensor) -> tensor // CHECK-NEXT: %[[dC:.*]] = memref.cast %[[C]] : memref to memref<*xf32> %res2 = tensor.cast %res: tensor to tensor<*xf32> // CHECK-NEXT: call @print_memref_f32(%[[dC]]) : (memref<*xf32>) -> () call @print_memref_f32(%res2) : (tensor<*xf32>) -> () // CHECK-DAG: memref.dealloc %[[A]] : memref<64xf32> // CHECK-DAG: memref.dealloc %[[B]] : memref<64xf32> // CHECK-DAG: memref.dealloc %[[C]] : memref // CHECK-NEXT: return return } // CHECK: func private @print_memref_f32(memref<*xf32>) func private @print_memref_f32(tensor<*xf32>) // ----- func private @some_use(memref) #TILE_MAP = affine_map<(d0)[s0] -> (3, -d0 + s0)> // CHECK-DAG: #[[$DYN_0D_MAP:.*]] = affine_map<()[s0] -> (s0)> // CHECK-DAG: #[[$DYN_1D_MAP:.*]] = affine_map<(d0)[s0, s1] -> (d0 * s1 + s0)> // CHECK-DAG: #[[$TILE_MAP:.*]] = affine_map<(d0)[s0] -> (3, -d0 + s0)> // CHECK: func @tiled_dot( // CHECK-SAME: %[[A:[a-zA-Z0-9]*]]: memref // CHECK-SAME: %[[B:[a-zA-Z0-9]*]]: memref // CHECK-SAME: %[[c:[a-zA-Z0-9]*]]: memref func @tiled_dot(%A: tensor, %B: tensor, %c: tensor {linalg.inplaceable = true}, %effecting: memref) -> tensor { %c3 = constant 3 : index %c0 = constant 0 : index // CHECK: %[[M:.*]] = memref.dim %[[A]], {{.*}} : memref %0 = tensor.dim %A, %c0 : tensor // CHECK: linalg.tiled_loop {{.*}} to (%[[M]]) {{.*}} %[[A]]{{.*}}%[[B]]{{.*}}outs{{.*}}%[[c]] %1 = linalg.tiled_loop (%arg3) = (%c0) to (%0) step (%c3) ins (%arg4 = %A: tensor, %use = %effecting : memref, %arg5 = %B: tensor) outs (%arg6 = %c: tensor) iterators["reduction"] { // CHECK-NOT: alloc %2 = tensor.dim %arg4, %c0 : tensor %3 = affine.min #TILE_MAP(%arg3)[%2] // CHECK: %[[SV_A:.*]] = memref.subview {{.*}} %4 = tensor.extract_slice %arg4[%arg3] [%3] [1] : tensor to tensor %5 = tensor.dim %arg5, %c0 : tensor %6 = affine.min #TILE_MAP(%arg3)[%5] // CHECK: %[[SV_B:.*]] = memref.subview {{.*}} %7 = tensor.extract_slice %arg5[%arg3] [%6] [1] : tensor to tensor // CHECK: linalg.dot ins(%[[SV_A]], %[[SV_B]] : memref, memref) outs(%{{.*}} : memref) %8 = linalg.dot ins(%4, %7 : tensor, tensor) outs(%arg6 : tensor) -> tensor // CHECK: call @some_use(%{{.*}}) : (memref) -> () call @some_use(%use) : (memref) -> () linalg.yield %8 : tensor // CHECK: linalg.yield // CHECK-NOT: tensor } // CHECK: return // CHECK-NOT: tensor return %1 : tensor } // ----- #TILE_MAP = affine_map<(d0)[s0] -> (3, -d0 + s0)> // CHECK-DAG: #[[$DYN_MAP:.*]] = affine_map<(d0)[s0, s1] -> (d0 * s1 + s0)> // CHECK: func @tiled_fill( // CHECK-SAME: %[[A:[a-zA-Z0-9]*]]: memref func @tiled_fill(%A: tensor {linalg.inplaceable = true}) -> tensor { %c3 = constant 3 : index %c0 = constant 0 : index %f0 = constant 0.0 : f32 // CHECK: %[[M:.*]] = memref.dim %[[A]], {{.*}} : memref %0 = tensor.dim %A, %c0 : tensor // CHECK: linalg.tiled_loop {{.*}} to (%[[M]]) {{.*}} outs{{.*}}%[[A]] %1 = linalg.tiled_loop (%arg3) = (%c0) to (%0) step (%c3) outs (%arg1 = %A: tensor) iterators["parallel"] { // CHECK-NOT: alloc %2 = tensor.dim %arg1, %c0 : tensor %3 = affine.min #TILE_MAP(%arg3)[%2] // CHECK: %[[SV_A:.*]] = memref.subview {{.*}} %4 = tensor.extract_slice %arg1[%arg3] [%3] [1] : tensor to tensor // CHECK: linalg.fill(%{{.*}}, %[[SV_A]]) : f32, memref %5 = linalg.fill(%f0, %4) : f32, tensor -> tensor %6 = tensor.insert_slice %5 into %arg1[%arg3] [%3] [1] : tensor into tensor linalg.yield %6 : tensor // CHECK: linalg.yield // CHECK-NOT: tensor } // CHECK: return // CHECK-NOT: tensor return %1 : tensor } // ----- // CHECK: #[[$DYNAMIC:.*]] = affine_map<(d0)[s0, s1] -> (d0 * s1 + s0)> // CHECK: func private @external_func(memref) func private @external_func(tensor) // CHECK: func @callee( // CHECK-SAME: %[[A:[0-9a-zA-Z]*]]: memref // CHECK-SAME: %[[B:[0-9a-zA-Z]*]]: memref // CHECK-SAME: %[[C:[0-9a-zA-Z]*]]: memref func @callee(%A : tensor {linalg.buffer_layout = affine_map<(i)[s0, s1] -> (i)>}, %B : tensor, %C : tensor) { // CHECK-NEXT: %[[CASTED:.*]] = memref.cast %[[A]] : memref to memref // CHECK-NEXT: call @external_func(%[[CASTED]]) : (memref) -> () call @external_func(%A) : (tensor) -> () // CHECK-NEXT: call @external_func(%[[B]]) : (memref) -> () call @external_func(%B) : (tensor) -> () // CHECK-NEXT: call @external_func(%[[C]]) : (memref) -> () call @external_func(%C) : (tensor) -> () return } // CHECK: func @entry( // CHECK-SAME: %[[A:[0-9a-zA-Z]*]]: memref // CHECK-SAME: %[[B:[0-9a-zA-Z]*]]: memref // CHECK-SAME: %[[C:[0-9a-zA-Z]*]]: memref func @entry(%A : tensor {linalg.buffer_layout = affine_map<(i)[s0, s1] -> (i)>}, %B : tensor {linalg.buffer_layout = affine_map<(i)[s0, s1] -> (i)>}, %C : tensor) { // CHECK-NEXT: %[[CASTED_B:.*]] = memref.cast %[[B]] : memref to memref // CHECK-NEXT: call @callee(%[[A]], %[[CASTED_B]], %[[C]]) call @callee(%A, %B, %C) : (tensor, tensor, tensor) -> () return } // ----- // CHECK: func @matmul( // CHECK-SAME: %[[A:[0-9a-zA-Z]*]]: memref<128x256xf32> // CHECK-SAME: %[[B:[0-9a-zA-Z]*]]: memref<256x192xf32> // CHECK-SAME: %[[C:[0-9a-zA-Z]*]]: memref<128x192xf32> func @matmul( %A: tensor<128x256xf32> {linalg.buffer_layout = affine_map<(d0, d1) -> (d0, d1)>, linalg.inplaceable = false}, %B: tensor<256x192xf32> {linalg.buffer_layout = affine_map<(d0, d1) -> (d0, d1)>, linalg.inplaceable = false}, %C: tensor<128x192xf32> {linalg.buffer_layout = affine_map<(d0, d1) -> (d0, d1)>, linalg.inplaceable = true}) -> tensor<128x192xf32> { %c0 = constant 0 : index %c256 = constant 256 : index %c32 = constant 32 : index %cst = constant 0.000000e+00 : f32 %c128 = constant 128 : index %c192 = constant 192 : index %c8 = constant 8 : index %c16 = constant 16 : index // CHECK: scf.for %[[I:.*]] = %0 = scf.for %arg3 = %c0 to %c128 step %c8 iter_args(%arg4 = %C) -> (tensor<128x192xf32>) { %1 = tensor.extract_slice %A[%arg3, 0] [8, 256] [1, 1] : tensor<128x256xf32> to tensor<8x256xf32> // CHECK: scf.for %[[J:.*]] = %2 = scf.for %arg5 = %c0 to %c192 step %c16 iter_args(%arg6 = %arg4) -> (tensor<128x192xf32>) { %3 = tensor.extract_slice %B[0, %arg5] [256, 16] [1, 1] : tensor<256x192xf32> to tensor<256x16xf32> // %4 does not match an insert_slice, it cannot be bufferized inplace and needs to alloc. // CHECK: %[[ALLOC:.*]] = memref.alloc() : memref<8x16xf32> // CHECK: %[[T:.*]] = memref.subview %[[C]][%[[I]], %[[J]]] [8, 16] [1, 1] // TODO: %4 is never read but just overwritten, this copy can be elided. // CHECK: linalg.copy(%[[T]], %[[ALLOC]]) %4 = tensor.extract_slice %C[%arg3, %arg5] [8, 16] [1, 1] : tensor<128x192xf32> to tensor<8x16xf32> // linalg.fill is inplace. // CHECK: linalg.fill(%{{.*}}, %[[ALLOC]]) : f32, memref<8x16xf32> %5 = linalg.fill(%cst, %4) : f32, tensor<8x16xf32> -> tensor<8x16xf32> // CHECK: scf.for %[[K:.*]] = %6 = scf.for %arg7 = %c0 to %c256 step %c32 iter_args(%arg8 = %5) -> (tensor<8x16xf32>) { %8 = tensor.extract_slice %1[0, %arg7] [8, 32] [1, 1] : tensor<8x256xf32> to tensor<8x32xf32> %9 = tensor.extract_slice %3[%arg7, 0] [32, 16] [1, 1] : tensor<256x16xf32> to tensor<32x16xf32> // linalg.matmul is inplace as well as the enclosing scf.for. // CHECK: linalg.matmul ins({{.*}} outs(%[[ALLOC]] %10 = linalg.matmul ins(%8, %9 : tensor<8x32xf32>, tensor<32x16xf32>) outs(%arg8 : tensor<8x16xf32>) -> tensor<8x16xf32> scf.yield %10 : tensor<8x16xf32> } // insert_slice is inplace but its source comes from an equivalent buffer // that is not in place. So we must insert a copy of the small buffer into // the bigger buffer. // CHECK: linalg.copy(%[[ALLOC]], %[[T]]) %7 = tensor.insert_slice %6 into %arg6[%arg3, %arg5] [8, 16] [1, 1] : tensor<8x16xf32> into tensor<128x192xf32> // CHECK: memref.dealloc %[[ALLOC]] scf.yield %7 : tensor<128x192xf32> } scf.yield %2 : tensor<128x192xf32> } return %0 : tensor<128x192xf32> }