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.Unreachable; 9 import com.android.tools.r8.graph.AppInfo; 10 import com.android.tools.r8.graph.Code; 11 import com.android.tools.r8.graph.DebugLocalInfo; 12 import com.android.tools.r8.graph.DexAccessFlags; 13 import com.android.tools.r8.graph.DexAnnotationSet; 14 import com.android.tools.r8.graph.DexAnnotationSetRefList; 15 import com.android.tools.r8.graph.DexClass; 16 import com.android.tools.r8.graph.DexEncodedField; 17 import com.android.tools.r8.graph.DexEncodedMethod; 18 import com.android.tools.r8.graph.DexItemFactory; 19 import com.android.tools.r8.graph.DexMethod; 20 import com.android.tools.r8.graph.DexProgramClass; 21 import com.android.tools.r8.graph.DexProto; 22 import com.android.tools.r8.graph.DexString; 23 import com.android.tools.r8.graph.DexType; 24 import com.android.tools.r8.graph.DexTypeList; 25 import com.android.tools.r8.graph.UseRegistry; 26 import com.android.tools.r8.ir.code.Add; 27 import com.android.tools.r8.ir.code.BasicBlock; 28 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo; 29 import com.android.tools.r8.ir.code.CatchHandlers; 30 import com.android.tools.r8.ir.code.Div; 31 import com.android.tools.r8.ir.code.IRCode; 32 import com.android.tools.r8.ir.code.Instruction; 33 import com.android.tools.r8.ir.code.Invoke; 34 import com.android.tools.r8.ir.code.Invoke.Type; 35 import com.android.tools.r8.ir.code.InvokeMethod; 36 import com.android.tools.r8.ir.code.InvokeStatic; 37 import com.android.tools.r8.ir.code.MoveType; 38 import com.android.tools.r8.ir.code.Mul; 39 import com.android.tools.r8.ir.code.NewInstance; 40 import com.android.tools.r8.ir.code.Rem; 41 import com.android.tools.r8.ir.code.Sub; 42 import com.android.tools.r8.ir.code.Value; 43 import com.android.tools.r8.ir.conversion.IRBuilder; 44 import com.android.tools.r8.ir.conversion.SourceCode; 45 import com.android.tools.r8.naming.ClassNameMapper; 46 import com.android.tools.r8.utils.InternalOptions; 47 import com.android.tools.r8.utils.StringUtils; 48 import com.android.tools.r8.utils.StringUtils.BraceType; 49 import com.google.common.collect.Sets; 50 import java.util.ArrayList; 51 import java.util.Comparator; 52 import java.util.HashMap; 53 import java.util.List; 54 import java.util.ListIterator; 55 import java.util.Map; 56 import java.util.Map.Entry; 57 import java.util.Set; 58 59 public class Outliner { 60 61 private final InternalOptions options; 62 private final Map<Outline, List<DexEncodedMethod>> candidates = new HashMap<>(); 63 private final Map<Outline, DexMethod> generatedOutlines = new HashMap<>(); 64 private final Set<DexEncodedMethod> methodsSelectedForOutlining = Sets.newIdentityHashSet(); 65 66 static final int MAX_IN_SIZE = 5; // Avoid using ranged calls for outlined code. 67 68 final private AppInfo appInfo; 69 final private DexItemFactory dexItemFactory; 70 71 // Representation of an outline. 72 // This includes the instructions in the outline, and a map from the arguments of this outline 73 // to the values in the instructions. 74 // 75 // E.g. an outline of two StringBuilder.append(String) calls could look like this: 76 // 77 // InvokeVirtual { v5 v6 } Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; 78 // InvokeVirtual { v5 v9 } Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; 79 // ReturnVoid 80 // 81 // It takes three arguments 82 // 83 // v5, v6, v9 84 // 85 // and the argument map is a list mapping the arguments to the in-values of all instructions in 86 // the order they are encountered, in this case: 87 // 88 // [0, 1, 0, 2] 89 // 90 // The actual value numbers (in this example v5, v6, v9 are "arbitrary", as the instruction in 91 // the outline are taken from the block where they are collected as candidates. The comparison 92 // of two outlines rely on the instructions and the argument mapping *not* the concrete values. 93 public class Outline implements Comparable<Outline> { 94 95 final List<Value> arguments; 96 final List<DexType> argumentTypes; 97 final List<Integer> argumentMap; 98 final List<Instruction> templateInstructions = new ArrayList<>(); 99 final public DexType returnType; 100 101 private DexProto proto; 102 103 // Build an outline over the instructions [start, end[. 104 // The arguments are the arguments to pass to an outline of these instructions. Outline(BasicBlock block, List<Value> arguments, List<DexType> argumentTypes, List<Integer> argumentMap, DexType returnType, int start, int end)105 Outline(BasicBlock block, List<Value> arguments, List<DexType> argumentTypes, 106 List<Integer> argumentMap, DexType returnType, int start, int end) { 107 this.arguments = arguments; 108 this.argumentTypes = argumentTypes; 109 this.argumentMap = argumentMap; 110 this.returnType = returnType; 111 112 List<Instruction> instructions = block.getInstructions(); 113 for (int i = start; i < end; i++) { 114 Instruction current = instructions.get(i); 115 if (current.isInvoke() || current.isNewInstance() || current.isArithmeticBinop()) { 116 templateInstructions.add(current); 117 } else if (current.isConstInstruction()) { 118 // Don't include const instructions in the template. 119 } else { 120 assert false : "Unexpected type of instruction in outlining template."; 121 } 122 } 123 } 124 argumentCount()125 int argumentCount() { 126 return arguments.size(); 127 } 128 buildProto()129 DexProto buildProto() { 130 if (proto == null) { 131 DexType[] argumentTypesArray = argumentTypes.toArray(new DexType[argumentTypes.size()]); 132 proto = dexItemFactory.createProto(returnType, argumentTypesArray); 133 } 134 return proto; 135 } 136 137 // Build the DexMethod for this outline. buildMethod(DexType clazz, DexString name)138 DexMethod buildMethod(DexType clazz, DexString name) { 139 return dexItemFactory.createMethod(clazz, buildProto(), name); 140 } 141 142 @Override equals(Object other)143 public boolean equals(Object other) { 144 if (!(other instanceof Outline)) { 145 return false; 146 } 147 List<Instruction> instructions0 = this.templateInstructions; 148 List<Instruction> instructions1 = ((Outline) other).templateInstructions; 149 if (instructions0.size() != instructions1.size()) { 150 return false; 151 } 152 for (int i = 0; i < instructions0.size(); i++) { 153 Instruction i0 = instructions0.get(i); 154 Instruction i1 = instructions1.get(i); 155 if (i0.getClass() != i1.getClass() || !i0.identicalNonValueParts(i1)) { 156 return false; 157 } 158 if ((i0.outValue() != null) != (i1.outValue() != null)) { 159 return false; 160 } 161 } 162 return argumentMap.equals(((Outline) other).argumentMap) 163 && returnType == ((Outline) other).returnType; 164 } 165 166 @Override hashCode()167 public int hashCode() { 168 final int MAX_HASH_INSTRUCTIONS = 5; 169 170 int hash = templateInstructions.size(); 171 int hashPart = 0; 172 for (int i = 0; i < templateInstructions.size() && i < MAX_HASH_INSTRUCTIONS; i++) { 173 Instruction instruction = templateInstructions.get(i); 174 if (instruction.isInvokeMethod()) { 175 hashPart = hashPart << 4; 176 hashPart += instruction.asInvokeMethod().getInvokedMethod().hashCode(); 177 } 178 hash = hash * 3 + hashPart; 179 } 180 return hash; 181 } 182 183 @Override compareTo(Outline other)184 public int compareTo(Outline other) { 185 if (this == other) { 186 return 0; 187 } 188 // First compare the proto. 189 int result; 190 result = buildProto().slowCompareTo(other.buildProto()); 191 if (result != 0) { 192 assert !equals(other); 193 return result; 194 } 195 assert argumentCount() == other.argumentCount(); 196 // Then compare the instructions (non value part). 197 List<Instruction> instructions0 = templateInstructions; 198 List<Instruction> instructions1 = other.templateInstructions; 199 result = instructions0.size() - instructions1.size(); 200 if (result != 0) { 201 assert !equals(other); 202 return result; 203 } 204 for (int i = 0; i < instructions0.size(); i++) { 205 Instruction i0 = instructions0.get(i); 206 Instruction i1 = instructions1.get(i); 207 result = i0.getInstructionName().compareTo(i1.getInstructionName()); 208 if (result != 0) { 209 assert !equals(other); 210 return result; 211 } 212 result = i0.inValues().size() - i1.inValues().size(); 213 if (result != 0) { 214 assert !equals(other); 215 return result; 216 } 217 result = (i0.outValue() != null ? 1 : 0) - (i1.outValue() != null ? 1 : 0); 218 if (result != 0) { 219 assert !equals(other); 220 return result; 221 } 222 result = i0.compareNonValueParts(i1); 223 if (result != 0) { 224 assert !equals(other); 225 return result; 226 } 227 } 228 // Finally compare the value part. 229 result = argumentMap.size() - other.argumentMap.size(); 230 if (result != 0) { 231 assert !equals(other); 232 return result; 233 } 234 for (int i = 0; i < argumentMap.size(); i++) { 235 result = argumentMap.get(i) - other.argumentMap.get(i); 236 if (result != 0) { 237 assert !equals(other); 238 return result; 239 } 240 } 241 assert equals(other); 242 return 0; 243 } 244 245 @Override toString()246 public String toString() { 247 // The printing of the code for an outline maps the value numbers to the arguments numbers. 248 int outRegisterNumber = arguments.size(); 249 StringBuilder builder = new StringBuilder(); 250 builder.append(returnType); 251 builder.append(" anOutline"); 252 StringUtils.append(builder, argumentTypes, ", ", BraceType.PARENS); 253 builder.append("\n"); 254 int argumentMapIndex = 0; 255 for (Instruction instruction : templateInstructions) { 256 String name = instruction.getInstructionName(); 257 StringUtils.appendRightPadded(builder, name, 20); 258 if (instruction.outValue() != null) { 259 builder.append("v" + outRegisterNumber); 260 builder.append(" <- "); 261 } 262 for (int i = 0; i < instruction.inValues().size(); i++) { 263 builder.append(i > 0 ? ", " : ""); 264 builder.append("v"); 265 int index = argumentMap.get(argumentMapIndex++); 266 if (index >= 0) { 267 builder.append(index); 268 } else { 269 builder.append(outRegisterNumber); 270 } 271 } 272 if (instruction.isInvoke()) { 273 builder.append("; method: "); 274 builder.append(instruction.asInvokeMethod().getInvokedMethod().toSourceString()); 275 } 276 if (instruction.isNewInstance()) { 277 builder.append(instruction.asNewInstance().clazz.toSourceString()); 278 } 279 builder.append("\n"); 280 } 281 if (returnType == dexItemFactory.voidType) { 282 builder.append("Return-Void"); 283 } else { 284 StringUtils.appendRightPadded(builder, "Return", 20); 285 builder.append("v" + (outRegisterNumber)); 286 } 287 builder.append("\n"); 288 builder.append(argumentMap); 289 return builder.toString(); 290 } 291 } 292 293 // Spot the outline opportunities in a basic block. 294 // This is the superclass for both collection candidates and actually replacing code. 295 // TODO(sgjesse): Collect more information in the candidate collection and reuse that for 296 // replacing. 297 abstract private class OutlineSpotter { 298 299 final DexEncodedMethod method; 300 final BasicBlock block; 301 final List<Instruction> instructions; 302 303 int start; 304 int index; 305 int actualInstructions; 306 List<Value> arguments; 307 List<DexType> argumentTypes; 308 List<Integer> argumentsMap; 309 int argumentRegisters; 310 DexType returnType; 311 Value returnValue; 312 int returnValueUsersLeft; 313 int pendingNewInstanceIndex = -1; 314 OutlineSpotter(DexEncodedMethod method, BasicBlock block)315 OutlineSpotter(DexEncodedMethod method, BasicBlock block) { 316 this.method = method; 317 this.block = block; 318 this.instructions = block.getInstructions(); 319 reset(0); 320 } 321 process()322 protected void process() { 323 List<Instruction> instructions = block.getInstructions(); 324 while (index < instructions.size()) { 325 Instruction current = instructions.get(index); 326 processInstruction(current); 327 } 328 } 329 330 // Get int in-values for an instruction. For commutative binary operations using the current 331 // return value (active out-value) make sure that that value is the left value. orderedInValues(Instruction instruction, Value returnValue)332 protected List<Value> orderedInValues(Instruction instruction, Value returnValue) { 333 List<Value> inValues = instruction.inValues(); 334 if (instruction.isBinop() && instruction.asBinop().isCommutative()) { 335 if (inValues.get(1) == returnValue) { 336 Value tmp = inValues.get(0); 337 inValues.set(0, inValues.get(1)); 338 inValues.set(1, tmp); 339 } 340 } 341 return inValues; 342 } 343 344 // Process instruction. Returns true if an outline candidate was found. processInstruction(Instruction instruction)345 private void processInstruction(Instruction instruction) { 346 // Figure out whether to include the instruction. 347 boolean include = false; 348 int instructionIncrement = 1; 349 if (instruction.isConstInstruction()) { 350 if (index == start) { 351 // Restart for first const instruction. 352 reset(index + 1); 353 return; 354 } else { 355 // Otherwise include const instructions. 356 include = true; 357 instructionIncrement = 0; 358 } 359 } else { 360 include = canIncludeInstruction(instruction); 361 } 362 363 if (include) { 364 actualInstructions += instructionIncrement; 365 366 // Add this instruction. 367 includeInstruction(instruction); 368 // Check if this instruction ends the outline. 369 if (actualInstructions >= options.outline.maxSize) { 370 candidate(start, index + 1); 371 } else { 372 index++; 373 } 374 } else if (index > start) { 375 // Do not add this instruction, candidate ends with previous instruction. 376 candidate(start, index); 377 } else { 378 // Restart search from next instruction. 379 reset(index + 1); 380 } 381 } 382 383 // Check if the current instruction can be included in the outline. canIncludeInstruction(Instruction instruction)384 private boolean canIncludeInstruction(Instruction instruction) { 385 // Find the users of the active out-value (potential return value). 386 int returnValueUsersLeftIfIncluded = returnValueUsersLeft; 387 if (returnValue != null) { 388 for (Value value : instruction.inValues()) { 389 if (value == returnValue) { 390 returnValueUsersLeftIfIncluded--; 391 } 392 } 393 } 394 395 // If this instruction has an out-value, but the previous one is still active end the 396 // outline. 397 if (instruction.outValue() != null && returnValueUsersLeftIfIncluded > 0) { 398 return false; 399 } 400 401 // Allow all new-instance instructions in a outline. 402 if (instruction.isNewInstance()) { 403 if (instruction.outValue().numberOfAllUsers() > 0) { 404 // Track the new-instance value to make sure the <init> call is part of the outline. 405 pendingNewInstanceIndex = index; 406 } 407 return true; 408 } 409 410 if (instruction.isArithmeticBinop()) { 411 return true; 412 } 413 414 // Otherwise we only allow invoke. 415 if (!instruction.isInvokeMethod()) { 416 return false; 417 } 418 InvokeMethod invoke = instruction.asInvokeMethod(); 419 boolean constructor = dexItemFactory.isConstructor(invoke.getInvokedMethod()); 420 421 // Lookup the encoded method. 422 DexMethod invokedMethod = invoke.getInvokedMethod(); 423 DexEncodedMethod target; 424 if (invoke.isInvokeStatic()) { 425 target = appInfo.lookupStaticTarget(invokedMethod); 426 } else if (invoke.isInvokeDirect()) { 427 target = appInfo.lookupDirectTarget(invokedMethod); 428 } else { 429 if (invokedMethod.getHolder().isArrayType()) { 430 return false; 431 } 432 target = appInfo.lookupVirtualTarget(invokedMethod.getHolder(), invokedMethod); 433 } 434 // If the encoded method is found check the access flags. 435 if (target != null) { 436 if (!target.accessFlags.isPublic()) { 437 return false; 438 } 439 DexClass holder = appInfo.definitionFor(invokedMethod.getHolder()); 440 if (!holder.accessFlags.isPublic()) { 441 return false; 442 } 443 } 444 445 // Find the number of in-going arguments, if adding this instruction. 446 int newArgumentRegisters = argumentRegisters; 447 if (instruction.inValues().size() > 0) { 448 List<Value> inValues = orderedInValues(instruction, returnValue); 449 for (int i = 0; i < inValues.size(); i++) { 450 Value value = inValues.get(i); 451 if (value == returnValue) { 452 continue; 453 } 454 if (invoke.isInvokeStatic()) { 455 newArgumentRegisters += value.requiredRegisters(); 456 } else { 457 // For virtual calls only re-use the receiver argument. 458 if (i > 0 || !arguments.contains(value)) { 459 newArgumentRegisters += value.requiredRegisters(); 460 } 461 } 462 } 463 } 464 if (newArgumentRegisters > MAX_IN_SIZE) { 465 return false; 466 } 467 468 // Only include constructors if the previous instruction is the corresponding new-instance. 469 if (constructor) { 470 if (start == index) { 471 return false; 472 } 473 assert index > 0; 474 int offset = 0; 475 Instruction previous; 476 do { 477 offset++; 478 previous = block.getInstructions().get(index - offset); 479 } while (previous.isConstInstruction()); 480 if (!previous.isNewInstance() || previous.outValue() != returnValue) { 481 return false; 482 } 483 // Clear pending new instance flag as the last thing, now the matching constructor is known 484 // to be included. 485 pendingNewInstanceIndex = -1; 486 } 487 return true; 488 } 489 490 // Add the current instruction to the outline. includeInstruction(Instruction instruction)491 private void includeInstruction(Instruction instruction) { 492 List<Value> inValues = orderedInValues(instruction, returnValue); 493 494 Value prevReturnValue = returnValue; 495 if (returnValue != null) { 496 for (Value value : inValues) { 497 if (value == returnValue) { 498 assert returnValueUsersLeft > 0; 499 returnValueUsersLeft--; 500 } 501 if (returnValueUsersLeft == 0) { 502 returnValue = null; 503 returnType = dexItemFactory.voidType; 504 } 505 } 506 } 507 508 if (instruction.isNewInstance()) { 509 assert returnValue == null; 510 updateReturnValueState(instruction.outValue(), instruction.asNewInstance().clazz); 511 return; 512 } 513 514 assert instruction.isInvoke() 515 || instruction.isConstInstruction() 516 || instruction.isArithmeticBinop(); 517 if (inValues.size() > 0) { 518 for (int i = 0; i < inValues.size(); i++) { 519 Value value = inValues.get(i); 520 if (value == prevReturnValue) { 521 argumentsMap.add(-1); 522 continue; 523 } 524 if (instruction.isInvoke() 525 && instruction.asInvoke().getType() != Type.STATIC 526 && instruction.asInvoke().getType() != Type.CUSTOM) { 527 InvokeMethod invoke = instruction.asInvokeMethod(); 528 int argumentIndex = arguments.indexOf(value); 529 // For virtual calls only re-use the receiver argument. 530 if (i == 0 && argumentIndex != -1) { 531 argumentsMap.add(argumentIndex); 532 } else { 533 arguments.add(value); 534 argumentRegisters += value.requiredRegisters(); 535 if (i == 0) { 536 argumentTypes.add(invoke.getInvokedMethod().getHolder()); 537 } else { 538 DexProto methodProto; 539 if (instruction.asInvoke().getType() == Type.POLYMORPHIC) { 540 // Type of argument of a polymorphic call must be take from the call site 541 methodProto = instruction.asInvokePolymorphic().getProto(); 542 } else { 543 methodProto = invoke.getInvokedMethod().proto; 544 } 545 // -1 due to receiver 546 argumentTypes.add(methodProto.parameters.values[i - 1]); 547 } 548 argumentsMap.add(arguments.size() - 1); 549 } 550 } else { 551 arguments.add(value); 552 if (instruction.isInvokeMethod()) { 553 argumentTypes 554 .add(instruction.asInvokeMethod().getInvokedMethod().proto.parameters.values[i]); 555 } else { 556 argumentTypes.add(instruction.asBinop().getNumericType().dexTypeFor(dexItemFactory)); 557 } 558 argumentsMap.add(arguments.size() - 1); 559 } 560 } 561 } 562 if (!instruction.isConstInstruction() && instruction.outValue() != null) { 563 assert returnValue == null; 564 if (instruction.isInvokeMethod()) { 565 updateReturnValueState( 566 instruction.outValue(), 567 instruction.asInvokeMethod().getInvokedMethod().proto.returnType); 568 } else { 569 updateReturnValueState( 570 instruction.outValue(), 571 instruction.asBinop().getNumericType().dexTypeFor(dexItemFactory)); 572 } 573 } 574 } 575 updateReturnValueState(Value newReturnValue, DexType newReturnType)576 private void updateReturnValueState(Value newReturnValue, DexType newReturnType) { 577 returnValueUsersLeft = newReturnValue.numberOfAllUsers(); 578 // If the return value is not used don't track it. 579 if (returnValueUsersLeft == 0) { 580 returnValue = null; 581 returnType = dexItemFactory.voidType; 582 } else { 583 returnValue = newReturnValue; 584 returnType = newReturnType; 585 } 586 } 587 588 handle(int start, int end, Outline outline)589 protected abstract void handle(int start, int end, Outline outline); 590 candidate(int start, int index)591 private void candidate(int start, int index) { 592 assert !instructions.get(start).isConstInstruction(); 593 594 if (pendingNewInstanceIndex != -1) { 595 if (pendingNewInstanceIndex == start) { 596 reset(index); 597 } else { 598 reset(pendingNewInstanceIndex); 599 } 600 return; 601 } 602 603 // Back out of any const instructions ending this candidate. 604 int end = index; 605 while (instructions.get(end - 1).isConstInstruction()) { 606 end--; 607 } 608 609 // Check if the candidate qualifies. 610 int nonConstInstructions = 0; 611 for (int i = start; i < end; i++) { 612 if (!instructions.get(i).isConstInstruction()) { 613 nonConstInstructions++; 614 } 615 } 616 if (nonConstInstructions < options.outline.minSize) { 617 reset(start + 1); 618 return; 619 } 620 621 Outline outline = new Outline( 622 block, arguments, argumentTypes, argumentsMap, returnType, start, end); 623 handle(start, end, outline); 624 625 // Start a new candidate search from the next instruction after this outline. 626 reset(index); 627 } 628 629 // Restart the collection of outline candidate to the given instruction start index. reset(int startIndex)630 private void reset(int startIndex) { 631 start = startIndex; 632 index = startIndex; 633 actualInstructions = 0; 634 arguments = new ArrayList<>(MAX_IN_SIZE); 635 argumentTypes = new ArrayList<>(MAX_IN_SIZE); 636 argumentsMap = new ArrayList<>(MAX_IN_SIZE); 637 argumentRegisters = 0; 638 returnType = dexItemFactory.voidType; 639 returnValue = null; 640 returnValueUsersLeft = 0; 641 pendingNewInstanceIndex = -1; 642 } 643 } 644 645 // Collect outlining candidates with the methods that can use them. 646 // TODO(sgjesse): This does not take several usages in the same method into account. 647 private class OutlineIdentifier extends OutlineSpotter { 648 OutlineIdentifier(DexEncodedMethod method, BasicBlock block)649 OutlineIdentifier(DexEncodedMethod method, BasicBlock block) { 650 super(method, block); 651 } 652 handle(int start, int end, Outline outline)653 protected void handle(int start, int end, Outline outline) { 654 synchronized (candidates) { 655 candidates.computeIfAbsent(outline, k -> new ArrayList<>()).add(method); 656 } 657 } 658 } 659 660 // Replace instructions with a call to the outlined method. 661 private class OutlineRewriter extends OutlineSpotter { 662 663 private final IRCode code; 664 private final ListIterator<BasicBlock> blocksIterator; 665 private final List<Integer> toRemove; 666 int argumentsMapIndex; 667 Value returnValue; 668 OutlineRewriter( DexEncodedMethod method, IRCode code, ListIterator<BasicBlock> blocksIterator, BasicBlock block, List<Integer> toRemove)669 OutlineRewriter( 670 DexEncodedMethod method, IRCode code, 671 ListIterator<BasicBlock> blocksIterator, BasicBlock block, List<Integer> toRemove) { 672 super(method, block); 673 this.code = code; 674 this.blocksIterator = blocksIterator; 675 this.toRemove = toRemove; 676 } 677 handle(int start, int end, Outline outline)678 protected void handle(int start, int end, Outline outline) { 679 if (candidates.containsKey(outline)) { 680 DexMethod m = generatedOutlines.get(outline); 681 assert m != null; 682 List<Instruction> instructions = block.getInstructions(); 683 List<Value> in = new ArrayList<>(); 684 returnValue = null; 685 argumentsMapIndex = 0; 686 for (int i = start; i < end; i++) { 687 Instruction current = instructions.get(i); 688 if (current.isConstInstruction()) { 689 // Leave any const instructions. 690 continue; 691 } 692 693 // Prepare to remove the instruction. 694 List<Value> inValues = orderedInValues(current, returnValue); 695 for (int j = 0; j < inValues.size(); j++) { 696 Value value = inValues.get(j); 697 value.removeUser(current); 698 int argumentIndex = outline.argumentMap.get(argumentsMapIndex++); 699 if (argumentIndex >= in.size()) { 700 assert argumentIndex == in.size(); 701 in.add(value); 702 } 703 } 704 if (current.outValue() != null) { 705 returnValue = current.outValue(); 706 } 707 // The invoke of the outline method will be placed at the last instruction index, 708 // so don't mark that for removal. 709 if (i < end - 1) { 710 toRemove.add(i); 711 } 712 } 713 assert m.proto.shorty.toString().length() - 1 == in.size(); 714 if (returnValue != null && returnValue.numberOfAllUsers() == 0) { 715 returnValue = null; 716 } 717 Invoke outlineInvoke = new InvokeStatic(m, returnValue, in); 718 outlineInvoke.setBlock(block); 719 instructions.get(end - 1).clearBlock(); 720 instructions.set(end - 1, outlineInvoke); 721 if (block.hasCatchHandlers()) { 722 // If the inserted invoke is inserted in a block with handlers, split the block after 723 // the inserted invoke. 724 // TODO(sgjesse): Integrate the use of an instruction iterator into the code above. 725 block.listIterator(end).split(code, blocksIterator); 726 } 727 } 728 } 729 } 730 Outliner(AppInfo appInfo, InternalOptions options)731 public Outliner(AppInfo appInfo, InternalOptions options) { 732 this.appInfo = appInfo; 733 this.dexItemFactory = appInfo.dexItemFactory; 734 this.options = options; 735 } 736 identifyCandidates(IRCode code, DexEncodedMethod method)737 public void identifyCandidates(IRCode code, DexEncodedMethod method) { 738 assert !(method.getCode() instanceof OutlineCode); 739 for (BasicBlock block : code.blocks) { 740 new OutlineIdentifier(method, block).process(); 741 } 742 } 743 selectMethodsForOutlining()744 public boolean selectMethodsForOutlining() { 745 assert methodsSelectedForOutlining.size() == 0; 746 List<Outline> toRemove = new ArrayList<>(); 747 for (Entry<Outline, List<DexEncodedMethod>> entry : candidates.entrySet()) { 748 if (entry.getValue().size() < options.outline.threshold) { 749 toRemove.add(entry.getKey()); 750 } else { 751 methodsSelectedForOutlining.addAll(entry.getValue()); 752 } 753 } 754 for (Outline outline : toRemove) { 755 candidates.remove(outline); 756 } 757 return methodsSelectedForOutlining.size() > 0; 758 } 759 getMethodsSelectedForOutlining()760 public Set<DexEncodedMethod> getMethodsSelectedForOutlining() { 761 return methodsSelectedForOutlining; 762 } 763 applyOutliningCandidate(IRCode code, DexEncodedMethod method)764 public void applyOutliningCandidate(IRCode code, DexEncodedMethod method) { 765 assert !(method.getCode() instanceof OutlineCode); 766 ListIterator<BasicBlock> blocksIterator = code.blocks.listIterator(); 767 while (blocksIterator.hasNext()) { 768 BasicBlock block = blocksIterator.next(); 769 List<Integer> toRemove = new ArrayList<>(); 770 new OutlineRewriter(method, code, blocksIterator, block, toRemove).process(); 771 block.removeInstructions(toRemove); 772 } 773 } 774 noProcessing(IRCode code, DexEncodedMethod method)775 static public void noProcessing(IRCode code, DexEncodedMethod method) { 776 // No operation. 777 } 778 779 private class OutlineSourceCode implements SourceCode { 780 781 final private Outline outline; 782 int argumentMapIndex = 0; 783 OutlineSourceCode(Outline outline)784 OutlineSourceCode(Outline outline) { 785 this.outline = outline; 786 } 787 788 @Override needsPrelude()789 public boolean needsPrelude() { 790 return outline.argumentCount() > 0; 791 } 792 793 @Override instructionCount()794 public int instructionCount() { 795 return outline.templateInstructions.size() + 1; 796 } 797 798 @Override instructionIndex(int instructionOffset)799 public int instructionIndex(int instructionOffset) { 800 return instructionOffset; 801 } 802 803 @Override instructionOffset(int instructionIndex)804 public int instructionOffset(int instructionIndex) { 805 return instructionIndex; 806 } 807 808 @Override getCurrentLocal(int register)809 public DebugLocalInfo getCurrentLocal(int register) { 810 return null; 811 } 812 813 @Override traceInstruction(int instructionIndex, IRBuilder builder)814 public int traceInstruction(int instructionIndex, IRBuilder builder) { 815 // There is just one block, and after the last instruction it is closed. 816 return instructionIndex == outline.templateInstructions.size() ? instructionIndex : -1; 817 } 818 819 @Override closedCurrentBlockWithFallthrough(int fallthroughInstructionIndex)820 public void closedCurrentBlockWithFallthrough(int fallthroughInstructionIndex) { 821 } 822 823 @Override closedCurrentBlock()824 public void closedCurrentBlock() { 825 } 826 827 @Override setUp()828 public void setUp() { 829 } 830 831 @Override clear()832 public void clear() { 833 } 834 835 @Override buildPrelude(IRBuilder builder)836 public void buildPrelude(IRBuilder builder) { 837 // Fill in the Argument instructions in the argument block. 838 for (int i = 0; i < outline.arguments.size(); i++) { 839 MoveType moveType = outline.arguments.get(i).outType(); 840 builder.addNonThisArgument(i, moveType); 841 } 842 } 843 844 @Override buildPostlude(IRBuilder builder)845 public void buildPostlude(IRBuilder builder) { 846 // Intentionally left empty. (Needed for Java-bytecode-frontend synchronization support.) 847 } 848 849 @Override buildInstruction(IRBuilder builder, int instructionIndex)850 public void buildInstruction(IRBuilder builder, int instructionIndex) { 851 if (instructionIndex == outline.templateInstructions.size()) { 852 if (outline.returnType == dexItemFactory.voidType) { 853 builder.addReturn(); 854 } else { 855 builder.addReturn(MoveType.fromDexType(outline.returnType), outline.argumentCount()); 856 } 857 return; 858 } 859 // Build IR from the template. 860 Instruction template = outline.templateInstructions.get(instructionIndex); 861 List<Value> inValues = new ArrayList<>(template.inValues().size()); 862 List<Value> templateInValues = template.inValues(); 863 // The template in-values are not re-ordered for commutative binary operations, as it does 864 // not matter. 865 for (int i = 0; i < templateInValues.size(); i++) { 866 Value value = templateInValues.get(i); 867 int register = outline.argumentMap.get(argumentMapIndex++); 868 if (register == -1) { 869 register = outline.argumentCount(); 870 } 871 inValues.add( 872 builder.readRegister(register, value.outType())); 873 } 874 Value outValue = null; 875 if (template.outValue() != null) { 876 Value value = template.outValue(); 877 outValue = builder 878 .writeRegister(outline.argumentCount(), value.outType(), ThrowingInfo.CAN_THROW); 879 } 880 881 Instruction newInstruction = null; 882 if (template.isInvoke()) { 883 Invoke templateInvoke = template.asInvoke(); 884 newInstruction = Invoke.createFromTemplate(templateInvoke, outValue, inValues); 885 } else if (template.isAdd()) { 886 Add templateInvoke = template.asAdd(); 887 newInstruction = new Add( 888 templateInvoke.getNumericType(), outValue, inValues.get(0), inValues.get(1)); 889 } else if (template.isMul()) { 890 Mul templateInvoke = template.asMul(); 891 newInstruction = new Mul( 892 templateInvoke.getNumericType(), outValue, inValues.get(0), inValues.get(1)); 893 } else if (template.isSub()) { 894 Sub templateInvoke = template.asSub(); 895 newInstruction = new Sub( 896 templateInvoke.getNumericType(), outValue, inValues.get(0), inValues.get(1)); 897 } else if (template.isDiv()) { 898 Div templateInvoke = template.asDiv(); 899 newInstruction = new Div( 900 templateInvoke.getNumericType(), outValue, inValues.get(0), inValues.get(1)); 901 } else if (template.isRem()) { 902 Rem templateInvoke = template.asRem(); 903 newInstruction = new Rem( 904 templateInvoke.getNumericType(), outValue, inValues.get(0), inValues.get(1)); 905 } else { 906 assert template.isNewInstance(); 907 NewInstance templateNewInstance = template.asNewInstance(); 908 newInstruction = new NewInstance(templateNewInstance.clazz, outValue); 909 } 910 builder.add(newInstruction); 911 } 912 913 @Override resolveAndBuildSwitch(int value, int fallthroughOffset, int payloadOffset, IRBuilder builder)914 public void resolveAndBuildSwitch(int value, int fallthroughOffset, int payloadOffset, 915 IRBuilder builder) { 916 throw new Unreachable("Unexpected call to resolveAndBuildSwitch"); 917 } 918 919 @Override resolveAndBuildNewArrayFilledData(int arrayRef, int payloadOffset, IRBuilder builder)920 public void resolveAndBuildNewArrayFilledData(int arrayRef, int payloadOffset, 921 IRBuilder builder) { 922 throw new Unreachable("Unexpected call to resolveAndBuildNewArrayFilledData"); 923 } 924 925 @Override getCurrentCatchHandlers()926 public CatchHandlers<Integer> getCurrentCatchHandlers() { 927 return null; 928 } 929 930 @Override verifyCurrentInstructionCanThrow()931 public boolean verifyCurrentInstructionCanThrow() { 932 // TODO(sgjesse): Check more here? 933 return true; 934 } 935 936 @Override verifyLocalInScope(DebugLocalInfo local)937 public boolean verifyLocalInScope(DebugLocalInfo local) { 938 return true; 939 } 940 941 @Override verifyRegister(int register)942 public boolean verifyRegister(int register) { 943 return true; 944 } 945 } 946 947 public class OutlineCode extends Code { 948 949 private Outline outline; 950 OutlineCode(Outline outline)951 OutlineCode(Outline outline) { 952 this.outline = outline; 953 } 954 955 @Override isOutlineCode()956 public boolean isOutlineCode() { 957 return true; 958 } 959 960 @Override asOutlineCode()961 public OutlineCode asOutlineCode() { 962 return this; 963 } 964 965 @Override buildIR(DexEncodedMethod encodedMethod, InternalOptions options)966 public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options) { 967 OutlineSourceCode source = new OutlineSourceCode(outline); 968 IRBuilder builder = new IRBuilder(encodedMethod, source, options); 969 return builder.build(); 970 } 971 972 @Override toString()973 public String toString() { 974 return outline.toString(); 975 } 976 977 @Override registerReachableDefinitions(UseRegistry registry)978 public void registerReachableDefinitions(UseRegistry registry) { 979 throw new Unreachable(); 980 } 981 982 @Override computeHashCode()983 protected int computeHashCode() { 984 return outline.hashCode(); 985 } 986 987 @Override computeEquals(Object other)988 protected boolean computeEquals(Object other) { 989 return outline.equals(other); 990 } 991 992 @Override toString(DexEncodedMethod method, ClassNameMapper naming)993 public String toString(DexEncodedMethod method, ClassNameMapper naming) { 994 return null; 995 } 996 } 997 998 buildOutlinerClass(DexType type)999 public DexProgramClass buildOutlinerClass(DexType type) { 1000 if (candidates.size() == 0) { 1001 return null; 1002 } 1003 1004 // Build the outlined methods. 1005 DexEncodedMethod[] direct = new DexEncodedMethod[candidates.size()]; 1006 int count = 0; 1007 1008 // By now the candidates are the actual selected outlines. Name the generated methods in a 1009 // consistent order, to provide deterministic output. 1010 List<Outline> outlines = new ArrayList<>(candidates.keySet()); 1011 outlines.sort(Comparator.naturalOrder()); 1012 for (Outline outline : outlines) { 1013 DexAccessFlags methodAccess = new DexAccessFlags(Constants.ACC_PUBLIC, Constants.ACC_STATIC); 1014 DexString methodName = dexItemFactory.createString(options.outline.methodPrefix + count); 1015 DexMethod method = outline.buildMethod(type, methodName); 1016 direct[count] = new DexEncodedMethod(method, methodAccess, DexAnnotationSet.empty(), 1017 DexAnnotationSetRefList.empty(), new OutlineCode(outline)); 1018 generatedOutlines.put(outline, method); 1019 count++; 1020 } 1021 // No need to sort the direct methods as they are generated in sorted order. 1022 1023 // Build the outliner class. 1024 DexType superType = dexItemFactory.createType("Ljava/lang/Object;"); 1025 DexTypeList interfaces = DexTypeList.empty(); 1026 DexString sourceFile = dexItemFactory.createString("outline"); 1027 DexAccessFlags accessFlags = new DexAccessFlags(Constants.ACC_PUBLIC); 1028 DexProgramClass clazz = new DexProgramClass( 1029 type, 1030 null, 1031 accessFlags, 1032 superType, 1033 interfaces, 1034 sourceFile, 1035 // TODO: Build dex annotations structure. 1036 DexAnnotationSet.empty(), 1037 DexEncodedField.EMPTY_ARRAY, // Static fields. 1038 DexEncodedField.EMPTY_ARRAY, // Instance fields. 1039 direct, 1040 DexEncodedMethod.EMPTY_ARRAY // Virtual methods. 1041 ); 1042 1043 return clazz; 1044 } 1045 } 1046