/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class Point { int x; int y; } public class Main { public static void main(String[] args) { final boolean boolean_throw = false; final boolean boolean_other_throw = false; assertEquals(3, $noinline$testDifferentFields( new Point(), new Point(), boolean_throw, boolean_other_throw)); assertEquals(1, $noinline$testRedundantStore(new Point(), boolean_throw, boolean_other_throw)); assertEquals(1, $noinline$testTryCatchBlocking(new Point(), boolean_throw)); assertEquals(1, $noinline$testTryCatchPhi(new Point(), boolean_throw)); assertEquals(2, $noinline$testTryCatchPhiWithTwoCatches(new Point(), new int[0])); assertEquals(1, $noinline$testKeepStoreInsideTry()); assertEquals(10, $noinline$testDontKeepStoreInsideCatch(new int[]{10})); assertEquals(30, $noinline$testDontKeepStoreInsideCatch(new int[]{})); assertEquals(10, $noinline$testKeepStoreInsideCatchWithOuterTry(new int[]{10})); assertEquals(30, $noinline$testKeepStoreInsideCatchWithOuterTry(new int[]{})); assertEquals(40, $noinline$testDontKeepStoreInsideFinally(new int[]{10})); try { assertEquals(30, $noinline$testDontKeepStoreInsideFinally(new int[]{})); throw new Error("Unreachable"); } catch (ArrayIndexOutOfBoundsException expected) { } assertEquals(10, $noinline$testDontKeepStoreInsideOuterCatch(new int[]{10})); assertEquals(100030, $noinline$testDontKeepStoreInsideOuterCatch(new int[]{})); assertEquals(150, $noinline$test40()); } /// CHECK-START: int Main.$noinline$testDifferentFields(Point, Point, boolean, boolean) load_store_elimination (before) /// CHECK-DAG: InstanceFieldSet field_name:Point.x /// CHECK-DAG: InstanceFieldSet field_name:Point.y /// CHECK-DAG: InstanceFieldGet field_name:Point.x /// CHECK-DAG: InstanceFieldGet field_name:Point.y /// CHECK-START: int Main.$noinline$testDifferentFields(Point, Point, boolean, boolean) load_store_elimination (after) /// CHECK-DAG: InstanceFieldSet field_name:Point.x /// CHECK-DAG: InstanceFieldSet field_name:Point.y /// CHECK-START: int Main.$noinline$testDifferentFields(Point, Point, boolean, boolean) load_store_elimination (after) /// CHECK-NOT: InstanceFieldGet field_name:Point.x /// CHECK-NOT: InstanceFieldGet field_name:Point.y // Consistency check to make sure the try/catches weren't removed by an earlier pass. /// CHECK-START: int Main.$noinline$testDifferentFields(Point, Point, boolean, boolean) load_store_elimination (after) /// CHECK: TryBoundary kind:entry /// CHECK: TryBoundary kind:entry // Different fields shouldn't alias. private static int $noinline$testDifferentFields( Point obj1, Point obj2, boolean boolean_throw, boolean boolean_other_throw) { try { if (boolean_throw) { throw new Error(); } } catch (Error e) { return 0; } obj1.x = 1; obj2.y = 2; int result = obj1.x + obj2.y; try { if (boolean_other_throw) { throw new Error(); } } catch (Error e) { return 0; } return result; } /// CHECK-START: int Main.$noinline$testRedundantStore(Point, boolean, boolean) load_store_elimination (before) /// CHECK-DAG: InstanceFieldSet field_name:Point.y /// CHECK-DAG: InstanceFieldSet field_name:Point.y /// CHECK-DAG: InstanceFieldGet field_name:Point.y /// CHECK-START: int Main.$noinline$testRedundantStore(Point, boolean, boolean) load_store_elimination (after) /// CHECK: InstanceFieldSet field_name:Point.y /// CHECK-NOT: InstanceFieldSet field_name:Point.y /// CHECK-START: int Main.$noinline$testRedundantStore(Point, boolean, boolean) load_store_elimination (after) /// CHECK-NOT: InstanceFieldGet field_name:Point.y // Consistency check to make sure the try/catches weren't removed by an earlier pass. /// CHECK-START: int Main.$noinline$testRedundantStore(Point, boolean, boolean) load_store_elimination (after) /// CHECK-DAG: TryBoundary kind:entry /// CHECK-DAG: TryBoundary kind:entry // Redundant store of the same value. private static int $noinline$testRedundantStore( Point obj, boolean boolean_throw, boolean boolean_other_throw) { try { if (boolean_throw) { throw new Error(); } } catch (Error e) { return 0; } obj.y = 1; obj.y = 1; try { if (boolean_other_throw) { throw new Error(); } } catch (Error e) { return 0; } return obj.y; } /// CHECK-START: int Main.$noinline$testTryCatchBlocking(Point, boolean) load_store_elimination (before) /// CHECK: InstanceFieldSet field_name:Point.y /// CHECK: InstanceFieldGet field_name:Point.y /// CHECK-START: int Main.$noinline$testTryCatchBlocking(Point, boolean) load_store_elimination (after) /// CHECK: InstanceFieldSet field_name:Point.y /// CHECK: InstanceFieldGet field_name:Point.y // Consistency check to make sure the try/catch wasn't removed by an earlier pass. /// CHECK-START: int Main.$noinline$testTryCatchBlocking(Point, boolean) load_store_elimination (after) /// CHECK: TryBoundary kind:entry // We cannot remove the Get since we might have thrown. private static int $noinline$testTryCatchBlocking(Point obj, boolean boolean_throw) { obj.y = 1; try { if (boolean_throw) { throw new Error(); } } catch (Error e) { } return obj.y; } /// CHECK-START: int Main.$noinline$testTryCatchPhi(Point, boolean) load_store_elimination (before) /// CHECK-DAG: InstanceFieldSet field_name:Point.y /// CHECK-DAG: InstanceFieldSet field_name:Point.y /// CHECK-DAG: InstanceFieldGet field_name:Point.y /// CHECK-START: int Main.$noinline$testTryCatchPhi(Point, boolean) load_store_elimination (after) /// CHECK-DAG: InstanceFieldSet field_name:Point.y /// CHECK-DAG: InstanceFieldSet field_name:Point.y /// CHECK-START: int Main.$noinline$testTryCatchPhi(Point, boolean) load_store_elimination (after) /// CHECK-NOT: InstanceFieldGet field_name:Point.y // Consistency check to make sure the try/catch wasn't removed by an earlier pass. /// CHECK-START: int Main.$noinline$testTryCatchPhi(Point, boolean) load_store_elimination (after) /// CHECK-DAG: TryBoundary kind:entry // We either threw and we set the value in the catch, or we didn't throw and we set the value // before the catch. We can solve that with a Phi and skip the get. private static int $noinline$testTryCatchPhi(Point obj, boolean boolean_throw) { obj.y = 1; try { if (boolean_throw) { throw new Error(); } } catch (Error e) { obj.y = 2; } return obj.y; } /// CHECK-START: int Main.$noinline$testTryCatchPhiWithTwoCatches(Point, int[]) load_store_elimination (before) /// CHECK-DAG: InstanceFieldSet field_name:Point.y /// CHECK-DAG: InstanceFieldSet field_name:Point.y /// CHECK-DAG: InstanceFieldSet field_name:Point.y /// CHECK-DAG: InstanceFieldGet field_name:Point.y /// CHECK-START: int Main.$noinline$testTryCatchPhiWithTwoCatches(Point, int[]) load_store_elimination (after) /// CHECK-DAG: InstanceFieldSet field_name:Point.y /// CHECK-DAG: InstanceFieldSet field_name:Point.y /// CHECK-DAG: InstanceFieldSet field_name:Point.y /// CHECK-START: int Main.$noinline$testTryCatchPhiWithTwoCatches(Point, int[]) load_store_elimination (after) /// CHECK-NOT: InstanceFieldGet field_name:Point.y // Consistency check to make sure the try/catch wasn't removed by an earlier pass. /// CHECK-START: int Main.$noinline$testTryCatchPhiWithTwoCatches(Point, int[]) load_store_elimination (after) /// CHECK-DAG: TryBoundary kind:entry private static int $noinline$testTryCatchPhiWithTwoCatches(Point obj, int[] numbers) { obj.y = 1; try { if (numbers[0] == 1) { throw new Error(); } } catch (ArrayIndexOutOfBoundsException e) { obj.y = 2; } catch (Error e) { obj.y = 3; } return obj.y; } // Check that we don't eliminate the first store to `main.sumForKeepStoreInsideTryCatch` since it // is observable. // Consistency check to make sure the try/catch wasn't removed by an earlier pass. /// CHECK-START: int Main.$noinline$testKeepStoreInsideTry() load_store_elimination (after) /// CHECK-DAG: TryBoundary kind:entry /// CHECK-START: int Main.$noinline$testKeepStoreInsideTry() load_store_elimination (before) /// CHECK: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch /// CHECK: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch /// CHECK-START: int Main.$noinline$testKeepStoreInsideTry() load_store_elimination (after) /// CHECK: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch /// CHECK: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch private static int $noinline$testKeepStoreInsideTry() { Main main = new Main(); main.sumForKeepStoreInsideTryCatch = 0; try { int[] array = {1}; main.sumForKeepStoreInsideTryCatch += array[0]; main.sumForKeepStoreInsideTryCatch += array[1]; throw new RuntimeException("Unreachable"); } catch (ArrayIndexOutOfBoundsException e) { return main.sumForKeepStoreInsideTryCatch; } } private static int $noinline$returnValue(int value) { return value; } /// CHECK-START: int Main.$noinline$testDontKeepStoreInsideCatch(int[]) load_store_elimination (before) /// CHECK: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch /// CHECK: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch /// CHECK-NOT: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch /// CHECK-START: int Main.$noinline$testDontKeepStoreInsideCatch(int[]) load_store_elimination (after) /// CHECK-NOT: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch private static int $noinline$testDontKeepStoreInsideCatch(int[] array) { Main main = new Main(); int value = 0; try { value = array[0]; } catch (Exception e) { // These sets can be eliminated even though we have invokes since this catch is not part of an // outer try. main.sumForKeepStoreInsideTryCatch += $noinline$returnValue(10); main.sumForKeepStoreInsideTryCatch += $noinline$returnValue(20); } return main.sumForKeepStoreInsideTryCatch + value; } /// CHECK-START: int Main.$noinline$testKeepStoreInsideCatchWithOuterTry(int[]) load_store_elimination (before) /// CHECK: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch /// CHECK: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch /// CHECK-NOT: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch /// CHECK-START: int Main.$noinline$testKeepStoreInsideCatchWithOuterTry(int[]) load_store_elimination (after) /// CHECK: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch /// CHECK: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch /// CHECK-NOT: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch private static int $noinline$testKeepStoreInsideCatchWithOuterTry(int[] array) { Main main = new Main(); int value = 0; try { try { value = array[0]; } catch (Exception e) { // These sets can't be eliminated since this catch is part of a outer try. main.sumForKeepStoreInsideTryCatch += $noinline$returnValue(10); main.sumForKeepStoreInsideTryCatch += $noinline$returnValue(20); } } catch (Exception e) { value = 100000; } return main.sumForKeepStoreInsideTryCatch + value; } // Note that there are four `InstanceFieldSet` instead of two since we split the `finally` block // into the normal path, and the exceptional path. /// CHECK-START: int Main.$noinline$testDontKeepStoreInsideFinally(int[]) load_store_elimination (before) /// CHECK: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch /// CHECK: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch /// CHECK: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch /// CHECK: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch /// CHECK-NOT: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch /// CHECK-START: int Main.$noinline$testDontKeepStoreInsideFinally(int[]) load_store_elimination (after) /// CHECK-NOT: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch private static int $noinline$testDontKeepStoreInsideFinally(int[] array) { Main main = new Main(); int value = 0; try { value = array[0]; } finally { // These sets can be eliminated even though we have invokes since this catch is not part of an // outer try. main.sumForKeepStoreInsideTryCatch += $noinline$returnValue(10); main.sumForKeepStoreInsideTryCatch += $noinline$returnValue(20); } return main.sumForKeepStoreInsideTryCatch + value; } // Checks that we are able to do LSE inside of catches which are outside of try blocks. /// CHECK-START: int Main.$noinline$testDontKeepStoreInsideOuterCatch(int[]) load_store_elimination (before) /// CHECK: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch /// CHECK: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch /// CHECK-NOT: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch // This store potentially can be eliminated too, but our phi creation logic doesn't realize it can // create a Phi for `main.sumForKeepStoreInsideTryCatch` and skip a store+load. /// CHECK-START: int Main.$noinline$testDontKeepStoreInsideOuterCatch(int[]) load_store_elimination (after) /// CHECK: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch /// CHECK-NOT: InstanceFieldSet field_name:Main.sumForKeepStoreInsideTryCatch private static int $noinline$testDontKeepStoreInsideOuterCatch(int[] array) { Main main = new Main(); int value = 0; try { value = array[0]; } catch (ArrayIndexOutOfBoundsException expected) { // These sets and gets are not considered to be part of a try so they are free to be // eliminated. main.sumForKeepStoreInsideTryCatch += $noinline$returnValue(10); main.sumForKeepStoreInsideTryCatch += $noinline$returnValue(20); try { value = array[0]; } catch (ArrayIndexOutOfBoundsException expectedToo) { value = 100000; } } return main.sumForKeepStoreInsideTryCatch + value; } /// CHECK-START: int Main.$noinline$test40() load_store_elimination (before) /// CHECK: ArraySet /// CHECK: DivZeroCheck /// CHECK: ArraySet /// CHECK: ArraySet // /// CHECK: ArraySet /// CHECK: ArraySet /// CHECK: DivZeroCheck /// CHECK: ArraySet // /// CHECK: ArraySet /// CHECK: ArraySet /// CHECK: ArraySet /// CHECK-NOT: ArraySet /// CHECK-START: int Main.$noinline$test40() load_store_elimination (after) /// CHECK: ArraySet /// CHECK: DivZeroCheck /// CHECK: ArraySet // /// CHECK: ArraySet /// CHECK: DivZeroCheck /// CHECK: ArraySet // /// CHECK-NOT: ArraySet // Like `test40` from 530-checker-lse but with $inline$ for the inner method so we check that we // have the array set inside try catches too. // Since we are inlining, we know the parameters and are able to elimnate (some) of the // `ArraySet`s. private static int $noinline$test40() { int[] array = new int[1]; try { $inline$fillArrayTest40(array, 100, 0); System.out.println("UNREACHABLE"); } catch (Throwable expected) { } assertEquals(1, array[0]); try { $inline$fillArrayTest40(array, 100, 1); System.out.println("UNREACHABLE"); } catch (Throwable expected) { } assertEquals(2, array[0]); $inline$fillArrayTest40(array, 100, 2); assertEquals(150, array[0]); return array[0]; } /// CHECK-START: void Main.$inline$fillArrayTest40(int[], int, int) load_store_elimination (before) /// CHECK: ArraySet /// CHECK: DivZeroCheck /// CHECK: ArraySet /// CHECK: DivZeroCheck /// CHECK: ArraySet /// CHECK-NOT: ArraySet /// CHECK-START: void Main.$inline$fillArrayTest40(int[], int, int) load_store_elimination (after) /// CHECK: ArraySet /// CHECK: DivZeroCheck /// CHECK: ArraySet /// CHECK: DivZeroCheck /// CHECK: ArraySet /// CHECK-NOT: ArraySet // Check that the stores to array[0] are not eliminated since we can throw in between the stores. private static void $inline$fillArrayTest40(int[] array, int a, int b) { array[0] = 1; int x = a / b; array[0] = 2; int y = a / (b - 1); array[0] = x + y; } private static void assertEquals(int expected, int actual) { if (expected != actual) { throw new AssertionError("Expected: " + expected + ", Actual: " + actual); } } int sumForKeepStoreInsideTryCatch; }