1 // Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file 2 // for details. All rights reserved. Use of this source code is governed by a 3 // BSD-style license that can be found in the LICENSE file. 4 5 package com.android.tools.r8.smali; 6 7 import static org.junit.Assert.assertEquals; 8 import static org.junit.Assert.assertTrue; 9 10 import com.android.tools.r8.code.Const4; 11 import com.android.tools.r8.code.IfEqz; 12 import com.android.tools.r8.code.IfGez; 13 import com.android.tools.r8.code.IfGtz; 14 import com.android.tools.r8.code.IfLez; 15 import com.android.tools.r8.code.IfLtz; 16 import com.android.tools.r8.code.IfNez; 17 import com.android.tools.r8.code.InvokeVirtual; 18 import com.android.tools.r8.code.Return; 19 import com.android.tools.r8.code.ReturnObject; 20 import com.android.tools.r8.graph.DexCode; 21 import com.android.tools.r8.graph.DexEncodedMethod; 22 import com.android.tools.r8.ir.code.If.Type; 23 import com.google.common.collect.ImmutableList; 24 import com.google.common.collect.Lists; 25 import java.util.ArrayList; 26 import java.util.Collections; 27 import java.util.List; 28 import org.junit.Test; 29 30 public class IfSimplificationTest extends SmaliTestBase { 31 32 static String[] ifOpcode; 33 static { 34 ifOpcode = new String[6]; 35 ifOpcode[Type.EQ.ordinal()] = "if-eq"; 36 ifOpcode[Type.NE.ordinal()] = "if-ne"; 37 ifOpcode[Type.LE.ordinal()] = "if-le"; 38 ifOpcode[Type.GE.ordinal()] = "if-ge"; 39 ifOpcode[Type.LT.ordinal()] = "if-lt"; 40 ifOpcode[Type.GT.ordinal()] = "if-gt"; 41 } 42 43 @Test ifZeroNeqZero()44 public void ifZeroNeqZero() { 45 DexEncodedMethod method = oneMethodApplication( 46 "int", 47 Collections.emptyList(), 48 1, 49 " const v0, 0", 50 " if-nez v0, :label_2", 51 ":label_1", 52 " return v0", 53 ":label_2", 54 " const v0, 1", 55 " goto :label_1"); 56 DexCode code = method.getCode().asDexCode(); 57 assertEquals(2, code.instructions.length); 58 assertTrue(code.instructions[0] instanceof Const4); 59 assertEquals(0, ((Const4) code.instructions[0]).B); 60 assertTrue(code.instructions[1] instanceof Return); 61 } 62 63 @Test ifTwoEqZero()64 public void ifTwoEqZero() { 65 DexEncodedMethod method = oneMethodApplication( 66 "int", 67 Collections.emptyList(), 68 1, 69 " const v0, 2", 70 " if-eqz v0, :label_2", 71 ":label_1", 72 " return v0", 73 ":label_2", 74 " const v0, 1", 75 " goto :label_1"); 76 DexCode code = method.getCode().asDexCode(); 77 assertEquals(2, code.instructions.length); 78 assertTrue(code.instructions[0] instanceof Const4); 79 assertEquals(2, ((Const4) code.instructions[0]).B); 80 assertTrue(code.instructions[1] instanceof Return); 81 } 82 83 @Test b()84 public void b() { 85 DexEncodedMethod method = oneMethodApplication( 86 "int", 87 Collections.singletonList("int"), 88 1, 89 " const v0, 0", 90 " if-nez v0, :label_2", 91 ":label_1", 92 " return v0", 93 ":label_2", 94 " if-nez p0, :label_3", 95 " const v0, 1", 96 " goto :label_1", 97 ":label_3", 98 " const v0, 2", 99 " goto :label_1"); 100 DexCode code = method.getCode().asDexCode(); 101 assertEquals(2, code.instructions.length); 102 assertTrue(code.instructions[0] instanceof Const4); 103 assertEquals(0, ((Const4) code.instructions[0]).B); 104 assertTrue(code.instructions[1] instanceof Return); 105 } 106 107 @Test c()108 public void c() { 109 DexEncodedMethod method = oneMethodApplication( 110 "int", 111 Collections.singletonList("int"), 112 1, 113 " const v0, 0", 114 " if-nez v0, :label_2", 115 ":label_1", 116 " return v0", 117 ":label_2", 118 " if-nez p0, :label_3", 119 " const v0, 1", 120 " goto :label_1", 121 ":label_3", 122 " const p0, 0", 123 " goto :label_2"); 124 DexCode code = method.getCode().asDexCode(); 125 assertEquals(2, code.instructions.length); 126 assertTrue(code.instructions[0] instanceof Const4); 127 assertEquals(0, ((Const4) code.instructions[0]).B); 128 assertTrue(code.instructions[1] instanceof Return); 129 } 130 131 @Test d()132 public void d() { 133 DexEncodedMethod method = oneMethodApplication( 134 "int", 135 Collections.singletonList("int"), 136 1, 137 " const v0, 0", 138 " if-nez v0, :label_2", 139 ":label_1", 140 " return v0", 141 ":label_2", 142 " if-nez p0, :label_3", 143 " const v0, 1", 144 " goto :label_4", 145 ":label_3", 146 " const p0, 0", 147 " goto :label_2", 148 ":label_4", 149 " if-nez p0, :label_5", 150 " const v0, 1", 151 " goto :label_4", 152 ":label_5", 153 " const p0, 0", 154 " goto :label_2"); 155 DexCode code = method.getCode().asDexCode(); 156 assertEquals(2, code.instructions.length); 157 assertTrue(code.instructions[0] instanceof Const4); 158 assertEquals(0, ((Const4) code.instructions[0]).B); 159 assertTrue(code.instructions[1] instanceof Return); 160 } 161 162 @Test e()163 public void e() { 164 DexEncodedMethod method = oneMethodApplication( 165 "int", 166 ImmutableList.of("int", "int", "int"), 167 1, 168 " const v0, 0", 169 " if-nez v0, :x", 170 " const v0, 1", 171 " if-nez p0, :x", 172 " const v0, 2", 173 " if-nez p1, :x", 174 " const v0, 3", 175 " if-nez p2, :return", 176 " const v0, 4", 177 " goto :return", 178 ":x", 179 " add-int v0, v0, p0", 180 ":return", 181 " return v0"); 182 DexCode code = method.getCode().asDexCode(); 183 assertEquals(12, code.instructions.length); 184 assertTrue(code.instructions[11] instanceof Return); 185 } 186 187 @Test f()188 public void f() { 189 DexEncodedMethod method = oneMethodApplication( 190 "int", 191 Collections.singletonList("int"), 192 1, 193 " const v0, 0", 194 " if-nez v0, :label_2", 195 ":label_1", 196 " return v0", 197 ":label_2", 198 " const v0, 1", 199 " goto :label_2"); 200 DexCode code = method.getCode().asDexCode(); 201 assertEquals(2, code.instructions.length); 202 assertTrue(code.instructions[0] instanceof Const4); 203 assertEquals(0, ((Const4) code.instructions[0]).B); 204 assertTrue(code.instructions[1] instanceof Return); 205 } 206 207 @Test simplifyNonZeroTests()208 public void simplifyNonZeroTests() { 209 class TestData { 210 211 final int a; 212 final int b; 213 final boolean results[]; 214 215 TestData(int a, int b) { 216 this.a = a; 217 this.b = b; 218 results = new boolean[6]; 219 results[Type.EQ.ordinal()] = a == b; 220 results[Type.NE.ordinal()] = a != b; 221 results[Type.LE.ordinal()] = a <= b; 222 results[Type.GE.ordinal()] = a >= b; 223 results[Type.LT.ordinal()] = a < b; 224 results[Type.GT.ordinal()] = a > b; 225 } 226 } 227 228 int[] testValues = new int[]{ 229 100, 230 1, 231 0, 232 -1, 233 100 234 }; 235 236 List<TestData> tests = new ArrayList<>(); 237 for (int i = 0; i < testValues.length; i++) { 238 for (int j = 0; j < testValues.length; j++) { 239 tests.add(new TestData(testValues[i], testValues[j])); 240 } 241 } 242 243 for (TestData test : tests) { 244 for (Type type : Type.values()) { 245 DexEncodedMethod method = oneMethodApplication( 246 "int", 247 Collections.singletonList("int"), 248 2, 249 " const v0, 0x" + Integer.toHexString(test.a), 250 " const v1, 0x" + Integer.toHexString(test.b), 251 " " + ifOpcode[type.ordinal()] + " v0, v1, :label_2", 252 " const v0, 0", 253 ":label_1", 254 " return v0", 255 ":label_2", 256 " const v0, 1", 257 " goto :label_1"); 258 DexCode code = method.getCode().asDexCode(); 259 assertEquals(2, code.instructions.length); 260 assertTrue(code.instructions[0] instanceof Const4); 261 int expected = test.results[type.ordinal()] ? 1 : 0; 262 assertEquals(expected, ((Const4) code.instructions[0]).B); 263 assertTrue(code.instructions[1] instanceof Return); 264 } 265 } 266 } 267 runRewriteIfWithConstZeroTest(Type type, boolean zeroLeft, Class expected)268 public void runRewriteIfWithConstZeroTest(Type type, boolean zeroLeft, Class expected) { 269 String ifInstruction; 270 if (zeroLeft) { 271 ifInstruction = " " + ifOpcode[type.ordinal()] + " v0, v1, :label_2"; 272 } else { 273 ifInstruction = " " + ifOpcode[type.ordinal()] + " v1, v0, :label_2"; 274 } 275 276 DexEncodedMethod method = oneMethodApplication( 277 "int", 278 Collections.singletonList("int"), 279 1, 280 " const v0, 0x00", 281 ifInstruction, 282 " const v0, 0", 283 ":label_1", 284 " return v0", 285 ":label_2", 286 " const v0, 1", 287 " goto :label_1"); 288 DexCode code = method.getCode().asDexCode(); 289 assertEquals(5, code.instructions.length); 290 assertTrue(expected.isInstance(code.instructions[0])); 291 assertTrue(code.instructions[4] instanceof Return); 292 } 293 294 @Test testRewriteIfWithConstZero()295 public void testRewriteIfWithConstZero() { 296 runRewriteIfWithConstZeroTest(Type.EQ, true, IfEqz.class); 297 runRewriteIfWithConstZeroTest(Type.NE, true, IfNez.class); 298 runRewriteIfWithConstZeroTest(Type.LE, true, IfGez.class); 299 runRewriteIfWithConstZeroTest(Type.GE, true, IfLez.class); 300 runRewriteIfWithConstZeroTest(Type.LT, true, IfGtz.class); 301 runRewriteIfWithConstZeroTest(Type.GT, true, IfLtz.class); 302 303 runRewriteIfWithConstZeroTest(Type.EQ, false, IfEqz.class); 304 runRewriteIfWithConstZeroTest(Type.NE, false, IfNez.class); 305 runRewriteIfWithConstZeroTest(Type.LE, false, IfLez.class); 306 runRewriteIfWithConstZeroTest(Type.GE, false, IfGez.class); 307 runRewriteIfWithConstZeroTest(Type.LT, false, IfLtz.class); 308 runRewriteIfWithConstZeroTest(Type.GT, false, IfGtz.class); 309 } 310 311 @Test x()312 public void x() { 313 DexEncodedMethod method = oneMethodApplication( 314 "Test", 315 Lists.newArrayList("Test", "java.lang.String[]", "java.lang.String", 316 "java.lang.String[]", "java.lang.String"), 317 10, 318 " const/4 v4, 0x00 # 0", 319 " invoke-virtual { v10 }, LTest;->a()LTest;", 320 " if-nez v4, :label_8", 321 " move-object v0, v4", 322 " :label_7", 323 " return-object v0", 324 " :label_8", 325 " invoke-static { v14 }, LTest;->a([Ljava/lang/String;)LTest;", 326 " move-result-object v2", 327 " invoke-virtual { v2 }, LTest;->a()Z", 328 " move-result v0", 329 " if-nez v0, :label_20", 330 " move-object v0, v4", 331 " goto :label_7", 332 " :label_20", 333 " iget-wide v0, v2, LTest;->a:J", 334 " iget-wide v6, v2, LTest;->b:J", 335 " invoke-virtual { v2 }, LTest;->c()Z", 336 " move-result v2", 337 " if-eqz v2, :label_33", 338 " invoke-virtual { v4 }, LTest;->a()V", 339 " :label_33", 340 " new-instance v5, LTest;", 341 " sget-object v2, LTest;->a:[Ljava/lang/String;", 342 " invoke-direct { v5, v2 }, LTest;-><init>([Ljava/lang/String;)V", 343 " invoke-virtual { v10 }, LTest;->a()LTest;", 344 " invoke-virtual { v4, v0, v1, v6, v7 }, LTest;->a(JJ)Ljava/util/List;", 345 " move-result-object v2", 346 " invoke-interface { v2 }, Ljava/util/List;->iterator()Ljava/util/Iterator;", 347 " move-result-object v6", 348 " move-wide v2, v0", 349 " :label_52", 350 " invoke-interface { v6 }, Ljava/util/Iterator;->hasNext()Z", 351 " move-result v0", 352 " if-eqz v0, :label_107", 353 " invoke-interface { v6 }, Ljava/util/Iterator;->next()Ljava/lang/Object;", 354 " move-result-object v0", 355 " check-cast v0, LTest;", 356 " const-wide/16 v8, 0x0000000000000001 # 1", 357 " add-long/2addr v2, v8", 358 " invoke-virtual { v5 }, LTest;->newRow()LTest;", 359 " move-result-object v1", 360 " invoke-static { v2, v3 }, Ljava/lang/Long;->valueOf(J)Ljava/lang/Long;", 361 " move-result-object v7", 362 " invoke-virtual { v1, v7 }, LTest;->a(Ljava/lang/Object;)LTest;", 363 " move-result-object v1", 364 " const-string v7, \"add\"", 365 " invoke-virtual { v1, v7 }, LTest;->a(Ljava/lang/Object;)LTest;", 366 " move-result-object v1", 367 " iget-object v7, v0, LTest;->a:Ljava/lang/String;", 368 " invoke-virtual { v1, v7 }, LTest;->a(Ljava/lang/Object;)LTest;", 369 " move-result-object v1", 370 " iget v7, v0, LTest;->b:I", 371 " invoke-static { v7 }, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;", 372 " move-result-object v7", 373 " invoke-virtual { v1, v7 }, LTest;->add(Ljava/lang/Object;)LTest;", 374 " move-result-object v1", 375 " iget-object v0, v0, LTest;->a:Ljava/lang/String;", 376 " invoke-virtual { v1, v0 }, LTest;->add(Ljava/lang/Object;)LTest;", 377 " goto :label_52", 378 " :label_107", 379 " iget-object v0, v4, LTest;->a:LTest;", 380 " const-string v1, \"text 1\"", 381 " const/4 v2, 0x00 # 0", 382 " invoke-virtual { v0, v1, v2 }, LTest;->a(Ljava/lang/String;I)LTest;", 383 " move-result-object v0", 384 " const-string v1, \"text 2\"", 385 " const-string v2, \"\"", 386 " invoke-interface { v0, v1, v2 }, LTest;->getString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", 387 " move-result-object v0", 388 " invoke-static { v5, v0 }, LTest;->a(LTest;Ljava/lang/String;)LTest;", 389 " move-result-object v0", 390 " goto :label_7" 391 ); 392 DexCode code = method.getCode().asDexCode(); 393 assertEquals(3, code.instructions.length); 394 assertTrue(code.instructions[0] instanceof InvokeVirtual); 395 assertTrue(code.instructions[1] instanceof Const4); 396 assertEquals(0, ((Const4) code.instructions[1]).B); 397 assertTrue(code.instructions[2] instanceof ReturnObject); 398 } 399 400 @Test y()401 public void y() { 402 DexEncodedMethod method = oneMethodApplication( 403 "boolean", 404 Lists.newArrayList("Test", "java.lang.Object"), 405 6, 406 " const-wide/16 v4, 0x0000000000000000L # 0", 407 " const/4 v0, 0x01 # 1", 408 " const/4 v3, 0x00 # 0", 409 " const/4 v1, 0x00 # 0", 410 " if-ne v6, v7, :label_8", 411 " :label_7", 412 " return v0", 413 " :label_8", 414 " if-nez v7, :label_12", 415 " move v0, v1", 416 " goto :label_7", 417 " :label_12", 418 " instance-of v2, v7, LTest;", 419 " if-nez v2, :label_18", 420 " move v0, v1", 421 " goto :label_7", 422 " :label_18", 423 " check-cast v7, LTest;", 424 " cmp-long v2, v4, v4", 425 " if-nez v2, :label_50", 426 " invoke-static { v3, v3 }, LTest;->a(Ljava/lang/Object;Ljava/lang/Object;)Z", 427 " move-result v2", 428 " if-eqz v2, :label_50", 429 " invoke-static { v3, v3 }, LTest;->a(Ljava/lang/Object;Ljava/lang/Object;)Z", 430 " move-result v2", 431 " if-eqz v2, :label_50", 432 " invoke-static { v1 }, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;", 433 " move-result-object v2", 434 " invoke-static { v1 }, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;", 435 " move-result-object v3", 436 " invoke-static { v2, v3 }, LTest;->a(Ljava/lang/Object;Ljava/lang/Object;)Z", 437 " move-result v2", 438 " if-nez v2, :label_7", 439 " :label_50", 440 " move v0, v1", 441 " goto :label_7" 442 ); 443 DexCode code = method.getCode().asDexCode(); 444 // TODO(sgjesse): Maybe this test is too fragile, as it leaves quite a lot of code, so the 445 // expectation might need changing with other optimizations. 446 assertEquals(29, code.instructions.length); 447 } 448 } 449