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.ir.optimize; 6 7 import com.android.tools.r8.dex.Constants; 8 import com.android.tools.r8.errors.CompilationError; 9 import com.android.tools.r8.graph.AppInfo; 10 import com.android.tools.r8.graph.DexClass; 11 import com.android.tools.r8.graph.DexEncodedMethod; 12 import com.android.tools.r8.graph.DexField; 13 import com.android.tools.r8.graph.DexItemFactory; 14 import com.android.tools.r8.graph.DexMethod; 15 import com.android.tools.r8.graph.DexProto; 16 import com.android.tools.r8.graph.DexType; 17 import com.android.tools.r8.ir.code.ArrayGet; 18 import com.android.tools.r8.ir.code.ArrayPut; 19 import com.android.tools.r8.ir.code.BasicBlock; 20 import com.android.tools.r8.ir.code.Binop; 21 import com.android.tools.r8.ir.code.CatchHandlers; 22 import com.android.tools.r8.ir.code.Cmp; 23 import com.android.tools.r8.ir.code.Cmp.Bias; 24 import com.android.tools.r8.ir.code.ConstNumber; 25 import com.android.tools.r8.ir.code.ConstString; 26 import com.android.tools.r8.ir.code.DominatorTree; 27 import com.android.tools.r8.ir.code.Goto; 28 import com.android.tools.r8.ir.code.IRCode; 29 import com.android.tools.r8.ir.code.If; 30 import com.android.tools.r8.ir.code.If.Type; 31 import com.android.tools.r8.ir.code.Instruction; 32 import com.android.tools.r8.ir.code.InstructionIterator; 33 import com.android.tools.r8.ir.code.InstructionListIterator; 34 import com.android.tools.r8.ir.code.Invoke; 35 import com.android.tools.r8.ir.code.InvokeDirect; 36 import com.android.tools.r8.ir.code.InvokeMethod; 37 import com.android.tools.r8.ir.code.InvokeVirtual; 38 import com.android.tools.r8.ir.code.MemberType; 39 import com.android.tools.r8.ir.code.MoveType; 40 import com.android.tools.r8.ir.code.NewArrayEmpty; 41 import com.android.tools.r8.ir.code.NewArrayFilledData; 42 import com.android.tools.r8.ir.code.NumericType; 43 import com.android.tools.r8.ir.code.Phi; 44 import com.android.tools.r8.ir.code.Return; 45 import com.android.tools.r8.ir.code.StaticGet; 46 import com.android.tools.r8.ir.code.StaticPut; 47 import com.android.tools.r8.ir.code.Switch; 48 import com.android.tools.r8.ir.code.Value; 49 import com.android.tools.r8.ir.conversion.OptimizationFeedback; 50 import com.android.tools.r8.utils.InternalOptions; 51 import com.android.tools.r8.utils.LongInterval; 52 import com.google.common.base.Equivalence; 53 import com.google.common.base.Equivalence.Wrapper; 54 import com.google.common.collect.ArrayListMultimap; 55 import com.google.common.collect.ImmutableList; 56 import com.google.common.collect.ListMultimap; 57 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap; 58 import it.unimi.dsi.fastutil.ints.Int2IntMap; 59 import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap; 60 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; 61 import it.unimi.dsi.fastutil.ints.IntArrayList; 62 import it.unimi.dsi.fastutil.ints.IntList; 63 import it.unimi.dsi.fastutil.objects.Reference2IntArrayMap; 64 import it.unimi.dsi.fastutil.objects.Reference2IntMap; 65 import java.util.ArrayList; 66 import java.util.Comparator; 67 import java.util.HashMap; 68 import java.util.HashSet; 69 import java.util.IdentityHashMap; 70 import java.util.Iterator; 71 import java.util.LinkedList; 72 import java.util.List; 73 import java.util.ListIterator; 74 import java.util.Map; 75 import java.util.Queue; 76 import java.util.Set; 77 78 public class CodeRewriter { 79 80 private static final int UNKNOWN_CAN_THROW = 0; 81 private static final int CAN_THROW = 1; 82 private static final int CANNOT_THROW = 2; 83 private static final int MAX_FILL_ARRAY_SIZE = 8 * Constants.KILOBYTE; 84 // This constant was determined by experimentation. 85 private static final int STOP_SHARED_CONSTANT_THRESHOLD = 50; 86 87 private final AppInfo appInfo; 88 private final DexItemFactory dexItemFactory; 89 private final Set<DexType> libraryClassesWithOptimizationInfo; 90 CodeRewriter(AppInfo appInfo, Set<DexType> libraryClassesWithOptimizationInfo)91 public CodeRewriter(AppInfo appInfo, Set<DexType> libraryClassesWithOptimizationInfo) { 92 this.appInfo = appInfo; 93 this.dexItemFactory = appInfo.dexItemFactory; 94 this.libraryClassesWithOptimizationInfo = libraryClassesWithOptimizationInfo; 95 } 96 97 /** 98 * Removes all debug positions that are not needed to maintain proper stack trace information. 99 * If a debug position is followed by another debug position and no instructions between the two 100 * can throw then it is unneeded (in a release build). 101 * If a block with a position has (normal) outgoing edges, this property depends on the 102 * possibility of the successors throwing before the next debug position is hit. 103 */ removedUnneededDebugPositions(IRCode code)104 public static boolean removedUnneededDebugPositions(IRCode code) { 105 computeThrowsColorForAllBlocks(code); 106 for (BasicBlock block : code.blocks) { 107 InstructionListIterator iterator = block.listIterator(); 108 while (iterator.hasNext()) { 109 Instruction instruction = iterator.next(); 110 if (instruction.isDebugPosition() 111 && getThrowsColorForBlock(block, iterator.nextIndex()) == CANNOT_THROW) { 112 iterator.remove(); 113 } 114 } 115 } 116 return true; 117 } 118 computeThrowsColorForAllBlocks(IRCode code)119 private static void computeThrowsColorForAllBlocks(IRCode code) { 120 // First pass colors blocks in reverse topological order, based on the instructions. 121 code.clearMarks(); 122 List<BasicBlock> blocks = code.blocks; 123 ArrayList<BasicBlock> worklist = new ArrayList<>(); 124 for (int i = blocks.size() - 1; i >= 0; i--) { 125 BasicBlock block = blocks.get(i); 126 // Mark the block as not-throwing if no successor implies otherwise. 127 // This ensures that a loop back to this block will be seen as non-throwing. 128 block.setColor(CANNOT_THROW); 129 int color = getThrowsColorForBlock(block, 0); 130 block.setColor(color); 131 if (color == UNKNOWN_CAN_THROW) { 132 worklist.add(block); 133 } 134 } 135 // A fixed point then ensures that we propagate the color backwards over normal edges. 136 ArrayList<BasicBlock> remaining = new ArrayList<>(worklist.size()); 137 while (!worklist.isEmpty()) { 138 ImmutableList<BasicBlock> work = new ImmutableList.Builder<BasicBlock>() 139 .addAll(worklist) 140 .addAll(remaining) 141 .build(); 142 worklist.clear(); 143 remaining.clear(); 144 for (BasicBlock block : work) { 145 if (!block.hasColor(UNKNOWN_CAN_THROW)) { 146 continue; 147 } 148 block.setColor(CANNOT_THROW); 149 int color = getThrowsColorForSuccessors(block); 150 block.setColor(color); 151 if (color == UNKNOWN_CAN_THROW) { 152 remaining.add(block); 153 } else { 154 for (BasicBlock predecessor : block.getNormalPredecessors()) { 155 if (predecessor.hasColor(UNKNOWN_CAN_THROW)) { 156 worklist.add(predecessor); 157 } 158 } 159 } 160 } 161 } 162 // Any remaining set of blocks represents a cycle of blocks containing no throwing instructions. 163 for (BasicBlock block : remaining) { 164 assert !block.canThrow(); 165 block.setColor(CANNOT_THROW); 166 } 167 } 168 getThrowsColorForBlock(BasicBlock block, int index)169 private static int getThrowsColorForBlock(BasicBlock block, int index) { 170 InstructionListIterator iterator = block.listIterator(index); 171 while (iterator.hasNext()) { 172 Instruction instruction = iterator.next(); 173 if (instruction.isDebugPosition()) { 174 return CANNOT_THROW; 175 } 176 if (instruction.instructionTypeCanThrow()) { 177 return CAN_THROW; 178 } 179 } 180 return getThrowsColorForSuccessors(block); 181 } 182 getThrowsColorForSuccessors(BasicBlock block)183 private static int getThrowsColorForSuccessors(BasicBlock block) { 184 int color = CANNOT_THROW; 185 for (BasicBlock successor : block.getNormalSucessors()) { 186 if (successor.hasColor(CAN_THROW)) { 187 return CAN_THROW; 188 } 189 if (successor.hasColor(UNKNOWN_CAN_THROW)) { 190 color = UNKNOWN_CAN_THROW; 191 } 192 } 193 return color; 194 } 195 removedTrivialGotos(IRCode code)196 private static boolean removedTrivialGotos(IRCode code) { 197 ListIterator<BasicBlock> iterator = code.listIterator(); 198 assert iterator.hasNext(); 199 BasicBlock block = iterator.next(); 200 BasicBlock nextBlock; 201 do { 202 nextBlock = iterator.hasNext() ? iterator.next() : null; 203 // Trivial goto block are only kept if they are self-targeting or are targeted by 204 // fallthroughs. 205 BasicBlock blk = block; // Additional local for lambda below. 206 assert !block.isTrivialGoto() 207 || block.exit().asGoto().getTarget() == block 208 || block.getPredecessors().stream().anyMatch((b) -> b.exit().fallthroughBlock() == blk); 209 // Trivial goto blocks never target the next block (in that case there should just be a 210 // fallthrough). 211 assert !block.isTrivialGoto() || block.exit().asGoto().getTarget() != nextBlock; 212 block = nextBlock; 213 } while (block != null); 214 return true; 215 } 216 endOfGotoChain(BasicBlock block)217 private static BasicBlock endOfGotoChain(BasicBlock block) { 218 block.mark(); 219 BasicBlock target = block; 220 while (target.isTrivialGoto()) { 221 BasicBlock nextTarget = target.exit().asGoto().getTarget(); 222 if (nextTarget.isMarked()) { 223 clearTrivialGotoMarks(block); 224 return nextTarget; 225 } 226 nextTarget.mark(); 227 target = nextTarget; 228 } 229 clearTrivialGotoMarks(block); 230 return target; 231 } 232 clearTrivialGotoMarks(BasicBlock block)233 private static void clearTrivialGotoMarks(BasicBlock block) { 234 while (block.isMarked()) { 235 block.clearMark(); 236 if (block.isTrivialGoto()) { 237 block = block.exit().asGoto().getTarget(); 238 } 239 } 240 } 241 collapsTrivialGoto( BasicBlock block, BasicBlock nextBlock, List<BasicBlock> blocksToRemove)242 private static void collapsTrivialGoto( 243 BasicBlock block, BasicBlock nextBlock, List<BasicBlock> blocksToRemove) { 244 245 // This is the base case for GOTO loops. 246 if (block.exit().asGoto().getTarget() == block) { 247 return; 248 } 249 250 BasicBlock target = endOfGotoChain(block); 251 252 boolean needed = false; 253 if (target != nextBlock) { 254 for (BasicBlock pred : block.getPredecessors()) { 255 if (pred.exit().fallthroughBlock() == block) { 256 needed = true; 257 break; 258 } 259 } 260 } 261 262 // This implies we are in a loop of GOTOs. In that case, we will iteratively remove each trival 263 // GOTO one-by-one until the above base case (one block targeting itself) is left. 264 if (target == block) { 265 target = block.exit().asGoto().getTarget(); 266 } 267 268 if (!needed) { 269 blocksToRemove.add(block); 270 for (BasicBlock pred : block.getPredecessors()) { 271 pred.replaceSuccessor(block, target); 272 } 273 for (BasicBlock succ : block.getSuccessors()) { 274 succ.getPredecessors().remove(block); 275 } 276 for (BasicBlock pred : block.getPredecessors()) { 277 if (!target.getPredecessors().contains(pred)) { 278 target.getPredecessors().add(pred); 279 } 280 } 281 } 282 } 283 collapsIfTrueTarget(BasicBlock block)284 private static void collapsIfTrueTarget(BasicBlock block) { 285 If insn = block.exit().asIf(); 286 BasicBlock target = insn.getTrueTarget(); 287 BasicBlock newTarget = endOfGotoChain(target); 288 BasicBlock fallthrough = insn.fallthroughBlock(); 289 BasicBlock newFallthrough = endOfGotoChain(fallthrough); 290 if (target != newTarget) { 291 insn.getBlock().replaceSuccessor(target, newTarget); 292 target.getPredecessors().remove(block); 293 if (!newTarget.getPredecessors().contains(block)) { 294 newTarget.getPredecessors().add(block); 295 } 296 } 297 if (block.exit().isIf()) { 298 insn = block.exit().asIf(); 299 if (insn.getTrueTarget() == newFallthrough) { 300 // Replace if with the same true and fallthrough target with a goto to the fallthrough. 301 block.replaceSuccessor(insn.getTrueTarget(), fallthrough); 302 assert block.exit().isGoto(); 303 assert block.exit().asGoto().getTarget() == fallthrough; 304 } 305 } 306 } 307 collapsNonFallthroughSwitchTargets(BasicBlock block)308 private static void collapsNonFallthroughSwitchTargets(BasicBlock block) { 309 Switch insn = block.exit().asSwitch(); 310 BasicBlock fallthroughBlock = insn.fallthroughBlock(); 311 Set<BasicBlock> replacedBlocks = new HashSet<>(); 312 for (int j = 0; j < insn.targetBlockIndices().length; j++) { 313 BasicBlock target = insn.targetBlock(j); 314 if (target != fallthroughBlock) { 315 BasicBlock newTarget = endOfGotoChain(target); 316 if (target != newTarget && !replacedBlocks.contains(target)) { 317 insn.getBlock().replaceSuccessor(target, newTarget); 318 target.getPredecessors().remove(block); 319 if (!newTarget.getPredecessors().contains(block)) { 320 newTarget.getPredecessors().add(block); 321 } 322 replacedBlocks.add(target); 323 } 324 } 325 } 326 } 327 rewriteSwitch(IRCode code)328 public void rewriteSwitch(IRCode code) { 329 for (BasicBlock block : code.blocks) { 330 InstructionListIterator iterator = block.listIterator(); 331 while (iterator.hasNext()) { 332 Instruction instruction = iterator.next(); 333 if (instruction.isSwitch()) { 334 Switch theSwitch = instruction.asSwitch(); 335 if (theSwitch.numberOfKeys() == 1) { 336 // Rewrite the switch to an if. 337 int fallthroughBlockIndex = theSwitch.getFallthroughBlockIndex(); 338 int caseBlockIndex = theSwitch.targetBlockIndices()[0]; 339 if (fallthroughBlockIndex < caseBlockIndex) { 340 block.swapSuccessorsByIndex(fallthroughBlockIndex, caseBlockIndex); 341 } 342 if (theSwitch.getFirstKey() == 0) { 343 iterator.replaceCurrentInstruction(new If(Type.EQ, theSwitch.value())); 344 } else { 345 ConstNumber labelConst = code.createIntConstant(theSwitch.getFirstKey()); 346 iterator.previous(); 347 iterator.add(labelConst); 348 Instruction dummy = iterator.next(); 349 assert dummy == theSwitch; 350 If theIf = new If(Type.EQ, ImmutableList.of(theSwitch.value(), labelConst.dest())); 351 iterator.replaceCurrentInstruction(theIf); 352 } 353 } 354 } 355 } 356 } 357 } 358 359 /** 360 * Inline the indirection of switch maps into the switch statement. 361 * <p> 362 * To ensure binary compatibility, javac generated code does not use ordinal values of enums 363 * directly in switch statements but instead generates a companion class that computes a mapping 364 * from switch branches to ordinals at runtime. As we have whole-program knowledge, we can 365 * analyze these maps and inline the indirection into the switch map again. 366 * <p> 367 * In particular, we look for code of the form 368 * 369 * <blockquote><pre> 370 * switch(CompanionClass.$switchmap$field[enumValue.ordinal()]) { 371 * ... 372 * } 373 * </pre></blockquote> 374 * See {@link #extractIndexMapFrom} and {@link #extractOrdinalsMapFor} for 375 * details of the companion class and ordinals computation. 376 */ removeSwitchMaps(IRCode code)377 public void removeSwitchMaps(IRCode code) { 378 for (BasicBlock block : code.blocks) { 379 InstructionListIterator it = block.listIterator(); 380 while (it.hasNext()) { 381 Instruction insn = it.next(); 382 // Pattern match a switch on a switch map as input. 383 if (insn.isSwitch()) { 384 Switch switchInsn = insn.asSwitch(); 385 Instruction input = switchInsn.inValues().get(0).definition; 386 if (input == null || !input.isArrayGet()) { 387 continue; 388 } 389 ArrayGet arrayGet = input.asArrayGet(); 390 Instruction index = arrayGet.index().definition; 391 if (index == null || !index.isInvokeVirtual()) { 392 continue; 393 } 394 InvokeVirtual ordinalInvoke = index.asInvokeVirtual(); 395 DexMethod ordinalMethod = ordinalInvoke.getInvokedMethod(); 396 DexClass enumClass = appInfo.definitionFor(ordinalMethod.holder); 397 if (enumClass == null 398 || (!enumClass.accessFlags.isEnum() && enumClass.type != dexItemFactory.enumType) 399 || ordinalMethod.name != dexItemFactory.ordinalMethodName 400 || ordinalMethod.proto.returnType != dexItemFactory.intType 401 || !ordinalMethod.proto.parameters.isEmpty()) { 402 continue; 403 } 404 Instruction array = arrayGet.array().definition; 405 if (array == null || !array.isStaticGet()) { 406 continue; 407 } 408 StaticGet staticGet = array.asStaticGet(); 409 if (staticGet.getField().name.toSourceString().startsWith("$SwitchMap$")) { 410 Int2ReferenceMap<DexField> indexMap = extractIndexMapFrom(staticGet.getField()); 411 if (indexMap == null || indexMap.isEmpty()) { 412 continue; 413 } 414 // Due to member rebinding, only the fields are certain to provide the actual enums 415 // class. 416 DexType switchMapHolder = indexMap.values().iterator().next().getHolder(); 417 Reference2IntMap ordinalsMap = extractOrdinalsMapFor(switchMapHolder); 418 if (ordinalsMap != null) { 419 Int2IntMap targetMap = new Int2IntArrayMap(); 420 IntList keys = new IntArrayList(switchInsn.numberOfKeys()); 421 for (int i = 0; i < switchInsn.numberOfKeys(); i++) { 422 assert switchInsn.targetBlockIndices()[i] != switchInsn.getFallthroughBlockIndex(); 423 int key = ordinalsMap.getInt(indexMap.get(switchInsn.getKey(i))); 424 keys.add(key); 425 targetMap.put(key, switchInsn.targetBlockIndices()[i]); 426 } 427 keys.sort(Comparator.naturalOrder()); 428 int[] targets = new int[keys.size()]; 429 for (int i = 0; i < keys.size(); i++) { 430 targets[i] = targetMap.get(keys.getInt(i)); 431 } 432 433 Switch newSwitch = new Switch(ordinalInvoke.outValue(), keys.toIntArray(), 434 targets, switchInsn.getFallthroughBlockIndex()); 435 // Replace the switch itself. 436 it.replaceCurrentInstruction(newSwitch); 437 // If the original input to the switch is now unused, remove it too. It is not dead 438 // as it might have side-effects but we ignore these here. 439 if (arrayGet.outValue().numberOfUsers() == 0) { 440 arrayGet.inValues().forEach(v -> v.removeUser(arrayGet)); 441 arrayGet.getBlock().removeInstruction(arrayGet); 442 } 443 if (staticGet.outValue().numberOfUsers() == 0) { 444 assert staticGet.inValues().isEmpty(); 445 staticGet.getBlock().removeInstruction(staticGet); 446 } 447 } 448 } 449 } 450 } 451 } 452 } 453 454 455 /** 456 * Extracts the mapping from ordinal values to switch case constants. 457 * <p> 458 * This is done by pattern-matching on the class initializer of the synthetic switch map class. 459 * For a switch 460 * 461 * <blockquote><pre> 462 * switch (day) { 463 * case WEDNESDAY: 464 * case FRIDAY: 465 * System.out.println("3 or 5"); 466 * break; 467 * case SUNDAY: 468 * System.out.println("7"); 469 * break; 470 * default: 471 * System.out.println("other"); 472 * } 473 * </pre></blockquote> 474 * 475 * the generated companing class initializer will have the form 476 * 477 * <blockquote><pre> 478 * class Switches$1 { 479 * static { 480 * $SwitchMap$switchmaps$Days[Days.WEDNESDAY.ordinal()] = 1; 481 * $SwitchMap$switchmaps$Days[Days.FRIDAY.ordinal()] = 2; 482 * $SwitchMap$switchmaps$Days[Days.SUNDAY.ordinal()] = 3; 483 * } 484 * </pre></blockquote> 485 * 486 * Note that one map per class is generated, so the map might contain additional entries as used 487 * by other switches in the class. 488 */ extractIndexMapFrom(DexField field)489 private Int2ReferenceMap<DexField> extractIndexMapFrom(DexField field) { 490 DexClass clazz = appInfo.definitionFor(field.getHolder()); 491 if (!clazz.accessFlags.isSynthetic()) { 492 return null; 493 } 494 DexEncodedMethod initializer = clazz.getClassInitializer(); 495 if (initializer == null || initializer.getCode() == null) { 496 return null; 497 } 498 IRCode code = initializer.getCode().buildIR(initializer, new InternalOptions()); 499 Int2ReferenceMap<DexField> switchMap = new Int2ReferenceArrayMap<>(); 500 for (BasicBlock block : code.blocks) { 501 InstructionListIterator it = block.listIterator(); 502 Instruction insn = it.nextUntil(i -> i.isStaticGet() && i.asStaticGet().getField() == field); 503 if (insn == null) { 504 continue; 505 } 506 for (Instruction use : insn.outValue().uniqueUsers()) { 507 if (use.isArrayPut()) { 508 Instruction index = use.asArrayPut().source().definition; 509 if (index == null || !index.isConstNumber()) { 510 return null; 511 } 512 int integerIndex = index.asConstNumber().getIntValue(); 513 Instruction value = use.asArrayPut().index().definition; 514 if (value == null || !value.isInvokeVirtual()) { 515 return null; 516 } 517 InvokeVirtual invoke = value.asInvokeVirtual(); 518 DexClass holder = appInfo.definitionFor(invoke.getInvokedMethod().holder); 519 if (holder == null || 520 (!holder.accessFlags.isEnum() && holder.type != dexItemFactory.enumType)) { 521 return null; 522 } 523 Instruction enumGet = invoke.arguments().get(0).definition; 524 if (enumGet == null || !enumGet.isStaticGet()) { 525 return null; 526 } 527 DexField enumField = enumGet.asStaticGet().getField(); 528 if (!appInfo.definitionFor(enumField.getHolder()).accessFlags.isEnum()) { 529 return null; 530 } 531 if (switchMap.put(integerIndex, enumField) != null) { 532 return null; 533 } 534 } else { 535 return null; 536 } 537 } 538 } 539 return switchMap; 540 } 541 542 /** 543 * Extracts the ordinal values for an Enum class from the classes static initializer. 544 * <p> 545 * An Enum class has a field for each value. In the class initializer, each field is initialized 546 * to a singleton object that represents the value. This code matches on the corresponding call 547 * to the constructor (instance initializer) and extracts the value of the second argument, which 548 * is the ordinal. 549 */ extractOrdinalsMapFor(DexType enumClass)550 private Reference2IntMap<DexField> extractOrdinalsMapFor(DexType enumClass) { 551 DexClass clazz = appInfo.definitionFor(enumClass); 552 if (clazz == null || clazz.isLibraryClass()) { 553 // We have to keep binary compatibility in tact for libraries. 554 return null; 555 } 556 DexEncodedMethod initializer = clazz.getClassInitializer(); 557 if (!clazz.accessFlags.isEnum() || initializer == null || initializer.getCode() == null) { 558 return null; 559 } 560 IRCode code = initializer.getCode().buildIR(initializer, new InternalOptions()); 561 Reference2IntMap<DexField> ordinalsMap = new Reference2IntArrayMap<>(); 562 ordinalsMap.defaultReturnValue(-1); 563 InstructionIterator it = code.instructionIterator(); 564 while (it.hasNext()) { 565 Instruction insn = it.next(); 566 if (!insn.isStaticPut()) { 567 continue; 568 } 569 StaticPut staticPut = insn.asStaticPut(); 570 if (staticPut.getField().type != enumClass) { 571 continue; 572 } 573 Instruction newInstance = staticPut.inValue().definition; 574 if (newInstance == null || !newInstance.isNewInstance()) { 575 continue; 576 } 577 Instruction ordinal = null; 578 for (Instruction ctorCall : newInstance.outValue().uniqueUsers()) { 579 if (!ctorCall.isInvokeDirect()) { 580 continue; 581 } 582 InvokeDirect invoke = ctorCall.asInvokeDirect(); 583 if (!dexItemFactory.isConstructor(invoke.getInvokedMethod()) 584 || invoke.arguments().size() < 3) { 585 continue; 586 } 587 ordinal = invoke.arguments().get(2).definition; 588 break; 589 } 590 if (ordinal == null || !ordinal.isConstNumber()) { 591 return null; 592 } 593 if (ordinalsMap.put(staticPut.getField(), ordinal.asConstNumber().getIntValue()) != -1) { 594 return null; 595 } 596 } 597 return ordinalsMap; 598 } 599 600 /** 601 * Rewrite all branch targets to the destination of trivial goto chains when possible. 602 * Does not rewrite fallthrough targets as that would require block reordering and the 603 * transformation only makes sense after SSA destruction where there are no phis. 604 */ collapsTrivialGotos(DexEncodedMethod method, IRCode code)605 public static void collapsTrivialGotos(DexEncodedMethod method, IRCode code) { 606 assert code.isConsistentGraph(); 607 List<BasicBlock> blocksToRemove = new ArrayList<>(); 608 // Rewrite all non-fallthrough targets to the end of trivial goto chains and remove 609 // first round of trivial goto blocks. 610 ListIterator<BasicBlock> iterator = code.listIterator(); 611 assert iterator.hasNext(); 612 BasicBlock block = iterator.next(); 613 BasicBlock nextBlock; 614 615 // The marks will be used for cycle detection. 616 code.clearMarks(); 617 do { 618 nextBlock = iterator.hasNext() ? iterator.next() : null; 619 if (block.isTrivialGoto()) { 620 collapsTrivialGoto(block, nextBlock, blocksToRemove); 621 } 622 if (block.exit().isIf()) { 623 collapsIfTrueTarget(block); 624 } 625 if (block.exit().isSwitch()) { 626 collapsNonFallthroughSwitchTargets(block); 627 } 628 block = nextBlock; 629 } while (nextBlock != null); 630 code.removeBlocks(blocksToRemove); 631 // Get rid of gotos to the next block. 632 while (!blocksToRemove.isEmpty()) { 633 blocksToRemove = new ArrayList<>(); 634 iterator = code.listIterator(); 635 block = iterator.next(); 636 do { 637 nextBlock = iterator.hasNext() ? iterator.next() : null; 638 if (block.isTrivialGoto()) { 639 collapsTrivialGoto(block, nextBlock, blocksToRemove); 640 } 641 block = nextBlock; 642 } while (block != null); 643 code.removeBlocks(blocksToRemove); 644 } 645 assert removedTrivialGotos(code); 646 assert code.isConsistentGraph(); 647 } 648 identifyReturnsArgument( DexEncodedMethod method, IRCode code, OptimizationFeedback feedback)649 public void identifyReturnsArgument( 650 DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) { 651 if (code.getNormalExitBlock() != null) { 652 Return ret = code.getNormalExitBlock().exit().asReturn(); 653 if (!ret.isReturnVoid()) { 654 Value returnValue = ret.returnValue(); 655 if (returnValue.isArgument()) { 656 // Find the argument number. 657 int index = code.collectArguments().indexOf(returnValue); 658 assert index != -1; 659 feedback.methodReturnsArgument(method, index); 660 } 661 if (returnValue.isConstant() && returnValue.definition.isConstNumber()) { 662 long value = returnValue.definition.asConstNumber().getRawValue(); 663 feedback.methodReturnsConstant(method, value); 664 } 665 if (returnValue.isNeverNull()) { 666 feedback.methodNeverReturnsNull(method); 667 } 668 } 669 } 670 } 671 checkArgumentType(InvokeMethod invoke, DexMethod target, int argumentIndex)672 private boolean checkArgumentType(InvokeMethod invoke, DexMethod target, int argumentIndex) { 673 DexType returnType = invoke.getInvokedMethod().proto.returnType; 674 // TODO(sgjesse): Insert cast if required. 675 if (invoke.isInvokeStatic()) { 676 return invoke.getInvokedMethod().proto.parameters.values[argumentIndex] == returnType; 677 } else { 678 if (argumentIndex == 0) { 679 return invoke.getInvokedMethod().getHolder() == returnType; 680 } else { 681 return invoke.getInvokedMethod().proto.parameters.values[argumentIndex - 1] == returnType; 682 } 683 } 684 } 685 686 // Replace result uses for methods where something is known about what is returned. rewriteMoveResult(IRCode code)687 public void rewriteMoveResult(IRCode code) { 688 if (!appInfo.hasSubtyping()) { 689 return; 690 } 691 InstructionIterator iterator = code.instructionIterator(); 692 while (iterator.hasNext()) { 693 Instruction current = iterator.next(); 694 if (current.isInvokeMethod()) { 695 InvokeMethod invoke = current.asInvokeMethod(); 696 if (invoke.outValue() != null) { 697 DexEncodedMethod target = invoke.computeSingleTarget(appInfo.withSubtyping()); 698 // We have a set of library classes with optimization information - consider those 699 // as well. 700 if ((target == null) && 701 libraryClassesWithOptimizationInfo.contains(invoke.getInvokedMethod().getHolder())) { 702 target = appInfo.definitionFor(invoke.getInvokedMethod()); 703 } 704 if (target != null) { 705 DexMethod invokedMethod = target.method; 706 // Check if the invoked method is known to return one of its arguments. 707 DexEncodedMethod definition = appInfo.definitionFor(invokedMethod); 708 if (definition != null && definition.getOptimizationInfo().returnsArgument()) { 709 int argumentIndex = definition.getOptimizationInfo().getReturnedArgument(); 710 // Replace the out value of the invoke with the argument and ignore the out value. 711 if (argumentIndex != -1 && checkArgumentType(invoke, target.method, argumentIndex)) { 712 Value argument = invoke.arguments().get(argumentIndex); 713 assert (invoke.outType() == argument.outType()) || 714 (invoke.outType() == MoveType.OBJECT 715 && argument.outType() == MoveType.SINGLE 716 && argument.getConstInstruction().asConstNumber().isZero()); 717 invoke.outValue().replaceUsers(argument); 718 invoke.setOutValue(null); 719 } 720 } 721 } 722 } 723 } 724 } 725 assert code.isConsistentGraph(); 726 } 727 728 // For supporting assert javac adds the static field $assertionsDisabled to all classes which 729 // have methods with assertions. This is used to support the Java VM -ea flag. 730 // 731 // The class: 732 // 733 // class A { 734 // void m() { 735 // assert xxx; 736 // } 737 // } 738 // 739 // Is compiled into: 740 // 741 // class A { 742 // static boolean $assertionsDisabled; 743 // static { 744 // $assertionsDisabled = A.class.desiredAssertionStatus(); 745 // } 746 // 747 // // method with "assert xxx"; 748 // void m() { 749 // if (!$assertionsDisabled) { 750 // if (xxx) { 751 // throw new AssertionError(...); 752 // } 753 // } 754 // } 755 // } 756 // 757 // With the rewriting below (and other rewritings) the resulting code is: 758 // 759 // class A { 760 // void m() { 761 // } 762 // } 763 // disableAssertions(IRCode code)764 public void disableAssertions(IRCode code) { 765 InstructionIterator iterator = code.instructionIterator(); 766 while (iterator.hasNext()) { 767 Instruction current = iterator.next(); 768 if (current.isInvokeMethod()) { 769 InvokeMethod invoke = current.asInvokeMethod(); 770 if (invoke.getInvokedMethod() == dexItemFactory.classMethods.desiredAssertionStatus) { 771 iterator.replaceCurrentInstruction(code.createFalse()); 772 } 773 } else if (current.isStaticPut()) { 774 StaticPut staticPut = current.asStaticPut(); 775 if (staticPut.getField().name == dexItemFactory.assertionsDisabled) { 776 iterator.remove(); 777 } 778 } else if (current.isStaticGet()) { 779 StaticGet staticGet = current.asStaticGet(); 780 if (staticGet.getField().name == dexItemFactory.assertionsDisabled) { 781 iterator.replaceCurrentInstruction(code.createTrue()); 782 } 783 } 784 } 785 } 786 canBeFolded(Instruction instruction)787 private boolean canBeFolded(Instruction instruction) { 788 return (instruction.isBinop() && instruction.asBinop().canBeFolded()) || 789 (instruction.isUnop() && instruction.asUnop().canBeFolded()); 790 } 791 foldConstants(IRCode code)792 public void foldConstants(IRCode code) { 793 Queue<BasicBlock> worklist = new LinkedList<>(); 794 worklist.addAll(code.blocks); 795 for (BasicBlock block = worklist.poll(); block != null; block = worklist.poll()) { 796 InstructionIterator iterator = block.iterator(); 797 while (iterator.hasNext()) { 798 Instruction current = iterator.next(); 799 Instruction folded; 800 if (canBeFolded(current)) { 801 folded = current.fold(code); 802 iterator.replaceCurrentInstruction(folded); 803 folded.outValue().uniqueUsers() 804 .forEach(instruction -> worklist.add(instruction.getBlock())); 805 } 806 } 807 } 808 assert code.isConsistentSSA(); 809 } 810 811 // Constants are canonicalized in the entry block. We split some of them when it is likely 812 // that having them canonicalized in the entry block will lead to poor code quality. splitConstants(IRCode code)813 public void splitConstants(IRCode code) { 814 for (BasicBlock block : code.blocks) { 815 // Split constants that flow into phis. It is likely that these constants will have moves 816 // generated for them anyway and we might as well insert a const instruction in the right 817 // predecessor block. 818 splitPhiConstants(code, block); 819 // Split constants that flow into ranged invokes. This gives the register allocator more 820 // freedom in assigning register to ranged invokes which can greatly reduce the number 821 // of register needed (and thereby code size as well). 822 splitRangedInvokeConstants(code, block); 823 } 824 } 825 splitRangedInvokeConstants(IRCode code, BasicBlock block)826 private void splitRangedInvokeConstants(IRCode code, BasicBlock block) { 827 InstructionListIterator it = block.listIterator(); 828 while (it.hasNext()) { 829 Instruction current = it.next(); 830 if (current.isInvoke() && current.asInvoke().requiredArgumentRegisters() > 5) { 831 Invoke invoke = current.asInvoke(); 832 it.previous(); 833 Map<ConstNumber, ConstNumber> oldToNew = new HashMap<>(); 834 for (int i = 0; i < invoke.inValues().size(); i++) { 835 Value value = invoke.inValues().get(i); 836 if (value.isConstant() && value.numberOfUsers() > 1) { 837 ConstNumber definition = value.getConstInstruction().asConstNumber(); 838 Value originalValue = definition.outValue(); 839 ConstNumber newNumber = oldToNew.get(definition); 840 if (newNumber == null) { 841 newNumber = ConstNumber.copyOf(code, definition); 842 it.add(newNumber); 843 oldToNew.put(definition, newNumber); 844 } 845 invoke.inValues().set(i, newNumber.outValue()); 846 originalValue.removeUser(invoke); 847 newNumber.outValue().addUser(invoke); 848 } 849 } 850 it.next(); 851 } 852 } 853 } 854 splitPhiConstants(IRCode code, BasicBlock block)855 private void splitPhiConstants(IRCode code, BasicBlock block) { 856 for (int i = 0; i < block.getPredecessors().size(); i++) { 857 Map<ConstNumber, ConstNumber> oldToNew = new IdentityHashMap<>(); 858 BasicBlock predecessor = block.getPredecessors().get(i); 859 for (Phi phi : block.getPhis()) { 860 Value operand = phi.getOperand(i); 861 if (!operand.isPhi() && operand.isConstant()) { 862 ConstNumber definition = operand.getConstInstruction().asConstNumber(); 863 ConstNumber newNumber = oldToNew.get(definition); 864 Value originalValue = definition.outValue(); 865 if (newNumber == null) { 866 newNumber = ConstNumber.copyOf(code, definition); 867 oldToNew.put(definition, newNumber); 868 insertConstantInBlock(newNumber, predecessor); 869 } 870 phi.getOperands().set(i, newNumber.outValue()); 871 originalValue.removePhiUser(phi); 872 newNumber.outValue().addPhiUser(phi); 873 } 874 } 875 } 876 } 877 shortenLiveRanges(IRCode code)878 public void shortenLiveRanges(IRCode code) { 879 // Currently, we are only shortening the live range of constants in the entry block. 880 // TODO(ager): Generalize this to shorten live ranges for more instructions? Currently 881 // doing so seems to make things worse. 882 Map<BasicBlock, List<Instruction>> addConstantInBlock = new HashMap<>(); 883 DominatorTree dominatorTree = new DominatorTree(code); 884 BasicBlock block = code.blocks.get(0); 885 InstructionListIterator it = block.listIterator(); 886 List<Instruction> toInsertInThisBlock = new ArrayList<>(); 887 while (it.hasNext()) { 888 Instruction instruction = it.next(); 889 if (instruction.isConstNumber()) { 890 // Collect the blocks for all users of the constant. 891 List<BasicBlock> userBlocks = new LinkedList<>(); 892 for (Instruction user : instruction.outValue().uniqueUsers()) { 893 userBlocks.add(user.getBlock()); 894 } 895 for (Phi phi : instruction.outValue().uniquePhiUsers()) { 896 userBlocks.add(phi.getBlock()); 897 } 898 // Locate the closest dominator block for all user blocks. 899 BasicBlock dominator = dominatorTree.closestDominator(userBlocks); 900 // If the closest dominator block is a block that uses the constant for a phi the constant 901 // needs to go in the immediate dominator block so that it is available for phi moves. 902 for (Phi phi : instruction.outValue().uniquePhiUsers()) { 903 if (phi.getBlock() == dominator) { 904 dominator = dominatorTree.immediateDominator(dominator); 905 break; 906 } 907 } 908 // Move the const instruction as close to its uses as possible. 909 it.detach(); 910 if (dominator != block) { 911 // Post-pone constant insertion in order to use a global heuristics. 912 List<Instruction> csts = addConstantInBlock.get(dominator); 913 if (csts == null) { 914 csts = new ArrayList<>(); 915 addConstantInBlock.put(dominator, csts); 916 } 917 csts.add(instruction); 918 } else { 919 toInsertInThisBlock.add(instruction); 920 } 921 } 922 } 923 924 // Heuristic to decide if constant instructions are shared in dominator block of usages or move 925 // to the usages. 926 for (Map.Entry<BasicBlock, List<Instruction>> entry : addConstantInBlock.entrySet()) { 927 if (entry.getValue().size() > STOP_SHARED_CONSTANT_THRESHOLD) { 928 // Too much constants in the same block, do not longer shared them except if they are used 929 // by phi instructions. 930 for (Instruction instruction : entry.getValue()) { 931 if (instruction.outValue().numberOfPhiUsers() != 0) { 932 // Add constant into the dominator block of usages. 933 insertConstantInBlock(instruction, entry.getKey()); 934 } else { 935 assert instruction.outValue().numberOfUsers() != 0; 936 ConstNumber constNumber = instruction.asConstNumber(); 937 Value constantValue = instruction.outValue(); 938 for (Instruction user : constantValue.uniqueUsers()) { 939 ConstNumber newCstNum = ConstNumber.copyOf(code, constNumber); 940 InstructionListIterator iterator = user.getBlock().listIterator(user); 941 iterator.previous(); 942 iterator.add(newCstNum); 943 user.replaceValue(constantValue, newCstNum.outValue()); 944 } 945 } 946 } 947 } else { 948 // Add constant into the dominator block of usages. 949 for (Instruction inst : entry.getValue()) { 950 insertConstantInBlock(inst, entry.getKey()); 951 } 952 } 953 } 954 955 for (Instruction toInsert : toInsertInThisBlock) { 956 insertConstantInBlock(toInsert, block); 957 } 958 assert code.isConsistentSSA(); 959 } 960 insertConstantInBlock(Instruction instruction, BasicBlock block)961 private void insertConstantInBlock(Instruction instruction, BasicBlock block) { 962 boolean hasCatchHandlers = block.hasCatchHandlers(); 963 InstructionListIterator insertAt = block.listIterator(); 964 // Place the instruction as late in the block as we can. It needs to go before users 965 // and if we have catch handlers it needs to be placed before the throwing instruction. 966 insertAt.nextUntil(i -> { 967 return i.inValues().contains(instruction.outValue()) 968 || i.isJumpInstruction() 969 || (hasCatchHandlers && i.instructionInstanceCanThrow()); 970 }); 971 insertAt.previous(); 972 insertAt.add(instruction); 973 } 974 computeArrayFilledData( NewArrayEmpty newArray, int size, BasicBlock block, int elementSize)975 private short[] computeArrayFilledData( 976 NewArrayEmpty newArray, int size, BasicBlock block, int elementSize) { 977 ConstNumber[] values = computeConstantArrayValues(newArray, block, size); 978 if (values == null) { 979 return null; 980 } 981 if (elementSize == 1) { 982 short[] result = new short[(size + 1) / 2]; 983 for (int i = 0; i < size; i += 2) { 984 short value = (short) (values[i].getIntValue() & 0xFF); 985 if (i + 1 < size) { 986 value |= (short) ((values[i + 1].getIntValue() & 0xFF) << 8); 987 } 988 result[i / 2] = value; 989 } 990 return result; 991 } 992 assert elementSize == 2 || elementSize == 4 || elementSize == 8; 993 int shortsPerConstant = elementSize / 2; 994 short[] result = new short[size * shortsPerConstant]; 995 for (int i = 0; i < size; i++) { 996 long value = values[i].getRawValue(); 997 for (int part = 0; part < shortsPerConstant; part++) { 998 result[i * shortsPerConstant + part] = (short) ((value >> (16 * part)) & 0xFFFFL); 999 } 1000 } 1001 return result; 1002 } 1003 computeConstantArrayValues( NewArrayEmpty newArray, BasicBlock block, int size)1004 private ConstNumber[] computeConstantArrayValues( 1005 NewArrayEmpty newArray, BasicBlock block, int size) { 1006 if (size > MAX_FILL_ARRAY_SIZE) { 1007 return null; 1008 } 1009 ConstNumber[] values = new ConstNumber[size]; 1010 int remaining = size; 1011 Set<Instruction> users = newArray.outValue().uniqueUsers(); 1012 // We allow the array instantiations to cross block boundaries as long as it hasn't encountered 1013 // an instruction instance that can throw an exception. 1014 InstructionListIterator it = block.listIterator(); 1015 it.nextUntil(i -> i == newArray); 1016 do { 1017 while (it.hasNext()) { 1018 Instruction instruction = it.next(); 1019 // If we encounter an instruction that can throw an exception we need to bail out of the 1020 // optimization so that we do not transform half-initialized arrays into fully initialized 1021 // arrays on exceptional edges. 1022 if (instruction.instructionInstanceCanThrow()) { 1023 return null; 1024 } 1025 if (!users.contains(instruction)) { 1026 continue; 1027 } 1028 // If the initialization sequence is broken by another use we cannot use a 1029 // fill-array-data instruction. 1030 if (!instruction.isArrayPut()) { 1031 return null; 1032 } 1033 ArrayPut arrayPut = instruction.asArrayPut(); 1034 if (!arrayPut.source().isConstant()) { 1035 return null; 1036 } 1037 assert arrayPut.index().isConstant(); 1038 int index = arrayPut.index().getConstInstruction().asConstNumber().getIntValue(); 1039 assert index >= 0 && index < values.length; 1040 if (values[index] != null) { 1041 return null; 1042 } 1043 ConstNumber value = arrayPut.source().getConstInstruction().asConstNumber(); 1044 values[index] = value; 1045 --remaining; 1046 if (remaining == 0) { 1047 return values; 1048 } 1049 } 1050 block = block.exit().isGoto() ? block.exit().asGoto().getTarget() : null; 1051 it = block != null ? block.listIterator() : null; 1052 } while (it != null); 1053 return null; 1054 } 1055 1056 private boolean isPrimitiveNewArrayWithConstantPositiveSize(Instruction instruction) { 1057 if (!(instruction instanceof NewArrayEmpty)) { 1058 return false; 1059 } 1060 NewArrayEmpty newArray = instruction.asNewArrayEmpty(); 1061 if (!newArray.size().isConstant()) { 1062 return false; 1063 } 1064 int size = newArray.size().getConstInstruction().asConstNumber().getIntValue(); 1065 if (size < 1) { 1066 return false; 1067 } 1068 if (!newArray.type.isPrimitiveArrayType()) { 1069 return false; 1070 } 1071 return true; 1072 } 1073 1074 /** 1075 * Replace NewArrayEmpty followed by stores of constants to all entries with NewArrayEmpty 1076 * and FillArrayData. 1077 */ 1078 public void simplifyArrayConstruction(IRCode code) { 1079 for (BasicBlock block : code.blocks) { 1080 // Map from the array value to the number of array put instruction to remove for that value. 1081 Map<Value, Integer> storesToRemoveForArray = new HashMap<>(); 1082 // First pass: identify candidates and insert fill array data instruction. 1083 InstructionListIterator it = block.listIterator(); 1084 while (it.hasNext()) { 1085 Instruction instruction = it.next(); 1086 if (!isPrimitiveNewArrayWithConstantPositiveSize(instruction)) { 1087 continue; 1088 } 1089 NewArrayEmpty newArray = instruction.asNewArrayEmpty(); 1090 int size = newArray.size().getConstInstruction().asConstNumber().getIntValue(); 1091 // If there is only one element it is typically smaller to generate the array put 1092 // instruction instead of fill array data. 1093 if (size == 1) { 1094 continue; 1095 } 1096 int elementSize = newArray.type.elementSizeForPrimitiveArrayType(); 1097 short[] contents = computeArrayFilledData(newArray, size, block, elementSize); 1098 if (contents == null) { 1099 continue; 1100 } 1101 storesToRemoveForArray.put(newArray.outValue(), size); 1102 int arraySize = newArray.size().getConstInstruction().asConstNumber().getIntValue(); 1103 NewArrayFilledData fillArray = new NewArrayFilledData( 1104 newArray.outValue(), elementSize, arraySize, contents); 1105 it.add(fillArray); 1106 } 1107 // Second pass: remove all the array put instructions for the array for which we have 1108 // inserted a fill array data instruction instead. 1109 if (!storesToRemoveForArray.isEmpty()) { 1110 do { 1111 it = block.listIterator(); 1112 while (it.hasNext()) { 1113 Instruction instruction = it.next(); 1114 if (instruction.isArrayPut()) { 1115 Value array = instruction.asArrayPut().array(); 1116 Integer toRemoveCount = storesToRemoveForArray.get(array); 1117 if (toRemoveCount != null && toRemoveCount > 0) { 1118 storesToRemoveForArray.put(array, toRemoveCount - 1); 1119 it.remove(); 1120 } 1121 } 1122 } 1123 block = block.exit().isGoto() ? block.exit().asGoto().getTarget() : null; 1124 } while (block != null); 1125 } 1126 } 1127 } 1128 1129 private class ExpressionEquivalence extends Equivalence<Instruction> { 1130 1131 @Override 1132 protected boolean doEquivalent(Instruction a, Instruction b) { 1133 if (a.getClass() != b.getClass() || !a.identicalNonValueParts(b)) { 1134 return false; 1135 } 1136 // For commutative binary operations any order of in-values are equal. 1137 if (a.isBinop() && a.asBinop().isCommutative()) { 1138 Value a0 = a.inValues().get(0); 1139 Value a1 = a.inValues().get(1); 1140 Value b0 = b.inValues().get(0); 1141 Value b1 = b.inValues().get(1); 1142 return (a0.equals(b0) && a1.equals(b1)) || (a0.equals(b1) && a1.equals(b0)); 1143 } else { 1144 // Compare all in-values. 1145 assert a.inValues().size() == b.inValues().size(); 1146 for (int i = 0; i < a.inValues().size(); i++) { 1147 if (!a.inValues().get(i).equals(b.inValues().get(i))) { 1148 return false; 1149 } 1150 } 1151 return true; 1152 } 1153 } 1154 1155 @Override 1156 protected int doHash(Instruction instruction) { 1157 final int prime = 29; 1158 int hash = instruction.getClass().hashCode(); 1159 if (instruction.isBinop()) { 1160 Binop binop = instruction.asBinop(); 1161 Value in0 = instruction.inValues().get(0); 1162 Value in1 = instruction.inValues().get(1); 1163 if (binop.isCommutative()) { 1164 hash += hash * prime + in0.hashCode() * in1.hashCode(); 1165 } else { 1166 hash += hash * prime + in0.hashCode(); 1167 hash += hash * prime + in1.hashCode(); 1168 } 1169 return hash; 1170 } else { 1171 for (Value value : instruction.inValues()) { 1172 hash += hash * prime + value.hashCode(); 1173 } 1174 } 1175 return hash; 1176 } 1177 } 1178 1179 private boolean shareCatchHandlers(Instruction i0, Instruction i1) { 1180 if (!i0.instructionTypeCanThrow()) { 1181 assert !i1.instructionTypeCanThrow(); 1182 return true; 1183 } 1184 assert i1.instructionTypeCanThrow(); 1185 // TODO(sgjesse): This could be even better by checking for the exceptions thrown, e.g. div 1186 // and rem only ever throw ArithmeticException. 1187 CatchHandlers<BasicBlock> ch0 = i0.getBlock().getCatchHandlers(); 1188 CatchHandlers<BasicBlock> ch1 = i1.getBlock().getCatchHandlers(); 1189 return ch0.equals(ch1); 1190 } 1191 1192 public void commonSubexpressionElimination(IRCode code) { 1193 final ListMultimap<Wrapper<Instruction>, Value> instructionToValue = ArrayListMultimap.create(); 1194 final DominatorTree dominatorTree = new DominatorTree(code); 1195 final ExpressionEquivalence equivalence = new ExpressionEquivalence(); 1196 1197 for (int i = 0; i < dominatorTree.getSortedBlocks().length; i++) { 1198 BasicBlock block = dominatorTree.getSortedBlocks()[i]; 1199 Iterator<Instruction> iterator = block.iterator(); 1200 while (iterator.hasNext()) { 1201 Instruction instruction = iterator.next(); 1202 if (instruction.isBinop() 1203 || instruction.isUnop() 1204 || instruction.isInstanceOf() 1205 || instruction.isCheckCast()) { 1206 List<Value> candidates = instructionToValue.get(equivalence.wrap(instruction)); 1207 boolean eliminated = false; 1208 if (candidates.size() > 0) { 1209 for (Value candidate : candidates) { 1210 if (dominatorTree.dominatedBy(block, candidate.definition.getBlock()) && 1211 shareCatchHandlers(instruction, candidate.definition)) { 1212 instruction.outValue().replaceUsers(candidate); 1213 eliminated = true; 1214 iterator.remove(); 1215 break; // Don't try any more candidates. 1216 } 1217 } 1218 } 1219 if (!eliminated) { 1220 instructionToValue.put(equivalence.wrap(instruction), instruction.outValue()); 1221 } 1222 } 1223 } 1224 } 1225 assert code.isConsistentSSA(); 1226 } 1227 1228 public void simplifyIf(IRCode code) { 1229 DominatorTree dominator = new DominatorTree(code); 1230 code.clearMarks(); 1231 for (BasicBlock block : code.blocks) { 1232 if (block.isMarked()) { 1233 continue; 1234 } 1235 if (block.exit().isIf()) { 1236 // First rewrite zero comparison. 1237 rewriteIfWithConstZero(block); 1238 1239 // Simplify if conditions when possible. 1240 If theIf = block.exit().asIf(); 1241 List<Value> inValues = theIf.inValues(); 1242 int cond; 1243 if (inValues.get(0).isConstant() 1244 && (theIf.isZeroTest() || inValues.get(1).isConstant())) { 1245 // Zero test with a constant of comparison between between two constants. 1246 if (theIf.isZeroTest()) { 1247 cond = inValues.get(0).getConstInstruction().asConstNumber().getIntValue(); 1248 } else { 1249 int left = inValues.get(0).getConstInstruction().asConstNumber().getIntValue(); 1250 int right = inValues.get(1).getConstInstruction().asConstNumber().getIntValue(); 1251 cond = left - right; 1252 } 1253 } else if (inValues.get(0).hasValueRange() 1254 && (theIf.isZeroTest() || inValues.get(1).hasValueRange())) { 1255 // Zero test with a value range, or comparison between between two values, 1256 // each with a value ranges. 1257 if (theIf.isZeroTest()) { 1258 if (inValues.get(0).isValueInRange(0)) { 1259 // Zero in in the range - can't determine the comparison. 1260 continue; 1261 } 1262 cond = Long.signum(inValues.get(0).getValueRange().getMin()); 1263 } else { 1264 LongInterval leftRange = inValues.get(0).getValueRange(); 1265 LongInterval rightRange = inValues.get(1).getValueRange(); 1266 if (leftRange.overlapsWith(rightRange)) { 1267 // Ranges overlap - can't determine the comparison. 1268 continue; 1269 } 1270 // There is no overlap. 1271 cond = Long.signum(leftRange.getMin() - rightRange.getMin()); 1272 } 1273 } else { 1274 continue; 1275 } 1276 BasicBlock target = theIf.targetFromCondition(cond); 1277 BasicBlock deadTarget = 1278 target == theIf.getTrueTarget() ? theIf.fallthroughBlock() : theIf.getTrueTarget(); 1279 List<BasicBlock> removedBlocks = block.unlink(deadTarget, dominator); 1280 for (BasicBlock removedBlock : removedBlocks) { 1281 if (!removedBlock.isMarked()) { 1282 removedBlock.mark(); 1283 } 1284 } 1285 assert theIf == block.exit(); 1286 replaceLastInstruction(block, new Goto()); 1287 assert block.exit().isGoto(); 1288 assert block.exit().asGoto().getTarget() == target; 1289 } 1290 } 1291 code.removeMarkedBlocks(); 1292 assert code.isConsistentSSA(); 1293 } 1294 1295 private void rewriteIfWithConstZero(BasicBlock block) { 1296 If theIf = block.exit().asIf(); 1297 if (theIf.isZeroTest()) { 1298 return; 1299 } 1300 1301 List<Value> inValues = theIf.inValues(); 1302 Value leftValue = inValues.get(0); 1303 Value rightValue = inValues.get(1); 1304 if (leftValue.isConstant() || rightValue.isConstant()) { 1305 if (leftValue.isConstant()) { 1306 int left = leftValue.getConstInstruction().asConstNumber().getIntValue(); 1307 if (left == 0) { 1308 If ifz = new If(theIf.getType().forSwappedOperands(), rightValue); 1309 replaceLastInstruction(block, ifz); 1310 assert block.exit() == ifz; 1311 } 1312 } else { 1313 assert rightValue.isConstant(); 1314 int right = rightValue.getConstInstruction().asConstNumber().getIntValue(); 1315 if (right == 0) { 1316 If ifz = new If(theIf.getType(), leftValue); 1317 replaceLastInstruction(block, ifz); 1318 assert block.exit() == ifz; 1319 } 1320 } 1321 } 1322 } 1323 1324 private void replaceLastInstruction(BasicBlock block, Instruction instruction) { 1325 InstructionListIterator iterator = block.listIterator(block.getInstructions().size()); 1326 iterator.previous(); 1327 iterator.replaceCurrentInstruction(instruction); 1328 } 1329 1330 public void rewriteLongCompareAndRequireNonNull(IRCode code, InternalOptions options) { 1331 if (options.canUseLongCompareAndObjectsNonNull()) { 1332 return; 1333 } 1334 1335 InstructionIterator iterator = code.instructionIterator(); 1336 while (iterator.hasNext()) { 1337 Instruction current = iterator.next(); 1338 if (current.isInvokeMethod()) { 1339 DexMethod invokedMethod = current.asInvokeMethod().getInvokedMethod(); 1340 if (invokedMethod == dexItemFactory.longMethods.compare) { 1341 // Rewrite calls to Long.compare for sdk versions that do not have that method. 1342 List<Value> inValues = current.inValues(); 1343 assert inValues.size() == 2; 1344 iterator.replaceCurrentInstruction( 1345 new Cmp(NumericType.LONG, Bias.NONE, current.outValue(), inValues.get(0), 1346 inValues.get(1))); 1347 } else if (invokedMethod == dexItemFactory.objectsMethods.requireNonNull) { 1348 // Rewrite calls to Objects.requireNonNull(Object) because Javac 9 start to use it for 1349 // synthesized null checks. 1350 InvokeVirtual callToGetClass = new InvokeVirtual(dexItemFactory.objectMethods.getClass, 1351 null, current.inValues()); 1352 if (current.outValue() != null) { 1353 current.outValue().replaceUsers(current.inValues().get(0)); 1354 current.setOutValue(null); 1355 } 1356 iterator.replaceCurrentInstruction(callToGetClass); 1357 } 1358 } 1359 } 1360 assert code.isConsistentSSA(); 1361 } 1362 1363 // Removes calls to Throwable.addSuppressed(Throwable) and rewrites 1364 // Throwable.getSuppressed() into new Throwable[0]. 1365 // 1366 // Note that addSuppressed() and getSuppressed() methods are final in 1367 // Throwable, so these changes don't have to worry about overrides. 1368 public void rewriteThrowableAddAndGetSuppressed(IRCode code) { 1369 DexItemFactory.ThrowableMethods throwableMethods = dexItemFactory.throwableMethods; 1370 1371 for (BasicBlock block : code.blocks) { 1372 InstructionListIterator iterator = block.listIterator(); 1373 while (iterator.hasNext()) { 1374 Instruction current = iterator.next(); 1375 if (current.isInvokeMethod()) { 1376 DexMethod invokedMethod = current.asInvokeMethod().getInvokedMethod(); 1377 1378 if (matchesMethodOfThrowable(invokedMethod, throwableMethods.addSuppressed)) { 1379 // Remove Throwable::addSuppressed(Throwable) call. 1380 iterator.remove(); 1381 } else if (matchesMethodOfThrowable(invokedMethod, throwableMethods.getSuppressed)) { 1382 Value destValue = current.outValue(); 1383 if (destValue == null) { 1384 // If the result of the call was not used we don't create 1385 // an empty array and just remove the call. 1386 iterator.remove(); 1387 continue; 1388 } 1389 1390 // Replace call to Throwable::getSuppressed() with new Throwable[0]. 1391 1392 // First insert the constant value *before* the current instruction. 1393 ConstNumber zero = code.createIntConstant(0); 1394 assert iterator.hasPrevious(); 1395 iterator.previous(); 1396 iterator.add(zero); 1397 1398 // Then replace the invoke instruction with NewArrayEmpty instruction. 1399 Instruction next = iterator.next(); 1400 assert current == next; 1401 NewArrayEmpty newArray = new NewArrayEmpty(destValue, zero.outValue(), 1402 dexItemFactory.createType(dexItemFactory.throwableArrayDescriptor)); 1403 iterator.replaceCurrentInstruction(newArray); 1404 } 1405 } 1406 } 1407 } 1408 assert code.isConsistentSSA(); 1409 } 1410 1411 private boolean matchesMethodOfThrowable(DexMethod invoked, DexMethod expected) { 1412 return invoked.name == expected.name 1413 && invoked.proto == expected.proto 1414 && isSubtypeOfThrowable(invoked.holder); 1415 } 1416 1417 private boolean isSubtypeOfThrowable(DexType type) { 1418 while (type != null && type != dexItemFactory.objectType) { 1419 if (type == dexItemFactory.throwableType) { 1420 return true; 1421 } 1422 DexClass dexClass = appInfo.definitionFor(type); 1423 if (dexClass == null) { 1424 throw new CompilationError("Class or interface " + type.toSourceString() + 1425 " required for desugaring of try-with-resources is not found."); 1426 } 1427 type = dexClass.superType; 1428 } 1429 return false; 1430 } 1431 1432 private Value addConstString(IRCode code, InstructionListIterator iterator, String s) { 1433 Value value = code.createValue(MoveType.OBJECT);; 1434 iterator.add(new ConstString(value, dexItemFactory.createString(s))); 1435 return value; 1436 } 1437 1438 /** 1439 * Insert code into <code>method</code> to log the argument types to System.out. 1440 * 1441 * The type is determined by calling getClass() on the argument. 1442 */ 1443 public void logArgumentTypes(DexEncodedMethod method, IRCode code) { 1444 List<Value> arguments = code.collectArguments(); 1445 BasicBlock block = code.blocks.getFirst(); 1446 InstructionListIterator iterator = block.listIterator(); 1447 1448 // Split arguments into their own block. 1449 iterator.nextUntil(instruction -> !instruction.isArgument()); 1450 iterator.previous(); 1451 iterator.split(code); 1452 iterator.previous(); 1453 1454 // Now that the block is split there should not be any catch handlers in the block. 1455 assert !block.hasCatchHandlers(); 1456 Value out = code.createValue(MoveType.OBJECT); 1457 DexType javaLangSystemType = dexItemFactory.createType("Ljava/lang/System;"); 1458 DexType javaIoPrintStreamType = dexItemFactory.createType("Ljava/io/PrintStream;"); 1459 1460 DexProto proto = dexItemFactory.createProto( 1461 dexItemFactory.voidType, new DexType[]{dexItemFactory.objectType}); 1462 DexMethod print = dexItemFactory.createMethod(javaIoPrintStreamType, proto, "print"); 1463 DexMethod printLn = dexItemFactory.createMethod(javaIoPrintStreamType, proto, "println"); 1464 1465 iterator.add( 1466 new StaticGet(MemberType.OBJECT, out, 1467 dexItemFactory.createField(javaLangSystemType, javaIoPrintStreamType, "out"))); 1468 1469 Value value = code.createValue(MoveType.OBJECT); 1470 iterator.add(new ConstString(value, dexItemFactory.createString("INVOKE "))); 1471 iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, value))); 1472 1473 value = code.createValue(MoveType.OBJECT);; 1474 iterator.add( 1475 new ConstString(value, dexItemFactory.createString(method.method.qualifiedName()))); 1476 iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, value))); 1477 1478 Value openParenthesis = addConstString(code, iterator, "("); 1479 Value comma = addConstString(code, iterator, ","); 1480 Value closeParenthesis = addConstString(code, iterator, ")"); 1481 Value indent = addConstString(code, iterator, " "); 1482 Value nul = addConstString(code, iterator, "(null)"); 1483 Value primitive = addConstString(code, iterator, "(primitive)"); 1484 Value empty = addConstString(code, iterator, ""); 1485 1486 iterator.add(new InvokeVirtual(printLn, null, ImmutableList.of(out, openParenthesis))); 1487 for (int i = 0; i < arguments.size(); i++) { 1488 iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, indent))); 1489 1490 // Add a block for end-of-line printing. 1491 BasicBlock eol = BasicBlock.createGotoBlock(code.blocks.size()); 1492 code.blocks.add(eol); 1493 1494 BasicBlock successor = block.unlinkSingleSuccessor(); 1495 block.link(eol); 1496 eol.link(successor); 1497 1498 Value argument = arguments.get(i); 1499 if (argument.outType() != MoveType.OBJECT) { 1500 iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, primitive))); 1501 } else { 1502 // Insert "if (argument != null) ...". 1503 successor = block.unlinkSingleSuccessor(); 1504 If theIf = new If(If.Type.NE, argument); 1505 BasicBlock ifBlock = BasicBlock.createIfBlock(code.blocks.size(), theIf); 1506 code.blocks.add(ifBlock); 1507 // Fallthrough block must be added right after the if. 1508 BasicBlock isNullBlock = BasicBlock.createGotoBlock(code.blocks.size()); 1509 code.blocks.add(isNullBlock); 1510 BasicBlock isNotNullBlock = BasicBlock.createGotoBlock(code.blocks.size()); 1511 code.blocks.add(isNotNullBlock); 1512 1513 // Link the added blocks together. 1514 block.link(ifBlock); 1515 ifBlock.link(isNotNullBlock); 1516 ifBlock.link(isNullBlock); 1517 isNotNullBlock.link(successor); 1518 isNullBlock.link(successor); 1519 1520 // Fill code into the blocks. 1521 iterator = isNullBlock.listIterator(); 1522 iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, nul))); 1523 iterator = isNotNullBlock.listIterator(); 1524 value = code.createValue(MoveType.OBJECT); 1525 iterator.add(new InvokeVirtual(dexItemFactory.objectMethods.getClass, value, 1526 ImmutableList.of(arguments.get(i)))); 1527 iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, value))); 1528 } 1529 1530 iterator = eol.listIterator(); 1531 if (i == arguments.size() - 1) { 1532 iterator.add(new InvokeVirtual(printLn, null, ImmutableList.of(out, closeParenthesis))); 1533 } else { 1534 iterator.add(new InvokeVirtual(printLn, null, ImmutableList.of(out, comma))); 1535 } 1536 block = eol; 1537 } 1538 // When we fall out of the loop the iterator is in the last eol block. 1539 iterator.add(new InvokeVirtual(printLn, null, ImmutableList.of(out, empty))); 1540 } 1541 } 1542