xref: /aosp_15_r20/art/test/639-checker-code-sinking/src/Main.java (revision 795d594fd825385562da6b089ea9b2033f3abf5a)
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