318 lines · plain
1// RUN: mlir-opt -split-input-file -test-liveness-analysis %s 2>&1 | FileCheck %s2 3// Positive test: Type (1.a) "is an operand of an op with memory effects"4// zero is live because it is stored in memory.5// CHECK-LABEL: test_tag: zero:6// CHECK-NEXT: result #0: live7func.func @test_1_type_1.a(%arg0: memref<i32>) {8 %c0_i32 = arith.constant {tag = "zero"} 0 : i329 memref.store %c0_i32, %arg0[] : memref<i32>10 return11}12 13// -----14 15// Positive test: Type (1.b) "is a non-forwarded branch operand and a block16// where its op could take the control has an op with memory effects"17// %arg2 is live because it can make the control go into a block with a memory18// effecting op.19// CHECK-LABEL: test_tag: br:20// CHECK-NEXT: operand #0: live21// CHECK-NEXT: operand #1: live22// CHECK-NEXT: operand #2: live23func.func @test_2_RegionBranchOpInterface_type_1.b(%arg0: memref<i32>, %arg1: memref<i32>, %arg2: i1) {24 %c0_i32 = arith.constant 0 : i3225 cf.cond_br %arg2, ^bb1(%c0_i32 : i32), ^bb2(%c0_i32 : i32) {tag = "br"}26^bb1(%0 : i32):27 memref.store %0, %arg0[] : memref<i32>28 cf.br ^bb329^bb2(%1 : i32):30 memref.store %1, %arg1[] : memref<i32>31 cf.br ^bb332^bb3:33 return34}35 36// -----37 38// Positive test: Type (1.b) "is a non-forwarded branch operand and a block39// where its op could take the control has an op with memory effects"40// %arg0 is live because it can make the control go into a block with a memory41// effecting op.42// CHECK-LABEL: test_tag: flag:43// CHECK-NEXT: operand #0: live44func.func @test_3_BranchOpInterface_type_1.b(%arg0: i32, %arg1: memref<i32>, %arg2: memref<i32>) {45 %c0_i32 = arith.constant 0 : i3246 cf.switch %arg0 : i32, [47 default: ^bb1,48 42: ^bb249 ] {tag = "flag"}50^bb1:51 memref.store %c0_i32, %arg1[] : memref<i32>52 cf.br ^bb353^bb2:54 memref.store %c0_i32, %arg2[] : memref<i32>55 cf.br ^bb356^bb3:57 return58}59 60// -----61 62// Positive test: Type(1.c) "is a non-forwarded branch operand and its branch63// op could result in different result"64// CHECK-LABEL: test_tag: cond_br:65// CHECK-NEXT: operand #0: live66// CHECK-NEXT: operand #1: live67// CHECK-NEXT: operand #2: live68func.func @test_branch_result_in_different_result_1.c(%arg0 : tensor<f32>, %arg1 : tensor<f32>, %arg2 : i1) -> tensor<f32> {69 cf.cond_br %arg2, ^bb1(%arg0 : tensor<f32>), ^bb2(%arg1 : tensor<f32>) {tag = "cond_br"}70^bb1(%0 : tensor<f32>):71 cf.br ^bb3(%0 : tensor<f32>)72^bb2(%1 : tensor<f32>):73 cf.br ^bb3(%1 : tensor<f32>)74^bb3(%2 : tensor<f32>):75 return %2 : tensor<f32>76}77 78// -----79 80// Positive test: Type(1.c) "is a non-forwarded branch operand and its branch81// op could result in different result"82// CHECK-LABEL: test_tag: region_branch:83// CHECK-NEXT: operand #0: live84func.func @test_region_branch_result_in_different_result_1.c(%arg0 : tensor<f32>, %arg1 : tensor<f32>, %arg2 : i1) -> tensor<f32> {85 %0 = scf.if %arg2 -> tensor<f32> {86 scf.yield %arg0 : tensor<f32>87 } else {88 scf.yield %arg1 : tensor<f32>89 } {tag="region_branch"}90 return %0 : tensor<f32>91}92 93// -----94 95func.func private @private(%arg0 : i32, %arg1 : i32) {96 func.return97}98 99// Positive test: Type (1.d) "is a non-forwarded call operand"100// CHECK-LABEL: test_tag: call101// CHECK-LABEL: operand #0: not live102// CHECK-LABEL: operand #1: not live103// CHECK-LABEL: operand #2: live104func.func @test_4_type_1.d(%arg0: i32, %arg1: i32, %device: i32, %m0: memref<i32>) {105 test.call_on_device @private(%arg0, %arg1), %device {tag = "call"} : (i32, i32, i32) -> ()106 return107}108 109// -----110 111// Positive test: Type (2) "is returned by a public function"112// zero is live because it is returned by a public function.113// CHECK-LABEL: test_tag: zero:114// CHECK-NEXT: result #0: live115func.func @test_5_type_2() -> (f32){116 %0 = arith.constant {tag = "zero"} 0.0 : f32117 return %0 : f32118}119 120// -----121 122// Positive test: Type (3) "is used to compute a value of type (1) or (2)"123// %arg1 is live because the scf.while has a live result and %arg1 is a124// non-forwarded branch operand.125// %arg2 is live because it is forwarded to the live result of the scf.while126// op.127// %arg5 is live because it is forwarded to %arg8 which is live.128// %arg8 is live because it is forwarded to %arg4 which is live as it writes129// to memory.130// Negative test:131// %arg3 is not live even though %arg1, %arg2, and %arg5 are live because it132// is neither a non-forwarded branch operand nor a forwarded operand that133// forwards to a live value. It actually is a forwarded operand that forwards134// to non-live values %0#1 and %arg7.135// CHECK-LABEL: test_tag: condition:136// CHECK-NEXT: operand #0: live137// CHECK-NEXT: operand #1: live138// CHECK-NEXT: operand #2: not live139// CHECK-NEXT: operand #3: live140// CHECK-LABEL: test_tag: add:141// CHECK-NEXT: operand #0: live142func.func @test_6_RegionBranchTerminatorOpInterface_type_3(%arg0: memref<i32>, %arg1: i1) -> (i32) {143 %c0_i32 = arith.constant 0 : i32144 %c1_i32 = arith.constant 1 : i32145 %c2_i32 = arith.constant 2 : i32146 %0:3 = scf.while (%arg2 = %c0_i32, %arg3 = %c1_i32, %arg4 = %c2_i32, %arg5 = %c2_i32) : (i32, i32, i32, i32) -> (i32, i32, i32) {147 memref.store %arg4, %arg0[] : memref<i32>148 scf.condition(%arg1) {tag = "condition"} %arg2, %arg3, %arg5 : i32, i32, i32149 } do {150 ^bb0(%arg6: i32, %arg7: i32, %arg8: i32):151 %1 = arith.addi %arg8, %arg8 {tag = "add"} : i32152 %c3_i32 = arith.constant 3 : i32153 scf.yield %arg6, %arg7, %arg8, %c3_i32 : i32, i32, i32, i32154 }155 return %0#0 : i32156}157 158// -----159 160func.func private @private0(%0 : i32) -> i32 {161 %1 = arith.addi %0, %0 {tag = "in_private0"} : i32162 func.return %1 : i32163}164 165// Positive test: Type (3) "is used to compute a value of type (1) or (2)"166// zero, ten, and one are live because they are used to decide the number of167// times the `for` loop executes, which in turn decides the value stored in168// memory.169// in_private0 and x are also live because they decide the value stored in170// memory.171// Negative test:172// y is not live even though the non-forwarded branch operand and x are live.173// CHECK-LABEL: test_tag: in_private0:174// CHECK-NEXT: operand #0: live175// CHECK-NEXT: operand #1: live176// CHECK-NEXT: result #0: live177// CHECK-LABEL: test_tag: zero:178// CHECK-NEXT: result #0: live179// CHECK-LABEL: test_tag: ten:180// CHECK-NEXT: result #0: live181// CHECK-LABEL: test_tag: one:182// CHECK-NEXT: result #0: live183// CHECK-LABEL: test_tag: x:184// CHECK-NEXT: result #0: live185// CHECK-LABEL: test_tag: y:186// CHECK-NEXT: result #0: not live187// CHECK-LABEL: test_tag: for:188// CHECK-NEXT: operand #0: live189// CHECK-NEXT: operand #1: live190// CHECK-NEXT: operand #2: live191// CHECK-NEXT: operand #3: live192// CHECK-NEXT: operand #4: not live193// CHECK-NEXT: result #0: live194// CHECK-NEXT: result #1: not live195// CHECK-NEXT: region: #0:196// CHECK-NEXT: argument: #0: live197// CHECK-NEXT: argument: #1: not live198// CHECK-NEXT: argument: #2: not live199func.func @test_7_type_3(%arg0: memref<i32>) {200 %c0 = arith.constant {tag = "zero"} 0 : index201 %c10 = arith.constant {tag = "ten"} 10 : index202 %c1 = arith.constant {tag = "one"} 1 : index203 %x = arith.constant {tag = "x"} 0 : i32204 %y = arith.constant {tag = "y"} 1 : i32205 %0:2 = scf.for %arg1 = %c0 to %c10 step %c1 iter_args(%arg2 = %x, %arg3 = %y) -> (i32, i32) {206 %1 = arith.addi %x, %x : i32207 %2 = func.call @private0(%1) : (i32) -> i32208 scf.yield %2, %arg3 : i32, i32209 } {tag = "for"}210 memref.store %0#0, %arg0[] : memref<i32>211 return212}213 214// -----215 216func.func private @private1(%0 : i32) -> i32 {217 %1 = func.call @private2(%0) : (i32) -> i32218 %2 = arith.muli %0, %1 {tag = "in_private1"} : i32219 func.return %2 : i32220}221 222func.func private @private2(%0 : i32) -> i32 {223 %cond = arith.index_cast %0 {tag = "in_private2"} : i32 to index224 %1 = scf.index_switch %cond -> i32225 case 1 {226 %ten = arith.constant 10 : i32227 scf.yield %ten : i32228 }229 case 2 {230 %twenty = arith.constant 20 : i32231 scf.yield %twenty : i32232 }233 default {234 %thirty = arith.constant 30 : i32235 scf.yield %thirty : i32236 }237 func.return %1 : i32238}239 240// Positive test: Type (3) "is used to compute a value of type (1) or (2)"241// in_private1, in_private2, and final are live because they are used to compute242// the value returned by this public function.243// CHECK-LABEL: test_tag: in_private1:244// CHECK-NEXT: operand #0: live245// CHECK-NEXT: operand #1: live246// CHECK-NEXT: result #0: live247// CHECK-LABEL: test_tag: in_private2:248// CHECK-NEXT: operand #0: live249// CHECK-NEXT: result #0: live250// CHECK-LABEL: test_tag: final:251// CHECK-NEXT: operand #0: live252// CHECK-NEXT: operand #1: live253// CHECK-NEXT: result #0: live254func.func @test_8_type_3(%arg: i32) -> (i32) {255 %0 = func.call @private1(%arg) : (i32) -> i32256 %final = arith.muli %0, %arg {tag = "final"} : i32257 return %final : i32258}259 260// -----261 262// Negative test: None of the types (1), (2), or (3)263// zero is not live because it has no effect outside the program: it doesn't264// affect the memory or the program output.265// CHECK-LABEL: test_tag: zero:266// CHECK-NEXT: result #0: not live267// CHECK-LABEL: test_tag: one:268// CHECK-NEXT: result #0: live269func.func @test_9_negative() -> (f32){270 %0 = arith.constant {tag = "zero"} 0.0 : f32271 %1 = arith.constant {tag = "one"} 1.0 : f32272 return %1 : f32273}274 275// -----276 277// Negative test: None of the types (1), (2), or (3)278// %1 is not live because it has no effect outside the program: it doesn't279// affect the memory or the program output. Even though it is returned by the280// function `@private_1`, it is never used by the caller.281// Note that this test clearly shows how this liveness analysis utility differs282// from the existing liveness utility present at283// llvm-project/mlir/include/mlir/Analysis/Liveness.h. The latter marks %1 as284// live as it exists the block of function `@private_1`, simply because it is285// computed inside and returned by the block, irrespective of whether or not it286// is used by the caller.287// CHECK-LABEL: test_tag: one:288// CHECK: result #0: not live289func.func private @private_1() -> (i32, i32) {290 %0 = arith.constant 0 : i32291 %1 = arith.addi %0, %0 {tag = "one"} : i32292 return %0, %1 : i32, i32293}294func.func @test_10_negative() -> (i32) {295 %0:2 = func.call @private_1() : () -> (i32, i32)296 return %0#0 : i32297}298 299// -----300 301// Test that we correctly set a liveness value for operations in dead block.302// These won't be visited by the dataflow framework so the analysis need to303// explicitly manage them.304// CHECK-LABEL: test_tag: dead_block_cmpi:305// CHECK-NEXT: operand #0: not live306// CHECK-NEXT: operand #1: not live307// CHECK-NEXT: result #0: not live308func.func @dead_block() {309 %false = arith.constant false310 %zero = arith.constant 0 : i64311 cf.cond_br %false, ^bb1, ^bb4312 ^bb1:313 %3 = arith.cmpi eq, %zero, %zero {tag = "dead_block_cmpi"} : i64314 cf.br ^bb1315 ^bb4:316 return317}318