1 /* 2 * Copyright (C) 2017 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 { 18 static class ValueHolder { getValue()19 int getValue() { 20 // Prevent inliner from matching the code pattern when calling this method to test 21 // the normal inlining path that does not inline in blocks that end with a `throw`. 22 $inline$nop(); 23 24 return 1; 25 } 26 $inline$nop()27 private void $inline$nop() {} 28 } 29 main(String[] args)30 public static void main(String[] args) throws Exception { 31 testSimpleUse(); 32 testTwoUses(); 33 testFieldStores(doThrow); 34 testFieldStoreCycle(); 35 testArrayStores(); 36 testOnlyStoreUses(); 37 testNoUse(); 38 testPhiInput(); 39 testVolatileStore(); 40 testCatchBlock(); 41 $noinline$testTwoThrowingPathsAndStringBuilderAppend(); 42 try { 43 $noinline$testSinkNewInstanceWithClinitCheck(); 44 throw new Exception("Unreachable"); 45 } catch (Error e) { 46 // expected 47 } 48 $noinline$testMethodEndsWithTryBoundary(); 49 doThrow = true; 50 try { 51 testInstanceSideEffects(); 52 } catch (Error e) { 53 // expected 54 System.out.println(e.getMessage()); 55 } 56 try { 57 testStaticSideEffects(); 58 } catch (Error e) { 59 // expected 60 System.out.println(e.getMessage()); 61 } 62 63 try { 64 testStoreStore(doThrow); 65 } catch (Error e) { 66 // expected 67 System.out.println(e.getMessage()); 68 } 69 } 70 71 /// CHECK-START: void Main.testSimpleUse() code_sinking (before) 72 /// CHECK: <<LoadClass:l\d+>> LoadClass class_name:java.lang.Object 73 /// CHECK: <<New:l\d+>> NewInstance [<<LoadClass>>] 74 /// CHECK: ConstructorFence [<<New>>] 75 /// CHECK: If 76 /// CHECK: begin_block 77 /// CHECK: Throw 78 79 /// CHECK-START: void Main.testSimpleUse() code_sinking (after) 80 /// CHECK-NOT: NewInstance 81 /// CHECK: If 82 /// CHECK: begin_block 83 /// CHECK: <<Error:l\d+>> LoadClass class_name:java.lang.Error 84 /// CHECK: <<LoadClass:l\d+>> LoadClass class_name:java.lang.Object 85 /// CHECK-NOT: begin_block 86 /// CHECK: <<New:l\d+>> NewInstance [<<LoadClass>>] 87 /// CHECK: ConstructorFence [<<New>>] 88 /// CHECK-NOT: begin_block 89 /// CHECK: NewInstance [<<Error>>] 90 /// CHECK: Throw testSimpleUse()91 public static void testSimpleUse() { 92 Object o = new Object(); 93 if (doThrow) { 94 throw new Error(o.toString()); 95 } 96 } 97 98 /// CHECK-START: void Main.testTwoUses() code_sinking (before) 99 /// CHECK: <<LoadClass:l\d+>> LoadClass class_name:java.lang.Object 100 /// CHECK: NewInstance [<<LoadClass>>] 101 /// CHECK: If 102 /// CHECK: begin_block 103 /// CHECK: Throw 104 105 /// CHECK-START: void Main.testTwoUses() code_sinking (after) 106 /// CHECK-NOT: NewInstance 107 /// CHECK: If 108 /// CHECK: begin_block 109 /// CHECK: <<Error:l\d+>> LoadClass class_name:java.lang.Error 110 /// CHECK: <<LoadClass:l\d+>> LoadClass class_name:java.lang.Object 111 /// CHECK-NOT: begin_block 112 /// CHECK: NewInstance [<<LoadClass>>] 113 /// CHECK-NOT: begin_block 114 /// CHECK: NewInstance [<<Error>>] 115 /// CHECK: Throw testTwoUses()116 public static void testTwoUses() { 117 Object o = new Object(); 118 if (doThrow) { 119 throw new Error(o.toString() + o.toString()); 120 } 121 } 122 123 // NB It might seem that we'd move the allocation and ifield-set but those are 124 // already moved into the throw block by a combo of partial-LSE and DCE. 125 // Instead all that is actually moved is the LoadClass. Also note the 126 // LoadClass can only be moved since it refers to the 'Main' class itself, 127 // meaning there's no need for any clinit/actual loading. 128 // 129 /// CHECK-START: void Main.testFieldStores(boolean) code_sinking (before) 130 /// CHECK: <<Int42:i\d+>> IntConstant 42 131 /// CHECK: begin_block 132 /// CHECK: <<LoadClass:l\d+>> LoadClass class_name:Main 133 /// CHECK: <<NewInstance:l\d+>> NewInstance [<<LoadClass>>] 134 /// CHECK: InstanceFieldSet [<<NewInstance>>,<<Int42>>] 135 /// CHECK: Throw 136 137 /// CHECK-START: void Main.testFieldStores(boolean) code_sinking (after) 138 /// CHECK: <<Int42:i\d+>> IntConstant 42 139 /// CHECK-NOT: NewInstance 140 /// CHECK: If 141 /// CHECK: begin_block 142 /// CHECK: <<Error:l\d+>> LoadClass class_name:java.lang.Error 143 /// CHECK-NOT: begin_block 144 /// CHECK: <<LoadClass:l\d+>> LoadClass class_name:Main 145 /// CHECK-NOT: begin_block 146 /// CHECK: <<NewInstance:l\d+>> NewInstance [<<LoadClass>>] 147 /// CHECK-NOT: begin_block 148 /// CHECK: InstanceFieldSet [<<NewInstance>>,<<Int42>>] 149 /// CHECK-NOT: begin_block 150 /// CHECK: <<Throw:l\d+>> NewInstance [<<Error>>] 151 /// CHECK-NOT: begin_block 152 /// CHECK: Throw [<<Throw>>] testFieldStores(boolean doThrow)153 public static void testFieldStores(boolean doThrow) { 154 Main m = new Main(); 155 m.intField = 42; 156 if (doThrow) { 157 throw new Error(m.toString()); 158 } 159 } 160 161 /// CHECK-START: void Main.testFieldStoreCycle() code_sinking (before) 162 /// CHECK: <<LoadClass:l\d+>> LoadClass class_name:Main 163 /// CHECK: <<NewInstance1:l\d+>> NewInstance [<<LoadClass>>] 164 /// CHECK: <<NewInstance2:l\d+>> NewInstance [<<LoadClass>>] 165 /// CHECK: InstanceFieldSet [<<NewInstance1>>,<<NewInstance2>>] 166 /// CHECK: InstanceFieldSet [<<NewInstance2>>,<<NewInstance1>>] 167 /// CHECK: If 168 /// CHECK: begin_block 169 /// CHECK: Throw 170 171 // TODO(ngeoffray): Handle allocation/store cycles. 172 /// CHECK-START: void Main.testFieldStoreCycle() code_sinking (after) 173 /// CHECK: begin_block 174 /// CHECK: <<LoadClass:l\d+>> LoadClass class_name:Main 175 /// CHECK: <<NewInstance1:l\d+>> NewInstance [<<LoadClass>>] 176 /// CHECK: <<NewInstance2:l\d+>> NewInstance [<<LoadClass>>] 177 /// CHECK: InstanceFieldSet [<<NewInstance1>>,<<NewInstance2>>] 178 /// CHECK: InstanceFieldSet [<<NewInstance2>>,<<NewInstance1>>] 179 /// CHECK: If 180 /// CHECK: begin_block 181 /// CHECK: Throw testFieldStoreCycle()182 public static void testFieldStoreCycle() { 183 Main m1 = new Main(); 184 Main m2 = new Main(); 185 m1.objectField = m2; 186 m2.objectField = m1; 187 if (doThrow) { 188 throw new Error(m1.toString() + m2.toString()); 189 } 190 } 191 192 /// CHECK-START: void Main.testArrayStores() code_sinking (before) 193 /// CHECK: <<Int1:i\d+>> IntConstant 1 194 /// CHECK: <<Int0:i\d+>> IntConstant 0 195 /// CHECK: <<LoadClass:l\d+>> LoadClass class_name:java.lang.Object[] 196 /// CHECK: <<NewArray:l\d+>> NewArray [<<LoadClass>>,<<Int1>>] 197 /// CHECK: ArraySet [<<NewArray>>,<<Int0>>,<<NewArray>>] 198 /// CHECK: If 199 /// CHECK: begin_block 200 /// CHECK: Throw 201 202 /// CHECK-START: void Main.testArrayStores() code_sinking (after) 203 /// CHECK: <<Int1:i\d+>> IntConstant 1 204 /// CHECK: <<Int0:i\d+>> IntConstant 0 205 /// CHECK-NOT: NewArray 206 /// CHECK: If 207 /// CHECK: begin_block 208 /// CHECK: <<Error:l\d+>> LoadClass class_name:java.lang.Error 209 /// CHECK: <<LoadClass:l\d+>> LoadClass class_name:java.lang.Object[] 210 /// CHECK-NOT: begin_block 211 /// CHECK: <<NewArray:l\d+>> NewArray [<<LoadClass>>,<<Int1>>] 212 /// CHECK-NOT: begin_block 213 /// CHECK: ArraySet [<<NewArray>>,<<Int0>>,<<NewArray>>] 214 /// CHECK-NOT: begin_block 215 /// CHECK: NewInstance [<<Error>>] 216 /// CHECK: Throw testArrayStores()217 public static void testArrayStores() { 218 Object[] o = new Object[1]; 219 o[0] = o; 220 if (doThrow) { 221 throw new Error(o.toString()); 222 } 223 } 224 225 // Make sure code sinking does not crash on dead allocations. testOnlyStoreUses()226 public static void testOnlyStoreUses() { 227 Main m = new Main(); 228 Object[] o = new Object[1]; // dead allocation, should eventually be removed b/35634932. 229 o[0] = m; 230 o = null; // Avoid environment uses for the array allocation. 231 if (doThrow) { 232 throw new Error(m.toString()); 233 } 234 } 235 236 // Make sure code sinking does not crash on dead code. testNoUse()237 public static void testNoUse() { 238 Main m = new Main(); 239 boolean load = Main.doLoop; // dead code, not removed because of environment use. 240 // Ensure one environment use for the static field 241 $opt$noinline$foo(); 242 load = false; 243 if (doThrow) { 244 throw new Error(m.toString()); 245 } 246 } 247 248 // Make sure we can move code only used by a phi. 249 /// CHECK-START: void Main.testPhiInput() code_sinking (before) 250 /// CHECK: <<Null:l\d+>> NullConstant 251 /// CHECK: <<LoadClass:l\d+>> LoadClass class_name:java.lang.Object 252 /// CHECK: <<NewInstance:l\d+>> NewInstance [<<LoadClass>>] 253 /// CHECK: If 254 /// CHECK: begin_block 255 /// CHECK: Phi [<<Null>>,<<NewInstance>>] 256 /// CHECK: Throw 257 258 /// CHECK-START: void Main.testPhiInput() code_sinking (after) 259 /// CHECK: <<Null:l\d+>> NullConstant 260 /// CHECK-NOT: NewInstance 261 /// CHECK: If 262 /// CHECK: begin_block 263 /// CHECK: <<LoadClass:l\d+>> LoadClass class_name:java.lang.Object 264 /// CHECK: <<NewInstance:l\d+>> NewInstance [<<LoadClass>>] 265 /// CHECK: begin_block 266 /// CHECK: Phi [<<Null>>,<<NewInstance>>] 267 /// CHECK: <<Error:l\d+>> LoadClass class_name:java.lang.Error 268 /// CHECK: NewInstance [<<Error>>] 269 /// CHECK: Throw testPhiInput()270 public static void testPhiInput() { 271 Object f = new Object(); 272 if (doThrow) { 273 Object o = null; 274 int i = 2; 275 if (doLoop) { 276 o = f; 277 i = 42; 278 } 279 throw new Error(o.toString() + i); 280 } 281 } 282 $opt$noinline$foo()283 static void $opt$noinline$foo() {} 284 285 // Check that we do not move volatile stores. 286 /// CHECK-START: void Main.testVolatileStore() code_sinking (before) 287 /// CHECK: <<Int42:i\d+>> IntConstant 42 288 /// CHECK: <<LoadClass:l\d+>> LoadClass class_name:Main 289 /// CHECK: <<NewInstance:l\d+>> NewInstance [<<LoadClass>>] 290 /// CHECK: InstanceFieldSet [<<NewInstance>>,<<Int42>>] 291 /// CHECK: If 292 /// CHECK: begin_block 293 /// CHECK: Throw 294 295 /// CHECK-START: void Main.testVolatileStore() code_sinking (after) 296 /// CHECK: <<Int42:i\d+>> IntConstant 42 297 /// CHECK: <<LoadClass:l\d+>> LoadClass class_name:Main 298 /// CHECK: <<NewInstance:l\d+>> NewInstance [<<LoadClass>>] 299 /// CHECK: InstanceFieldSet [<<NewInstance>>,<<Int42>>] 300 /// CHECK: If 301 /// CHECK: begin_block 302 /// CHECK: Throw testVolatileStore()303 public static void testVolatileStore() { 304 Main m = new Main(); 305 m.volatileField = 42; 306 if (doThrow) { 307 throw new Error(m.toString()); 308 } 309 } 310 $noinline$testMethodEndsWithTryBoundary()311 private static void $noinline$testMethodEndsWithTryBoundary() throws Exception { 312 assertEquals(0, $noinline$testDontSinkToReturnBranch(0, 0, false, new Object())); 313 assertEquals(1, $noinline$testSinkToThrowBranch(0, 0, true, new Object())); 314 try { 315 $noinline$testSinkToThrowBranch(0, 0, false, new Object()); 316 throw new Exception("Unreachable"); 317 } catch (Error expected) { 318 } 319 } 320 321 // Consistency check: only one add 322 /// CHECK-START: int Main.$noinline$testDontSinkToReturnBranch(int, int, boolean, java.lang.Object) code_sinking (before) 323 /// CHECK: Add 324 /// CHECK-NOT: Add 325 326 /// CHECK-START: int Main.$noinline$testDontSinkToReturnBranch(int, int, boolean, java.lang.Object) code_sinking (before) 327 /// CHECK: Add 328 /// CHECK-NEXT: If 329 330 /// CHECK-START: int Main.$noinline$testDontSinkToReturnBranch(int, int, boolean, java.lang.Object) code_sinking (after) 331 /// CHECK: Add 332 /// CHECK-NEXT: If $noinline$testDontSinkToReturnBranch(int a, int b, boolean flag, Object obj)333 private static int $noinline$testDontSinkToReturnBranch(int a, int b, boolean flag, Object obj) { 334 int c = a + b; 335 if (flag) { 336 return 1; 337 } 338 339 synchronized (obj) { 340 return $noinline$returnSameValue(c); 341 } 342 } 343 $noinline$returnSameValue(int value)344 private static int $noinline$returnSameValue(int value) { 345 return value; 346 } 347 348 // Consistency check: only one add 349 /// CHECK-START: int Main.$noinline$testSinkToThrowBranch(int, int, boolean, java.lang.Object) code_sinking (before) 350 /// CHECK: Add 351 /// CHECK-NOT: Add 352 353 /// CHECK-START: int Main.$noinline$testSinkToThrowBranch(int, int, boolean, java.lang.Object) code_sinking (before) 354 /// CHECK: Add 355 /// CHECK: If 356 357 /// CHECK-START: int Main.$noinline$testSinkToThrowBranch(int, int, boolean, java.lang.Object) code_sinking (after) 358 /// CHECK: If 359 /// CHECK: Add $noinline$testSinkToThrowBranch(int a, int b, boolean flag, Object obj)360 private static int $noinline$testSinkToThrowBranch(int a, int b, boolean flag, Object obj) { 361 int c = a + b; 362 if (flag) { 363 return 1; 364 } 365 366 synchronized (obj) { 367 throw new Error(Integer.toString(c)); 368 } 369 } 370 testInstanceSideEffects()371 public static void testInstanceSideEffects() { 372 int a = mainField.intField; 373 $noinline$changeIntField(); 374 if (doThrow) { 375 throw new Error("" + a); 376 } 377 } 378 $noinline$changeIntField()379 static void $noinline$changeIntField() { 380 mainField.intField = 42; 381 } 382 testStaticSideEffects()383 public static void testStaticSideEffects() { 384 Object o = obj; 385 $noinline$changeStaticObjectField(); 386 if (doThrow) { 387 throw new Error(o.getClass().toString()); 388 } 389 } 390 $noinline$changeStaticObjectField()391 static void $noinline$changeStaticObjectField() { 392 obj = new Main(); 393 } 394 395 // Test that we preserve the order of stores. 396 // NB It might seem that we'd move the allocation and ifield-set but those are 397 // already moved into the throw block by a combo of partial-LSE and DCE. 398 // Instead all that is actually moved is the LoadClass. Also note the 399 // LoadClass can only be moved since it refers to the 'Main' class itself, 400 // meaning there's no need for any clinit/actual loading. 401 // 402 /// CHECK-START: void Main.testStoreStore(boolean) code_sinking (before) 403 /// CHECK: <<Int42:i\d+>> IntConstant 42 404 /// CHECK: <<Int43:i\d+>> IntConstant 43 405 /// CHECK: <<LoadClass:l\d+>> LoadClass class_name:Main 406 /// CHECK: <<NewInstance:l\d+>> NewInstance [<<LoadClass>>] 407 /// CHECK-DAG: InstanceFieldSet [<<NewInstance>>,<<Int42>>] 408 /// CHECK-DAG: InstanceFieldSet [<<NewInstance>>,<<Int43>>] 409 /// CHECK: Throw 410 /// CHECK-NOT: InstanceFieldSet 411 412 /// CHECK-START: void Main.testStoreStore(boolean) code_sinking (after) 413 /// CHECK: <<Int42:i\d+>> IntConstant 42 414 /// CHECK: <<Int43:i\d+>> IntConstant 43 415 /// CHECK-NOT: NewInstance 416 /// CHECK: If 417 /// CHECK: begin_block 418 /// CHECK: <<Error:l\d+>> LoadClass class_name:java.lang.Error 419 /// CHECK-NOT: begin_block 420 /// CHECK: <<LoadClass:l\d+>> LoadClass class_name:Main 421 /// CHECK: <<NewInstance:l\d+>> NewInstance [<<LoadClass>>] 422 /// CHECK-NOT: begin_block 423 /// CHECK-DAG: InstanceFieldSet [<<NewInstance>>,<<Int42>>] 424 /// CHECK-DAG: InstanceFieldSet [<<NewInstance>>,<<Int43>>] 425 /// CHECK-NOT: begin_block 426 /// CHECK: NewInstance [<<Error>>] 427 /// CHECK: Throw 428 /// CHECK-NOT: InstanceFieldSet testStoreStore(boolean doThrow)429 public static void testStoreStore(boolean doThrow) { 430 Main m = new Main(); 431 m.intField = 42; 432 m.intField2 = 43; 433 if (doThrow) { 434 throw new Error(m.$opt$noinline$toString()); 435 } 436 } 437 doStaticNativeCallLiveVreg()438 static native void doStaticNativeCallLiveVreg(); 439 440 // Test ensures that 'o' has been moved into the if despite the InvokeStaticOrDirect. 441 // 442 /// CHECK-START: void Main.testSinkingOverInvoke() code_sinking (before) 443 /// CHECK: <<Int1:i\d+>> IntConstant 1 444 /// CHECK: <<Int0:i\d+>> IntConstant 0 445 /// CHECK: <<LoadClass:l\d+>> LoadClass class_name:java.lang.Object[] 446 /// CHECK-NOT: begin_block 447 /// CHECK: NewArray [<<LoadClass>>,<<Int1>>] 448 /// CHECK: If 449 /// CHECK: begin_block 450 /// CHECK: Throw 451 452 /// CHECK-START: void Main.testSinkingOverInvoke() code_sinking (after) 453 /// CHECK: <<Int1:i\d+>> IntConstant 1 454 /// CHECK: <<Int0:i\d+>> IntConstant 0 455 /// CHECK: If 456 /// CHECK: begin_block 457 /// CHECK: <<LoadClass:l\d+>> LoadClass class_name:java.lang.Object[] 458 /// CHECK: NewArray [<<LoadClass>>,<<Int1>>] 459 /// CHECK: Throw testSinkingOverInvoke()460 static void testSinkingOverInvoke() { 461 Object[] o = new Object[1]; 462 o[0] = o; 463 doStaticNativeCallLiveVreg(); 464 if (doThrow) { 465 throw new Error(o.toString()); 466 } 467 } 468 $opt$noinline$toString()469 public String $opt$noinline$toString() { 470 return "" + intField; 471 } 472 testCatchBlock()473 private static void testCatchBlock() { 474 assertEquals(456, testDoNotSinkToTry()); 475 assertEquals(456, testSinkWithinTryBlock()); 476 assertEquals(456, testSinkRightBeforeTryBlock()); 477 assertEquals(456, testDoNotSinkToCatchInsideTryWithMoreThings(false, false)); 478 assertEquals(456, DoNotSinkWithOOMThrow()); 479 } 480 481 /// CHECK-START: int Main.testDoNotSinkToTry() code_sinking (before) 482 /// CHECK: <<ObjLoadClass:l\d+>> LoadClass class_name:java.lang.Object 483 /// CHECK: NewInstance [<<ObjLoadClass>>] 484 /// CHECK: TryBoundary kind:entry 485 486 /// CHECK-START: int Main.testDoNotSinkToTry() code_sinking (after) 487 /// CHECK: <<ObjLoadClass:l\d+>> LoadClass class_name:java.lang.Object 488 /// CHECK: NewInstance [<<ObjLoadClass>>] 489 /// CHECK: TryBoundary kind:entry 490 491 // Consistency check to make sure there's only one entry TryBoundary. 492 /// CHECK-START: int Main.testDoNotSinkToTry() code_sinking (after) 493 /// CHECK: TryBoundary kind:entry 494 /// CHECK-NOT: TryBoundary kind:entry 495 496 // Tests that we don't sink the Object creation into the try. testDoNotSinkToTry()497 private static int testDoNotSinkToTry() { 498 Object o = new Object(); 499 try { 500 if (doEarlyReturn) { 501 throw new Error(o.toString()); 502 } 503 } catch (Error e) { 504 throw new Error(); 505 } 506 return 456; 507 } 508 509 /// CHECK-START: int Main.testSinkWithinTryBlock() code_sinking (before) 510 /// CHECK: <<ObjLoadClass:l\d+>> LoadClass class_name:java.lang.Object 511 /// CHECK: NewInstance [<<ObjLoadClass>>] 512 /// CHECK: If 513 514 /// CHECK-START: int Main.testSinkWithinTryBlock() code_sinking (after) 515 /// CHECK: If 516 /// CHECK: <<ObjLoadClass:l\d+>> LoadClass class_name:java.lang.Object 517 /// CHECK: NewInstance [<<ObjLoadClass>>] testSinkWithinTryBlock()518 private static int testSinkWithinTryBlock() { 519 try { 520 Object o = new Object(); 521 if (doEarlyReturn) { 522 throw new Error(o.toString()); 523 } 524 } catch (Error e) { 525 return 123; 526 } 527 return 456; 528 } 529 530 /// CHECK-START: int Main.testSinkRightBeforeTryBlock() code_sinking (before) 531 /// CHECK: <<ObjLoadClass:l\d+>> LoadClass class_name:java.lang.Object 532 /// CHECK: NewInstance [<<ObjLoadClass>>] 533 /// CHECK: If 534 /// CHECK: TryBoundary kind:entry 535 536 /// CHECK-START: int Main.testSinkRightBeforeTryBlock() code_sinking (after) 537 /// CHECK: If 538 /// CHECK: <<ObjLoadClass:l\d+>> LoadClass class_name:java.lang.Object 539 /// CHECK: NewInstance [<<ObjLoadClass>>] 540 /// CHECK: TryBoundary kind:entry testSinkRightBeforeTryBlock()541 private static int testSinkRightBeforeTryBlock() { 542 Object o = new Object(); 543 if (doEarlyReturn) { 544 try { 545 throw new Error(o.toString()); 546 } catch (Error e) { 547 return 123; 548 } 549 } 550 return 456; 551 } 552 553 /// CHECK-START: int Main.testDoNotSinkToCatchInsideTryWithMoreThings(boolean, boolean) code_sinking (before) 554 /// CHECK-NOT: TryBoundary kind:entry 555 /// CHECK: <<ObjLoadClass:l\d+>> LoadClass class_name:java.lang.Object 556 /// CHECK: NewInstance [<<ObjLoadClass>>] 557 558 /// CHECK-START: int Main.testDoNotSinkToCatchInsideTryWithMoreThings(boolean, boolean) code_sinking (after) 559 /// CHECK-NOT: TryBoundary kind:entry 560 /// CHECK: <<ObjLoadClass:l\d+>> LoadClass class_name:java.lang.Object 561 /// CHECK: NewInstance [<<ObjLoadClass>>] 562 563 // Tests that we don't sink the Object creation into a catch handler surrounded by try/catch, even 564 // when that inner catch is not at the boundary of the outer try catch. testDoNotSinkToCatchInsideTryWithMoreThings(boolean a, boolean b)565 private static int testDoNotSinkToCatchInsideTryWithMoreThings(boolean a, boolean b) { 566 Object o = new Object(); 567 try { 568 if (a) { 569 System.out.println(a); 570 } 571 try { 572 if (doEarlyReturn) { 573 return 123; 574 } 575 } catch (Error e) { 576 throw new Error(o.toString()); 577 } 578 if (b) { 579 System.out.println(b); 580 } 581 } catch (Error e) { 582 throw new Error(); 583 } 584 return 456; 585 } 586 587 private static class ObjectWithInt { 588 int x; 589 } 590 591 /// CHECK-START: int Main.DoNotSinkWithOOMThrow() code_sinking (before) 592 /// CHECK: <<LoadClass:l\d+>> LoadClass class_name:Main$ObjectWithInt 593 /// CHECK: <<Clinit:l\d+>> ClinitCheck [<<LoadClass>>] 594 /// CHECK: NewInstance [<<Clinit>>] 595 /// CHECK: TryBoundary kind:entry 596 597 /// CHECK-START: int Main.DoNotSinkWithOOMThrow() code_sinking (after) 598 /// CHECK: <<LoadClass:l\d+>> LoadClass class_name:Main$ObjectWithInt 599 /// CHECK: <<Clinit:l\d+>> ClinitCheck [<<LoadClass>>] 600 /// CHECK: NewInstance [<<Clinit>>] 601 /// CHECK: TryBoundary kind:entry 602 603 // Consistency check to make sure there's only one entry TryBoundary. 604 /// CHECK-START: int Main.DoNotSinkWithOOMThrow() code_sinking (after) 605 /// CHECK: TryBoundary kind:entry 606 /// CHECK-NOT: TryBoundary kind:entry DoNotSinkWithOOMThrow()607 private static int DoNotSinkWithOOMThrow() throws OutOfMemoryError { 608 int x = 0; 609 ObjectWithInt obj = new ObjectWithInt(); 610 try { 611 // We want an if/else here so that the catch block will have a catch phi. 612 if (doThrow) { 613 x = 1; 614 // Doesn't really matter what we throw we just want it to not be caught by the 615 // NullPointerException below. 616 throw new OutOfMemoryError(Integer.toString(obj.x)); 617 } else { 618 x = 456; 619 } 620 } catch (NullPointerException e) { 621 } 622 623 // We want to use obj over here so that it doesn't get deleted by LSE. 624 if (obj.x == 123) { 625 return 123; 626 } 627 return x; 628 } 629 $noinline$testTwoThrowingPathsAndStringBuilderAppend()630 private static void $noinline$testTwoThrowingPathsAndStringBuilderAppend() { 631 try { 632 $noinline$twoThrowingPathsAndStringBuilderAppend(null); 633 throw new Error("Unreachable"); 634 } catch (Error expected) { 635 assertEquals("Object is null", expected.getMessage()); 636 } 637 try { 638 $noinline$twoThrowingPathsAndStringBuilderAppend(new Object()); 639 throw new Error("Unreachable"); 640 } catch (Error expected) { 641 assertEquals("s1s2", expected.getMessage()); 642 } 643 } 644 645 // Consistency check: only one ClinitCheck 646 /// CHECK-START: void Main.$noinline$testSinkNewInstanceWithClinitCheck() code_sinking (before) 647 /// CHECK: ClinitCheck 648 /// CHECK-NOT: ClinitCheck 649 650 /// CHECK-START: void Main.$noinline$testSinkNewInstanceWithClinitCheck() code_sinking (before) 651 /// CHECK: <<Check:l\d+>> ClinitCheck 652 /// CHECK: NewInstance [<<Check>>] 653 /// CHECK: NewInstance 654 /// CHECK: If 655 656 /// CHECK-START: void Main.$noinline$testSinkNewInstanceWithClinitCheck() code_sinking (after) 657 /// CHECK: <<Check:l\d+>> ClinitCheck 658 /// CHECK: If 659 /// CHECK: NewInstance 660 /// CHECK: NewInstance [<<Check>>] 661 662 /// CHECK-START: void Main.$noinline$testSinkNewInstanceWithClinitCheck() prepare_for_register_allocation (before) 663 /// CHECK-NOT: If 664 665 // We have an instruction that can throw between the ClinitCheck and its NewInstance. 666 667 /// CHECK-START: void Main.$noinline$testSinkNewInstanceWithClinitCheck() prepare_for_register_allocation (before) 668 /// CHECK: <<Check:l\d+>> ClinitCheck 669 /// CHECK: NewInstance 670 /// CHECK: NewInstance [<<Check>>] 671 672 // We can remove the ClinitCheck by merging it with the LoadClass right before. 673 674 /// CHECK-START: void Main.$noinline$testSinkNewInstanceWithClinitCheck() prepare_for_register_allocation (after) 675 /// CHECK-NOT: ClinitCheck $noinline$testSinkNewInstanceWithClinitCheck()676 private static void $noinline$testSinkNewInstanceWithClinitCheck() { 677 ValueHolder vh = new ValueHolder(); 678 Object o = new Object(); 679 680 // The if will always be true but we don't know this after LSE. Code sinking will sink code 681 // since this is an uncommon branch, but then we will have everything in one block before 682 // prepare_for_register_allocation for the crash to appear. 683 staticIntField = 1; 684 int value = staticIntField; 685 if (value == 1) { 686 throw new Error(Integer.toString(vh.getValue()) + o.toString()); 687 } 688 } 689 690 // We currently do not inline the `StringBuilder` constructor. 691 // When we did, the `StringBuilderAppend` pattern recognition was looking for 692 // the inlined `NewArray` (and its associated `LoadClass`) and checked in 693 // debug build that the `StringBuilder` has an environment use from this 694 // `NewArray` (and maybe from `LoadClass`). However, code sinking was pruning 695 // the environment of the `NewArray`, leading to a crash when compiling the 696 // code below on the device (we do not inline `core-oj` on host). b/252799691 697 698 // We currently have a heuristic that disallows inlining methods if their basic blocks end with a 699 // throw. We could add code so that `requireNonNull`'s block doesn't end with a throw but that 700 // would mean that the string builder optimization wouldn't fire as it requires all uses to be in 701 // the same block. If `requireNonNull` is inlined at some point, we need to re-mark it as $inline$ 702 // so that the test is operational again. 703 704 /// CHECK-START: void Main.$noinline$twoThrowingPathsAndStringBuilderAppend(java.lang.Object) inliner (before) 705 /// CHECK: InvokeStaticOrDirect method_name:Main.requireNonNull 706 707 /// CHECK-START: void Main.$noinline$twoThrowingPathsAndStringBuilderAppend(java.lang.Object) inliner (after) 708 /// CHECK: InvokeStaticOrDirect method_name:Main.requireNonNull $noinline$twoThrowingPathsAndStringBuilderAppend(Object o)709 private static void $noinline$twoThrowingPathsAndStringBuilderAppend(Object o) { 710 String s1 = "s1"; 711 String s2 = "s2"; 712 StringBuilder sb = new StringBuilder(); 713 714 // Before inlining, the environment use from this invoke prevents the 715 // `StringBuilderAppend` pattern recognition. After inlining, we end up 716 // with two paths ending with a `Throw` and we could sink the `sb` 717 // instructions from above down to those below, enabling the 718 // `StringBuilderAppend` pattern recognition. 719 // (But that does not happen when the `StringBuilder` constructor is 720 // not inlined, see above.) 721 requireNonNull(o); 722 723 String s1s2 = sb.append(s1).append(s2).toString(); 724 sb = null; 725 throw new Error(s1s2); 726 } 727 requireNonNull(Object o)728 private static void requireNonNull(Object o) { 729 if (o == null) { 730 throw new Error("Object is null"); 731 } 732 } 733 assertEquals(int expected, int actual)734 private static void assertEquals(int expected, int actual) { 735 if (expected != actual) { 736 throw new AssertionError("Expected: " + expected + ", Actual: " + actual); 737 } 738 } 739 assertEquals(String expected, String actual)740 private static void assertEquals(String expected, String actual) { 741 if (!expected.equals(actual)) { 742 throw new AssertionError("Expected: " + expected + ", Actual: " + actual); 743 } 744 } 745 746 volatile int volatileField; 747 int intField; 748 int intField2; 749 Object objectField; 750 static boolean doThrow; 751 static boolean doLoop; 752 static boolean doEarlyReturn; 753 static boolean doOtherEarlyReturn; 754 static int staticIntField; 755 static Main mainField = new Main(); 756 static Object obj = new Object(); 757 } 758