1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 public class Main { main(String[] args)18 public static void main(String[] args) throws Exception { 19 // Basic test 20 assertEquals(0, $noinline$testSimplifyThrow(1)); 21 22 // Basic test for non-trivial blocks (i.e. not just an invoke and a Goto) 23 assertEquals(0, $noinline$testSimplifyTwoThrows(1)); 24 25 // Try catch tests 26 assertEquals(0, $noinline$testDoNotSimplifyInTry(1)); 27 assertEquals(0, $noinline$testSimplifyInCatch(1)); 28 assertEquals(0, $noinline$testDoNotSimplifyInCatchInOuterTry(1)); 29 } 30 alwaysThrows()31 private static void alwaysThrows() throws Error { 32 throw new Error(""); 33 } 34 35 /// CHECK-START: int Main.$noinline$testSimplifyThrow(int) dead_code_elimination$after_inlining (before) 36 /// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true 37 /// CHECK-DAG: Exit block:<<ExitBlock:B\d+>> 38 /// CHECK-DAG: Goto block:<<InvokeBlock>> target:<<TargetBlock:B\d+>> 39 /// CHECK-EVAL: "<<ExitBlock>>" != "<<TargetBlock>>" 40 41 /// CHECK-START: int Main.$noinline$testSimplifyThrow(int) dead_code_elimination$after_inlining (after) 42 /// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true 43 /// CHECK-DAG: Exit block:<<ExitBlock:B\d+>> 44 /// CHECK-DAG: Goto block:<<InvokeBlock>> target:<<ExitBlock>> 45 46 // Tests that we simplify the always throwing branch directly to the exit. $noinline$testSimplifyThrow(int num)47 private static int $noinline$testSimplifyThrow(int num) { 48 if (num == 0) { 49 alwaysThrows(); 50 } 51 return 0; 52 } 53 54 /// CHECK-START: int Main.$noinline$testSimplifyTwoThrows(int) dead_code_elimination$after_inlining (before) 55 /// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true 56 /// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock>> method_name:Main.alwaysThrows always_throws:true 57 /// CHECK-DAG: Exit block:<<ExitBlock:B\d+>> 58 /// CHECK-DAG: Goto block:<<InvokeBlock>> target:<<TargetBlock:B\d+>> 59 /// CHECK-EVAL: "<<ExitBlock>>" != "<<TargetBlock>>" 60 61 /// CHECK-START: int Main.$noinline$testSimplifyTwoThrows(int) dead_code_elimination$after_inlining (after) 62 /// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true 63 /// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock>> method_name:Main.alwaysThrows always_throws:true 64 /// CHECK-DAG: Exit block:<<ExitBlock:B\d+>> 65 /// CHECK-DAG: Goto block:<<InvokeBlock>> target:<<ExitBlock>> 66 67 // Tests that we simplify the always throwing branch directly to the exit, even with blocks that 68 // are not just the throwing instruction and a Goto. $noinline$testSimplifyTwoThrows(int num)69 private static int $noinline$testSimplifyTwoThrows(int num) { 70 if (num == 0) { 71 alwaysThrows(); 72 alwaysThrows(); 73 } 74 return 0; 75 } 76 77 /// CHECK-START: int Main.$noinline$testSimplifyThrowWithTryCatch(int) dead_code_elimination$after_inlining (before) 78 /// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true 79 /// CHECK-DAG: Exit block:<<ExitBlock:B\d+>> 80 /// CHECK-DAG: Goto block:<<InvokeBlock>> target:<<TargetBlock:B\d+>> 81 /// CHECK-EVAL: "<<ExitBlock>>" != "<<TargetBlock>>" 82 83 /// CHECK-START: int Main.$noinline$testSimplifyThrowWithTryCatch(int) dead_code_elimination$after_inlining (after) 84 /// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true 85 /// CHECK-DAG: Exit block:<<ExitBlock:B\d+>> 86 /// CHECK-DAG: Goto block:<<InvokeBlock>> target:<<ExitBlock>> 87 88 // Consistency check to make sure we have the try catches in the graph at this stage. 89 /// CHECK-START: int Main.$noinline$testSimplifyThrowWithTryCatch(int) dead_code_elimination$after_inlining (before) 90 /// CHECK: TryBoundary kind:entry 91 /// CHECK: TryBoundary kind:entry 92 93 // Tests that we simplify the always throwing branch directly to the exit, with non-blocking try 94 // catches in the graph. $noinline$testSimplifyThrowWithTryCatch(int num)95 private static int $noinline$testSimplifyThrowWithTryCatch(int num) { 96 try { 97 if (num == 123) { 98 throw new Error(); 99 } 100 } catch (Error e) { 101 return 123; 102 } 103 104 if (num == 0) { 105 alwaysThrows(); 106 } 107 108 try { 109 if (num == 456) { 110 throw new Error(); 111 } 112 } catch (Error e) { 113 return 456; 114 } 115 return 0; 116 } 117 $inline$testDoNotSimplifyInner(int num)118 private static void $inline$testDoNotSimplifyInner(int num) { 119 alwaysThrows(); 120 while (num == 0) { 121 // We should never hit this since we are always throwing. 122 System.out.println(num); 123 } 124 } 125 126 /// CHECK-START: int Main.$noinline$testDoNotSimplifyInTry(int) dead_code_elimination$after_inlining (before) 127 /// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true 128 /// CHECK-DAG: Exit block:<<ExitBlock:B\d+>> 129 /// CHECK-DAG: Goto block:<<InvokeBlock>> target:<<TargetBlock:B\d+>> 130 /// CHECK-EVAL: "<<ExitBlock>>" != "<<TargetBlock>>" 131 132 /// CHECK-START: int Main.$noinline$testDoNotSimplifyInTry(int) dead_code_elimination$after_inlining (after) 133 /// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true 134 /// CHECK-DAG: Exit block:<<ExitBlock:B\d+>> 135 /// CHECK-DAG: Goto block:<<InvokeBlock>> target:<<TargetBlock:B\d+>> 136 /// CHECK-EVAL: "<<ExitBlock>>" != "<<TargetBlock>>" 137 138 // Consistency check to make sure we have the try catch in the graph at this stage. 139 /// CHECK-START: int Main.$noinline$testDoNotSimplifyInTry(int) dead_code_elimination$after_inlining (before) 140 /// CHECK: TryBoundary kind:entry 141 142 // Consistency check to that we do not simplify it by the last DCE pass either 143 /// CHECK-START: int Main.$noinline$testDoNotSimplifyInTry(int) dead_code_elimination$final (after) 144 /// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true 145 /// CHECK-DAG: Exit block:<<ExitBlock:B\d+>> 146 /// CHECK-DAG: Goto block:<<InvokeBlock>> target:<<TargetBlock:B\d+>> 147 /// CHECK-EVAL: "<<ExitBlock>>" != "<<TargetBlock>>" 148 149 // Tests that we have the necessary conditions for us to simplify the always throwing instruction 150 // (e.g. InvokeStaticOrDirect followed by a Goto) but we are blocking this due to being in a try. 151 // Changing the Goto here for the exit would be wrong since we want to flow to the catch rather 152 // than the Exit. The preconditions are tricky to do with just one function (since we will have an 153 // invoke followed by a TryBoundary rather than a Goto) but we can do so with the help of the 154 // inliner. $noinline$testDoNotSimplifyInTry(int num)155 private static int $noinline$testDoNotSimplifyInTry(int num) { 156 try { 157 $inline$testDoNotSimplifyInner(num); 158 } catch (Error e) { 159 return 0; 160 } 161 return 123; 162 } 163 164 /// CHECK-START: int Main.$noinline$testSimplifyInCatch(int) dead_code_elimination$after_inlining (before) 165 /// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true 166 /// CHECK-DAG: Exit block:<<ExitBlock:B\d+>> 167 /// CHECK-DAG: Goto block:<<InvokeBlock>> target:<<TargetBlock:B\d+>> 168 /// CHECK-EVAL: "<<ExitBlock>>" != "<<TargetBlock>>" 169 170 /// CHECK-START: int Main.$noinline$testSimplifyInCatch(int) dead_code_elimination$after_inlining (after) 171 /// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true 172 /// CHECK-DAG: Exit block:<<ExitBlock:B\d+>> 173 /// CHECK-DAG: Goto block:<<InvokeBlock>> target:<<ExitBlock>> 174 175 // Consistency check to make sure we have the try catch in the graph at this stage. 176 /// CHECK-START: int Main.$noinline$testSimplifyInCatch(int) dead_code_elimination$after_inlining (before) 177 /// CHECK: TryBoundary kind:entry 178 179 // We are able to simplify the `alwaysThrows` even though we are inside of the catch { ... } since 180 // the if makes it so that we are not the first block of the catch and therefore not in the 181 // "catch_block". $noinline$testSimplifyInCatch(int num)182 private static int $noinline$testSimplifyInCatch(int num) { 183 try { 184 throw new Error(); 185 } catch (Error e) { 186 if (num == 0) { 187 alwaysThrows(); 188 } 189 return 0; 190 } 191 } 192 193 /// CHECK-START: int Main.$noinline$testDoNotSimplifyInCatchInOuterTry(int) dead_code_elimination$after_inlining (before) 194 /// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true 195 /// CHECK-DAG: Exit block:<<ExitBlock:B\d+>> 196 /// CHECK-DAG: Goto block:<<InvokeBlock>> target:<<TargetBlock:B\d+>> 197 /// CHECK-EVAL: "<<ExitBlock>>" != "<<TargetBlock>>" 198 199 /// CHECK-START: int Main.$noinline$testDoNotSimplifyInCatchInOuterTry(int) dead_code_elimination$after_inlining (after) 200 /// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true 201 /// CHECK-DAG: Exit block:<<ExitBlock:B\d+>> 202 /// CHECK-DAG: Goto block:<<InvokeBlock>> target:<<TargetBlock:B\d+>> 203 /// CHECK-EVAL: "<<ExitBlock>>" != "<<TargetBlock>>" 204 205 // Consistency check to make sure we have the try catches in the graph at this stage. 206 /// CHECK-START: int Main.$noinline$testDoNotSimplifyInCatchInOuterTry(int) dead_code_elimination$after_inlining (before) 207 /// CHECK-DAG: TryBoundary kind:entry 208 /// CHECK-DAG: TryBoundary kind:entry 209 210 // Consistency check to that we do not simplify it by the last DCE pass either 211 /// CHECK-START: int Main.$noinline$testDoNotSimplifyInCatchInOuterTry(int) dead_code_elimination$final (after) 212 /// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true 213 /// CHECK-DAG: Exit block:<<ExitBlock:B\d+>> 214 /// CHECK-DAG: Goto block:<<InvokeBlock>> target:<<TargetBlock:B\d+>> 215 /// CHECK-EVAL: "<<ExitBlock>>" != "<<TargetBlock>>" 216 217 // Similar to testSimplifyInCatch, but now the throw is in an outer try and we shouldn't simplify 218 // it. Like in testDoNotSimplifyInTry, we need the help of the inliner to have an invoke followed 219 // by a Goto. $noinline$testDoNotSimplifyInCatchInOuterTry(int num)220 private static int $noinline$testDoNotSimplifyInCatchInOuterTry(int num) { 221 try { 222 try { 223 throw new Error(); 224 } catch (Error e) { 225 if (num == 0) { 226 $inline$testDoNotSimplifyInner(num); 227 } 228 return 0; 229 } 230 } catch (Error e) { 231 return 123; 232 } 233 } 234 assertEquals(int expected, int actual)235 static void assertEquals(int expected, int actual) { 236 if (expected != actual) { 237 throw new AssertionError("Expected " + expected + " got " + actual); 238 } 239 } 240 } 241