Files
clang-p2996/mlir/test/Analysis/DataFlow/test-next-access.mlir
drblallo 2bd6642533 [mlir][dataflow]Fix dense backward dataflow intraprocedural hook (#76865)
The dataflow analysis framework within MLIR allows to customize the
transfer function when a `call-like` operation is encuntered.

The check to see if the analysis was executed in intraprocedural mode
was executed after the check to see if the callee had the
CallableOpInterface, and thus intraprocedural analyses would behave as
interpocedural ones when performing indirect calls.

This commit fixes the issue by performing the check for
intraprocedurality first.

Dense forward analyses were already behaving correctly.
https://github.com/llvm/llvm-project/blob/main/mlir/lib/Analysis/DataFlow/DenseAnalysis.cpp#L63

Co-authored-by: massimo <mo.fioravanti@gmail.com>
2024-01-04 10:28:12 +01:00

596 lines
21 KiB
MLIR

// RUN: mlir-opt %s --test-next-access --split-input-file |\
// RUN: FileCheck %s --check-prefixes=CHECK,IP
// RUN: mlir-opt %s --test-next-access='interprocedural=false' \
// RUN: --split-input-file |\
// RUN: FileCheck %s --check-prefixes=CHECK,LOCAL
// RUN: mlir-opt %s --test-next-access='assume-func-reads=true' \
// RUN: --split-input-file |\
// RUN: FileCheck %s --check-prefixes=CHECK,IP_AR
// RUN: mlir-opt %s \
// RUN: --test-next-access='interprocedural=false assume-func-reads=true' \
// RUN: --split-input-file | FileCheck %s --check-prefixes=CHECK,LC_AR
// Check prefixes are as follows:
// 'check': common for all runs;
// 'ip_ar': interpocedural runs assuming calls to external functions read
// all arguments;
// 'ip': interprocedural runs not assuming function calls reading;
// 'local': local (non-interprocedural) analysis not assuming calls reading;
// 'lc_ar': local analysis assuming external calls reading all arguments.
// CHECK-LABEL: @trivial
func.func @trivial(%arg0: memref<f32>, %arg1: f32) -> f32 {
// CHECK: name = "store"
// CHECK-SAME: next_access = ["unknown", ["load"]]
memref.store %arg1, %arg0[] {name = "store"} : memref<f32>
// CHECK: name = "load"
// CHECK-SAME: next_access = ["unknown"]
%0 = memref.load %arg0[] {name = "load"} : memref<f32>
return %0 : f32
}
// CHECK-LABEL: @chain
func.func @chain(%arg0: memref<f32>, %arg1: f32) -> f32 {
// CHECK: name = "store"
// CHECK-SAME: next_access = ["unknown", ["load 1"]]
memref.store %arg1, %arg0[] {name = "store"} : memref<f32>
// CHECK: name = "load 1"
// CHECK-SAME: next_access = {{\[}}["load 2"]]
%0 = memref.load %arg0[] {name = "load 1"} : memref<f32>
// CHECK: name = "load 2"
// CHECK-SAME: next_access = ["unknown"]
%1 = memref.load %arg0[] {name = "load 2"} : memref<f32>
%2 = arith.addf %0, %1 : f32
return %2 : f32
}
// CHECK-LABEL: @branch
func.func @branch(%arg0: memref<f32>, %arg1: f32, %arg2: i1) -> f32 {
// CHECK: name = "store"
// CHECK-SAME: next_access = ["unknown", ["load 1", "load 2"]]
memref.store %arg1, %arg0[] {name = "store"} : memref<f32>
cf.cond_br %arg2, ^bb0, ^bb1
^bb0:
%0 = memref.load %arg0[] {name = "load 1"} : memref<f32>
cf.br ^bb2(%0 : f32)
^bb1:
%1 = memref.load %arg0[] {name = "load 2"} : memref<f32>
cf.br ^bb2(%1 : f32)
^bb2(%phi: f32):
return %phi : f32
}
// CHECK-LABEL @dead_branch
func.func @dead_branch(%arg0: memref<f32>, %arg1: f32) -> f32 {
// CHECK: name = "store"
// CHECK-SAME: next_access = ["unknown", ["load 2"]]
memref.store %arg1, %arg0[] {name = "store"} : memref<f32>
cf.br ^bb1
^bb0:
// CHECK: name = "load 1"
// CHECK-SAME: next_access = "not computed"
%0 = memref.load %arg0[] {name = "load 1"} : memref<f32>
cf.br ^bb2(%0 : f32)
^bb1:
%1 = memref.load %arg0[] {name = "load 2"} : memref<f32>
cf.br ^bb2(%1 : f32)
^bb2(%phi: f32):
return %phi : f32
}
// CHECK-LABEL: @loop
func.func @loop(%arg0: memref<?xf32>, %arg1: f32, %arg2: index, %arg3: index, %arg4: index) -> f32 {
%c0 = arith.constant 0.0 : f32
// CHECK: name = "pre"
// CHECK-SAME: next_access = {{\[}}["outside", "loop"], "unknown"]
memref.load %arg0[%arg4] {name = "pre"} : memref<?xf32>
%l = scf.for %i = %arg2 to %arg3 step %arg4 iter_args(%ia = %c0) -> (f32) {
// CHECK: name = "loop"
// CHECK-SAME: next_access = {{\[}}["outside", "loop"], "unknown"]
%0 = memref.load %arg0[%i] {name = "loop"} : memref<?xf32>
%1 = arith.addf %ia, %0 : f32
scf.yield %1 : f32
}
%v = memref.load %arg0[%arg3] {name = "outside"} : memref<?xf32>
%2 = arith.addf %v, %l : f32
return %2 : f32
}
// CHECK-LABEL: @conditional
func.func @conditional(%cond: i1, %arg0: memref<f32>) {
// CHECK: name = "pre"
// CHECK-SAME: next_access = {{\[}}["post", "then"]]
memref.load %arg0[] {name = "pre"}: memref<f32>
scf.if %cond {
// CHECK: name = "then"
// CHECK-SAME: next_access = {{\[}}["post"]]
memref.load %arg0[] {name = "then"} : memref<f32>
}
memref.load %arg0[] {name = "post"} : memref<f32>
return
}
// CHECK-LABEL: @two_sided_conditional
func.func @two_sided_conditional(%cond: i1, %arg0: memref<f32>) {
// CHECK: name = "pre"
// CHECK-SAME: next_access = {{\[}}["then", "else"]]
memref.load %arg0[] {name = "pre"}: memref<f32>
scf.if %cond {
// CHECK: name = "then"
// CHECK-SAME: next_access = {{\[}}["post"]]
memref.load %arg0[] {name = "then"} : memref<f32>
} else {
// CHECK: name = "else"
// CHECK-SAME: next_access = {{\[}}["post"]]
memref.load %arg0[] {name = "else"} : memref<f32>
}
memref.load %arg0[] {name = "post"} : memref<f32>
return
}
// CHECK-LABEL: @dead_conditional
func.func @dead_conditional(%arg0: memref<f32>) {
%false = arith.constant 0 : i1
// CHECK: name = "pre"
// CHECK-SAME: next_access = {{\[}}["post"]]
memref.load %arg0[] {name = "pre"}: memref<f32>
scf.if %false {
// CHECK: name = "then"
// CHECK-SAME: next_access = "not computed"
memref.load %arg0[] {name = "then"} : memref<f32>
}
memref.load %arg0[] {name = "post"} : memref<f32>
return
}
// CHECK-LABEL: @known_conditional
func.func @known_conditional(%arg0: memref<f32>) {
%false = arith.constant 0 : i1
// CHECK: name = "pre"
// CHECK-SAME: next_access = {{\[}}["else"]]
memref.load %arg0[] {name = "pre"}: memref<f32>
scf.if %false {
// CHECK: name = "then"
// CHECK-SAME: next_access = "not computed"
memref.load %arg0[] {name = "then"} : memref<f32>
} else {
// CHECK: name = "else"
// CHECK-SAME: next_access = {{\[}}["post"]]
memref.load %arg0[] {name = "else"} : memref<f32>
}
memref.load %arg0[] {name = "post"} : memref<f32>
return
}
// CHECK-LABEL: @loop_cf
func.func @loop_cf(%arg0: memref<?xf32>, %arg1: f32, %arg2: index, %arg3: index, %arg4: index) -> f32 {
%cst = arith.constant 0.000000e+00 : f32
// CHECK: name = "pre"
// CHECK-SAME: next_access = {{\[}}["loop", "outside"], "unknown"]
%0 = memref.load %arg0[%arg4] {name = "pre"} : memref<?xf32>
cf.br ^bb1(%arg2, %cst : index, f32)
^bb1(%1: index, %2: f32):
%3 = arith.cmpi slt, %1, %arg3 : index
cf.cond_br %3, ^bb2, ^bb3
^bb2:
// CHECK: name = "loop"
// CHECK-SAME: next_access = {{\[}}["loop", "outside"], "unknown"]
%4 = memref.load %arg0[%1] {name = "loop"} : memref<?xf32>
%5 = arith.addf %2, %4 : f32
%6 = arith.addi %1, %arg4 : index
cf.br ^bb1(%6, %5 : index, f32)
^bb3:
%7 = memref.load %arg0[%arg3] {name = "outside"} : memref<?xf32>
%8 = arith.addf %7, %2 : f32
return %8 : f32
}
// CHECK-LABEL @conditional_cf
func.func @conditional_cf(%arg0: i1, %arg1: memref<f32>) {
// CHECK: name = "pre"
// CHECK-SAME: next_access = {{\[}}["then", "post"]]
%0 = memref.load %arg1[] {name = "pre"} : memref<f32>
cf.cond_br %arg0, ^bb1, ^bb2
^bb1:
// CHECK: name = "then"
// CHECK-SAME: next_access = {{\[}}["post"]]
%1 = memref.load %arg1[] {name = "then"} : memref<f32>
cf.br ^bb2
^bb2:
%2 = memref.load %arg1[] {name = "post"} : memref<f32>
return
}
// CHECK-LABEL: @two_sided_conditional_cf
func.func @two_sided_conditional_cf(%arg0: i1, %arg1: memref<f32>) {
// CHECK: name = "pre"
// CHECK-SAME: next_access = {{\[}}["then", "else"]]
%0 = memref.load %arg1[] {name = "pre"} : memref<f32>
cf.cond_br %arg0, ^bb1, ^bb2
^bb1:
// CHECK: name = "then"
// CHECK-SAME: next_access = {{\[}}["post"]]
%1 = memref.load %arg1[] {name = "then"} : memref<f32>
cf.br ^bb3
^bb2:
// CHECK: name = "else"
// CHECK-SAME: next_access = {{\[}}["post"]]
%2 = memref.load %arg1[] {name = "else"} : memref<f32>
cf.br ^bb3
^bb3:
%3 = memref.load %arg1[] {name = "post"} : memref<f32>
return
}
// CHECK-LABEL: @dead_conditional_cf
func.func @dead_conditional_cf(%arg0: memref<f32>) {
%false = arith.constant false
// CHECK: name = "pre"
// CHECK-SAME: next_access = {{\[}}["post"]]
%0 = memref.load %arg0[] {name = "pre"} : memref<f32>
cf.cond_br %false, ^bb1, ^bb2
^bb1:
// CHECK: name = "then"
// CHECK-SAME: next_access = "not computed"
%1 = memref.load %arg0[] {name = "then"} : memref<f32>
cf.br ^bb2
^bb2:
%2 = memref.load %arg0[] {name = "post"} : memref<f32>
return
}
// CHECK-LABEL: @known_conditional_cf
func.func @known_conditional_cf(%arg0: memref<f32>) {
%false = arith.constant false
// CHECK: name = "pre"
// CHECK-SAME: next_access = {{\[}}["else"]]
%0 = memref.load %arg0[] {name = "pre"} : memref<f32>
cf.cond_br %false, ^bb1, ^bb2
^bb1:
// CHECK: name = "then"
// CHECK-SAME: next_access = "not computed"
%1 = memref.load %arg0[] {name = "then"} : memref<f32>
cf.br ^bb3
^bb2:
// CHECK: name = "else"
// CHECK-SAME: next_access = {{\[}}["post"]]
%2 = memref.load %arg0[] {name = "else"} : memref<f32>
cf.br ^bb3
^bb3:
%3 = memref.load %arg0[] {name = "post"} : memref<f32>
return
}
// -----
func.func private @callee1(%arg0: memref<f32>) {
// IP: name = "callee1"
// IP-SAME: next_access = {{\[}}["post"]]
// LOCAL: name = "callee1"
// LOCAL-SAME: next_access = ["unknown"]
memref.load %arg0[] {name = "callee1"} : memref<f32>
return
}
func.func private @callee2(%arg0: memref<f32>) {
// CHECK: name = "callee2"
// CHECK-SAME: next_access = "not computed"
memref.load %arg0[] {name = "callee2"} : memref<f32>
return
}
// CHECK-LABEL: @simple_call
func.func @simple_call(%arg0: memref<f32>) {
// IP: name = "caller"
// IP-SAME: next_access = {{\[}}["callee1"]]
// LOCAL: name = "caller"
// LOCAL-SAME: next_access = ["unknown"]
// LC_AR: name = "caller"
// LC_AR-SAME: next_access = {{\[}}["call"]]
memref.load %arg0[] {name = "caller"} : memref<f32>
func.call @callee1(%arg0) {name = "call"} : (memref<f32>) -> ()
memref.load %arg0[] {name = "post"} : memref<f32>
return
}
// -----
// CHECK-LABEL: @infinite_recursive_call
func.func @infinite_recursive_call(%arg0: memref<f32>) {
// IP: name = "pre"
// IP-SAME: next_access = {{\[}}["pre"]]
// LOCAL: name = "pre"
// LOCAL-SAME: next_access = ["unknown"]
// LC_AR: name = "pre"
// LC_AR-SAME: next_access = {{\[}}["call"]]
memref.load %arg0[] {name = "pre"} : memref<f32>
func.call @infinite_recursive_call(%arg0) {name = "call"} : (memref<f32>) -> ()
memref.load %arg0[] {name = "post"} : memref<f32>
return
}
// -----
// CHECK-LABEL: @recursive_call
func.func @recursive_call(%arg0: memref<f32>, %cond: i1) {
// IP: name = "pre"
// IP-SAME: next_access = {{\[}}["post", "pre"]]
// LOCAL: name = "pre"
// LOCAL-SAME: next_access = ["unknown"]
// LC_AR: name = "pre"
// LC_AR-SAME: next_access = {{\[}}["post", "call"]]
memref.load %arg0[] {name = "pre"} : memref<f32>
scf.if %cond {
func.call @recursive_call(%arg0, %cond) {name = "call"} : (memref<f32>, i1) -> ()
}
memref.load %arg0[] {name = "post"} : memref<f32>
return
}
// -----
// CHECK-LABEL: @recursive_call_cf
func.func @recursive_call_cf(%arg0: memref<f32>, %cond: i1) {
// IP: name = "pre"
// IP-SAME: next_access = {{\[}}["pre", "post"]]
// LOCAL: name = "pre"
// LOCAL-SAME: next_access = ["unknown"]
// LC_AR: name = "pre"
// LC_AR-SAME: next_access = {{\[}}["call", "post"]]
%0 = memref.load %arg0[] {name = "pre"} : memref<f32>
cf.cond_br %cond, ^bb1, ^bb2
^bb1:
call @recursive_call_cf(%arg0, %cond) {name = "call"} : (memref<f32>, i1) -> ()
cf.br ^bb2
^bb2:
%2 = memref.load %arg0[] {name = "post"} : memref<f32>
return
}
// -----
func.func private @callee1(%arg0: memref<f32>) {
// IP: name = "callee1"
// IP-SAME: next_access = {{\[}}["post"]]
// LOCAL: name = "callee1"
// LOCAL-SAME: next_access = ["unknown"]
memref.load %arg0[] {name = "callee1"} : memref<f32>
return
}
func.func private @callee2(%arg0: memref<f32>) {
// IP: name = "callee2"
// IP-SAME: next_access = {{\[}}["post"]]
// LOCAL: name = "callee2"
// LOCAL-SAME: next_access = ["unknown"]
memref.load %arg0[] {name = "callee2"} : memref<f32>
return
}
func.func @conditonal_call(%arg0: memref<f32>, %cond: i1) {
// IP: name = "pre"
// IP-SAME: next_access = {{\[}}["callee1", "callee2"]]
// LOCAL: name = "pre"
// LOCAL-SAME: next_access = ["unknown"]
// LC_AR: name = "pre"
// LC_AR-SAME: next_access = {{\[}}["call1", "call2"]]
memref.load %arg0[] {name = "pre"} : memref<f32>
scf.if %cond {
func.call @callee1(%arg0) {name = "call1"} : (memref<f32>) -> ()
} else {
func.call @callee2(%arg0) {name = "call2"} : (memref<f32>) -> ()
}
memref.load %arg0[] {name = "post"} : memref<f32>
return
}
// -----
// In this test, the "call" operation also accesses %arg0 itself before
// transferring control flow to the callee. Therefore, the order of accesses is
// "caller" -> "call" -> "callee" -> "post"
func.func private @callee(%arg0: memref<f32>) {
// IP: name = "callee"
// IP-SAME: next_access = {{\[}}["post"]]
// LOCAL: name = "callee"
// LOCAL-SAME: next_access = ["unknown"]
memref.load %arg0[] {name = "callee"} : memref<f32>
return
}
// CHECK-LABEL: @call_and_store_before
func.func @call_and_store_before(%arg0: memref<f32>) {
// IP: name = "caller"
// IP-SAME: next_access = {{\[}}["call"]]
// LOCAL: name = "caller"
// LOCAL-SAME: next_access = ["unknown"]
// LC_AR: name = "caller"
// LC_AR-SAME: next_access = {{\[}}["call"]]
memref.load %arg0[] {name = "caller"} : memref<f32>
// Note that the access after the entire call is "post".
// CHECK: name = "call"
// CHECK-SAME: next_access = {{\[}}["post"], ["post"]]
test.call_and_store @callee(%arg0), %arg0 {name = "call", store_before_call = true} : (memref<f32>, memref<f32>) -> ()
// CHECK: name = "post"
// CHECK-SAME: next_access = ["unknown"]
memref.load %arg0[] {name = "post"} : memref<f32>
return
}
// -----
// In this test, the "call" operation also accesses %arg0 itself after getting
// control flow back from the callee. Therefore, the order of accesses is
// "caller" -> "callee" -> "call" -> "post"
func.func private @callee(%arg0: memref<f32>) {
// IP: name = "callee"
// IP-SAME: next_access = {{\[}}["call"]]
// LOCAL: name = "callee"
// LOCAL-SAME: next_access = ["unknown"]
memref.load %arg0[] {name = "callee"} : memref<f32>
return
}
// CHECK-LABEL: @call_and_store_after
func.func @call_and_store_after(%arg0: memref<f32>) {
// IP: name = "caller"
// IP-SAME: next_access = {{\[}}["callee"]]
// LOCAL: name = "caller"
// LOCAL-SAME: next_access = ["unknown"]
// LC_AR: name = "caller"
// LC_AR-SAME: next_access = {{\[}}["call"]]
memref.load %arg0[] {name = "caller"} : memref<f32>
// CHECK: name = "call"
// CHECK-SAME: next_access = {{\[}}["post"], ["post"]]
test.call_and_store @callee(%arg0), %arg0 {name = "call", store_before_call = false} : (memref<f32>, memref<f32>) -> ()
// CHECK: name = "post"
// CHECK-SAME: next_access = ["unknown"]
memref.load %arg0[] {name = "post"} : memref<f32>
return
}
// -----
// In this test, the "region" operation also accesses %arg0 itself before
// entering the region. Therefore:
// - the next access of "pre" is the "region" operation itself;
// - at the entry of the block, the next access is "post".
// CHECK-LABEL: @store_with_a_region
func.func @store_with_a_region_before(%arg0: memref<f32>) {
// CHECK: name = "pre"
// CHECK-SAME: next_access = {{\[}}["region"]]
memref.load %arg0[] {name = "pre"} : memref<f32>
// CHECK: name = "region"
// CHECK-SAME: next_access = {{\[}}["post"]]
// CHECK-SAME: next_at_entry_point = {{\[}}{{\[}}["post"]]]
test.store_with_a_region %arg0 attributes { name = "region", store_before_region = true } {
test.store_with_a_region_terminator
} : memref<f32>
memref.load %arg0[] {name = "post"} : memref<f32>
return
}
// In this test, the "region" operation also accesses %arg0 itself after
// exiting from the region. Therefore:
// - the next access of "pre" is the "region" operation itself;
// - at the entry of the block, the next access is "region".
// CHECK-LABEL: @store_with_a_region
func.func @store_with_a_region_after(%arg0: memref<f32>) {
// CHECK: name = "pre"
// CHECK-SAME: next_access = {{\[}}["region"]]
memref.load %arg0[] {name = "pre"} : memref<f32>
// CHECK: name = "region"
// CHECK-SAME: next_access = {{\[}}["post"]]
// CHECK-SAME: next_at_entry_point = {{\[}}{{\[}}["region"]]]
test.store_with_a_region %arg0 attributes { name = "region", store_before_region = false } {
test.store_with_a_region_terminator
} : memref<f32>
memref.load %arg0[] {name = "post"} : memref<f32>
return
}
// In this test, the operation with a region stores to %arg0 before going to the
// region. Therefore:
// - the next access of "pre" is the "region" operation itself;
// - the next access of the "region" operation (computed as the next access
// *after* said operation) is the "post" operation;
// - the next access of the "inner" operation is also "post";
// - the next access at the entry point of the region of the "region" operation
// is the "inner" operation.
// That is, the order of access is: "pre" -> "region" -> "inner" -> "post".
// CHECK-LABEL: @store_with_a_region_before_containing_a_load
func.func @store_with_a_region_before_containing_a_load(%arg0: memref<f32>) {
// CHECK: name = "pre"
// CHECK-SAME: next_access = {{\[}}["region"]]
memref.load %arg0[] {name = "pre"} : memref<f32>
// CHECK: name = "region"
// CHECK-SAME: next_access = {{\[}}["post"]]
// CHECK-SAME: next_at_entry_point = {{\[}}{{\[}}["inner"]]]
test.store_with_a_region %arg0 attributes { name = "region", store_before_region = true } {
// CHECK: name = "inner"
// CHECK-SAME: next_access = {{\[}}["post"]]
memref.load %arg0[] {name = "inner"} : memref<f32>
test.store_with_a_region_terminator
} : memref<f32>
// CHECK: name = "post"
// CHECK-SAME: next_access = ["unknown"]
memref.load %arg0[] {name = "post"} : memref<f32>
return
}
// In this test, the operation with a region stores to %arg0 after exiting from
// the region. Therefore:
// - the next access of "pre" is "inner";
// - the next access of the "region" operation (computed as the next access
// *after* said operation) is the "post" operation);
// - the next access at the entry point of the region of the "region" operation
// is the "inner" operation;
// - the next access of the "inner" operation is the "region" operation itself.
// That is, the order of access is "pre" -> "inner" -> "region" -> "post".
// CHECK-LABEL: @store_with_a_region_after_containing_a_load
func.func @store_with_a_region_after_containing_a_load(%arg0: memref<f32>) {
// CHECK: name = "pre"
// CHECK-SAME: next_access = {{\[}}["inner"]]
memref.load %arg0[] {name = "pre"} : memref<f32>
// CHECK: name = "region"
// CHECK-SAME: next_access = {{\[}}["post"]]
// CHECK-SAME: next_at_entry_point = {{\[}}{{\[}}["inner"]]]
test.store_with_a_region %arg0 attributes { name = "region", store_before_region = false } {
// CHECK: name = "inner"
// CHECK-SAME: next_access = {{\[}}["region"]]
memref.load %arg0[] {name = "inner"} : memref<f32>
test.store_with_a_region_terminator
} : memref<f32>
// CHECK: name = "post"
// CHECK-SAME: next_access = ["unknown"]
memref.load %arg0[] {name = "post"} : memref<f32>
return
}
// -----
func.func private @opaque_callee(%arg0: memref<f32>)
// CHECK-LABEL: @call_opaque_callee
func.func @call_opaque_callee(%arg0: memref<f32>) {
// IP: name = "pre"
// IP-SAME: next_access = ["unknown"]
// IP_AR: name = "pre"
// IP_AR-SAME: next_access = {{\[}}["call"]]
// LOCAL: name = "pre"
// LOCAL-SAME: next_access = ["unknown"]
// LC_AR: name = "pre"
// LC_AR-SAME: next_access = {{\[}}["call"]]
memref.load %arg0[] {name = "pre"} : memref<f32>
func.call @opaque_callee(%arg0) {name = "call"} : (memref<f32>) -> ()
memref.load %arg0[] {name = "post"} : memref<f32>
return
}
// -----
// CHECK-LABEL: @indirect_call
func.func @indirect_call(%arg0: memref<f32>, %arg1: (memref<f32>) -> ()) {
// IP: name = "pre"
// IP-SAME: next_access = ["unknown"]
// IP_AR: name = "pre"
// IP_AR-SAME: next_access = ["unknown"]
// LOCAL: name = "pre"
// LOCAL-SAME: next_access = ["unknown"]
// LC_AR: name = "pre"
// LC_AR-SAME: next_access = {{\[}}["call"]]
memref.load %arg0[] {name = "pre"} : memref<f32>
func.call_indirect %arg1(%arg0) {name = "call"} : (memref<f32>) -> ()
memref.load %arg0[] {name = "post"} : memref<f32>
return
}