// 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, %arg1: f32) -> f32 { // CHECK: name = "store" // CHECK-SAME: next_access = ["unknown", ["load"]] memref.store %arg1, %arg0[] {name = "store"} : memref // CHECK: name = "load" // CHECK-SAME: next_access = ["unknown"] %0 = memref.load %arg0[] {name = "load"} : memref return %0 : f32 } // CHECK-LABEL: @chain func.func @chain(%arg0: memref, %arg1: f32) -> f32 { // CHECK: name = "store" // CHECK-SAME: next_access = ["unknown", ["load 1"]] memref.store %arg1, %arg0[] {name = "store"} : memref // CHECK: name = "load 1" // CHECK-SAME: next_access = {{\[}}["load 2"]] %0 = memref.load %arg0[] {name = "load 1"} : memref // CHECK: name = "load 2" // CHECK-SAME: next_access = ["unknown"] %1 = memref.load %arg0[] {name = "load 2"} : memref %2 = arith.addf %0, %1 : f32 return %2 : f32 } // CHECK-LABEL: @branch func.func @branch(%arg0: memref, %arg1: f32, %arg2: i1) -> f32 { // CHECK: name = "store" // CHECK-SAME: next_access = ["unknown", ["load 1", "load 2"]] memref.store %arg1, %arg0[] {name = "store"} : memref cf.cond_br %arg2, ^bb0, ^bb1 ^bb0: %0 = memref.load %arg0[] {name = "load 1"} : memref cf.br ^bb2(%0 : f32) ^bb1: %1 = memref.load %arg0[] {name = "load 2"} : memref cf.br ^bb2(%1 : f32) ^bb2(%phi: f32): return %phi : f32 } // CHECK-LABEL @dead_branch func.func @dead_branch(%arg0: memref, %arg1: f32) -> f32 { // CHECK: name = "store" // CHECK-SAME: next_access = ["unknown", ["load 2"]] memref.store %arg1, %arg0[] {name = "store"} : memref cf.br ^bb1 ^bb0: // CHECK: name = "load 1" // CHECK-SAME: next_access = "not computed" %0 = memref.load %arg0[] {name = "load 1"} : memref cf.br ^bb2(%0 : f32) ^bb1: %1 = memref.load %arg0[] {name = "load 2"} : memref cf.br ^bb2(%1 : f32) ^bb2(%phi: f32): return %phi : f32 } // CHECK-LABEL: @loop func.func @loop(%arg0: memref, %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 %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 %1 = arith.addf %ia, %0 : f32 scf.yield %1 : f32 } %v = memref.load %arg0[%arg3] {name = "outside"} : memref %2 = arith.addf %v, %l : f32 return %2 : f32 } // CHECK-LABEL: @conditional func.func @conditional(%cond: i1, %arg0: memref) { // CHECK: name = "pre" // CHECK-SAME: next_access = {{\[}}["post", "then"]] memref.load %arg0[] {name = "pre"}: memref scf.if %cond { // CHECK: name = "then" // CHECK-SAME: next_access = {{\[}}["post"]] memref.load %arg0[] {name = "then"} : memref } memref.load %arg0[] {name = "post"} : memref return } // CHECK-LABEL: @two_sided_conditional func.func @two_sided_conditional(%cond: i1, %arg0: memref) { // CHECK: name = "pre" // CHECK-SAME: next_access = {{\[}}["then", "else"]] memref.load %arg0[] {name = "pre"}: memref scf.if %cond { // CHECK: name = "then" // CHECK-SAME: next_access = {{\[}}["post"]] memref.load %arg0[] {name = "then"} : memref } else { // CHECK: name = "else" // CHECK-SAME: next_access = {{\[}}["post"]] memref.load %arg0[] {name = "else"} : memref } memref.load %arg0[] {name = "post"} : memref return } // CHECK-LABEL: @dead_conditional func.func @dead_conditional(%arg0: memref) { %false = arith.constant 0 : i1 // CHECK: name = "pre" // CHECK-SAME: next_access = {{\[}}["post"]] memref.load %arg0[] {name = "pre"}: memref scf.if %false { // CHECK: name = "then" // CHECK-SAME: next_access = "not computed" memref.load %arg0[] {name = "then"} : memref } memref.load %arg0[] {name = "post"} : memref return } // CHECK-LABEL: @known_conditional func.func @known_conditional(%arg0: memref) { %false = arith.constant 0 : i1 // CHECK: name = "pre" // CHECK-SAME: next_access = {{\[}}["else"]] memref.load %arg0[] {name = "pre"}: memref scf.if %false { // CHECK: name = "then" // CHECK-SAME: next_access = "not computed" memref.load %arg0[] {name = "then"} : memref } else { // CHECK: name = "else" // CHECK-SAME: next_access = {{\[}}["post"]] memref.load %arg0[] {name = "else"} : memref } memref.load %arg0[] {name = "post"} : memref return } // CHECK-LABEL: @loop_cf func.func @loop_cf(%arg0: memref, %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 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 %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 %8 = arith.addf %7, %2 : f32 return %8 : f32 } // CHECK-LABEL @conditional_cf func.func @conditional_cf(%arg0: i1, %arg1: memref) { // CHECK: name = "pre" // CHECK-SAME: next_access = {{\[}}["then", "post"]] %0 = memref.load %arg1[] {name = "pre"} : memref cf.cond_br %arg0, ^bb1, ^bb2 ^bb1: // CHECK: name = "then" // CHECK-SAME: next_access = {{\[}}["post"]] %1 = memref.load %arg1[] {name = "then"} : memref cf.br ^bb2 ^bb2: %2 = memref.load %arg1[] {name = "post"} : memref return } // CHECK-LABEL: @two_sided_conditional_cf func.func @two_sided_conditional_cf(%arg0: i1, %arg1: memref) { // CHECK: name = "pre" // CHECK-SAME: next_access = {{\[}}["then", "else"]] %0 = memref.load %arg1[] {name = "pre"} : memref cf.cond_br %arg0, ^bb1, ^bb2 ^bb1: // CHECK: name = "then" // CHECK-SAME: next_access = {{\[}}["post"]] %1 = memref.load %arg1[] {name = "then"} : memref cf.br ^bb3 ^bb2: // CHECK: name = "else" // CHECK-SAME: next_access = {{\[}}["post"]] %2 = memref.load %arg1[] {name = "else"} : memref cf.br ^bb3 ^bb3: %3 = memref.load %arg1[] {name = "post"} : memref return } // CHECK-LABEL: @dead_conditional_cf func.func @dead_conditional_cf(%arg0: memref) { %false = arith.constant false // CHECK: name = "pre" // CHECK-SAME: next_access = {{\[}}["post"]] %0 = memref.load %arg0[] {name = "pre"} : memref cf.cond_br %false, ^bb1, ^bb2 ^bb1: // CHECK: name = "then" // CHECK-SAME: next_access = "not computed" %1 = memref.load %arg0[] {name = "then"} : memref cf.br ^bb2 ^bb2: %2 = memref.load %arg0[] {name = "post"} : memref return } // CHECK-LABEL: @known_conditional_cf func.func @known_conditional_cf(%arg0: memref) { %false = arith.constant false // CHECK: name = "pre" // CHECK-SAME: next_access = {{\[}}["else"]] %0 = memref.load %arg0[] {name = "pre"} : memref cf.cond_br %false, ^bb1, ^bb2 ^bb1: // CHECK: name = "then" // CHECK-SAME: next_access = "not computed" %1 = memref.load %arg0[] {name = "then"} : memref cf.br ^bb3 ^bb2: // CHECK: name = "else" // CHECK-SAME: next_access = {{\[}}["post"]] %2 = memref.load %arg0[] {name = "else"} : memref cf.br ^bb3 ^bb3: %3 = memref.load %arg0[] {name = "post"} : memref return } // ----- func.func private @callee1(%arg0: memref) { // IP: name = "callee1" // IP-SAME: next_access = {{\[}}["post"]] // LOCAL: name = "callee1" // LOCAL-SAME: next_access = ["unknown"] memref.load %arg0[] {name = "callee1"} : memref return } func.func private @callee2(%arg0: memref) { // CHECK: name = "callee2" // CHECK-SAME: next_access = "not computed" memref.load %arg0[] {name = "callee2"} : memref return } // CHECK-LABEL: @simple_call func.func @simple_call(%arg0: memref) { // 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 func.call @callee1(%arg0) {name = "call"} : (memref) -> () memref.load %arg0[] {name = "post"} : memref return } // ----- // CHECK-LABEL: @infinite_recursive_call func.func @infinite_recursive_call(%arg0: memref) { // 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 func.call @infinite_recursive_call(%arg0) {name = "call"} : (memref) -> () memref.load %arg0[] {name = "post"} : memref return } // ----- // CHECK-LABEL: @recursive_call func.func @recursive_call(%arg0: memref, %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 scf.if %cond { func.call @recursive_call(%arg0, %cond) {name = "call"} : (memref, i1) -> () } memref.load %arg0[] {name = "post"} : memref return } // ----- // CHECK-LABEL: @recursive_call_cf func.func @recursive_call_cf(%arg0: memref, %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 cf.cond_br %cond, ^bb1, ^bb2 ^bb1: call @recursive_call_cf(%arg0, %cond) {name = "call"} : (memref, i1) -> () cf.br ^bb2 ^bb2: %2 = memref.load %arg0[] {name = "post"} : memref return } // ----- func.func private @callee1(%arg0: memref) { // IP: name = "callee1" // IP-SAME: next_access = {{\[}}["post"]] // LOCAL: name = "callee1" // LOCAL-SAME: next_access = ["unknown"] memref.load %arg0[] {name = "callee1"} : memref return } func.func private @callee2(%arg0: memref) { // IP: name = "callee2" // IP-SAME: next_access = {{\[}}["post"]] // LOCAL: name = "callee2" // LOCAL-SAME: next_access = ["unknown"] memref.load %arg0[] {name = "callee2"} : memref return } func.func @conditonal_call(%arg0: memref, %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 scf.if %cond { func.call @callee1(%arg0) {name = "call1"} : (memref) -> () } else { func.call @callee2(%arg0) {name = "call2"} : (memref) -> () } memref.load %arg0[] {name = "post"} : memref 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) { // IP: name = "callee" // IP-SAME: next_access = {{\[}}["post"]] // LOCAL: name = "callee" // LOCAL-SAME: next_access = ["unknown"] memref.load %arg0[] {name = "callee"} : memref return } // CHECK-LABEL: @call_and_store_before func.func @call_and_store_before(%arg0: memref) { // 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 // 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, memref) -> () // CHECK: name = "post" // CHECK-SAME: next_access = ["unknown"] memref.load %arg0[] {name = "post"} : memref 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) { // IP: name = "callee" // IP-SAME: next_access = {{\[}}["call"]] // LOCAL: name = "callee" // LOCAL-SAME: next_access = ["unknown"] memref.load %arg0[] {name = "callee"} : memref return } // CHECK-LABEL: @call_and_store_after func.func @call_and_store_after(%arg0: memref) { // 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 // CHECK: name = "call" // CHECK-SAME: next_access = {{\[}}["post"], ["post"]] test.call_and_store @callee(%arg0), %arg0 {name = "call", store_before_call = false} : (memref, memref) -> () // CHECK: name = "post" // CHECK-SAME: next_access = ["unknown"] memref.load %arg0[] {name = "post"} : memref 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) { // CHECK: name = "pre" // CHECK-SAME: next_access = {{\[}}["region"]] memref.load %arg0[] {name = "pre"} : memref // 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 memref.load %arg0[] {name = "post"} : memref 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) { // CHECK: name = "pre" // CHECK-SAME: next_access = {{\[}}["region"]] memref.load %arg0[] {name = "pre"} : memref // 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 memref.load %arg0[] {name = "post"} : memref 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) { // CHECK: name = "pre" // CHECK-SAME: next_access = {{\[}}["region"]] memref.load %arg0[] {name = "pre"} : memref // 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 test.store_with_a_region_terminator } : memref // CHECK: name = "post" // CHECK-SAME: next_access = ["unknown"] memref.load %arg0[] {name = "post"} : memref 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) { // CHECK: name = "pre" // CHECK-SAME: next_access = {{\[}}["inner"]] memref.load %arg0[] {name = "pre"} : memref // 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 test.store_with_a_region_terminator } : memref // CHECK: name = "post" // CHECK-SAME: next_access = ["unknown"] memref.load %arg0[] {name = "post"} : memref return } // ----- func.func private @opaque_callee(%arg0: memref) // CHECK-LABEL: @call_opaque_callee func.func @call_opaque_callee(%arg0: memref) { // 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 func.call @opaque_callee(%arg0) {name = "call"} : (memref) -> () memref.load %arg0[] {name = "post"} : memref return } // ----- // CHECK-LABEL: @indirect_call func.func @indirect_call(%arg0: memref, %arg1: (memref) -> ()) { // 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 func.call_indirect %arg1(%arg0) {name = "call"} : (memref) -> () memref.load %arg0[] {name = "post"} : memref return }