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