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.DexClass; 7 import com.android.tools.r8.graph.DexEncodedMethod; 8 import com.android.tools.r8.graph.DexType; 9 import com.android.tools.r8.ir.code.InvokeDirect; 10 import com.android.tools.r8.ir.code.InvokeInterface; 11 import com.android.tools.r8.ir.code.InvokeMethod; 12 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver; 13 import com.android.tools.r8.ir.code.InvokePolymorphic; 14 import com.android.tools.r8.ir.code.InvokeStatic; 15 import com.android.tools.r8.ir.code.InvokeSuper; 16 import com.android.tools.r8.ir.code.InvokeVirtual; 17 import com.android.tools.r8.ir.code.Value; 18 import com.android.tools.r8.ir.conversion.CallGraph; 19 import com.android.tools.r8.ir.optimize.Inliner.InlineAction; 20 import com.android.tools.r8.ir.optimize.Inliner.Reason; 21 import com.android.tools.r8.logging.Log; 22 23 /** 24 * The InliningOracle contains information needed for when inlining 25 * other methods into @method. 26 */ 27 public class InliningOracle { 28 29 final Inliner inliner; 30 final DexEncodedMethod method; 31 final Value receiver; 32 final CallGraph callGraph; 33 final private InliningInfo info; 34 InliningOracle( Inliner inliner, DexEncodedMethod method, Value receiver, CallGraph callGraph)35 public InliningOracle( 36 Inliner inliner, 37 DexEncodedMethod method, 38 Value receiver, 39 CallGraph callGraph) { 40 this.inliner = inliner; 41 this.method = method; 42 this.receiver = receiver; 43 this.callGraph = callGraph; 44 info = Log.ENABLED ? new InliningInfo(method) : null; 45 } 46 finish()47 public void finish() { 48 if (Log.ENABLED) { 49 System.out.println(info.toString()); 50 } 51 } 52 validateCandidate(InvokeMethod invoke)53 DexEncodedMethod validateCandidate(InvokeMethod invoke) { 54 DexEncodedMethod candidate = invoke.computeSingleTarget(inliner.appInfo); 55 if ((candidate == null) 56 || (candidate.getCode() == null) 57 || inliner.appInfo.definitionFor(candidate.method.getHolder()).isLibraryClass()) { 58 if (info != null) { 59 info.exclude(invoke, "No inlinee"); 60 } 61 return null; 62 } 63 if (method == candidate) { 64 // Cannot handle recursive inlining at this point. 65 // Bridge methods should never have recursive calls. 66 assert !candidate.getOptimizationInfo().forceInline(); 67 return null; 68 } 69 70 if (candidate.accessFlags.isSynchronized()) { 71 // Don't inline if target is synchronized. 72 if (info != null) { 73 info.exclude(invoke, "Inlinee candidate is synchronized"); 74 } 75 return null; 76 } 77 78 if (callGraph.isBreaker(method, candidate)) { 79 // Cycle breaker so abort to preserve compilation order. 80 return null; 81 } 82 83 if (!inliner.hasInliningAccess(method, candidate)) { 84 if (info != null) { 85 info.exclude(invoke, "Inlinee candidate does not have right access flags"); 86 } 87 return null; 88 } 89 return candidate; 90 } 91 computeInliningReason(DexEncodedMethod target)92 private Reason computeInliningReason(DexEncodedMethod target) { 93 if (target.getOptimizationInfo().forceInline()) { 94 return Reason.FORCE; 95 } 96 if (callGraph.hasSingleCallSite(target)) { 97 return Reason.SINGLE_CALLER; 98 } 99 if (isDoubleInliningTarget(target)) { 100 return Reason.DUAL_CALLER; 101 } 102 return Reason.SIMPLE; 103 } 104 computeForInvokeWithReceiver(InvokeMethodWithReceiver invoke)105 public InlineAction computeForInvokeWithReceiver(InvokeMethodWithReceiver invoke) { 106 boolean receiverIsNeverNull = invoke.receiverIsNeverNull(); 107 if (!receiverIsNeverNull) { 108 if (info != null) { 109 info.exclude(invoke, "receiver for candidate can be null"); 110 } 111 return null; 112 } 113 DexEncodedMethod target = invoke.computeSingleTarget(inliner.appInfo); 114 if (target == null) { 115 // Abort inlining attempt if we cannot find single target. 116 if (info != null) { 117 info.exclude(invoke, "could not find single target"); 118 } 119 return null; 120 } 121 122 if (target == method) { 123 // Bridge methods should never have recursive calls. 124 assert !target.getOptimizationInfo().forceInline(); 125 return null; 126 } 127 128 if (target.getCode() == null) { 129 return null; 130 } 131 132 DexClass holder = inliner.appInfo.definitionFor(target.method.getHolder()); 133 if (holder.isInterface()) { 134 // Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception at runtime. 135 if (info != null) { 136 info.exclude(invoke, "Do not inline target if method holder is an interface class"); 137 } 138 return null; 139 } 140 141 if (holder.isLibraryClass()) { 142 // Library functions should not be inlined. 143 return null; 144 } 145 146 // Don't inline if target is synchronized. 147 if (target.accessFlags.isSynchronized()) { 148 if (info != null) { 149 info.exclude(invoke, "target is synchronized"); 150 } 151 return null; 152 } 153 154 Reason reason = computeInliningReason(target); 155 // Determine if this should be inlined no matter how big it is. 156 if (!target.isInliningCandidate(method, reason != Reason.SIMPLE)) { 157 // Abort inlining attempt if the single target is not an inlining candidate. 158 if (info != null) { 159 info.exclude(invoke, "target is not identified for inlining"); 160 } 161 return null; 162 } 163 164 if (callGraph.isBreaker(method, target)) { 165 // Cycle breaker so abort to preserve compilation order. 166 return null; 167 } 168 169 // Abort inlining attempt if method -> target access is not right. 170 if (!inliner.hasInliningAccess(method, target)) { 171 if (info != null) { 172 info.exclude(invoke, "target does not have right access"); 173 } 174 return null; 175 } 176 177 // Attempt to inline a candidate that is only called twice. 178 if ((reason == Reason.DUAL_CALLER) && (inliner.doubleInlining(method, target) == null)) { 179 if (info != null) { 180 info.exclude(invoke, "target is not ready for double inlining"); 181 } 182 return null; 183 } 184 185 if (info != null) { 186 info.include(invoke.getType(), target); 187 } 188 return new InlineAction(target, invoke, reason); 189 } 190 computeForInvokeVirtual(InvokeVirtual invoke)191 public InlineAction computeForInvokeVirtual(InvokeVirtual invoke) { 192 return computeForInvokeWithReceiver(invoke); 193 } 194 computeForInvokeInterface(InvokeInterface invoke)195 public InlineAction computeForInvokeInterface(InvokeInterface invoke) { 196 return computeForInvokeWithReceiver(invoke); 197 } 198 computeForInvokeDirect(InvokeDirect invoke)199 public InlineAction computeForInvokeDirect(InvokeDirect invoke) { 200 return computeForInvokeWithReceiver(invoke); 201 } 202 canInlineStaticInvoke(DexEncodedMethod method, DexEncodedMethod target)203 private boolean canInlineStaticInvoke(DexEncodedMethod method, DexEncodedMethod target) { 204 // Only proceed with inlining a static invoke if: 205 // - the holder for the target equals the holder for the method, or 206 // - there is no class initializer. 207 DexType targetHolder = target.method.getHolder(); 208 if (method.method.getHolder() == targetHolder) { 209 return true; 210 } 211 DexClass clazz = inliner.appInfo.definitionFor(targetHolder); 212 return (clazz != null) && (!clazz.hasNonTrivialClassInitializer()); 213 } 214 isDoubleInliningTarget(DexEncodedMethod candidate)215 private synchronized boolean isDoubleInliningTarget(DexEncodedMethod candidate) { 216 // 10 is found from measuring. 217 return callGraph.hasDoubleCallSite(candidate) 218 && candidate.getCode().isDexCode() 219 && (candidate.getCode().asDexCode().instructions.length <= 10); 220 } 221 computeForInvokeStatic(InvokeStatic invoke)222 public InlineAction computeForInvokeStatic(InvokeStatic invoke) { 223 DexEncodedMethod candidate = validateCandidate(invoke); 224 if (candidate == null) { 225 return null; 226 } 227 Reason reason = computeInliningReason(candidate); 228 // Determine if this should be inlined no matter how big it is. 229 if (!candidate.isInliningCandidate(method, reason != Reason.SIMPLE)) { 230 // Abort inlining attempt if the single target is not an inlining candidate. 231 if (info != null) { 232 info.exclude(invoke, "target is not identified for inlining"); 233 } 234 return null; 235 } 236 237 // Abort inlining attempt if we can not guarantee class for static target has been initialized. 238 if (!canInlineStaticInvoke(method, candidate)) { 239 if (info != null) { 240 info.exclude(invoke, "target is static but we cannot guarantee class has been initialized"); 241 } 242 return null; 243 } 244 245 // Attempt to inline a candidate that is only called twice. 246 if ((reason == Reason.DUAL_CALLER) && (inliner.doubleInlining(method, candidate) == null)) { 247 if (info != null) { 248 info.exclude(invoke, "target is not ready for double inlining"); 249 } 250 return null; 251 } 252 253 if (info != null) { 254 info.include(invoke.getType(), candidate); 255 } 256 return new InlineAction(candidate, invoke, reason); 257 } 258 computeForInvokeSuper(InvokeSuper invoke)259 public InlineAction computeForInvokeSuper(InvokeSuper invoke) { 260 DexEncodedMethod candidate = validateCandidate(invoke); 261 if (candidate == null) { 262 if (info != null) { 263 info.exclude(invoke, "not a valid inlining target"); 264 } 265 return null; 266 } 267 if (info != null) { 268 info.include(invoke.getType(), candidate); 269 } 270 return new InlineAction(candidate, invoke, Reason.SIMPLE); 271 } 272 computeForInvokePolymorpic(InvokePolymorphic invoke)273 public InlineAction computeForInvokePolymorpic(InvokePolymorphic invoke) { 274 // TODO: No inlining of invoke polymorphic for now. 275 if (info != null) { 276 info.exclude(invoke, "inlining through invoke signature polymorpic is not supported"); 277 } 278 return null; 279 } 280 } 281