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. It does so in `after_gvn` since it 162 // requires the VisitIf optimization which happens later in the graph. 163 164 /// CHECK-START: int Main.$noinline$testOptimizeAfterOneBranchDisappears(int, boolean) dead_code_elimination$after_gvn (before) 165 /// CHECK: Throw 166 167 /// CHECK-START: int Main.$noinline$testOptimizeAfterOneBranchDisappears(int, boolean) dead_code_elimination$after_gvn (before) 168 /// CHECK: TryBoundary 169 /// CHECK: TryBoundary 170 /// CHECK: TryBoundary 171 /// CHECK: TryBoundary 172 /// CHECK-NOT: TryBoundary 173 174 /// CHECK-START: int Main.$noinline$testOptimizeAfterOneBranchDisappears(int, boolean) dead_code_elimination$after_gvn (before) 175 /// CHECK: flags "catch_block" 176 /// CHECK-NOT: flags "catch_block" 177 178 /// CHECK-START: int Main.$noinline$testOptimizeAfterOneBranchDisappears(int, boolean) dead_code_elimination$after_gvn (after) 179 /// CHECK-NOT: Throw 180 181 /// CHECK-START: int Main.$noinline$testOptimizeAfterOneBranchDisappears(int, boolean) dead_code_elimination$after_gvn (after) 182 /// CHECK-NOT: TryBoundary 183 184 /// CHECK-START: int Main.$noinline$testOptimizeAfterOneBranchDisappears(int, boolean) dead_code_elimination$after_gvn (after) 185 /// CHECK-NOT: flags "catch_block" $noinline$testOptimizeAfterOneBranchDisappears(int a, boolean val)186 public static int $noinline$testOptimizeAfterOneBranchDisappears(int a, boolean val) { 187 try { 188 for (int i = 0; i < 4; i++) { 189 // TryBoundary kind:entry 190 a /= 3; 191 // TryBoundary kind:exit 192 } 193 194 if (val && !val) { 195 // TryBoundary kind:entry 196 throw new Error(); 197 // TryBoundary kind:exit 198 } 199 } catch (Error e) { 200 return -1000; 201 } 202 return a; 203 } 204 205 /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNested(int) dead_code_elimination$initial (before) 206 /// CHECK: TryBoundary 207 /// CHECK: TryBoundary 208 /// CHECK: TryBoundary 209 /// CHECK: TryBoundary 210 /// CHECK-NOT: TryBoundary 211 212 /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNested(int) dead_code_elimination$initial (before) 213 /// CHECK: flags "catch_block" 214 /// CHECK: flags "catch_block" 215 /// CHECK-NOT: flags "catch_block" 216 217 /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNested(int) dead_code_elimination$initial (after) 218 /// CHECK-NOT: TryBoundary 219 220 /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNested(int) dead_code_elimination$initial (after) 221 /// CHECK-NOT: flags "catch_block" $noinline$testRemoveTryBoundaryNested(int a)222 public static int $noinline$testRemoveTryBoundaryNested(int a) { 223 try { 224 // TryBoundary kind:entry 225 a /= 2; 226 // TryBoundary kind:exit 227 try { 228 // TryBoundary kind:entry 229 a /= 3; 230 // TryBoundary kind:exit 231 } catch (Error e) { 232 return -2000; 233 } 234 } catch (Exception e) { 235 return -1000; 236 } 237 return a; 238 } 239 240 // We can remove the `TryBoundary` instructions surrounding `a /= 2;` but since the inner try can 241 // throw, we must keep both the inner and outer catches as they are catch handlers of the inner 242 // try. 243 244 /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNestedButNotCatch(int, boolean) dead_code_elimination$initial (before) 245 /// CHECK: TryBoundary 246 /// CHECK: TryBoundary 247 /// CHECK: TryBoundary 248 /// CHECK: TryBoundary 249 /// CHECK-NOT: TryBoundary 250 251 /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNestedButNotCatch(int, boolean) dead_code_elimination$initial (before) 252 /// CHECK: flags "catch_block" 253 /// CHECK: flags "catch_block" 254 /// CHECK-NOT: flags "catch_block" 255 256 /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNestedButNotCatch(int, boolean) dead_code_elimination$initial (after) 257 /// CHECK: TryBoundary 258 /// CHECK: TryBoundary 259 /// CHECK-NOT: TryBoundary 260 261 /// CHECK-START: int Main.$noinline$testRemoveTryBoundaryNestedButNotCatch(int, boolean) dead_code_elimination$initial (after) 262 /// CHECK: flags "catch_block" 263 /// CHECK: flags "catch_block" 264 /// CHECK-NOT: flags "catch_block" $noinline$testRemoveTryBoundaryNestedButNotCatch(int a, boolean val)265 public static int $noinline$testRemoveTryBoundaryNestedButNotCatch(int a, boolean val) { 266 try { 267 // TryBoundary kind:entry 268 a /= 2; 269 // TryBoundary kind:exit 270 try { 271 if (val) { 272 // TryBoundary kind:entry 273 throw new Error(); 274 // TryBoundary kind:exit 275 } 276 // TryBoundary kind:exit 277 } catch (Error e) { 278 return -2000; 279 } 280 } catch (Exception e) { 281 return -1000; 282 } 283 return a; 284 } 285 286 // We eliminate the return -1000 catch block which is outside of the loop in 287 // dead_code_elimination$initial. We can do so since we eliminated the TryBoundary of `a /= 2;`. 288 289 /// CHECK-START: int Main.$noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(int, boolean) dead_code_elimination$initial (before) 290 /// CHECK: TryBoundary 291 /// CHECK: TryBoundary 292 /// CHECK: TryBoundary 293 /// CHECK: TryBoundary 294 /// CHECK-NOT: TryBoundary 295 296 /// CHECK-START: int Main.$noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(int, boolean) dead_code_elimination$initial (before) 297 /// CHECK: flags "catch_block" 298 /// CHECK: flags "catch_block" 299 /// CHECK: flags "catch_block" 300 /// CHECK-NOT: flags "catch_block" 301 302 /// CHECK-START: int Main.$noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(int, boolean) dead_code_elimination$initial (before) 303 /// CHECK: IntConstant -1000 304 305 /// CHECK-START: int Main.$noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(int, boolean) dead_code_elimination$initial (after) 306 /// CHECK: TryBoundary 307 /// CHECK: TryBoundary 308 /// CHECK-NOT: TryBoundary 309 310 /// CHECK-START: int Main.$noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(int, boolean) dead_code_elimination$initial (after) 311 /// CHECK: flags "catch_block" 312 /// CHECK: flags "catch_block" 313 /// CHECK-NOT: flags "catch_block" 314 315 /// CHECK-START: int Main.$noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(int, boolean) dead_code_elimination$initial (after) 316 /// CHECK-NOT: IntConstant -1000 317 318 // When removing that block, we are removing a block outside of a loop but we still need to update 319 // the loop information in the graph since we removed TryBoundary instructions inside of a loop 320 // and now `a /= 2;` is not considered part of a loop (Cannot throw so it will not `continue` and 321 // will always return). 322 323 /// CHECK-START: int Main.$noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(int, boolean) dead_code_elimination$initial (before) 324 /// CHECK: Div loop:B2 325 326 /// CHECK-START: int Main.$noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(int, boolean) dead_code_elimination$initial (after) 327 /// CHECK-NOT: Div loop:B2 328 329 /// CHECK-START: int Main.$noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop(int, boolean) dead_code_elimination$initial (after) 330 /// CHECK: Div 331 /// CHECK-NOT: Div $noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop( int a, boolean val)332 public static int $noinline$testNestedTryBoundariesWithLoopAndCatchOutsideOfLoop( 333 int a, boolean val) { 334 try { 335 for (int i = 0; i < 4; ++i) { 336 try { 337 try { 338 if (val) { 339 // TryBoundary kind:entry 340 throw new Error(); 341 // TryBoundary kind:exit 342 } 343 // TryBoundary kind:exit 344 } catch (Exception e) { 345 continue; 346 } 347 // TryBoundary kind:entry 348 a /= 2; 349 // TryBoundary kind:exit 350 return a; 351 } catch (Error e) { 352 continue; 353 } 354 } 355 } catch (Exception e) { 356 return -1000; 357 } 358 return a; 359 } 360 } 361