• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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