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