// RUN: mlir-opt -test-last-modified --split-input-file %s 2>&1 |\ // RUN: FileCheck %s --check-prefixes=CHECK,IP,IP_ONLY // RUN: mlir-opt -test-last-modified='assume-func-writes=true' \ // RUN: --split-input-file %s 2>&1 |\ // RUN: FileCheck %s --check-prefixes=CHECK,IP,IP_AW // RUN: mlir-opt -test-last-modified='interprocedural=false' \ // RUN: --split-input-file %s 2>&1 |\ // RUN: FileCheck %s --check-prefixes=CHECK,LOCAL // RUN: mlir-opt \ // RUN: -test-last-modified='interprocedural=false assume-func-writes=true' \ // RUN: --split-input-file %s 2>&1 |\ // RUN: FileCheck %s --check-prefixes=CHECK,LC_AW // Check prefixes are as follows: // 'check': common for all runs; // 'ip': interprocedural runs; // 'ip_aw': interpocedural runs assuming calls to external functions write to // all arguments; // 'ip_only': interprocedural runs not assuming calls writing; // 'local': local (non-interprocedural) analysis not assuming calls writing; // 'lc_aw': local analysis assuming external calls writing to all arguments. // CHECK-LABEL: test_tag: test_callsite // IP: operand #0 // IP-NEXT: - a // LOCAL: operand #0 // LOCAL-NEXT: - // LC_AW: operand #0 // LC_AW-NEXT: - func.func private @single_callsite_fn(%ptr: memref) -> memref { return {tag = "test_callsite"} %ptr : memref } func.func @test_callsite() { %ptr = memref.alloc() : memref %c0 = arith.constant 0 : i32 memref.store %c0, %ptr[] {tag_name = "a"} : memref %0 = func.call @single_callsite_fn(%ptr) : (memref) -> memref return } // CHECK-LABEL: test_tag: test_return_site // IP: operand #0 // IP-NEXT: - b // LOCAL: operand #0 // LOCAL-NEXT: - // LC_AW: operand #0 // LC_AW-NEXT: - func.func private @single_return_site_fn(%ptr: memref) -> memref { %c0 = arith.constant 0 : i32 memref.store %c0, %ptr[] {tag_name = "b"} : memref return %ptr : memref } // CHECK-LABEL: test_tag: test_multiple_callsites // IP: operand #0 // IP-NEXT: write0 // IP-NEXT: write1 // LOCAL: operand #0 // LOCAL-NEXT: - // LC_AW: operand #0 // LC_AW-NEXT: - func.func @test_return_site(%ptr: memref) -> memref { %0 = func.call @single_return_site_fn(%ptr) : (memref) -> memref return {tag = "test_return_site"} %0 : memref } func.func private @multiple_callsite_fn(%ptr: memref) -> memref { return {tag = "test_multiple_callsites"} %ptr : memref } func.func @test_multiple_callsites(%a: i32, %ptr: memref) -> memref { memref.store %a, %ptr[] {tag_name = "write0"} : memref %0 = func.call @multiple_callsite_fn(%ptr) : (memref) -> memref memref.store %a, %ptr[] {tag_name = "write1"} : memref %1 = func.call @multiple_callsite_fn(%ptr) : (memref) -> memref return %ptr : memref } // CHECK-LABEL: test_tag: test_multiple_return_sites // IP: operand #0 // IP-NEXT: return0 // IP-NEXT: return1 // LOCAL: operand #0 // LOCAL-NEXT: - // LC_AW: operand #0 // LC_AW-NEXT: - func.func private @multiple_return_site_fn(%cond: i1, %a: i32, %ptr: memref) -> memref { cf.cond_br %cond, ^a, ^b ^a: memref.store %a, %ptr[] {tag_name = "return0"} : memref return %ptr : memref ^b: memref.store %a, %ptr[] {tag_name = "return1"} : memref return %ptr : memref } func.func @test_multiple_return_sites(%cond: i1, %a: i32, %ptr: memref) -> memref { %0 = func.call @multiple_return_site_fn(%cond, %a, %ptr) : (i1, i32, memref) -> memref return {tag = "test_multiple_return_sites"} %0 : memref } // ----- // CHECK-LABEL: test_tag: after_call // IP: operand #0 // IP-NEXT: - write0 // LOCAL: operand #0 // LOCAL-NEXT: - // LC_AW: operand #0 // LC_AW-NEXT: - func.call func.func private @void_return(%ptr: memref) { return } func.func @test_call_void_return() { %ptr = memref.alloc() : memref %c0 = arith.constant 0 : i32 memref.store %c0, %ptr[] {tag_name = "write0"} : memref func.call @void_return(%ptr) : (memref) -> () memref.load %ptr[] {tag = "after_call"} : memref return } // ----- func.func private @callee(%arg0: memref) -> memref { %2 = arith.constant 2.0 : f32 memref.load %arg0[] {tag = "call_and_store_before::enter_callee"} : memref memref.store %2, %arg0[] {tag_name = "callee"} : memref memref.load %arg0[] {tag = "exit_callee"} : memref return %arg0 : memref } // In this test, the "call" operation also stores to %arg0 itself before // transferring control flow to the callee. Therefore, the order of accesses is // "pre" -> "call" -> "callee" -> "post" // CHECK-LABEL: test_tag: call_and_store_before::enter_callee: // IP: operand #0 // IP: - call // LOCAL: operand #0 // LOCAL: - // LC_AW: operand #0 // LC_AW: - // CHECK: test_tag: exit_callee: // CHECK: operand #0 // CHECK: - callee // CHECK: test_tag: before_call: // CHECK: operand #0 // CHECK: - pre // CHECK: test_tag: after_call: // IP: operand #0 // IP: - callee // LOCAL: operand #0 // LOCAL: - // LC_AW: operand #0 // LC_AW: - call // CHECK: test_tag: return: // CHECK: operand #0 // CHECK: - post func.func @call_and_store_before(%arg0: memref) -> memref { %0 = arith.constant 0.0 : f32 %1 = arith.constant 1.0 : f32 memref.store %0, %arg0[] {tag_name = "pre"} : memref memref.load %arg0[] {tag = "before_call"} : memref test.call_and_store @callee(%arg0), %arg0 {tag_name = "call", store_before_call = true} : (memref, memref) -> () memref.load %arg0[] {tag = "after_call"} : memref memref.store %1, %arg0[] {tag_name = "post"} : memref return {tag = "return"} %arg0 : memref } // ----- func.func private @callee(%arg0: memref) -> memref { %2 = arith.constant 2.0 : f32 memref.load %arg0[] {tag = "call_and_store_after::enter_callee"} : memref memref.store %2, %arg0[] {tag_name = "callee"} : memref memref.load %arg0[] {tag = "exit_callee"} : memref return %arg0 : memref } // In this test, the "call" operation also stores to %arg0 itself after getting // control flow back from the callee. Therefore, the order of accesses is // "pre" -> "callee" -> "call" -> "post" // CHECK-LABEL: test_tag: call_and_store_after::enter_callee: // IP: operand #0 // IP: - pre // LOCAL: operand #0 // LOCAL: - // LC_AW: operand #0 // LC_AW: - // CHECK: test_tag: exit_callee: // CHECK: operand #0 // CHECK: - callee // CHECK: test_tag: before_call: // CHECK: operand #0 // CHECK: - pre // CHECK: test_tag: after_call: // IP: operand #0 // IP: - call // LOCAL: operand #0 // LOCAL: - // LC_AW: operand #0 // LC_AW: - call // CHECK: test_tag: return: // CHECK: operand #0 // CHECK: - post func.func @call_and_store_after(%arg0: memref) -> memref { %0 = arith.constant 0.0 : f32 %1 = arith.constant 1.0 : f32 memref.store %0, %arg0[] {tag_name = "pre"} : memref memref.load %arg0[] {tag = "before_call"} : memref test.call_and_store @callee(%arg0), %arg0 {tag_name = "call", store_before_call = false} : (memref, memref) -> () memref.load %arg0[] {tag = "after_call"} : memref memref.store %1, %arg0[] {tag_name = "post"} : memref return {tag = "return"} %arg0 : memref } // ----- func.func private @void_return(%ptr: memref) // CHECK-LABEL: test_tag: after_opaque_call: // CHECK: operand #0 // IP_ONLY: - // IP_AW: - func.call func.func @test_opaque_call_return() { %ptr = memref.alloc() : memref %c0 = arith.constant 0 : i32 memref.store %c0, %ptr[] {tag_name = "write0"} : memref func.call @void_return(%ptr) : (memref) -> () memref.load %ptr[] {tag = "after_opaque_call"} : memref return }