| // 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; |
| |
| import com.android.tools.r8.errors.Unreachable; |
| import com.android.tools.r8.graph.AppInfo.ResolutionResult; |
| import com.android.tools.r8.graph.DexClass; |
| import com.android.tools.r8.graph.DexEncodedField; |
| import com.android.tools.r8.graph.DexEncodedMethod; |
| import com.android.tools.r8.graph.DexField; |
| import com.android.tools.r8.graph.DexMethod; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.graph.GraphLense; |
| import com.android.tools.r8.ir.code.Invoke.Type; |
| import com.android.tools.r8.ir.optimize.Inliner.Constraint; |
| import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness; |
| import java.util.Collection; |
| |
| // Computes the inlining constraint for a given instruction. |
| public class InliningConstraints { |
| |
| private AppInfoWithLiveness appInfo; |
| |
| // Currently used only by the vertical class merger (in all other cases this is the identity). |
| // |
| // When merging a type A into its subtype B we need to inline A.<init>() into B.<init>(). |
| // Therefore, we need to be sure that A.<init>() can in fact be inlined into B.<init>() *before* |
| // we merge the two classes. However, at this point, we may reject the method A.<init>() from |
| // being inlined into B.<init>() only because it is not declared in the same class as B (which |
| // it would be after merging A and B). |
| // |
| // To circumvent this problem, the vertical class merger creates a graph lense that maps the |
| // type A to B, to create a temporary view of what the world would look like after class merging. |
| private GraphLense graphLense; |
| |
| public InliningConstraints(AppInfoWithLiveness appInfo) { |
| this(appInfo, GraphLense.getIdentityLense()); |
| } |
| |
| public InliningConstraints(AppInfoWithLiveness appInfo, GraphLense graphLense) { |
| assert graphLense.isContextFreeForMethods(); |
| this.appInfo = appInfo; |
| this.graphLense = graphLense; |
| } |
| |
| public Constraint forAlwaysMaterializingUser() { |
| return Constraint.ALWAYS; |
| } |
| |
| public Constraint forArgument() { |
| return Constraint.ALWAYS; |
| } |
| |
| public Constraint forArrayGet() { |
| return Constraint.ALWAYS; |
| } |
| |
| public Constraint forArrayLength() { |
| return Constraint.ALWAYS; |
| } |
| |
| public Constraint forArrayPut() { |
| return Constraint.ALWAYS; |
| } |
| |
| public Constraint forBinop() { |
| return Constraint.ALWAYS; |
| } |
| |
| public Constraint forCheckCast(DexType type, DexType invocationContext) { |
| return Constraint.classIsVisible(invocationContext, type, appInfo); |
| } |
| |
| public Constraint forConstClass(DexType type, DexType invocationContext) { |
| return Constraint.classIsVisible(invocationContext, type, appInfo); |
| } |
| |
| public Constraint forConstInstruction() { |
| return Constraint.ALWAYS; |
| } |
| |
| public Constraint forDebugLocalRead() { |
| return Constraint.ALWAYS; |
| } |
| |
| public Constraint forDebugLocalsChange() { |
| return Constraint.ALWAYS; |
| } |
| |
| public Constraint forDebugPosition() { |
| return Constraint.ALWAYS; |
| } |
| |
| public Constraint forInstanceGet(DexField field, DexType invocationContext) { |
| DexField lookup = graphLense.lookupField(field); |
| return forFieldInstruction( |
| lookup, appInfo.lookupInstanceTarget(lookup.clazz, lookup), invocationContext); |
| } |
| |
| public Constraint forInstanceOf(DexType type, DexType invocationContext) { |
| return Constraint.classIsVisible(invocationContext, type, appInfo); |
| } |
| |
| public Constraint forInstancePut(DexField field, DexType invocationContext) { |
| DexField lookup = graphLense.lookupField(field); |
| return forFieldInstruction( |
| lookup, appInfo.lookupInstanceTarget(lookup.clazz, lookup), invocationContext); |
| } |
| |
| public Constraint forInvoke(DexMethod method, Type type, DexType invocationContext) { |
| switch (type) { |
| case DIRECT: |
| return forInvokeDirect(method, invocationContext); |
| case INTERFACE: |
| return forInvokeInterface(method, invocationContext); |
| case STATIC: |
| return forInvokeStatic(method, invocationContext); |
| case SUPER: |
| return forInvokeSuper(method, invocationContext); |
| case VIRTUAL: |
| return forInvokeVirtual(method, invocationContext); |
| case CUSTOM: |
| return forInvokeCustom(); |
| case POLYMORPHIC: |
| return forInvokePolymorphic(method, invocationContext); |
| default: |
| throw new Unreachable("Unexpected type: " + type); |
| } |
| } |
| |
| public Constraint forInvokeCustom() { |
| return Constraint.NEVER; |
| } |
| |
| public Constraint forInvokeDirect(DexMethod method, DexType invocationContext) { |
| DexMethod lookup = graphLense.lookupMethod(method); |
| return forSingleTargetInvoke(lookup, appInfo.lookupDirectTarget(lookup), invocationContext); |
| } |
| |
| public Constraint forInvokeInterface(DexMethod method, DexType invocationContext) { |
| DexMethod lookup = graphLense.lookupMethod(method); |
| return forVirtualInvoke(lookup, appInfo.lookupInterfaceTargets(lookup), invocationContext); |
| } |
| |
| public Constraint forInvokeMultiNewArray(DexType type, DexType invocationContext) { |
| return Constraint.classIsVisible(invocationContext, type, appInfo); |
| } |
| |
| public Constraint forInvokeNewArray(DexType type, DexType invocationContext) { |
| return Constraint.classIsVisible(invocationContext, type, appInfo); |
| } |
| |
| public Constraint forInvokePolymorphic(DexMethod method, DexType invocationContext) { |
| return Constraint.NEVER; |
| } |
| |
| public Constraint forInvokeStatic(DexMethod method, DexType invocationContext) { |
| DexMethod lookup = graphLense.lookupMethod(method); |
| return forSingleTargetInvoke(lookup, appInfo.lookupStaticTarget(lookup), invocationContext); |
| } |
| |
| public Constraint forInvokeSuper(DexMethod method, DexType invocationContext) { |
| // The semantics of invoke super depend on the context. |
| return Constraint.SAMECLASS; |
| } |
| |
| public Constraint forInvokeVirtual(DexMethod method, DexType invocationContext) { |
| DexMethod lookup = graphLense.lookupMethod(method); |
| return forVirtualInvoke(lookup, appInfo.lookupVirtualTargets(lookup), invocationContext); |
| } |
| |
| public Constraint forJumpInstruction() { |
| return Constraint.ALWAYS; |
| } |
| |
| public Constraint forLoad() { |
| return Constraint.ALWAYS; |
| } |
| |
| public Constraint forMonitor() { |
| // Conservative choice. |
| return Constraint.NEVER; |
| } |
| |
| public Constraint forMove() { |
| return Constraint.ALWAYS; |
| } |
| |
| public Constraint forMoveException() { |
| // TODO(64432527): Revisit this constraint. |
| return Constraint.NEVER; |
| } |
| |
| public Constraint forNewArrayEmpty(DexType type, DexType invocationContext) { |
| return Constraint.classIsVisible(invocationContext, type, appInfo); |
| } |
| |
| public Constraint forNewArrayFilledData() { |
| return Constraint.ALWAYS; |
| } |
| |
| public Constraint forNewInstance(DexType type, DexType invocationContext) { |
| return Constraint.classIsVisible(invocationContext, type, appInfo); |
| } |
| |
| public Constraint forNonNull() { |
| return Constraint.ALWAYS; |
| } |
| |
| public Constraint forPop() { |
| return Constraint.ALWAYS; |
| } |
| |
| public Constraint forReturn() { |
| return Constraint.ALWAYS; |
| } |
| |
| public Constraint forStaticGet(DexField field, DexType invocationContext) { |
| DexField lookup = graphLense.lookupField(field); |
| return forFieldInstruction( |
| lookup, appInfo.lookupStaticTarget(lookup.clazz, lookup), invocationContext); |
| } |
| |
| public Constraint forStaticPut(DexField field, DexType invocationContext) { |
| DexField lookup = graphLense.lookupField(field); |
| return forFieldInstruction( |
| lookup, appInfo.lookupStaticTarget(lookup.clazz, lookup), invocationContext); |
| } |
| |
| public Constraint forStore() { |
| return Constraint.ALWAYS; |
| } |
| |
| public Constraint forThrow() { |
| return Constraint.ALWAYS; |
| } |
| |
| public Constraint forUnop() { |
| return Constraint.ALWAYS; |
| } |
| |
| private Constraint forFieldInstruction( |
| DexField field, DexEncodedField target, DexType invocationContext) { |
| // Resolve the field if possible and decide whether the instruction can inlined. |
| DexType fieldHolder = graphLense.lookupType(field.clazz); |
| DexClass fieldClass = appInfo.definitionFor(fieldHolder); |
| if (target != null && fieldClass != null) { |
| Constraint fieldConstraint = |
| Constraint.deriveConstraint(invocationContext, fieldHolder, target.accessFlags, appInfo); |
| Constraint classConstraint = |
| Constraint.deriveConstraint( |
| invocationContext, fieldHolder, fieldClass.accessFlags, appInfo); |
| return Constraint.min(fieldConstraint, classConstraint); |
| } |
| return Constraint.NEVER; |
| } |
| |
| private Constraint forSingleTargetInvoke( |
| DexMethod method, DexEncodedMethod target, DexType invocationContext) { |
| if (method.holder.isArrayType()) { |
| return Constraint.ALWAYS; |
| } |
| if (target != null) { |
| DexType methodHolder = graphLense.lookupType(target.method.holder); |
| DexClass methodClass = appInfo.definitionFor(methodHolder); |
| if (methodClass != null) { |
| Constraint methodConstraint = |
| Constraint.deriveConstraint( |
| invocationContext, methodHolder, target.accessFlags, appInfo); |
| // We also have to take the constraint of the enclosing class into account. |
| Constraint classConstraint = |
| Constraint.deriveConstraint( |
| invocationContext, methodHolder, methodClass.accessFlags, appInfo); |
| return Constraint.min(methodConstraint, classConstraint); |
| } |
| } |
| return Constraint.NEVER; |
| } |
| |
| private Constraint forVirtualInvoke( |
| DexMethod method, Collection<DexEncodedMethod> targets, DexType invocationContext) { |
| if (method.holder.isArrayType()) { |
| return Constraint.ALWAYS; |
| } |
| if (targets == null) { |
| return Constraint.NEVER; |
| } |
| |
| // Perform resolution and derive inlining constraints based on the accessibility of the |
| // resolution result. |
| ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method); |
| DexEncodedMethod resolutionTarget = resolutionResult.asResultOfResolve(); |
| if (resolutionTarget == null) { |
| // This will fail at runtime. |
| return Constraint.NEVER; |
| } |
| |
| DexType methodHolder = graphLense.lookupType(resolutionTarget.method.holder); |
| DexClass methodClass = appInfo.definitionFor(methodHolder); |
| assert methodClass != null; |
| Constraint methodConstraint = |
| Constraint.deriveConstraint( |
| invocationContext, methodHolder, resolutionTarget.accessFlags, appInfo); |
| // We also have to take the constraint of the enclosing class of the resolution result |
| // into account. We do not allow inlining this method if it is calling something that |
| // is inaccessible. Inlining in that case could move the code to another package making a |
| // call succeed that should not succeed. Conversely, if the resolution result is accessible, |
| // we have to make sure that inlining cannot make it inaccessible. |
| Constraint classConstraint = |
| Constraint.deriveConstraint( |
| invocationContext, methodHolder, methodClass.accessFlags, appInfo); |
| Constraint result = Constraint.min(methodConstraint, classConstraint); |
| if (result == Constraint.NEVER) { |
| return result; |
| } |
| |
| // For each of the actual potential targets, derive constraints based on the accessibility |
| // of the method itself. |
| for (DexEncodedMethod target : targets) { |
| methodHolder = graphLense.lookupType(target.method.holder); |
| assert appInfo.definitionFor(methodHolder) != null; |
| methodConstraint = |
| Constraint.deriveConstraint(invocationContext, methodHolder, target.accessFlags, appInfo); |
| result = Constraint.min(result, methodConstraint); |
| if (result == Constraint.NEVER) { |
| return result; |
| } |
| } |
| |
| return result; |
| } |
| } |