1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 public class Main { main(String[] args)18 public static void main(String[] args) throws Exception { 19 assertEquals(2, $noinline$testDivideOverTen(20)); 20 assertEquals(-2, $noinline$testDivideOverTen(-20)); 21 assertEquals(0, $noinline$testSimpleDivisionInLoop(0)); 22 assertEquals(1, $noinline$testSimpleDivisionInLoop(81)); 23 assertEquals(10, $noinline$testOptimizeSeparateBranches(60, true)); 24 assertEquals(10, $noinline$testOptimizeSeparateBranches(80, false)); 25 assertEquals(1, $noinline$testDoNotOptimizeOneBranchThrows(81, false)); 26 assertEquals(-1000, $noinline$testDoNotOptimizeOneBranchThrows(81, true)); 27 assertEquals(1, $noinline$testOptimizeAfterOneBranchDisappears(81, false)); 28 assertEquals(10, $noinline$testRemoveTryBoundaryNested(60)); 29 assertEquals(-2000, $noinline$testRemoveTryBoundaryNestedButNotCatch(60, true)); 30 assertEquals(30, $noinline$testRemoveTryBoundaryNestedButNotCatch(60, false)); 31 assertEquals(30, $noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(60, false)); 32 } 33 assertEquals(int expected, int result)34 public static void assertEquals(int expected, int result) { 35 if (expected != result) { 36 throw new Error("Expected: " + expected + ", found: " + result); 37 } 38 } 39 40 // Check that this version cannot remove the TryBoundary instructions since we may throw. 41 42 /// CHECK-START: int Main.$inline$division(int, int) register (after) 43 /// CHECK: TryBoundary 44 /// CHECK: TryBoundary 45 /// CHECK-NOT: TryBoundary 46 47 /// CHECK-START: int Main.$inline$division(int, int) register (after) 48 /// CHECK: flags "catch_block" $inline$division(int a, int b)49 private static int $inline$division(int a, int b) { 50 try { 51 return a / b; 52 } catch (Error unexpected) { 53 return -1000; 54 } 55 } 56 57 // Check that we can remove the TryBoundary afer inlining since we know we can't throw. 58 59 /// CHECK-START: int Main.$noinline$testDivideOverTen(int) inliner (after) 60 /// CHECK-NOT: TryBoundary 61 62 /// CHECK-START: int Main.$noinline$testDivideOverTen(int) inliner (after) 63 /// CHECK-NOT: flags "catch_block" $noinline$testDivideOverTen(int a)64 private static int $noinline$testDivideOverTen(int a) { 65 return $inline$division(a, 10); 66 } 67 68 /// CHECK-START: int Main.$noinline$testSimpleDivisionInLoop(int) dead_code_elimination$initial (before) 69 /// CHECK: TryBoundary 70 /// CHECK: TryBoundary 71 /// CHECK-NOT: TryBoundary 72 73 /// CHECK-START: int Main.$noinline$testSimpleDivisionInLoop(int) dead_code_elimination$initial (before) 74 /// CHECK: flags "catch_block" 75 76 /// CHECK-START: int Main.$noinline$testSimpleDivisionInLoop(int) dead_code_elimination$initial (after) 77 /// CHECK-NOT: TryBoundary 78 79 /// CHECK-START: int Main.$noinline$testSimpleDivisionInLoop(int) dead_code_elimination$initial (after) 80 /// CHECK-NOT: flags "catch_block" $noinline$testSimpleDivisionInLoop(int a)81 private static int $noinline$testSimpleDivisionInLoop(int a) { 82 try { 83 for (int i = 0; i < 4; i++) { 84 a /= 3; 85 } 86 } catch (Error unexpected) { 87 return -1000; 88 } 89 return a; 90 } 91 92 // Even though the `TryBoundary`s are split, we can remove them as nothing in the try can throw. 93 94 /// CHECK-START: int Main.$noinline$testOptimizeSeparateBranches(int, boolean) dead_code_elimination$initial (before) 95 /// CHECK: TryBoundary 96 /// CHECK: TryBoundary 97 /// CHECK: TryBoundary 98 /// CHECK-NOT: TryBoundary 99 100 /// CHECK-START: int Main.$noinline$testOptimizeSeparateBranches(int, boolean) dead_code_elimination$initial (before) 101 /// CHECK: flags "catch_block" 102 /// CHECK-NOT: flags "catch_block" 103 104 /// CHECK-START: int Main.$noinline$testOptimizeSeparateBranches(int, boolean) dead_code_elimination$initial (after) 105 /// CHECK-NOT: TryBoundary 106 107 /// CHECK-START: int Main.$noinline$testOptimizeSeparateBranches(int, boolean) dead_code_elimination$initial (after) 108 /// CHECK-NOT: flags "catch_block" $noinline$testOptimizeSeparateBranches(int a, boolean val)109 private static int $noinline$testOptimizeSeparateBranches(int a, boolean val) { 110 try { 111 if (val) { 112 // TryBoundary kind:entry 113 a /= 3; 114 } else { 115 // TryBoundary kind:entry 116 a /= 4; 117 } 118 a /= 2; 119 // TryBoundary kind:exit 120 } catch (Error unexpected) { 121 return -1000; 122 } 123 return a; 124 } 125 126 // Even though the `a /= 3;` can't throw, we don't eliminate any `TryBoundary` instructions. This 127 // is because we have the `throw new Error();` in the try as well. We could potentially support 128 // removing some `TryBoundary` instructions and not all in the try, but this would complicate the 129 // code and wouldn't bring code size reductions since we would be unable to remove the catch 130 // block. 131 132 /// CHECK-START: int Main.$noinline$testDoNotOptimizeOneBranchThrows(int, boolean) register (after) 133 /// CHECK: TryBoundary 134 /// CHECK: TryBoundary 135 /// CHECK: TryBoundary 136 /// CHECK: TryBoundary 137 /// CHECK-NOT: TryBoundary 138 139 /// CHECK-START: int Main.$noinline$testDoNotOptimizeOneBranchThrows(int, boolean) register (after) 140 /// CHECK: flags "catch_block" $noinline$testDoNotOptimizeOneBranchThrows(int a, boolean val)141 public static int $noinline$testDoNotOptimizeOneBranchThrows(int a, boolean val) { 142 try { 143 for (int i = 0; i < 4; i++) { 144 // TryBoundary kind:entry 145 a /= 3; 146 // TryBoundary kind:exit 147 } 148 149 if (val) { 150 // TryBoundary kind:entry 151 throw new Error(); 152 // TryBoundary kind:exit 153 } 154 } catch (Error e) { 155 return -1000; 156 } 157 return a; 158 } 159 160 // The throw gets eliminated by `SimplifyIfs` in DCE, so we can detect that nothing can throw in 161 // the graph and eliminate the `TryBoundary` instructions. 162 163 /// CHECK-START: int Main.$noinline$testOptimizeAfterOneBranchDisappears(int, boolean) dead_code_elimination$initial (before) 164 /// CHECK: Throw 165 166 /// CHECK-START: int Main.$noinline$testOptimizeAfterOneBranchDisappears(int, boolean) dead_code_elimination$initial (before) 167 /// CHECK: TryBoundary 168 /// CHECK: TryBoundary 169 /// CHECK: TryBoundary 170 /// CHECK: TryBoundary 171 /// CHECK-NOT: TryBoundary 172 173 /// CHECK-START: int Main.$noinline$testOptimizeAfterOneBranchDisappears(int, boolean) dead_code_elimination$initial (before) 174 /// CHECK: flags "catch_block" 175 /// CHECK-NOT: flags "catch_block" 176 177 /// CHECK-START: int Main.$noinline$testOptimizeAfterOneBranchDisappears(int, boolean) dead_code_elimination$initial (after) 178 /// CHECK-NOT: Throw 179 180 /// CHECK-START: int Main.$noinline$testOptimizeAfterOneBranchDisappears(int, boolean) dead_code_elimination$initial (after) 181 /// CHECK-NOT: TryBoundary 182 183 /// CHECK-START: int Main.$noinline$testOptimizeAfterOneBranchDisappears(int, boolean) dead_code_elimination$initial (after) 184 /// CHECK-NOT: flags "catch_block" $noinline$testOptimizeAfterOneBranchDisappears(int a, boolean val)185 public static int $noinline$testOptimizeAfterOneBranchDisappears(int a, boolean val) { 186 try { 187 for (int i = 0; i < 4; i++) { 188 // TryBoundary kind:entry 189 a /= 3; 190 // TryBoundary kind:exit 191 } 192 193 if (val && !val) { 194 // TryBoundary kind:entry 195 throw new Error(); 196 // TryBoundary kind:exit 197 } 198 } catch (Error e) { 199 return -1000; 200 } 201 return a; 202 } 203 204 /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNested(int) dead_code_elimination$initial (before) 205 /// CHECK: TryBoundary 206 /// CHECK: TryBoundary 207 /// CHECK: TryBoundary 208 /// CHECK: TryBoundary 209 /// CHECK-NOT: TryBoundary 210 211 /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNested(int) dead_code_elimination$initial (before) 212 /// CHECK: flags "catch_block" 213 /// CHECK: flags "catch_block" 214 /// CHECK-NOT: flags "catch_block" 215 216 /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNested(int) dead_code_elimination$initial (after) 217 /// CHECK-NOT: TryBoundary 218 219 /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNested(int) dead_code_elimination$initial (after) 220 /// CHECK-NOT: flags "catch_block" $noinline$testRemoveTryBoundaryNested(int a)221 public static int $noinline$testRemoveTryBoundaryNested(int a) { 222 try { 223 // TryBoundary kind:entry 224 a /= 2; 225 // TryBoundary kind:exit 226 try { 227 // TryBoundary kind:entry 228 a /= 3; 229 // TryBoundary kind:exit 230 } catch (Error e) { 231 return -2000; 232 } 233 } catch (Exception e) { 234 return -1000; 235 } 236 return a; 237 } 238 239 // We can remove the `TryBoundary` instructions surrounding `a /= 2;` but since the inner try can 240 // throw, we must keep both the inner and outer catches as they are catch handlers of the inner 241 // try. 242 243 /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNestedButNotCatch(int, boolean) dead_code_elimination$initial (before) 244 /// CHECK: TryBoundary 245 /// CHECK: TryBoundary 246 /// CHECK: TryBoundary 247 /// CHECK: TryBoundary 248 /// CHECK-NOT: TryBoundary 249 250 /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNestedButNotCatch(int, boolean) dead_code_elimination$initial (before) 251 /// CHECK: flags "catch_block" 252 /// CHECK: flags "catch_block" 253 /// CHECK-NOT: flags "catch_block" 254 255 /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNestedButNotCatch(int, boolean) dead_code_elimination$initial (after) 256 /// CHECK: TryBoundary 257 /// CHECK: TryBoundary 258 /// CHECK-NOT: TryBoundary 259 260 /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNestedButNotCatch(int, boolean) dead_code_elimination$initial (after) 261 /// CHECK: flags "catch_block" 262 /// CHECK: flags "catch_block" 263 /// CHECK-NOT: flags "catch_block" $noinline$testRemoveTryBoundaryNestedButNotCatch(int a, boolean val)264 public static int $noinline$testRemoveTryBoundaryNestedButNotCatch(int a, boolean val) { 265 try { 266 // TryBoundary kind:entry 267 a /= 2; 268 // TryBoundary kind:exit 269 try { 270 if (val) { 271 // TryBoundary kind:entry 272 throw new Error(); 273 // TryBoundary kind:exit 274 } 275 // TryBoundary kind:exit 276 } catch (Error e) { 277 return -2000; 278 } 279 } catch (Exception e) { 280 return -1000; 281 } 282 return a; 283 } 284 285 // We eliminate the return -1000 catch block which is outside of the loop in 286 // dead_code_elimination$initial. We can do so since we eliminated the TryBoundary of `a /= 2;`. 287 288 /// CHECK-START: int Main.$noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(int, boolean) dead_code_elimination$initial (before) 289 /// CHECK: TryBoundary 290 /// CHECK: TryBoundary 291 /// CHECK: TryBoundary 292 /// CHECK: TryBoundary 293 /// CHECK-NOT: TryBoundary 294 295 /// CHECK-START: int Main.$noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(int, boolean) dead_code_elimination$initial (before) 296 /// CHECK: flags "catch_block" 297 /// CHECK: flags "catch_block" 298 /// CHECK: flags "catch_block" 299 /// CHECK-NOT: flags "catch_block" 300 301 /// CHECK-START: int Main.$noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(int, boolean) dead_code_elimination$initial (before) 302 /// CHECK: IntConstant -1000 303 304 /// CHECK-START: int Main.$noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(int, boolean) dead_code_elimination$initial (after) 305 /// CHECK: TryBoundary 306 /// CHECK: TryBoundary 307 /// CHECK-NOT: TryBoundary 308 309 /// CHECK-START: int Main.$noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(int, boolean) dead_code_elimination$initial (after) 310 /// CHECK: flags "catch_block" 311 /// CHECK: flags "catch_block" 312 /// CHECK-NOT: flags "catch_block" 313 314 /// CHECK-START: int Main.$noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(int, boolean) dead_code_elimination$initial (after) 315 /// CHECK-NOT: IntConstant -1000 316 317 // When removing that block, we are removing a block outside of a loop but we still need to update 318 // the loop information in the graph since we removed TryBoundary instructions inside of a loop 319 // and now `a /= 2;` is not considered part of a loop (Cannot throw so it will not `continue` and 320 // will always return). 321 322 /// CHECK-START: int Main.$noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(int, boolean) dead_code_elimination$initial (before) 323 /// CHECK: Div loop:B2 324 325 /// CHECK-START: int Main.$noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(int, boolean) dead_code_elimination$initial (after) 326 /// CHECK-NOT: Div loop:B2 327 328 /// CHECK-START: int Main.$noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(int, boolean) dead_code_elimination$initial (after) 329 /// CHECK: Div 330 /// CHECK-NOT: Div $noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop( int a, boolean val)331 public static int $noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop( 332 int a, boolean val) { 333 try { 334 for (int i = 0; i < 4; ++i) { 335 try { 336 try { 337 if (val) { 338 // TryBoundary kind:entry 339 throw new Error(); 340 // TryBoundary kind:exit 341 } 342 // TryBoundary kind:exit 343 } catch (Exception e) { 344 continue; 345 } 346 // TryBoundary kind:entry 347 a /= 2; 348 // TryBoundary kind:exit 349 return a; 350 } catch (Error e) { 351 continue; 352 } 353 } 354 } catch (Exception e) { 355 return -1000; 356 } 357 return a; 358 } 359 } 360