• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2017, 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 package com.android.tools.r8.ir.optimize;
5 
6 import com.android.tools.r8.graph.AppInfoWithSubtyping;
7 import com.android.tools.r8.graph.DexCode;
8 import com.android.tools.r8.graph.DexEncodedMethod;
9 import com.android.tools.r8.graph.DexType;
10 import com.android.tools.r8.graph.GraphLense;
11 import com.android.tools.r8.ir.code.BasicBlock;
12 import com.android.tools.r8.ir.code.DominatorTree;
13 import com.android.tools.r8.ir.code.IRCode;
14 import com.android.tools.r8.ir.code.Instruction;
15 import com.android.tools.r8.ir.code.InstructionIterator;
16 import com.android.tools.r8.ir.code.InstructionListIterator;
17 import com.android.tools.r8.ir.code.Invoke;
18 import com.android.tools.r8.ir.code.InvokeMethod;
19 import com.android.tools.r8.ir.code.Value;
20 import com.android.tools.r8.ir.code.ValueNumberGenerator;
21 import com.android.tools.r8.ir.conversion.CallGraph;
22 import com.android.tools.r8.ir.conversion.IRConverter;
23 import com.android.tools.r8.ir.conversion.LensCodeRewriter;
24 import com.android.tools.r8.ir.conversion.OptimizationFeedback;
25 import com.android.tools.r8.logging.Log;
26 import com.android.tools.r8.utils.InternalOptions;
27 import com.google.common.collect.Sets;
28 import java.util.ArrayList;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.ListIterator;
32 import java.util.Map;
33 import java.util.Set;
34 
35 public class Inliner {
36 
37   private static final int INLINING_INSTRUCTION_LIMIT = 5;
38 
39   protected final AppInfoWithSubtyping appInfo;
40   private final GraphLense graphLense;
41   private final InternalOptions options;
42 
43   // State for inlining methods which are known to be called twice.
44   private boolean applyDoubleInlining = false;
45   private final Set<DexEncodedMethod> doubleInlineCallers = Sets.newIdentityHashSet();
46   private final Set<DexEncodedMethod> doubleInlineSelectedTargets = Sets.newIdentityHashSet();
47   private final Map<DexEncodedMethod, DexEncodedMethod> doubleInlineeCandidates = new HashMap<>();
48 
Inliner(AppInfoWithSubtyping appInfo, GraphLense graphLense, InternalOptions options)49   public Inliner(AppInfoWithSubtyping appInfo, GraphLense graphLense, InternalOptions options) {
50     this.appInfo = appInfo;
51     this.graphLense = graphLense;
52     this.options = options;
53   }
54 
instructionAllowedForInlining( DexEncodedMethod method, Instruction instruction)55   private Constraint instructionAllowedForInlining(
56       DexEncodedMethod method, Instruction instruction) {
57     Constraint result = instruction.inliningConstraint(appInfo, method.method.holder);
58     if ((result == Constraint.NEVER) && instruction.isDebugInstruction()) {
59       return Constraint.ALWAYS;
60     }
61     return result;
62   }
63 
identifySimpleMethods(IRCode code, DexEncodedMethod method)64   public Constraint identifySimpleMethods(IRCode code, DexEncodedMethod method) {
65     DexCode dex = method.getCode().asDexCode();
66     // We have generated code for a method and we want to figure out whether the method is a
67     // candidate for inlining. The code is the final IR after optimizations.
68     if (dex.instructions.length > INLINING_INSTRUCTION_LIMIT) {
69       return Constraint.NEVER;
70     }
71     Constraint result = Constraint.ALWAYS;
72     ListIterator<BasicBlock> iterator = code.listIterator();
73     assert iterator.hasNext();
74     BasicBlock block = iterator.next();
75     BasicBlock nextBlock;
76     do {
77       nextBlock = iterator.hasNext() ? iterator.next() : null;
78       InstructionListIterator it = block.listIterator();
79       while (it.hasNext()) {
80         Instruction instruction = it.next();
81         Constraint state = instructionAllowedForInlining(method, instruction);
82         if (state == Constraint.NEVER) {
83           return Constraint.NEVER;
84         }
85         if (state.ordinal() < result.ordinal()) {
86           result = state;
87         }
88       }
89       block = nextBlock;
90     } while (block != null);
91     return result;
92   }
93 
hasInliningAccess(DexEncodedMethod method, DexEncodedMethod target)94   boolean hasInliningAccess(DexEncodedMethod method, DexEncodedMethod target) {
95     if (target.accessFlags.isPublic()) {
96       return true;
97     }
98     DexType methodHolder = method.method.getHolder();
99     DexType targetHolder = target.method.getHolder();
100     if (target.accessFlags.isPrivate()) {
101       return methodHolder == targetHolder;
102     }
103     if (target.accessFlags.isProtected() &&
104         methodHolder.isSubtypeOf(targetHolder, appInfo)) {
105       return true;
106     }
107     return methodHolder.isSamePackage(targetHolder);
108   }
109 
doubleInlining(DexEncodedMethod method, DexEncodedMethod target)110   synchronized DexEncodedMethod doubleInlining(DexEncodedMethod method,
111       DexEncodedMethod target) {
112     if (!applyDoubleInlining) {
113       if (doubleInlineeCandidates.containsKey(target)) {
114         // Both calls can be inlined.
115         doubleInlineCallers.add(doubleInlineeCandidates.get(target));
116         doubleInlineCallers.add(method);
117         doubleInlineSelectedTargets.add(target);
118       } else {
119         // First call can be inlined.
120         doubleInlineeCandidates.put(target, method);
121       }
122       // Just preparing for double inlining.
123       return null;
124     } else {
125       // Don't perform the actual inlining if this was not selected.
126       if (!doubleInlineSelectedTargets.contains(target)) {
127         return null;
128       }
129     }
130     return target;
131   }
132 
processDoubleInlineCallers(IRConverter converter, OptimizationFeedback feedback)133   public synchronized void processDoubleInlineCallers(IRConverter converter,
134       OptimizationFeedback feedback) {
135     if (doubleInlineCallers.size() > 0) {
136       applyDoubleInlining = true;
137       for (DexEncodedMethod method : doubleInlineCallers) {
138         converter.processMethod(method, feedback, Outliner::noProcessing);
139         assert method.isProcessed();
140       }
141     }
142   }
143 
144   public enum Constraint {
145     // The ordinal values are important so please do not reorder.
146     NEVER,    // Never inline this.
147     PRIVATE,  // Only inline this into methods with same holder.
148     PACKAGE,  // Only inline this into methods with holders from same package.
149     ALWAYS,   // No restrictions for inlining this.
150   }
151 
152   public enum Reason {
153     FORCE,         // Inlinee is marked for forced inlining (bridge method or renamed constructor).
154     SINGLE_CALLER, // Inlinee has precisely one caller.
155     DUAL_CALLER,   // Inlinee has precisely two callers.
156     SIMPLE,        // Inlinee has simple code suitable for inlining.
157   }
158 
159   static public class InlineAction {
160 
161     public final DexEncodedMethod target;
162     public final Invoke invoke;
163     public final Reason reason;
164 
InlineAction(DexEncodedMethod target, Invoke invoke, Reason reason)165     public InlineAction(DexEncodedMethod target, Invoke invoke, Reason reason) {
166       this.target = target;
167       this.invoke = invoke;
168       this.reason = reason;
169     }
170 
forceInline()171     public boolean forceInline() {
172       return reason != Reason.SIMPLE;
173     }
174 
buildIR(ValueNumberGenerator generator, AppInfoWithSubtyping appInfo, GraphLense graphLense, InternalOptions options)175     public IRCode buildIR(ValueNumberGenerator generator, AppInfoWithSubtyping appInfo,
176         GraphLense graphLense, InternalOptions options) {
177       if (target.isProcessed()) {
178         assert target.getCode().isDexCode();
179         return target.buildIR(generator, options);
180       } else {
181         // Build the IR for a yet not processed method, and perform minimal IR processing.
182         IRCode code;
183         if (target.getCode().isJarCode()) {
184           code = target.getCode().asJarCode().buildIR(target, generator, options);
185         } else {
186           code = target.getCode().asDexCode().buildIR(target, generator, options);
187         }
188         new LensCodeRewriter(graphLense, appInfo).rewrite(code, target);
189         return code;
190       }
191     }
192   }
193 
numberOfInstructions(IRCode code)194   private int numberOfInstructions(IRCode code) {
195     int numOfInstructions = 0;
196     for (BasicBlock block : code.blocks) {
197       numOfInstructions += block.getInstructions().size();
198     }
199     return numOfInstructions;
200   }
201 
legalConstructorInline(DexEncodedMethod method, IRCode code)202   private boolean legalConstructorInline(DexEncodedMethod method, IRCode code) {
203     // In the Java VM Specification section "4.10.2.4. Instance Initialization Methods and
204     // Newly Created Objects" it says:
205     //
206     // Before that method invokes another instance initialization method of myClass or its direct
207     // superclass on this, the only operation the method can perform on this is assigning fields
208     // declared within myClass.
209     //
210 
211     // Allow inlining a constructor into a constructor, as the constructor code is expected to
212     // adhere to the VM specification.
213     if (method.accessFlags.isConstructor()) {
214       return true;
215     }
216 
217     // Don't allow inlining a constructor into a non-constructor if the first use of the
218     // un-initialized object is not an argument of an invoke of <init>.
219     InstructionIterator iterator = code.instructionIterator();
220     Instruction instruction = iterator.next();
221     // A constructor always has the un-initialized object as the first argument.
222     assert instruction.isArgument();
223     Value unInitializedObject = instruction.outValue();
224     while (iterator.hasNext()) {
225       instruction = iterator.next();
226       if (instruction.inValues().contains(unInitializedObject)) {
227         return instruction.isInvokeDirect()
228             && appInfo.dexItemFactory
229             .isConstructor(instruction.asInvokeDirect().getInvokedMethod());
230       }
231     }
232     assert false : "Execution should never reach this point";
233     return false;
234   }
235 
236   /// Computer the receiver value for the holder method.
receiverValue(DexEncodedMethod method, IRCode code)237   private Value receiverValue(DexEncodedMethod method, IRCode code) {
238     // Ignore static methods.
239     if (method.accessFlags.isStatic()) {
240       return null;
241     }
242     // Find the outValue of the first argument instruction in the first block.
243     return code.collectArguments().get(0);
244   }
245 
performInlining(DexEncodedMethod method, IRCode code, CallGraph callGraph)246   public void performInlining(DexEncodedMethod method, IRCode code, CallGraph callGraph) {
247     int instruction_allowance = 1500;
248     instruction_allowance -= numberOfInstructions(code);
249     if (instruction_allowance < 0) {
250       return;
251     }
252     computeReceiverMustBeNonNull(code);
253     Value receiver = receiverValue(method, code);
254     InliningOracle oracle = new InliningOracle(this, method, receiver, callGraph);
255 
256     List<BasicBlock> blocksToRemove = new ArrayList<>();
257     ListIterator<BasicBlock> blockIterator = code.listIterator();
258     while (blockIterator.hasNext() && (instruction_allowance >= 0)) {
259       BasicBlock block = blockIterator.next();
260       if (blocksToRemove.contains(block)) {
261         continue;
262       }
263       InstructionListIterator iterator = block.listIterator();
264       while (iterator.hasNext() && (instruction_allowance >= 0)) {
265         Instruction current = iterator.next();
266         if (current.isInvokeMethod()) {
267           InvokeMethod invoke = current.asInvokeMethod();
268           InlineAction result = invoke.computeInlining(oracle);
269           if (result != null) {
270             DexEncodedMethod target = appInfo.lookup(invoke.getType(), invoke.getInvokedMethod());
271             if (target == null) {
272               // The declared target cannot be found so skip inlining.
273               continue;
274             }
275             boolean forceInline = result.reason == Reason.FORCE;
276             if (!target.isProcessed() && !forceInline) {
277               // Do not inline code that was not processed unless we have to force inline.
278               continue;
279             }
280             IRCode inlinee = result
281                 .buildIR(code.valueNumberGenerator, appInfo, graphLense, options);
282             if (inlinee != null) {
283               // TODO(sgjesse): Get rid of this additional check by improved inlining.
284               if (block.hasCatchHandlers() && inlinee.getNormalExitBlock() == null) {
285                 continue;
286               }
287               if (callGraph.isBreaker(method, target)) {
288                 // Make sure we don't inline a call graph breaker.
289                 continue;
290               }
291               // If this code did not go through the full pipeline, apply inlining to make sure
292               // that force inline targets get processed.
293               if (!target.isProcessed()) {
294                 assert forceInline;
295                 if (Log.ENABLED) {
296                   Log.verbose(getClass(), "Forcing extra inline on " + target.toSourceString());
297                 }
298                 performInlining(target, inlinee, callGraph);
299               }
300               // Make sure constructor inlining is legal.
301               if (target.accessFlags.isConstructor() && !legalConstructorInline(method, inlinee)) {
302                 continue;
303               }
304               // Ensure the container is compatible with the target.
305              if (!forceInline
306                  && !result.target.isPublicInlining()
307                  && (method.method.getHolder() != result.target.method.getHolder())) {
308                 continue;
309               }
310               DexType downcast = null;
311               if (invoke.isInvokeMethodWithReceiver()) {
312                 // If the invoke has a receiver but the declared method holder is different
313                 // from the computed target holder, inlining requires a downcast of the receiver.
314                 if (result.target.method.getHolder() != target.method.getHolder()) {
315                   downcast = result.target.method.getHolder();
316                 }
317               }
318               // Inline the inlinee code in place of the invoke instruction
319               // Back up before the invoke instruction.
320               iterator.previous();
321               instruction_allowance -= numberOfInstructions(inlinee);
322               if (instruction_allowance >= 0 || result.forceInline()) {
323                 iterator.inlineInvoke(code, inlinee, blockIterator, blocksToRemove, downcast);
324               }
325               // If we inlined the invoke from a bridge method, it is no longer a bridge method.
326               if (method.accessFlags.isBridge()) {
327                 method.accessFlags.unsetSynthetic();
328                 method.accessFlags.unsetBridge();
329               }
330             }
331           }
332         }
333       }
334     }
335     oracle.finish();
336     code.removeBlocks(blocksToRemove);
337     assert code.isConsistentSSA();
338   }
339 
340   // Determine whether the receiver of an invocation is guaranteed to be non-null based on
341   // the dominator tree. If a method call is dominated by another method call with the same
342   // receiver, the receiver most be non-null when we reach the dominated call.
343   //
344   // We bail out for exception handling. If an invoke is covered by a try block we cannot use
345   // dominance to determine that the receiver is non-null at a dominated call:
346   //
347   // Object o;
348   // try {
349   //   o.m();
350   // } catch (NullPointerException e) {
351   //   o.f();  // Dominated by other call with receiver o, but o is null.
352   // }
353   //
computeReceiverMustBeNonNull(IRCode code)354   private void computeReceiverMustBeNonNull(IRCode code) {
355     DominatorTree dominatorTree = new DominatorTree(code);
356     InstructionIterator it = code.instructionIterator();
357     while (it.hasNext()) {
358       Instruction instruction = it.next();
359       if (instruction.isInvokeMethodWithReceiver()) {
360         Value receiverValue = instruction.inValues().get(0);
361         for (Instruction user : receiverValue.uniqueUsers()) {
362           if (user.isInvokeMethodWithReceiver() &&
363               user.inValues().get(0) == receiverValue &&
364               !user.getBlock().hasCatchHandlers() &&
365               dominatorTree.strictlyDominatedBy(instruction.getBlock(), user.getBlock())) {
366             instruction.asInvokeMethodWithReceiver().setIsDominatedByCallWithSameReceiver();
367             break;
368           }
369         }
370       }
371     }
372   }
373 }
374