1// RUN: mlir-opt -test-last-modified --split-input-file %s 2>&1 |\ 2// RUN: FileCheck %s --check-prefixes=CHECK,IP,IP_ONLY 3// RUN: mlir-opt -test-last-modified='assume-func-writes=true' \ 4// RUN: --split-input-file %s 2>&1 |\ 5// RUN: FileCheck %s --check-prefixes=CHECK,IP,IP_AW 6// RUN: mlir-opt -test-last-modified='interprocedural=false' \ 7// RUN: --split-input-file %s 2>&1 |\ 8// RUN: FileCheck %s --check-prefixes=CHECK,LOCAL 9// RUN: mlir-opt \ 10// RUN: -test-last-modified='interprocedural=false assume-func-writes=true' \ 11// RUN: --split-input-file %s 2>&1 |\ 12// RUN: FileCheck %s --check-prefixes=CHECK,LC_AW 13 14// Check prefixes are as follows: 15// 'check': common for all runs; 16// 'ip': interprocedural runs; 17// 'ip_aw': interpocedural runs assuming calls to external functions write to 18// all arguments; 19// 'ip_only': interprocedural runs not assuming calls writing; 20// 'local': local (non-interprocedural) analysis not assuming calls writing; 21// 'lc_aw': local analysis assuming external calls writing to all arguments. 22 23// CHECK-LABEL: test_tag: test_callsite 24// IP: operand #0 25// IP-NEXT: - a 26// LOCAL: operand #0 27// LOCAL-NEXT: - <unknown> 28// LC_AW: operand #0 29// LC_AW-NEXT: - <unknown> 30func.func private @single_callsite_fn(%ptr: memref<i32>) -> memref<i32> { 31 return {tag = "test_callsite"} %ptr : memref<i32> 32} 33 34func.func @test_callsite() { 35 %ptr = memref.alloc() : memref<i32> 36 %c0 = arith.constant 0 : i32 37 memref.store %c0, %ptr[] {tag_name = "a"} : memref<i32> 38 %0 = func.call @single_callsite_fn(%ptr) : (memref<i32>) -> memref<i32> 39 return 40} 41 42// CHECK-LABEL: test_tag: test_return_site 43// IP: operand #0 44// IP-NEXT: - b 45// LOCAL: operand #0 46// LOCAL-NEXT: - <unknown> 47// LC_AW: operand #0 48// LC_AW-NEXT: - <unknown> 49func.func private @single_return_site_fn(%ptr: memref<i32>) -> memref<i32> { 50 %c0 = arith.constant 0 : i32 51 memref.store %c0, %ptr[] {tag_name = "b"} : memref<i32> 52 return %ptr : memref<i32> 53} 54 55// CHECK-LABEL: test_tag: test_multiple_callsites 56// IP: operand #0 57// IP-NEXT: write0 58// IP-NEXT: write1 59// LOCAL: operand #0 60// LOCAL-NEXT: - <unknown> 61// LC_AW: operand #0 62// LC_AW-NEXT: - <unknown> 63func.func @test_return_site(%ptr: memref<i32>) -> memref<i32> { 64 %0 = func.call @single_return_site_fn(%ptr) : (memref<i32>) -> memref<i32> 65 return {tag = "test_return_site"} %0 : memref<i32> 66} 67 68func.func private @multiple_callsite_fn(%ptr: memref<i32>) -> memref<i32> { 69 return {tag = "test_multiple_callsites"} %ptr : memref<i32> 70} 71 72func.func @test_multiple_callsites(%a: i32, %ptr: memref<i32>) -> memref<i32> { 73 memref.store %a, %ptr[] {tag_name = "write0"} : memref<i32> 74 %0 = func.call @multiple_callsite_fn(%ptr) : (memref<i32>) -> memref<i32> 75 memref.store %a, %ptr[] {tag_name = "write1"} : memref<i32> 76 %1 = func.call @multiple_callsite_fn(%ptr) : (memref<i32>) -> memref<i32> 77 return %ptr : memref<i32> 78} 79 80// CHECK-LABEL: test_tag: test_multiple_return_sites 81// IP: operand #0 82// IP-NEXT: return0 83// IP-NEXT: return1 84// LOCAL: operand #0 85// LOCAL-NEXT: - <unknown> 86// LC_AW: operand #0 87// LC_AW-NEXT: - <unknown> 88func.func private @multiple_return_site_fn(%cond: i1, %a: i32, %ptr: memref<i32>) -> memref<i32> { 89 cf.cond_br %cond, ^a, ^b 90 91^a: 92 memref.store %a, %ptr[] {tag_name = "return0"} : memref<i32> 93 return %ptr : memref<i32> 94 95^b: 96 memref.store %a, %ptr[] {tag_name = "return1"} : memref<i32> 97 return %ptr : memref<i32> 98} 99 100func.func @test_multiple_return_sites(%cond: i1, %a: i32, %ptr: memref<i32>) -> memref<i32> { 101 %0 = func.call @multiple_return_site_fn(%cond, %a, %ptr) : (i1, i32, memref<i32>) -> memref<i32> 102 return {tag = "test_multiple_return_sites"} %0 : memref<i32> 103} 104 105// ----- 106 107// CHECK-LABEL: test_tag: after_call 108// IP: operand #0 109// IP-NEXT: - write0 110// LOCAL: operand #0 111// LOCAL-NEXT: - <unknown> 112// LC_AW: operand #0 113// LC_AW-NEXT: - func.call 114func.func private @void_return(%ptr: memref<i32>) { 115 return 116} 117 118func.func @test_call_void_return() { 119 %ptr = memref.alloc() : memref<i32> 120 %c0 = arith.constant 0 : i32 121 memref.store %c0, %ptr[] {tag_name = "write0"} : memref<i32> 122 func.call @void_return(%ptr) : (memref<i32>) -> () 123 memref.load %ptr[] {tag = "after_call"} : memref<i32> 124 return 125} 126 127// ----- 128 129func.func private @callee(%arg0: memref<f32>) -> memref<f32> { 130 %2 = arith.constant 2.0 : f32 131 memref.load %arg0[] {tag = "call_and_store_before::enter_callee"} : memref<f32> 132 memref.store %2, %arg0[] {tag_name = "callee"} : memref<f32> 133 memref.load %arg0[] {tag = "exit_callee"} : memref<f32> 134 return %arg0 : memref<f32> 135} 136// In this test, the "call" operation also stores to %arg0 itself before 137// transferring control flow to the callee. Therefore, the order of accesses is 138// "pre" -> "call" -> "callee" -> "post" 139 140// CHECK-LABEL: test_tag: call_and_store_before::enter_callee: 141// IP: operand #0 142// IP: - call 143// LOCAL: operand #0 144// LOCAL: - <unknown> 145// LC_AW: operand #0 146// LC_AW: - <unknown> 147 148// CHECK: test_tag: exit_callee: 149// CHECK: operand #0 150// CHECK: - callee 151 152// CHECK: test_tag: before_call: 153// CHECK: operand #0 154// CHECK: - pre 155 156// CHECK: test_tag: after_call: 157// IP: operand #0 158// IP: - callee 159// LOCAL: operand #0 160// LOCAL: - <unknown> 161// LC_AW: operand #0 162// LC_AW: - call 163 164// CHECK: test_tag: return: 165// CHECK: operand #0 166// CHECK: - post 167func.func @call_and_store_before(%arg0: memref<f32>) -> memref<f32> { 168 %0 = arith.constant 0.0 : f32 169 %1 = arith.constant 1.0 : f32 170 memref.store %0, %arg0[] {tag_name = "pre"} : memref<f32> 171 memref.load %arg0[] {tag = "before_call"} : memref<f32> 172 test.call_and_store @callee(%arg0), %arg0 {tag_name = "call", store_before_call = true} : (memref<f32>, memref<f32>) -> () 173 memref.load %arg0[] {tag = "after_call"} : memref<f32> 174 memref.store %1, %arg0[] {tag_name = "post"} : memref<f32> 175 return {tag = "return"} %arg0 : memref<f32> 176} 177 178// ----- 179 180func.func private @callee(%arg0: memref<f32>) -> memref<f32> { 181 %2 = arith.constant 2.0 : f32 182 memref.load %arg0[] {tag = "call_and_store_after::enter_callee"} : memref<f32> 183 memref.store %2, %arg0[] {tag_name = "callee"} : memref<f32> 184 memref.load %arg0[] {tag = "exit_callee"} : memref<f32> 185 return %arg0 : memref<f32> 186} 187 188// In this test, the "call" operation also stores to %arg0 itself after getting 189// control flow back from the callee. Therefore, the order of accesses is 190// "pre" -> "callee" -> "call" -> "post" 191 192// CHECK-LABEL: test_tag: call_and_store_after::enter_callee: 193// IP: operand #0 194// IP: - pre 195// LOCAL: operand #0 196// LOCAL: - <unknown> 197// LC_AW: operand #0 198// LC_AW: - <unknown> 199 200// CHECK: test_tag: exit_callee: 201// CHECK: operand #0 202// CHECK: - callee 203 204// CHECK: test_tag: before_call: 205// CHECK: operand #0 206// CHECK: - pre 207 208// CHECK: test_tag: after_call: 209// IP: operand #0 210// IP: - call 211// LOCAL: operand #0 212// LOCAL: - <unknown> 213// LC_AW: operand #0 214// LC_AW: - call 215 216// CHECK: test_tag: return: 217// CHECK: operand #0 218// CHECK: - post 219func.func @call_and_store_after(%arg0: memref<f32>) -> memref<f32> { 220 %0 = arith.constant 0.0 : f32 221 %1 = arith.constant 1.0 : f32 222 memref.store %0, %arg0[] {tag_name = "pre"} : memref<f32> 223 memref.load %arg0[] {tag = "before_call"} : memref<f32> 224 test.call_and_store @callee(%arg0), %arg0 {tag_name = "call", store_before_call = false} : (memref<f32>, memref<f32>) -> () 225 memref.load %arg0[] {tag = "after_call"} : memref<f32> 226 memref.store %1, %arg0[] {tag_name = "post"} : memref<f32> 227 return {tag = "return"} %arg0 : memref<f32> 228} 229 230// ----- 231 232func.func private @void_return(%ptr: memref<i32>) 233 234// CHECK-LABEL: test_tag: after_opaque_call: 235// CHECK: operand #0 236// IP_ONLY: - <unknown> 237// IP_AW: - func.call 238func.func @test_opaque_call_return() { 239 %ptr = memref.alloc() : memref<i32> 240 %c0 = arith.constant 0 : i32 241 memref.store %c0, %ptr[] {tag_name = "write0"} : memref<i32> 242 func.call @void_return(%ptr) : (memref<i32>) -> () 243 memref.load %ptr[] {tag = "after_opaque_call"} : memref<i32> 244 return 245} 246