blob: a0b6141b9a13152d0313bdda7483f83a53ccda1d [file] [log] [blame]
// RUN: mlir-opt -allow-unregistered-dialect %s -affine-loop-fusion -split-input-file | FileCheck %s
// RUN: mlir-opt -allow-unregistered-dialect %s -affine-loop-fusion="fusion-maximal" -split-input-file | FileCheck %s --check-prefix=MAXIMAL
// Part I of fusion tests in mlir/test/Transforms/loop-fusion.mlir.
// Part II of fusion tests in mlir/test/Transforms/loop-fusion-2.mlir
// Part IV of fusion tests in mlir/test/Transforms/loop-fusion-4.mlir
// -----
// Test case from github bug 777.
// CHECK-LABEL: func @mul_add_0
func @mul_add_0(%arg0: memref<3x4xf32>, %arg1: memref<4x3xf32>, %arg2: memref<3x3xf32>, %arg3: memref<3x3xf32>) {
%cst = arith.constant 0.000000e+00 : f32
%0 = memref.alloc() : memref<3x3xf32>
affine.for %arg4 = 0 to 3 {
affine.for %arg5 = 0 to 3 {
affine.store %cst, %0[%arg4, %arg5] : memref<3x3xf32>
}
}
affine.for %arg4 = 0 to 3 {
affine.for %arg5 = 0 to 3 {
affine.for %arg6 = 0 to 4 {
%1 = affine.load %arg1[%arg6, %arg5] : memref<4x3xf32>
%2 = affine.load %arg0[%arg4, %arg6] : memref<3x4xf32>
%3 = arith.mulf %2, %1 : f32
%4 = affine.load %0[%arg4, %arg5] : memref<3x3xf32>
%5 = arith.addf %4, %3 : f32
affine.store %5, %0[%arg4, %arg5] : memref<3x3xf32>
}
}
}
affine.for %arg4 = 0 to 3 {
affine.for %arg5 = 0 to 3 {
%6 = affine.load %arg2[%arg4, %arg5] : memref<3x3xf32>
%7 = affine.load %0[%arg4, %arg5] : memref<3x3xf32>
%8 = arith.addf %7, %6 : f32
affine.store %8, %arg3[%arg4, %arg5] : memref<3x3xf32>
}
}
// CHECK: affine.for %[[i0:.*]] = 0 to 3 {
// CHECK-NEXT: affine.for %[[i1:.*]] = 0 to 3 {
// CHECK-NEXT: affine.store %{{.*}}, %{{.*}}[0, 0] : memref<1x1xf32>
// CHECK-NEXT: affine.for %[[i2:.*]] = 0 to 4 {
// CHECK-NEXT: affine.load %{{.*}}[%[[i2]], %[[i1]]] : memref<4x3xf32>
// CHECK-NEXT: affine.load %{{.*}}[%[[i0]], %[[i2]]] : memref<3x4xf32>
// CHECK-NEXT: arith.mulf %{{.*}}, %{{.*}} : f32
// CHECK-NEXT: affine.load %{{.*}}[0, 0] : memref<1x1xf32>
// CHECK-NEXT: arith.addf %{{.*}}, %{{.*}} : f32
// CHECK-NEXT: affine.store %{{.*}}, %{{.*}}[0, 0] : memref<1x1xf32>
// CHECK-NEXT: }
// CHECK-NEXT: affine.load %{{.*}}[%[[i0]], %[[i1]]] : memref<3x3xf32>
// CHECK-NEXT: affine.load %{{.*}}[0, 0] : memref<1x1xf32>
// CHECK-NEXT: arith.addf %{{.*}}, %{{.*}} : f32
// CHECK-NEXT: affine.store %{{.*}}, %{{.*}}[%[[i0]], %[[i1]]] : memref<3x3xf32>
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: return
return
}
// -----
// Verify that 'fuseProducerConsumerNodes' fuse a producer loop with a store
// that has multiple outgoing edges.
// CHECK-LABEL: func @should_fuse_multi_outgoing_edge_store_producer
func @should_fuse_multi_outgoing_edge_store_producer(%a : memref<1xf32>) {
%cst = arith.constant 0.000000e+00 : f32
affine.for %arg0 = 0 to 1 {
affine.store %cst, %a[%arg0] : memref<1xf32>
}
affine.for %arg0 = 0 to 1 {
%0 = affine.load %a[%arg0] : memref<1xf32>
}
affine.for %arg0 = 0 to 1 {
%0 = affine.load %a[%arg0] : memref<1xf32>
}
// CHECK: affine.for %{{.*}} = 0 to 1 {
// CHECK-NEXT: affine.store
// CHECK-NEXT: affine.load
// CHECK-NEXT: affine.load
// CHECK-NEXT: }
return
}
// -----
// Verify that 'fuseProducerConsumerNodes' fuses a producer loop that: 1) has
// multiple outgoing edges, 2) producer store has a single outgoing edge.
// Sibling loop fusion should not fuse any of these loops due to
// dependencies on external memrefs '%a' and '%b'.
// CHECK-LABEL: func @should_fuse_producer_with_multi_outgoing_edges
func @should_fuse_producer_with_multi_outgoing_edges(%a : memref<1xf32>, %b : memref<1xf32>) {
%cst = arith.constant 0.000000e+00 : f32
affine.for %arg0 = 0 to 1 {
%0 = affine.load %a[%arg0] : memref<1xf32>
affine.store %cst, %b[%arg0] : memref<1xf32>
}
affine.for %arg0 = 0 to 1 {
affine.store %cst, %a[%arg0] : memref<1xf32>
%1 = affine.load %b[%arg0] : memref<1xf32>
}
// CHECK: affine.for %{{.*}} = 0 to 1
// CHECK-NEXT: affine.load %[[A:.*]][{{.*}}]
// CHECK-NEXT: affine.store %{{.*}}, %[[B:.*]][{{.*}}]
// CHECK-NEXT: affine.store %{{.*}}, %[[A]]
// CHECK-NEXT: affine.load %[[B]]
// CHECK-NOT: affine.for %{{.*}}
// CHECK: return
return
}
// MAXIMAL-LABEL: func @reshape_into_matmul
func @reshape_into_matmul(%lhs : memref<1024x1024xf32>,
%R: memref<16x64x1024xf32>, %out: memref<1024x1024xf32>) {
%rhs = memref.alloc() : memref<1024x1024xf32>
// Reshape from 3-d to 2-d.
affine.for %i0 = 0 to 16 {
affine.for %i1 = 0 to 64 {
affine.for %k = 0 to 1024 {
%v = affine.load %R[%i0, %i1, %k] : memref<16x64x1024xf32>
affine.store %v, %rhs[64*%i0 + %i1, %k] : memref<1024x1024xf32>
}
}
}
// Matmul.
affine.for %i = 0 to 1024 {
affine.for %j = 0 to 1024 {
affine.for %k = 0 to 1024 {
%0 = affine.load %rhs[%k, %j] : memref<1024x1024xf32>
%1 = affine.load %lhs[%i, %k] : memref<1024x1024xf32>
%2 = arith.mulf %1, %0 : f32
%3 = affine.load %out[%i, %j] : memref<1024x1024xf32>
%4 = arith.addf %3, %2 : f32
affine.store %4, %out[%i, %j] : memref<1024x1024xf32>
}
}
}
return
}
// MAXIMAL-NEXT: memref.alloc
// MAXIMAL-NEXT: affine.for
// MAXIMAL-NEXT: affine.for
// MAXIMAL-NEXT: affine.for
// MAXIMAL-NOT: affine.for
// MAXIMAL: return
// -----
// CHECK-LABEL: func @vector_loop
func @vector_loop(%a : memref<10x20xf32>, %b : memref<10x20xf32>,
%c : memref<10x20xf32>) {
affine.for %j = 0 to 10 {
affine.for %i = 0 to 5 {
%ld0 = affine.vector_load %a[%j, %i*4] : memref<10x20xf32>, vector<4xf32>
affine.vector_store %ld0, %b[%j, %i*4] : memref<10x20xf32>, vector<4xf32>
}
}
affine.for %j = 0 to 10 {
affine.for %i = 0 to 5 {
%ld0 = affine.vector_load %b[%j, %i*4] : memref<10x20xf32>, vector<4xf32>
affine.vector_store %ld0, %c[%j, %i*4] : memref<10x20xf32>, vector<4xf32>
}
}
return
}
// CHECK: affine.for
// CHECK-NEXT: affine.for
// CHECK-NEXT: affine.vector_load
// CHECK-NEXT: affine.vector_store
// CHECK-NEXT: affine.vector_load
// CHECK-NEXT: affine.vector_store
// CHECK-NOT: affine.for
// -----
// CHECK-LABEL: func @multi_outgoing_edges
func @multi_outgoing_edges(%in0 : memref<32xf32>,
%in1 : memref<32xf32>) {
affine.for %d = 0 to 32 {
%lhs = affine.load %in0[%d] : memref<32xf32>
%rhs = affine.load %in1[%d] : memref<32xf32>
%add = arith.addf %lhs, %rhs : f32
affine.store %add, %in0[%d] : memref<32xf32>
}
affine.for %d = 0 to 32 {
%lhs = affine.load %in0[%d] : memref<32xf32>
%rhs = affine.load %in1[%d] : memref<32xf32>
%add = arith.subf %lhs, %rhs : f32
affine.store %add, %in0[%d] : memref<32xf32>
}
affine.for %d = 0 to 32 {
%lhs = affine.load %in0[%d] : memref<32xf32>
%rhs = affine.load %in1[%d] : memref<32xf32>
%add = arith.mulf %lhs, %rhs : f32
affine.store %add, %in0[%d] : memref<32xf32>
}
affine.for %d = 0 to 32 {
%lhs = affine.load %in0[%d] : memref<32xf32>
%rhs = affine.load %in1[%d] : memref<32xf32>
%add = arith.divf %lhs, %rhs : f32
affine.store %add, %in0[%d] : memref<32xf32>
}
return
}
// CHECK: affine.for
// CHECK-NOT: affine.for
// CHECK: arith.addf
// CHECK-NOT: affine.for
// CHECK: arith.subf
// CHECK-NOT: affine.for
// CHECK: arith.mulf
// CHECK-NOT: affine.for
// CHECK: arith.divf
// -----
// Test fusion when dynamically shaped memrefs are used with constant trip count loops.
// CHECK-LABEL: func @calc
func @calc(%arg0: memref<?xf32>, %arg1: memref<?xf32>, %arg2: memref<?xf32>, %len: index) {
%c1 = arith.constant 1 : index
%1 = memref.alloc(%len) : memref<?xf32>
affine.for %arg4 = 1 to 10 {
%7 = affine.load %arg0[%arg4] : memref<?xf32>
%8 = affine.load %arg1[%arg4] : memref<?xf32>
%9 = arith.addf %7, %8 : f32
affine.store %9, %1[%arg4] : memref<?xf32>
}
affine.for %arg4 = 1 to 10 {
%7 = affine.load %1[%arg4] : memref<?xf32>
%8 = affine.load %arg1[%arg4] : memref<?xf32>
%9 = arith.mulf %7, %8 : f32
affine.store %9, %arg2[%arg4] : memref<?xf32>
}
return
}
// CHECK: memref.alloc() : memref<1xf32>
// CHECK: affine.for %arg{{.*}} = 1 to 10 {
// CHECK-NEXT: affine.load %arg{{.*}}
// CHECK-NEXT: affine.load %arg{{.*}}
// CHECK-NEXT: arith.addf
// CHECK-NEXT: affine.store %{{.*}}, %{{.*}}[0] : memref<1xf32>
// CHECK-NEXT: affine.load %{{.*}}[0] : memref<1xf32>
// CHECK-NEXT: affine.load %arg{{.*}}[%arg{{.*}}] : memref<?xf32>
// CHECK-NEXT: arith.mulf
// CHECK-NEXT: affine.store %{{.*}}, %arg{{.*}}[%arg{{.*}}] : memref<?xf32>
// CHECK-NEXT: }
// CHECK-NEXT: return
// -----
// CHECK-LABEL: func @should_not_fuse_since_non_affine_users
func @should_not_fuse_since_non_affine_users(%in0 : memref<32xf32>,
%in1 : memref<32xf32>) {
affine.for %d = 0 to 32 {
%lhs = affine.load %in0[%d] : memref<32xf32>
%rhs = affine.load %in1[%d] : memref<32xf32>
%add = arith.addf %lhs, %rhs : f32
affine.store %add, %in0[%d] : memref<32xf32>
}
affine.for %d = 0 to 32 {
%lhs = memref.load %in0[%d] : memref<32xf32>
%rhs = memref.load %in1[%d] : memref<32xf32>
%add = arith.subf %lhs, %rhs : f32
memref.store %add, %in0[%d] : memref<32xf32>
}
affine.for %d = 0 to 32 {
%lhs = affine.load %in0[%d] : memref<32xf32>
%rhs = affine.load %in1[%d] : memref<32xf32>
%add = arith.mulf %lhs, %rhs : f32
affine.store %add, %in0[%d] : memref<32xf32>
}
return
}
// CHECK: affine.for
// CHECK: arith.addf
// CHECK: affine.for
// CHECK: arith.subf
// CHECK: affine.for
// CHECK: arith.mulf
// -----
// CHECK-LABEL: func @should_not_fuse_since_top_level_non_affine_users
func @should_not_fuse_since_top_level_non_affine_users(%in0 : memref<32xf32>,
%in1 : memref<32xf32>) {
%sum = memref.alloc() : memref<f32>
affine.for %d = 0 to 32 {
%lhs = affine.load %in0[%d] : memref<32xf32>
%rhs = affine.load %in1[%d] : memref<32xf32>
%add = arith.addf %lhs, %rhs : f32
memref.store %add, %sum[] : memref<f32>
affine.store %add, %in0[%d] : memref<32xf32>
}
%load_sum = memref.load %sum[] : memref<f32>
affine.for %d = 0 to 32 {
%lhs = affine.load %in0[%d] : memref<32xf32>
%rhs = affine.load %in1[%d] : memref<32xf32>
%add = arith.mulf %lhs, %rhs : f32
%sub = arith.subf %add, %load_sum: f32
affine.store %sub, %in0[%d] : memref<32xf32>
}
memref.dealloc %sum : memref<f32>
return
}
// CHECK: affine.for
// CHECK: arith.addf
// CHECK: affine.for
// CHECK: arith.mulf
// CHECK: arith.subf
// -----
// CHECK-LABEL: func @should_not_fuse_since_top_level_non_affine_mem_write_users
func @should_not_fuse_since_top_level_non_affine_mem_write_users(
%in0 : memref<32xf32>, %in1 : memref<32xf32>) {
%c0 = arith.constant 0 : index
%cst_0 = arith.constant 0.000000e+00 : f32
affine.for %d = 0 to 32 {
%lhs = affine.load %in0[%d] : memref<32xf32>
%rhs = affine.load %in1[%d] : memref<32xf32>
%add = arith.addf %lhs, %rhs : f32
affine.store %add, %in0[%d] : memref<32xf32>
}
memref.store %cst_0, %in0[%c0] : memref<32xf32>
affine.for %d = 0 to 32 {
%lhs = affine.load %in0[%d] : memref<32xf32>
%rhs = affine.load %in1[%d] : memref<32xf32>
%add = arith.addf %lhs, %rhs: f32
affine.store %add, %in0[%d] : memref<32xf32>
}
return
}
// CHECK: affine.for
// CHECK: arith.addf
// CHECK: affine.for
// CHECK: arith.addf
// -----
// MAXIMAL-LABEL: func @fuse_minor_affine_map
func @fuse_minor_affine_map(%in: memref<128xf32>, %out: memref<20x512xf32>) {
%tmp = memref.alloc() : memref<128xf32>
affine.for %arg4 = 0 to 128 {
%ld = affine.load %in[%arg4] : memref<128xf32>
affine.store %ld, %tmp[%arg4] : memref<128xf32>
}
affine.for %arg3 = 0 to 20 {
affine.for %arg4 = 0 to 512 {
%ld = affine.load %tmp[%arg4 mod 128] : memref<128xf32>
affine.store %ld, %out[%arg3, %arg4] : memref<20x512xf32>
}
}
return
}
// TODO: The size of the private memref is not properly computed in the presence
// of the 'mod' operation. It should be memref<1xf32> instead of
// memref<128xf32>: https://bugs.llvm.org/show_bug.cgi?id=46973
// MAXIMAL: memref.alloc() : memref<128xf32>
// MAXIMAL: affine.for
// MAXIMAL-NEXT: affine.for
// MAXIMAL-NOT: affine.for
// MAXIMAL: return
// -----
// CHECK-LABEL: func @should_fuse_multi_store_producer_and_privatize_memfefs
func @should_fuse_multi_store_producer_and_privatize_memfefs() {
%a = memref.alloc() : memref<10xf32>
%b = memref.alloc() : memref<10xf32>
%c = memref.alloc() : memref<10xf32>
%cst = arith.constant 0.000000e+00 : f32
affine.for %arg0 = 0 to 10 {
affine.store %cst, %a[%arg0] : memref<10xf32>
affine.store %cst, %b[%arg0] : memref<10xf32>
affine.store %cst, %c[%arg0] : memref<10xf32>
%0 = affine.load %c[%arg0] : memref<10xf32>
}
affine.for %arg0 = 0 to 10 {
%0 = affine.load %a[%arg0] : memref<10xf32>
}
affine.for %arg0 = 0 to 10 {
%0 = affine.load %b[%arg0] : memref<10xf32>
}
// All the memrefs should be privatized except '%c', which is not involved in
// the producer-consumer fusion.
// CHECK: affine.for %{{.*}} = 0 to 10 {
// CHECK-NEXT: affine.store %{{.*}}, %{{.*}}[0] : memref<1xf32>
// CHECK-NEXT: affine.store %{{.*}}, %{{.*}}[0] : memref<1xf32>
// CHECK-NEXT: affine.store %{{.*}}, %{{.*}}[%{{.*}}] : memref<10xf32>
// CHECK-NEXT: affine.load %{{.*}}[%{{.*}}] : memref<10xf32>
// CHECK-NEXT: affine.load %{{.*}}[0] : memref<1xf32>
// CHECK-NEXT: affine.load %{{.*}}[0] : memref<1xf32>
// CHECK-NEXT: }
return
}
func @should_fuse_multi_store_producer_with_escaping_memrefs_and_remove_src(
%a : memref<10xf32>, %b : memref<10xf32>) {
%cst = arith.constant 0.000000e+00 : f32
affine.for %i0 = 0 to 10 {
affine.store %cst, %a[%i0] : memref<10xf32>
affine.store %cst, %b[%i0] : memref<10xf32>
}
affine.for %i1 = 0 to 10 {
%0 = affine.load %a[%i1] : memref<10xf32>
}
affine.for %i2 = 0 to 10 {
%0 = affine.load %b[%i2] : memref<10xf32>
}
// Producer loop '%i0' should be removed after fusion since fusion is maximal.
// No memref should be privatized since they escape the function, and the
// producer is removed after fusion.
// CHECK: affine.for %{{.*}} = 0 to 10 {
// CHECK-NEXT: affine.store %{{.*}}, %{{.*}}[%{{.*}}] : memref<10xf32>
// CHECK-NEXT: affine.store %{{.*}}, %{{.*}}[%{{.*}}] : memref<10xf32>
// CHECK-NEXT: affine.load %{{.*}}[%{{.*}}] : memref<10xf32>
// CHECK-NEXT: affine.load %{{.*}}[%{{.*}}] : memref<10xf32>
// CHECK-NEXT: }
// CHECK-NOT: affine.for
return
}
// -----
func @should_fuse_multi_store_producer_with_escaping_memrefs_and_preserve_src(
%a : memref<10xf32>, %b : memref<10xf32>) {
%cst = arith.constant 0.000000e+00 : f32
affine.for %i0 = 0 to 10 {
affine.store %cst, %a[%i0] : memref<10xf32>
affine.store %cst, %b[%i0] : memref<10xf32>
}
affine.for %i1 = 0 to 5 {
%0 = affine.load %a[%i1] : memref<10xf32>
}
affine.for %i2 = 0 to 10 {
%0 = affine.load %b[%i2] : memref<10xf32>
}
// Loops '%i0' and '%i2' should be fused first and '%i0' should be removed
// since fusion is maximal. Then the fused loop and '%i1' should be fused
// and the fused loop shouldn't be removed since fusion is not maximal.
// CHECK: affine.for %{{.*}} = 0 to 10 {
// CHECK-NEXT: affine.store %{{.*}}, %{{.*}}[%{{.*}}] : memref<10xf32>
// CHECK-NEXT: affine.store %{{.*}}, %{{.*}}[%{{.*}}] : memref<10xf32>
// CHECK-NEXT: affine.load %{{.*}}[%{{.*}}] : memref<10xf32>
// CHECK-NEXT: }
// CHECK: affine.for %{{.*}} = 0 to 5 {
// CHECK-NEXT: affine.store %{{.*}} : memref<1xf32>
// CHECK-NEXT: affine.store %{{.*}} : memref<10xf32>
// CHECK-NEXT: affine.load %{{.*}} : memref<10xf32>
// CHECK-NEXT: affine.load %{{.*}} : memref<1xf32>
// CHECK-NEXT: }
// CHECK-NOT: affine.for
return
}
func @should_not_fuse_due_to_dealloc(%arg0: memref<16xf32>){
%A = memref.alloc() : memref<16xf32>
%C = memref.alloc() : memref<16xf32>
%cst_1 = arith.constant 1.000000e+00 : f32
affine.for %arg1 = 0 to 16 {
%a = affine.load %arg0[%arg1] : memref<16xf32>
affine.store %a, %A[%arg1] : memref<16xf32>
affine.store %a, %C[%arg1] : memref<16xf32>
}
memref.dealloc %C : memref<16xf32>
%B = memref.alloc() : memref<16xf32>
affine.for %arg1 = 0 to 16 {
%a = affine.load %A[%arg1] : memref<16xf32>
%b = arith.addf %cst_1, %a : f32
affine.store %b, %B[%arg1] : memref<16xf32>
}
memref.dealloc %A : memref<16xf32>
return
}
// CHECK-LABEL: func @should_not_fuse_due_to_dealloc
// CHECK: affine.for
// CHECK-NEXT: affine.load
// CHECK-NEXT: affine.store
// CHECK-NEXT: affine.store
// CHECK: memref.dealloc
// CHECK: affine.for
// CHECK-NEXT: affine.load
// CHECK-NEXT: arith.addf
// CHECK-NEXT: affine.store
// -----
// CHECK-LABEL: func @should_fuse_defining_node_has_no_dependence_from_source_node
func @should_fuse_defining_node_has_no_dependence_from_source_node(
%a : memref<10xf32>, %b : memref<f32>) -> () {
affine.for %i0 = 0 to 10 {
%0 = affine.load %b[] : memref<f32>
affine.store %0, %a[%i0] : memref<10xf32>
}
%0 = affine.load %b[] : memref<f32>
affine.for %i1 = 0 to 10 {
%1 = affine.load %a[%i1] : memref<10xf32>
%2 = arith.divf %0, %1 : f32
}
// Loops '%i0' and '%i1' should be fused even though there is a defining
// node between the loops. It is because the node has no dependence from '%i0'.
// CHECK: affine.load %{{.*}}[] : memref<f32>
// CHECK-NEXT: affine.for %{{.*}} = 0 to 10 {
// CHECK-NEXT: affine.load %{{.*}}[] : memref<f32>
// CHECK-NEXT: affine.store %{{.*}}, %{{.*}}[%{{.*}}] : memref<10xf32>
// CHECK-NEXT: affine.load %{{.*}}[%{{.*}}] : memref<10xf32>
// CHECK-NEXT: arith.divf
// CHECK-NEXT: }
// CHECK-NOT: affine.for
return
}
// -----
// CHECK-LABEL: func @should_not_fuse_defining_node_has_dependence_from_source_loop
func @should_not_fuse_defining_node_has_dependence_from_source_loop(
%a : memref<10xf32>, %b : memref<f32>) -> () {
%cst = arith.constant 0.000000e+00 : f32
affine.for %i0 = 0 to 10 {
affine.store %cst, %b[] : memref<f32>
affine.store %cst, %a[%i0] : memref<10xf32>
}
%0 = affine.load %b[] : memref<f32>
affine.for %i1 = 0 to 10 {
%1 = affine.load %a[%i1] : memref<10xf32>
%2 = arith.divf %0, %1 : f32
}
// Loops '%i0' and '%i1' should not be fused because the defining node
// of '%0' used in '%i1' has dependence from loop '%i0'.
// CHECK: affine.for %{{.*}} = 0 to 10 {
// CHECK-NEXT: affine.store %{{.*}}, %{{.*}}[] : memref<f32>
// CHECK-NEXT: affine.store %{{.*}}, %{{.*}}[%{{.*}}] : memref<10xf32>
// CHECK-NEXT: }
// CHECK-NEXT: affine.load %{{.*}}[] : memref<f32>
// CHECK: affine.for %{{.*}} = 0 to 10 {
// CHECK-NEXT: affine.load %{{.*}}[%{{.*}}] : memref<10xf32>
// CHECK-NEXT: arith.divf
// CHECK-NEXT: }
return
}
// -----
// CHECK-LABEL: func @should_not_fuse_defining_node_has_transitive_dependence_from_source_loop
func @should_not_fuse_defining_node_has_transitive_dependence_from_source_loop(
%a : memref<10xf32>, %b : memref<10xf32>, %c : memref<f32>) -> () {
%cst = arith.constant 0.000000e+00 : f32
affine.for %i0 = 0 to 10 {
affine.store %cst, %a[%i0] : memref<10xf32>
affine.store %cst, %b[%i0] : memref<10xf32>
}
affine.for %i1 = 0 to 10 {
%1 = affine.load %b[%i1] : memref<10xf32>
affine.store %1, %c[] : memref<f32>
}
%0 = affine.load %c[] : memref<f32>
affine.for %i2 = 0 to 10 {
%1 = affine.load %a[%i2] : memref<10xf32>
%2 = arith.divf %0, %1 : f32
}
// When loops '%i0' and '%i2' are evaluated first, they should not be
// fused. The defining node of '%0' in loop '%i2' has transitive dependence
// from loop '%i0'. After that, loops '%i0' and '%i1' are evaluated, and they
// will be fused as usual.
// CHECK: affine.for %{{.*}} = 0 to 10 {
// CHECK-NEXT: affine.store %{{.*}}, %{{.*}}[%{{.*}}] : memref<10xf32>
// CHECK-NEXT: affine.store %{{.*}}, %{{.*}}[%{{.*}}] : memref<10xf32>
// CHECK-NEXT: affine.load %{{.*}}[%{{.*}}] : memref<10xf32>
// CHECK-NEXT: affine.store %{{.*}}, %{{.*}}[] : memref<f32>
// CHECK-NEXT: }
// CHECK-NEXT: affine.load %{{.*}}[] : memref<f32>
// CHECK: affine.for %{{.*}} = 0 to 10 {
// CHECK-NEXT: affine.load %{{.*}}[%{{.*}}] : memref<10xf32>
// CHECK-NEXT: arith.divf
// CHECK-NEXT: }
// CHECK-NOT: affine.for
return
}
// -----
// CHECK-LABEL: func @should_not_fuse_dest_loop_nest_return_value
func @should_not_fuse_dest_loop_nest_return_value(
%a : memref<10xf32>) -> () {
%cst = arith.constant 0.000000e+00 : f32
affine.for %i0 = 0 to 10 {
affine.store %cst, %a[%i0] : memref<10xf32>
}
%b = affine.for %i1 = 0 to 10 step 2 iter_args(%b_iter = %cst) -> f32 {
%load_a = affine.load %a[%i1] : memref<10xf32>
affine.yield %load_a: f32
}
// CHECK: affine.for %{{.*}} = 0 to 10 {
// CHECK-NEXT: affine.store %{{.*}}, %{{.*}}[%{{.*}}] : memref<10xf32>
// CHECK-NEXT: }
// CHECK: affine.for %{{.*}} = 0 to 10 step 2 iter_args(%{{.*}} = %{{.*}}) -> (f32) {
// CHECK-NEXT: affine.load
// CHECK-NEXT: affine.yield
// CHECK-NEXT: }
return
}
// -----
// CHECK-LABEL: func @should_not_fuse_src_loop_nest_return_value
func @should_not_fuse_src_loop_nest_return_value(
%a : memref<10xf32>) -> () {
%cst = arith.constant 1.000000e+00 : f32
%b = affine.for %i = 0 to 10 step 2 iter_args(%b_iter = %cst) -> f32 {
%c = arith.addf %b_iter, %b_iter : f32
affine.store %c, %a[%i] : memref<10xf32>
affine.yield %c: f32
}
affine.for %i1 = 0 to 10 {
%1 = affine.load %a[%i1] : memref<10xf32>
}
// CHECK: %{{.*}} = affine.for %{{.*}} = 0 to 10 step 2 iter_args(%{{.*}} = %{{.*}}) -> (f32) {
// CHECK-NEXT: %{{.*}} = arith.addf %{{.*}}, %{{.*}} : f32
// CHECK-NEXT: affine.store %{{.*}}, %{{.*}}[%{{.*}}] : memref<10xf32>
// CHECK-NEXT: affine.yield %{{.*}} : f32
// CHECK-NEXT: }
// CHECK: affine.for %{{.*}} = 0 to 10 {
// CHECK-NEXT: affine.load %{{.*}}[%{{.*}}] : memref<10xf32>
// CHECK-NEXT: }
return
}
// -----
func private @some_function(memref<16xf32>)
func @call_op_prevents_fusion(%arg0: memref<16xf32>){
%A = memref.alloc() : memref<16xf32>
%cst_1 = arith.constant 1.000000e+00 : f32
affine.for %arg1 = 0 to 16 {
%a = affine.load %arg0[%arg1] : memref<16xf32>
affine.store %a, %A[%arg1] : memref<16xf32>
}
call @some_function(%A) : (memref<16xf32>) -> ()
%B = memref.alloc() : memref<16xf32>
affine.for %arg1 = 0 to 16 {
%a = affine.load %A[%arg1] : memref<16xf32>
%b = arith.addf %cst_1, %a : f32
affine.store %b, %B[%arg1] : memref<16xf32>
}
return
}
// CHECK-LABEL: func @call_op_prevents_fusion
// CHECK: affine.for
// CHECK-NEXT: affine.load
// CHECK-NEXT: affine.store
// CHECK: call
// CHECK: affine.for
// CHECK-NEXT: affine.load
// CHECK-NEXT: arith.addf
// CHECK-NEXT: affine.store
// -----
func private @some_function()
func @call_op_does_not_prevent_fusion(%arg0: memref<16xf32>){
%A = memref.alloc() : memref<16xf32>
%cst_1 = arith.constant 1.000000e+00 : f32
affine.for %arg1 = 0 to 16 {
%a = affine.load %arg0[%arg1] : memref<16xf32>
affine.store %a, %A[%arg1] : memref<16xf32>
}
call @some_function() : () -> ()
%B = memref.alloc() : memref<16xf32>
affine.for %arg1 = 0 to 16 {
%a = affine.load %A[%arg1] : memref<16xf32>
%b = arith.addf %cst_1, %a : f32
affine.store %b, %B[%arg1] : memref<16xf32>
}
return
}
// CHECK-LABEL: func @call_op_does_not_prevent_fusion
// CHECK: affine.for
// CHECK-NOT: affine.for
// -----
// Test for source that writes to an escaping memref and has two consumers.
// Fusion should create private memrefs in place of `%arg0` since the source is
// not to be removed after fusion and the destinations do not write to `%arg0`.
// This should enable both the consumers to benefit from fusion, which would not
// be possible if private memrefs were not created.
func @should_fuse_with_both_consumers_separately(%arg0: memref<10xf32>) {
%cf7 = arith.constant 7.0 : f32
affine.for %i0 = 0 to 10 {
affine.store %cf7, %arg0[%i0] : memref<10xf32>
}
affine.for %i1 = 0 to 7 {
%v0 = affine.load %arg0[%i1] : memref<10xf32>
}
affine.for %i1 = 5 to 9 {
%v0 = affine.load %arg0[%i1] : memref<10xf32>
}
return
}
// CHECK-LABEL: func @should_fuse_with_both_consumers_separately
// CHECK: affine.for
// CHECK-NEXT: affine.store
// CHECK: affine.for
// CHECK-NEXT: affine.store
// CHECK-NEXT: affine.load
// CHECK: affine.for
// CHECK-NEXT: affine.store
// CHECK-NEXT: affine.load
// -----
// Fusion is avoided when the slice computed is invalid. Comments below describe
// incorrect backward slice computation. Similar logic applies for forward slice
// as well.
func @no_fusion_cannot_compute_valid_slice() {
%A = memref.alloc() : memref<5xf32>
%B = memref.alloc() : memref<6xf32>
%C = memref.alloc() : memref<5xf32>
%cst = arith.constant 0. : f32
affine.for %arg0 = 0 to 5 {
%a = affine.load %A[%arg0] : memref<5xf32>
affine.store %a, %B[%arg0 + 1] : memref<6xf32>
}
affine.for %arg0 = 0 to 5 {
// Backward slice computed will be:
// slice ( src loop: 0, dst loop: 1, depth: 1 : insert point: (1, 0)
// loop bounds: [(d0) -> (d0 - 1), (d0) -> (d0)] )
// Resulting fusion would be as below. It is easy to note the out-of-bounds
// access by 'affine.load'.
// #map0 = affine_map<(d0) -> (d0 - 1)>
// #map1 = affine_map<(d0) -> (d0)>
// affine.for %arg1 = #map0(%arg0) to #map1(%arg0) {
// %5 = affine.load %1[%arg1] : memref<5xf32>
// ...
// ...
// }
%a = affine.load %B[%arg0] : memref<6xf32>
%b = arith.mulf %a, %cst : f32
affine.store %b, %C[%arg0] : memref<5xf32>
}
return
}
// CHECK-LABEL: func @no_fusion_cannot_compute_valid_slice
// CHECK: affine.for
// CHECK-NEXT: affine.load
// CHECK-NEXT: affine.store
// CHECK: affine.for
// CHECK-NEXT: affine.load
// CHECK-NEXT: arith.mulf
// CHECK-NEXT: affine.store
// MAXIMAL-LABEL: func @reduce_add_f32_f32(
func @reduce_add_f32_f32(%arg0: memref<64x64xf32, 1>, %arg1: memref<1x64xf32, 1>, %arg2: memref<1x64xf32, 1>) {
%cst_0 = arith.constant 0.000000e+00 : f32
%cst_1 = arith.constant 1.000000e+00 : f32
%0 = memref.alloca() : memref<f32, 1>
%1 = memref.alloca() : memref<f32, 1>
affine.for %arg3 = 0 to 1 {
affine.for %arg4 = 0 to 64 {
%accum = affine.for %arg5 = 0 to 64 iter_args (%prevAccum = %cst_0) -> f32 {
%4 = affine.load %arg0[%arg5, %arg4] : memref<64x64xf32, 1>
%5 = arith.addf %prevAccum, %4 : f32
affine.yield %5 : f32
}
%accum_dbl = arith.addf %accum, %accum : f32
affine.store %accum_dbl, %arg1[%arg3, %arg4] : memref<1x64xf32, 1>
}
}
affine.for %arg3 = 0 to 1 {
affine.for %arg4 = 0 to 64 {
%accum = affine.for %arg5 = 0 to 64 iter_args (%prevAccum = %cst_1) -> f32 {
%4 = affine.load %arg0[%arg5, %arg4] : memref<64x64xf32, 1>
%5 = arith.mulf %prevAccum, %4 : f32
affine.yield %5 : f32
}
%accum_sqr = arith.mulf %accum, %accum : f32
affine.store %accum_sqr, %arg2[%arg3, %arg4] : memref<1x64xf32, 1>
}
}
return
}
// The two loops here get maximally sibling-fused at the innermost
// insertion point. Test checks if the innermost reduction loop of the fused loop
// gets promoted into its outerloop.
// MAXIMAL-SAME: %[[arg_0:.*]]: memref<64x64xf32, 1>,
// MAXIMAL-SAME: %[[arg_1:.*]]: memref<1x64xf32, 1>,
// MAXIMAL-SAME: %[[arg_2:.*]]: memref<1x64xf32, 1>) {
// MAXIMAL: %[[cst:.*]] = arith.constant 0 : index
// MAXIMAL-NEXT: %[[cst_0:.*]] = arith.constant 0.000000e+00 : f32
// MAXIMAL-NEXT: %[[cst_1:.*]] = arith.constant 1.000000e+00 : f32
// MAXIMAL: affine.for %[[idx_0:.*]] = 0 to 1 {
// MAXIMAL-NEXT: affine.for %[[idx_1:.*]] = 0 to 64 {
// MAXIMAL-NEXT: %[[results:.*]]:2 = affine.for %[[idx_2:.*]] = 0 to 64 iter_args(%[[iter_0:.*]] = %[[cst_1]], %[[iter_1:.*]] = %[[cst_0]]) -> (f32, f32) {
// MAXIMAL-NEXT: %[[val_0:.*]] = affine.load %[[arg_0]][%[[idx_2]], %[[idx_1]]] : memref<64x64xf32, 1>
// MAXIMAL-NEXT: %[[reduc_0:.*]] = arith.addf %[[iter_1]], %[[val_0]] : f32
// MAXIMAL-NEXT: %[[val_1:.*]] = affine.load %[[arg_0]][%[[idx_2]], %[[idx_1]]] : memref<64x64xf32, 1>
// MAXIMAL-NEXT: %[[reduc_1:.*]] = arith.mulf %[[iter_0]], %[[val_1]] : f32
// MAXIMAL-NEXT: affine.yield %[[reduc_1]], %[[reduc_0]] : f32, f32
// MAXIMAL-NEXT: }
// MAXIMAL-NEXT: %[[reduc_0_dbl:.*]] = arith.addf %[[results:.*]]#1, %[[results]]#1 : f32
// MAXIMAL-NEXT: affine.store %[[reduc_0_dbl]], %[[arg_1]][%[[cst]], %[[idx_1]]] : memref<1x64xf32, 1>
// MAXIMAL-NEXT: %[[reduc_1_sqr:.*]] = arith.mulf %[[results]]#0, %[[results]]#0 : f32
// MAXIMAL-NEXT: affine.store %[[reduc_1_sqr]], %[[arg_2]][%[[idx_0]], %[[idx_1]]] : memref<1x64xf32, 1>
// MAXIMAL-NEXT: }
// MAXIMAL-NEXT: }
// MAXIMAL-NEXT: return
// MAXIMAL-NEXT: }
// -----
// CHECK-LABEL: func @reduce_add_non_innermost
func @reduce_add_non_innermost(%arg0: memref<64x64xf32, 1>, %arg1: memref<1x64xf32, 1>, %arg2: memref<1x64xf32, 1>) {
%cst = arith.constant 0.000000e+00 : f32
%cst_0 = arith.constant 1.000000e+00 : f32
%0 = memref.alloca() : memref<f32, 1>
%1 = memref.alloca() : memref<f32, 1>
affine.for %arg3 = 0 to 1 {
affine.for %arg4 = 0 to 64 {
%accum = affine.for %arg5 = 0 to 64 iter_args (%prevAccum = %cst) -> f32 {
%4 = affine.load %arg0[%arg5, %arg4] : memref<64x64xf32, 1>
%5 = arith.addf %prevAccum, %4 : f32
affine.yield %5 : f32
}
%accum_dbl = arith.addf %accum, %accum : f32
affine.store %accum_dbl, %arg1[%arg3, %arg4] : memref<1x64xf32, 1>
}
}
affine.for %arg3 = 0 to 1 {
affine.for %arg4 = 0 to 64 {
%accum = affine.for %arg5 = 0 to 64 iter_args (%prevAccum = %cst_0) -> f32 {
%4 = affine.load %arg0[%arg5, %arg4] : memref<64x64xf32, 1>
%5 = arith.mulf %prevAccum, %4 : f32
affine.yield %5 : f32
}
%accum_sqr = arith.mulf %accum, %accum : f32
affine.store %accum_sqr, %arg2[%arg3, %arg4] : memref<1x64xf32, 1>
}
}
return
}
// Test checks the loop structure is preserved after sibling fusion.
// CHECK: affine.for
// CHECK-NEXT: affine.for
// CHECK-NEXT: affine.for
// CHECK affine.for
// -----
// CHECK-LABEL: func @fuse_large_number_of_loops
func @fuse_large_number_of_loops(%arg0: memref<20x10xf32, 1>, %arg1: memref<20x10xf32, 1>, %arg2: memref<20x10xf32, 1>, %arg3: memref<20x10xf32, 1>, %arg4: memref<20x10xf32, 1>, %arg5: memref<f32, 1>, %arg6: memref<f32, 1>, %arg7: memref<f32, 1>, %arg8: memref<f32, 1>, %arg9: memref<20x10xf32, 1>, %arg10: memref<20x10xf32, 1>, %arg11: memref<20x10xf32, 1>, %arg12: memref<20x10xf32, 1>) {
%cst = arith.constant 1.000000e+00 : f32
%0 = memref.alloc() : memref<f32, 1>
affine.store %cst, %0[] : memref<f32, 1>
%1 = memref.alloc() : memref<20x10xf32, 1>
affine.for %arg13 = 0 to 20 {
affine.for %arg14 = 0 to 10 {
%21 = affine.load %arg6[] : memref<f32, 1>
affine.store %21, %1[%arg13, %arg14] : memref<20x10xf32, 1>
}
}
%2 = memref.alloc() : memref<20x10xf32, 1>
affine.for %arg13 = 0 to 20 {
affine.for %arg14 = 0 to 10 {
%21 = affine.load %1[%arg13, %arg14] : memref<20x10xf32, 1>
%22 = affine.load %arg3[%arg13, %arg14] : memref<20x10xf32, 1>
%23 = arith.mulf %22, %21 : f32
affine.store %23, %2[%arg13, %arg14] : memref<20x10xf32, 1>
}
}
%3 = memref.alloc() : memref<f32, 1>
%4 = affine.load %arg6[] : memref<f32, 1>
%5 = affine.load %0[] : memref<f32, 1>
%6 = arith.subf %5, %4 : f32
affine.store %6, %3[] : memref<f32, 1>
%7 = memref.alloc() : memref<20x10xf32, 1>
affine.for %arg13 = 0 to 20 {
affine.for %arg14 = 0 to 10 {
%21 = affine.load %3[] : memref<f32, 1>
affine.store %21, %7[%arg13, %arg14] : memref<20x10xf32, 1>
}
}
%8 = memref.alloc() : memref<20x10xf32, 1>
affine.for %arg13 = 0 to 20 {
affine.for %arg14 = 0 to 10 {
%21 = affine.load %arg1[%arg13, %arg14] : memref<20x10xf32, 1>
%22 = affine.load %7[%arg13, %arg14] : memref<20x10xf32, 1>
%23 = arith.mulf %22, %21 : f32
affine.store %23, %8[%arg13, %arg14] : memref<20x10xf32, 1>
}
}
%9 = memref.alloc() : memref<20x10xf32, 1>
affine.for %arg13 = 0 to 20 {
affine.for %arg14 = 0 to 10 {
%21 = affine.load %arg1[%arg13, %arg14] : memref<20x10xf32, 1>
%22 = affine.load %8[%arg13, %arg14] : memref<20x10xf32, 1>
%23 = arith.mulf %22, %21 : f32
affine.store %23, %9[%arg13, %arg14] : memref<20x10xf32, 1>
}
}
affine.for %arg13 = 0 to 20 {
affine.for %arg14 = 0 to 10 {
%21 = affine.load %9[%arg13, %arg14] : memref<20x10xf32, 1>
%22 = affine.load %2[%arg13, %arg14] : memref<20x10xf32, 1>
%23 = arith.addf %22, %21 : f32
affine.store %23, %arg11[%arg13, %arg14] : memref<20x10xf32, 1>
}
}
%10 = memref.alloc() : memref<20x10xf32, 1>
affine.for %arg13 = 0 to 20 {
affine.for %arg14 = 0 to 10 {
%21 = affine.load %1[%arg13, %arg14] : memref<20x10xf32, 1>
%22 = affine.load %arg2[%arg13, %arg14] : memref<20x10xf32, 1>
%23 = arith.mulf %22, %21 : f32
affine.store %23, %10[%arg13, %arg14] : memref<20x10xf32, 1>
}
}
affine.for %arg13 = 0 to 20 {
affine.for %arg14 = 0 to 10 {
%21 = affine.load %8[%arg13, %arg14] : memref<20x10xf32, 1>
%22 = affine.load %10[%arg13, %arg14] : memref<20x10xf32, 1>
%23 = arith.addf %22, %21 : f32
affine.store %23, %arg10[%arg13, %arg14] : memref<20x10xf32, 1>
}
}
%11 = memref.alloc() : memref<20x10xf32, 1>
affine.for %arg13 = 0 to 20 {
affine.for %arg14 = 0 to 10 {
%21 = affine.load %arg10[%arg13, %arg14] : memref<20x10xf32, 1>
%22 = affine.load %arg10[%arg13, %arg14] : memref<20x10xf32, 1>
%23 = arith.mulf %22, %21 : f32
affine.store %23, %11[%arg13, %arg14] : memref<20x10xf32, 1>
}
}
%12 = memref.alloc() : memref<20x10xf32, 1>
affine.for %arg13 = 0 to 20 {
affine.for %arg14 = 0 to 10 {
%21 = affine.load %11[%arg13, %arg14] : memref<20x10xf32, 1>
%22 = affine.load %arg11[%arg13, %arg14] : memref<20x10xf32, 1>
%23 = arith.subf %22, %21 : f32
affine.store %23, %12[%arg13, %arg14] : memref<20x10xf32, 1>
}
}
%13 = memref.alloc() : memref<20x10xf32, 1>
affine.for %arg13 = 0 to 20 {
affine.for %arg14 = 0 to 10 {
%21 = affine.load %arg7[] : memref<f32, 1>
affine.store %21, %13[%arg13, %arg14] : memref<20x10xf32, 1>
}
}
%14 = memref.alloc() : memref<20x10xf32, 1>
affine.for %arg13 = 0 to 20 {
affine.for %arg14 = 0 to 10 {
%21 = affine.load %arg4[%arg13, %arg14] : memref<20x10xf32, 1>
%22 = affine.load %13[%arg13, %arg14] : memref<20x10xf32, 1>
%23 = arith.mulf %22, %21 : f32
affine.store %23, %14[%arg13, %arg14] : memref<20x10xf32, 1>
}
}
%15 = memref.alloc() : memref<20x10xf32, 1>
affine.for %arg13 = 0 to 20 {
affine.for %arg14 = 0 to 10 {
%21 = affine.load %arg8[] : memref<f32, 1>
affine.store %21, %15[%arg13, %arg14] : memref<20x10xf32, 1>
}
}
%16 = memref.alloc() : memref<20x10xf32, 1>
affine.for %arg13 = 0 to 20 {
affine.for %arg14 = 0 to 10 {
%21 = affine.load %15[%arg13, %arg14] : memref<20x10xf32, 1>
%22 = affine.load %12[%arg13, %arg14] : memref<20x10xf32, 1>
%23 = arith.addf %22, %21 : f32
affine.store %23, %16[%arg13, %arg14] : memref<20x10xf32, 1>
}
}
%17 = memref.alloc() : memref<20x10xf32, 1>
affine.for %arg13 = 0 to 20 {
affine.for %arg14 = 0 to 10 {
%21 = affine.load %16[%arg13, %arg14] : memref<20x10xf32, 1>
%22 = math.sqrt %21 : f32
affine.store %22, %17[%arg13, %arg14] : memref<20x10xf32, 1>
}
}
%18 = memref.alloc() : memref<20x10xf32, 1>
affine.for %arg13 = 0 to 20 {
affine.for %arg14 = 0 to 10 {
%21 = affine.load %arg5[] : memref<f32, 1>
affine.store %21, %18[%arg13, %arg14] : memref<20x10xf32, 1>
}
}
%19 = memref.alloc() : memref<20x10xf32, 1>
affine.for %arg13 = 0 to 20 {
affine.for %arg14 = 0 to 10 {
%21 = affine.load %arg1[%arg13, %arg14] : memref<20x10xf32, 1>
%22 = affine.load %18[%arg13, %arg14] : memref<20x10xf32, 1>
%23 = arith.mulf %22, %21 : f32
affine.store %23, %19[%arg13, %arg14] : memref<20x10xf32, 1>
}
}
%20 = memref.alloc() : memref<20x10xf32, 1>
affine.for %arg13 = 0 to 20 {
affine.for %arg14 = 0 to 10 {
%21 = affine.load %17[%arg13, %arg14] : memref<20x10xf32, 1>
%22 = affine.load %19[%arg13, %arg14] : memref<20x10xf32, 1>
%23 = arith.divf %22, %21 : f32
affine.store %23, %20[%arg13, %arg14] : memref<20x10xf32, 1>
}
}
affine.for %arg13 = 0 to 20 {
affine.for %arg14 = 0 to 10 {
%21 = affine.load %20[%arg13, %arg14] : memref<20x10xf32, 1>
%22 = affine.load %14[%arg13, %arg14] : memref<20x10xf32, 1>
%23 = arith.addf %22, %21 : f32
affine.store %23, %arg12[%arg13, %arg14] : memref<20x10xf32, 1>
}
}
affine.for %arg13 = 0 to 20 {
affine.for %arg14 = 0 to 10 {
%21 = affine.load %arg12[%arg13, %arg14] : memref<20x10xf32, 1>
%22 = affine.load %arg0[%arg13, %arg14] : memref<20x10xf32, 1>
%23 = arith.subf %22, %21 : f32
affine.store %23, %arg9[%arg13, %arg14] : memref<20x10xf32, 1>
}
}
return
}
// CHECK: affine.for
// CHECK: affine.for
// CHECK-NOT: affine.for
// Add further tests in mlir/test/Transforms/loop-fusion-4.mlir