| // Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| package com.android.tools.r8.ir.optimize.classinliner; |
| |
| import com.android.tools.r8.errors.Unreachable; |
| import com.android.tools.r8.graph.DexClass; |
| import com.android.tools.r8.graph.DexEncodedMethod; |
| import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility; |
| import com.android.tools.r8.graph.DexEncodedMethod.OptimizationInfo; |
| import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer; |
| import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.TrivialClassInitializer; |
| import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.TrivialInstanceInitializer; |
| import com.android.tools.r8.graph.DexField; |
| import com.android.tools.r8.graph.DexItemFactory; |
| import com.android.tools.r8.graph.DexMethod; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsage; |
| import com.android.tools.r8.ir.code.BasicBlock; |
| import com.android.tools.r8.ir.code.ConstNumber; |
| import com.android.tools.r8.ir.code.IRCode; |
| import com.android.tools.r8.ir.code.If; |
| import com.android.tools.r8.ir.code.InstanceGet; |
| import com.android.tools.r8.ir.code.Instruction; |
| import com.android.tools.r8.ir.code.Invoke.Type; |
| import com.android.tools.r8.ir.code.InvokeDirect; |
| import com.android.tools.r8.ir.code.InvokeMethod; |
| import com.android.tools.r8.ir.code.InvokeMethodWithReceiver; |
| import com.android.tools.r8.ir.code.Value; |
| import com.android.tools.r8.ir.optimize.Inliner.InlineAction; |
| import com.android.tools.r8.ir.optimize.Inliner.InliningInfo; |
| import com.android.tools.r8.ir.optimize.Inliner.Reason; |
| import com.android.tools.r8.ir.optimize.InliningOracle; |
| import com.android.tools.r8.ir.optimize.classinliner.ClassInliner.InlinerAction; |
| import com.android.tools.r8.kotlin.KotlinInfo; |
| import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness; |
| import com.android.tools.r8.utils.Pair; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Sets; |
| import java.util.ArrayList; |
| import java.util.IdentityHashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.function.Predicate; |
| import java.util.function.Supplier; |
| |
| final class InlineCandidateProcessor { |
| private static final ImmutableSet<If.Type> ALLOWED_ZERO_TEST_TYPES = |
| ImmutableSet.of(If.Type.EQ, If.Type.NE); |
| |
| private final DexItemFactory factory; |
| private final AppInfoWithLiveness appInfo; |
| private final Predicate<DexType> isClassEligible; |
| private final Predicate<DexEncodedMethod> isProcessedConcurrently; |
| private final DexEncodedMethod method; |
| private final Instruction root; |
| |
| private Value eligibleInstance; |
| private DexType eligibleClass; |
| private DexClass eligibleClassDefinition; |
| |
| private final Map<InvokeMethod, InliningInfo> methodCallsOnInstance |
| = new IdentityHashMap<>(); |
| private final Map<InvokeMethod, InliningInfo> extraMethodCalls |
| = new IdentityHashMap<>(); |
| private final List<Pair<InvokeMethod, Integer>> unusedArguments |
| = new ArrayList<>(); |
| |
| private int estimatedCombinedSizeForInlining = 0; |
| |
| InlineCandidateProcessor( |
| DexItemFactory factory, AppInfoWithLiveness appInfo, |
| Predicate<DexType> isClassEligible, |
| Predicate<DexEncodedMethod> isProcessedConcurrently, |
| DexEncodedMethod method, Instruction root) { |
| this.factory = factory; |
| this.isClassEligible = isClassEligible; |
| this.method = method; |
| this.root = root; |
| this.appInfo = appInfo; |
| this.isProcessedConcurrently = isProcessedConcurrently; |
| } |
| |
| int getEstimatedCombinedSizeForInlining() { |
| return estimatedCombinedSizeForInlining; |
| } |
| |
| // Checks if the root instruction defines eligible value, i.e. the value |
| // exists and we have a definition of its class. |
| boolean isInstanceEligible() { |
| eligibleInstance = root.outValue(); |
| if (eligibleInstance == null) { |
| return false; |
| } |
| |
| eligibleClass = isNewInstance() ? |
| root.asNewInstance().clazz : root.asStaticGet().getField().type; |
| eligibleClassDefinition = appInfo.definitionFor(eligibleClass); |
| return eligibleClassDefinition != null; |
| } |
| |
| // Checks if the class is eligible and is properly used. Regarding general class |
| // eligibility rules see comment on computeClassEligible(...). |
| // |
| // In addition to class being eligible this method also checks: |
| // -- for 'new-instance' root: |
| // * class itself does not have static initializer |
| // -- for 'static-get' root: |
| // * class does not have instance fields |
| // * class is final |
| // * class has class initializer marked as TrivialClassInitializer, and |
| // class initializer initializes the field we are reading here. |
| boolean isClassAndUsageEligible() { |
| if (!isClassEligible.test(eligibleClass)) { |
| return false; |
| } |
| |
| if (isNewInstance()) { |
| // NOTE: if the eligible class does not directly extend java.lang.Object, |
| // we also have to guarantee that it is initialized with initializer classified as |
| // TrivialInstanceInitializer. This will be checked in areInstanceUsersEligible(...). |
| |
| // There must be no static initializer on the class itself. |
| return !eligibleClassDefinition.hasClassInitializer(); |
| } |
| |
| assert root.isStaticGet(); |
| |
| // Checking if we can safely inline class implemented following singleton-like |
| // pattern, by which we assume a static final field holding on to the reference |
| // initialized in class constructor. |
| // |
| // In general we are targeting cases when the class is defined as: |
| // |
| // class X { |
| // static final X F; |
| // static { |
| // F = new X(); |
| // } |
| // } |
| // |
| // and being used as follows: |
| // |
| // void foo() { |
| // f = X.F; |
| // f.bar(); |
| // } |
| // |
| // The main difference from the similar case of class inliner with 'new-instance' |
| // instruction is that in this case the instance we inline is not just leaked, but |
| // is actually published via X.F field. There are several risks we need to address |
| // in this case: |
| // |
| // Risk: instance stored in field X.F has changed after it was initialized in |
| // class initializer |
| // Solution: we assume that final field X.F is not modified outside the class |
| // initializer. In rare cases when it is (e.g. via reflections) it should |
| // be marked with keep rules |
| // |
| // Risk: instance stored in field X.F is not initialized yet |
| // Solution: not initialized instance can only be visible if X.<clinit> |
| // triggers other class initialization which references X.F. This |
| // situation should never happen if we: |
| // -- don't allow any superclasses to have static initializer, |
| // -- don't allow any subclasses, |
| // -- guarantee the class has trivial class initializer |
| // (see CodeRewriter::computeClassInitializerInfo), and |
| // -- guarantee the instance is initialized with trivial instance |
| // initializer (see CodeRewriter::computeInstanceInitializerInfo) |
| // |
| // Risk: instance stored in field X.F was mutated |
| // Solution: we require that class X does not have any instance fields, and |
| // if any of its superclasses has instance fields, accessing them will make |
| // this instance not eligible for inlining. I.e. even though the instance is |
| // publicized and its state has been mutated, it will not effect the logic |
| // of class inlining |
| // |
| |
| if (eligibleClassDefinition.instanceFields().length > 0 || |
| !eligibleClassDefinition.accessFlags.isFinal()) { |
| return false; |
| } |
| |
| // Singleton instance must be initialized in class constructor. |
| DexEncodedMethod classInitializer = eligibleClassDefinition.getClassInitializer(); |
| if (classInitializer == null || isProcessedConcurrently.test(classInitializer)) { |
| return false; |
| } |
| |
| TrivialInitializer info = |
| classInitializer.getOptimizationInfo().getTrivialInitializerInfo(); |
| assert info == null || info instanceof TrivialClassInitializer; |
| DexField instanceField = root.asStaticGet().getField(); |
| // Singleton instance field must NOT be pinned. |
| return info != null && |
| ((TrivialClassInitializer) info).field == instanceField && |
| !appInfo.isPinned(eligibleClassDefinition.lookupStaticField(instanceField).field); |
| } |
| |
| // Checks if the inlining candidate instance users are eligible, |
| // see comment on processMethodCode(...). |
| boolean areInstanceUsersEligible(Supplier<InliningOracle> defaultOracle) { |
| // No Phi users. |
| if (eligibleInstance.numberOfPhiUsers() > 0) { |
| return false; // Not eligible. |
| } |
| |
| for (Instruction user : eligibleInstance.uniqueUsers()) { |
| // Field read/write. |
| if (user.isInstanceGet() || |
| (user.isInstancePut() && user.asInstancePut().value() != eligibleInstance)) { |
| DexField field = user.asFieldInstruction().getField(); |
| if (field.clazz == eligibleClass && |
| eligibleClassDefinition.lookupInstanceField(field) != null) { |
| // Since class inliner currently only supports classes directly extending |
| // java.lang.Object, we don't need to worry about fields defined in superclasses. |
| continue; |
| } |
| return false; // Not eligible. |
| } |
| |
| // Eligible constructor call (for new instance roots only). |
| if (user.isInvokeDirect() && root.isNewInstance()) { |
| InliningInfo inliningInfo = isEligibleConstructorCall(user.asInvokeDirect()); |
| if (inliningInfo != null) { |
| methodCallsOnInstance.put(user.asInvokeDirect(), inliningInfo); |
| continue; |
| } |
| } |
| |
| // Eligible virtual method call on the instance as a receiver. |
| if (user.isInvokeVirtual() || user.isInvokeInterface()) { |
| InliningInfo inliningInfo = isEligibleDirectMethodCall(user.asInvokeMethodWithReceiver()); |
| if (inliningInfo != null) { |
| methodCallsOnInstance.put(user.asInvokeMethodWithReceiver(), inliningInfo); |
| continue; |
| } |
| } |
| |
| // Eligible usage as an invocation argument. |
| if (user.isInvokeMethod()) { |
| if (isExtraMethodCallEligible(defaultOracle, user.asInvokeMethod())) { |
| continue; |
| } |
| } |
| |
| // Allow some IF instructions. |
| if (user.isIf()) { |
| If ifInsn = user.asIf(); |
| If.Type type = ifInsn.getType(); |
| if (ifInsn.isZeroTest() && (type == If.Type.EQ || type == If.Type.NE)) { |
| // Allow ==/!= null tests, we know that the instance is a non-null value. |
| continue; |
| } |
| } |
| |
| return false; // Not eligible. |
| } |
| return true; |
| } |
| |
| // Process inlining, includes the following steps: |
| // |
| // * replace unused instance usages as arguments which are never used |
| // * inline extra methods if any, collect new direct method calls |
| // * inline direct methods if any |
| // * remove superclass initializer call and field reads |
| // * remove field writes |
| // * remove root instruction |
| // |
| void processInlining(IRCode code, InlinerAction inliner) { |
| replaceUsagesAsUnusedArgument(code); |
| forceInlineExtraMethodInvocations(inliner); |
| forceInlineDirectMethodInvocations(inliner); |
| removeMiscUsages(code); |
| removeFieldReads(code); |
| removeFieldWrites(); |
| removeInstruction(root); |
| } |
| |
| private void replaceUsagesAsUnusedArgument(IRCode code) { |
| for (Pair<InvokeMethod, Integer> unusedArgument : unusedArguments) { |
| InvokeMethod invoke = unusedArgument.getFirst(); |
| BasicBlock block = invoke.getBlock(); |
| |
| ConstNumber nullValue = code.createConstNull(); |
| nullValue.setPosition(invoke.getPosition()); |
| LinkedList<Instruction> instructions = block.getInstructions(); |
| instructions.add(instructions.indexOf(invoke), nullValue); |
| nullValue.setBlock(block); |
| |
| int argIndex = unusedArgument.getSecond() + (invoke.isInvokeMethodWithReceiver() ? 1 : 0); |
| invoke.replaceValue(argIndex, nullValue.outValue()); |
| } |
| unusedArguments.clear(); |
| } |
| |
| private void forceInlineExtraMethodInvocations(InlinerAction inliner) { |
| if (extraMethodCalls.isEmpty()) { |
| return; |
| } |
| |
| // Inline extra methods. |
| inliner.inline(extraMethodCalls); |
| |
| // Reset the collections. |
| methodCallsOnInstance.clear(); |
| extraMethodCalls.clear(); |
| unusedArguments.clear(); |
| estimatedCombinedSizeForInlining = 0; |
| |
| // Repeat user analysis |
| if (!areInstanceUsersEligible(() -> { |
| throw new Unreachable("Inlining oracle is expected to be needed"); |
| })) { |
| throw new Unreachable("Analysis must succeed after inlining of extra methods"); |
| } |
| assert extraMethodCalls.isEmpty(); |
| assert unusedArguments.isEmpty(); |
| } |
| |
| private void forceInlineDirectMethodInvocations(InlinerAction inliner) { |
| if (!methodCallsOnInstance.isEmpty()) { |
| inliner.inline(methodCallsOnInstance); |
| } |
| } |
| |
| // Remove miscellaneous users before handling field reads. |
| private void removeMiscUsages(IRCode code) { |
| boolean needToRemoveUnreachableBlocks = false; |
| for (Instruction user : eligibleInstance.uniqueUsers()) { |
| // Remove the call to superclass constructor. |
| if (root.isNewInstance() && |
| user.isInvokeDirect() && |
| factory.isConstructor(user.asInvokeDirect().getInvokedMethod()) && |
| user.asInvokeDirect().getInvokedMethod().holder == eligibleClassDefinition.superType) { |
| removeInstruction(user); |
| continue; |
| } |
| |
| if (user.isIf()) { |
| If ifInsn = user.asIf(); |
| assert ifInsn.isZeroTest() |
| : "Unexpected usage in non-zero-test IF instruction: " + user; |
| BasicBlock block = user.getBlock(); |
| If.Type type = ifInsn.getType(); |
| assert type == If.Type.EQ || type == If.Type.NE |
| : "Unexpected type in zero-test IF instruction: " + user; |
| BasicBlock newBlock = type == If.Type.EQ |
| ? ifInsn.fallthroughBlock() : ifInsn.getTrueTarget(); |
| BasicBlock blockToRemove = type == If.Type.EQ |
| ? ifInsn.getTrueTarget() : ifInsn.fallthroughBlock(); |
| assert newBlock != blockToRemove; |
| |
| block.replaceSuccessor(blockToRemove, newBlock); |
| blockToRemove.removePredecessor(block); |
| assert block.exit().isGoto(); |
| assert block.exit().asGoto().getTarget() == newBlock; |
| needToRemoveUnreachableBlocks = true; |
| continue; |
| } |
| |
| if (user.isInstanceGet() || user.isInstancePut()) { |
| // Leave field reads/writes until next steps. |
| continue; |
| } |
| |
| throw new Unreachable("Unexpected usage left after method inlining: " + user); |
| } |
| |
| if (needToRemoveUnreachableBlocks) { |
| code.removeUnreachableBlocks(); |
| } |
| } |
| |
| // Replace field reads with appropriate values, insert phis when needed. |
| private void removeFieldReads(IRCode code) { |
| Map<DexField, FieldValueHelper> fieldHelpers = new IdentityHashMap<>(); |
| for (Instruction user : eligibleInstance.uniqueUsers()) { |
| if (user.isInstanceGet()) { |
| // Replace a field read with appropriate value. |
| replaceFieldRead(code, user.asInstanceGet(), fieldHelpers); |
| continue; |
| } |
| |
| if (user.isInstancePut()) { |
| // Skip in this iteration since these instructions are needed to |
| // properly calculate what value should field reads be replaced with. |
| continue; |
| } |
| |
| throw new Unreachable("Unexpected usage left after method inlining: " + user); |
| } |
| } |
| |
| private void replaceFieldRead(IRCode code, |
| InstanceGet fieldRead, Map<DexField, FieldValueHelper> fieldHelpers) { |
| Value value = fieldRead.outValue(); |
| if (value != null) { |
| FieldValueHelper helper = fieldHelpers.computeIfAbsent( |
| fieldRead.getField(), field -> new FieldValueHelper(field, code, root)); |
| Value newValue = helper.getValueForFieldRead(fieldRead.getBlock(), fieldRead); |
| value.replaceUsers(newValue); |
| for (FieldValueHelper fieldValueHelper : fieldHelpers.values()) { |
| fieldValueHelper.replaceValue(value, newValue); |
| } |
| assert value.numberOfAllUsers() == 0; |
| } |
| removeInstruction(fieldRead); |
| } |
| |
| private void removeFieldWrites() { |
| for (Instruction user : eligibleInstance.uniqueUsers()) { |
| if (!user.isInstancePut()) { |
| throw new Unreachable("Unexpected usage left after field reads removed: " + user); |
| } |
| if (user.asInstancePut().getField().clazz != eligibleClass) { |
| throw new Unreachable("Unexpected field write left after field reads removed: " + user); |
| } |
| removeInstruction(user); |
| } |
| } |
| |
| private InliningInfo isEligibleConstructorCall(InvokeDirect initInvoke) { |
| // Must be a constructor of the exact same class. |
| DexMethod init = initInvoke.getInvokedMethod(); |
| if (!factory.isConstructor(init)) { |
| return null; |
| } |
| // Must be a constructor called on the receiver. |
| if (initInvoke.inValues().lastIndexOf(eligibleInstance) != 0) { |
| return null; |
| } |
| |
| assert init.holder == eligibleClass |
| : "Inlined constructor? [invoke: " + initInvoke + |
| ", expected class: " + eligibleClass + "]"; |
| |
| DexEncodedMethod definition = appInfo.definitionFor(init); |
| if (definition == null || isProcessedConcurrently.test(definition)) { |
| return null; |
| } |
| |
| // Don't inline code w/o normal returns into block with catch handlers (b/64432527). |
| if (initInvoke.getBlock().hasCatchHandlers() && |
| definition.getOptimizationInfo().neverReturnsNormally()) { |
| return null; |
| } |
| |
| // If the superclass of the initializer is NOT java.lang.Object, the super class |
| // initializer being called must be classified as TrivialInstanceInitializer. |
| // |
| // NOTE: since we already classified the class as eligible, it does not have |
| // any class initializers in superclass chain or in superinterfaces, see |
| // details in ClassInliner::computeClassEligible(...). |
| if (eligibleClassDefinition.superType != factory.objectType) { |
| TrivialInitializer info = definition.getOptimizationInfo().getTrivialInitializerInfo(); |
| if (!(info instanceof TrivialInstanceInitializer)) { |
| return null; |
| } |
| } |
| |
| if (!definition.isInliningCandidate(method, Reason.SIMPLE, appInfo)) { |
| // We won't be able to inline it here. |
| |
| // Note that there may be some false negatives here since the method may |
| // reference private fields of its class which are supposed to be replaced |
| // with arguments after inlining. We should try and improve it later. |
| |
| // Using -allowaccessmodification mitigates this. |
| return null; |
| } |
| |
| return definition.getOptimizationInfo().getClassInlinerEligibility() != null |
| ? new InliningInfo(definition, eligibleClass) : null; |
| } |
| |
| private InliningInfo isEligibleDirectMethodCall(InvokeMethodWithReceiver invoke) { |
| if (invoke.inValues().lastIndexOf(eligibleInstance) > 0) { |
| return null; // Instance passed as an argument. |
| } |
| return isEligibleMethodCall(!invoke.getBlock().hasCatchHandlers(), invoke.getInvokedMethod(), |
| eligibility -> !eligibility.returnsReceiver || |
| invoke.outValue() == null || invoke.outValue().numberOfAllUsers() == 0); |
| } |
| |
| private InliningInfo isEligibleIndirectMethodCall(DexMethod callee) { |
| return isEligibleMethodCall(false, callee, eligibility -> !eligibility.returnsReceiver); |
| } |
| |
| private InliningInfo isEligibleMethodCall(boolean allowMethodsWithoutNormalReturns, |
| DexMethod callee, Predicate<ClassInlinerEligibility> eligibilityAcceptanceCheck) { |
| |
| DexEncodedMethod singleTarget = findSingleTarget(callee); |
| if (singleTarget == null || isProcessedConcurrently.test(singleTarget)) { |
| return null; |
| } |
| if (method == singleTarget) { |
| return null; // Don't inline itself. |
| } |
| |
| OptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo(); |
| |
| ClassInlinerEligibility eligibility = optimizationInfo.getClassInlinerEligibility(); |
| if (eligibility == null) { |
| return null; |
| } |
| |
| // If the method returns receiver and the return value is actually |
| // used in the code the method is not eligible. |
| if (!eligibilityAcceptanceCheck.test(eligibility)) { |
| return null; |
| } |
| |
| // Don't inline code w/o normal returns into block with catch handlers (b/64432527). |
| if (!allowMethodsWithoutNormalReturns && optimizationInfo.neverReturnsNormally()) { |
| return null; |
| } |
| |
| if (!singleTarget.isInliningCandidate(method, Reason.SIMPLE, appInfo)) { |
| // We won't be able to inline it here. |
| |
| // Note that there may be some false negatives here since the method may |
| // reference private fields of its class which are supposed to be replaced |
| // with arguments after inlining. We should try and improve it later. |
| |
| // Using -allowaccessmodification mitigates this. |
| return null; |
| } |
| |
| markSizeForInlining(singleTarget); |
| return new InliningInfo(singleTarget, eligibleClass); |
| } |
| |
| // Analyzes if a method invoke the eligible instance is passed to is eligible. In short, |
| // it can be eligible if: |
| // |
| // -- eligible instance is passed as argument #N which is not used in the method, |
| // such cases are collected in 'unusedArguments' parameter and later replaced |
| // with 'null' value |
| // |
| // -- eligible instance is passed as argument #N which is only used in the method to |
| // call a method on this object (we call it indirect method call), and method is |
| // eligible according to the same rules defined for direct method call eligibility |
| // (except we require the method receiver to not be used in return instruction) |
| // |
| // -- eligible instance is used in zero-test 'if' instructions testing if the value |
| // is null/not-null (since we know the instance is not null, those checks can |
| // be rewritten) |
| // |
| // -- method itself can be inlined |
| // |
| private boolean isExtraMethodCallEligible( |
| Supplier<InliningOracle> defaultOracle, InvokeMethod invokeMethod) { |
| |
| List<Value> arguments = Lists.newArrayList(invokeMethod.inValues()); |
| |
| // Don't consider constructor invocations and super calls, since |
| // we don't want to forcibly inline them. |
| if (invokeMethod.isInvokeSuper() || |
| (invokeMethod.isInvokeDirect() && factory.isConstructor(invokeMethod.getInvokedMethod()))) { |
| return false; |
| } |
| |
| // Remove receiver from arguments. |
| if (invokeMethod.isInvokeMethodWithReceiver()) { |
| if (arguments.get(0) == eligibleInstance) { |
| // If we got here with invocation on receiver the user is ineligible. |
| return false; |
| } |
| arguments.remove(0); |
| } |
| |
| // Need single target. |
| DexEncodedMethod singleTarget = invokeMethod.computeSingleTarget(appInfo); |
| if (singleTarget == null || isProcessedConcurrently.test(singleTarget)) { |
| return false; // Not eligible. |
| } |
| |
| OptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo(); |
| |
| // Don't inline code w/o normal returns into block with catch handlers (b/64432527). |
| if (invokeMethod.getBlock().hasCatchHandlers() && optimizationInfo.neverReturnsNormally()) { |
| return false; |
| } |
| |
| // Go through all arguments, see if all usages of eligibleInstance are good. |
| for (int argIndex = 0; argIndex < arguments.size(); argIndex++) { |
| Value argument = arguments.get(argIndex); |
| if (argument != eligibleInstance) { |
| continue; // Nothing to worry about. |
| } |
| |
| // Have parameter usage info? |
| ParameterUsage parameterUsage = optimizationInfo.getParameterUsages(argIndex); |
| if (parameterUsage == null) { |
| return false; // Don't know anything. |
| } |
| |
| if (parameterUsage.notUsed()) { |
| // Reference can be removed since it's not used. |
| unusedArguments.add(new Pair<>(invokeMethod, argIndex)); |
| continue; |
| } |
| |
| if (parameterUsage.returnValue && |
| !(invokeMethod.outValue() == null || invokeMethod.outValue().numberOfAllUsers() == 0)) { |
| // Used as return value which is not ignored. |
| return false; |
| } |
| |
| if (!Sets.difference(parameterUsage.ifZeroTest, ALLOWED_ZERO_TEST_TYPES).isEmpty()) { |
| // Used in unsupported zero-check-if kinds. |
| return false; |
| } |
| |
| for (Pair<Type, DexMethod> call : parameterUsage.callsReceiver) { |
| if (call.getFirst() != Type.VIRTUAL && call.getFirst() != Type.INTERFACE) { |
| // Don't support direct and super calls yet. |
| return false; |
| } |
| |
| // Is the method called indirectly still eligible? |
| InliningInfo potentialInliningInfo = isEligibleIndirectMethodCall(call.getSecond()); |
| if (potentialInliningInfo == null) { |
| return false; |
| } |
| |
| // Check if the method is inline-able by standard inliner. |
| InlineAction inlineAction = |
| invokeMethod.computeInlining(defaultOracle.get(), method.method.holder); |
| if (inlineAction == null) { |
| return false; |
| } |
| } |
| |
| extraMethodCalls.put(invokeMethod, new InliningInfo(singleTarget, null)); |
| } |
| |
| // Looks good. |
| markSizeForInlining(singleTarget); |
| return true; |
| } |
| |
| private boolean exemptFromInstructionLimit(DexEncodedMethod inlinee) { |
| DexType inlineeHolder = inlinee.method.holder; |
| if (appInfo.isPinned(inlineeHolder)) { |
| return false; |
| } |
| DexClass inlineeClass = appInfo.definitionFor(inlineeHolder); |
| assert inlineeClass != null; |
| |
| KotlinInfo kotlinInfo = inlineeClass.getKotlinInfo(); |
| return kotlinInfo != null && |
| kotlinInfo.isSyntheticClass() && |
| kotlinInfo.asSyntheticClass().isLambda(); |
| } |
| |
| private void markSizeForInlining(DexEncodedMethod inlinee) { |
| if (!exemptFromInstructionLimit(inlinee)) { |
| estimatedCombinedSizeForInlining += inlinee.getCode().estimatedSizeForInlining(); |
| } |
| } |
| |
| private boolean isNewInstance() { |
| return root.isNewInstance(); |
| } |
| |
| private DexEncodedMethod findSingleTarget(DexMethod callee) { |
| // We don't use computeSingleTarget(...) on invoke since it sometimes fails to |
| // find the single target, while this code may be more successful since we exactly |
| // know what is the actual type of the receiver. |
| |
| // Note that we also intentionally limit ourselves to methods directly defined in |
| // the instance's class. This may be improved later. |
| return eligibleClassDefinition.lookupVirtualMethod(callee); |
| } |
| |
| private void removeInstruction(Instruction instruction) { |
| instruction.inValues().forEach(v -> v.removeUser(instruction)); |
| instruction.getBlock().removeInstruction(instruction); |
| } |
| } |