| // 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.jar; |
| |
| import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION; |
| |
| import com.android.tools.r8.errors.Unreachable; |
| import com.android.tools.r8.graph.AppView; |
| import com.android.tools.r8.graph.DexClass; |
| 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.graph.GraphLense.GraphLenseLookupResult; |
| import com.android.tools.r8.graph.JarApplicationReader; |
| import com.android.tools.r8.ir.code.Invoke; |
| import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; |
| import com.android.tools.r8.ir.optimize.InliningConstraints; |
| import com.android.tools.r8.shaking.AppInfoWithLiveness; |
| import org.objectweb.asm.Handle; |
| import org.objectweb.asm.MethodVisitor; |
| import org.objectweb.asm.Opcodes; |
| import org.objectweb.asm.Type; |
| import org.objectweb.asm.tree.TryCatchBlockNode; |
| |
| // This visitor can be used to determine if a piece of jar code has any instructions that the |
| // inliner would not be willing to inline. This can be used to determine if a method can be force |
| // inlined although its IR is still not available. |
| // |
| // Note that this class has only been implemented for the hooks in InliningConstraints that may |
| // return a non-ALWAYS inlining constraint (e.g., InliningConstraints.forReturn is not called). |
| public class InliningConstraintVisitor extends MethodVisitor { |
| |
| private final JarApplicationReader application; |
| private final AppView<AppInfoWithLiveness> appView; |
| private final GraphLense graphLense; |
| private final InliningConstraints inliningConstraints; |
| private final DexEncodedMethod method; |
| private final DexType invocationContext; |
| |
| private ConstraintWithTarget constraint; |
| |
| public InliningConstraintVisitor( |
| JarApplicationReader application, |
| AppView<AppInfoWithLiveness> appView, |
| GraphLense graphLense, |
| DexEncodedMethod method, |
| DexType invocationContext) { |
| super(ASM_VERSION); |
| assert graphLense.isContextFreeForMethods(); |
| this.application = application; |
| this.appView = appView; |
| this.graphLense = graphLense; |
| this.inliningConstraints = new InliningConstraints(appView, graphLense); |
| this.method = method; |
| this.invocationContext = invocationContext; |
| |
| // Model a synchronized method as having a monitor instruction. |
| this.constraint = method.accessFlags.isSynchronized() |
| ? inliningConstraints.forMonitor() : ConstraintWithTarget.ALWAYS; |
| } |
| |
| public void disallowStaticInterfaceMethodCalls() { |
| inliningConstraints.disallowStaticInterfaceMethodCalls(); |
| } |
| |
| public ConstraintWithTarget getConstraint() { |
| return constraint; |
| } |
| |
| private void updateConstraint(ConstraintWithTarget other) { |
| constraint = ConstraintWithTarget.meet(constraint, other, appView); |
| } |
| |
| // Used to signal that the result is ready, such that we do not need to visit all instructions of |
| // the method, if we can see early on that it cannot be inlined anyway. |
| public boolean isFinished() { |
| return constraint == ConstraintWithTarget.NEVER; |
| } |
| |
| public void accept(TryCatchBlockNode tryCatchBlock) { |
| // Model a try-catch as a move-exception instruction. |
| updateConstraint(inliningConstraints.forMoveException()); |
| } |
| |
| @Override |
| public void visitFieldInsn(int opcode, String owner, String name, String desc) { |
| DexField field = application.getField(owner, name, desc); |
| switch (opcode) { |
| case Opcodes.GETFIELD: |
| updateConstraint(inliningConstraints.forInstanceGet(field, invocationContext)); |
| break; |
| |
| case Opcodes.PUTFIELD: |
| updateConstraint(inliningConstraints.forInstancePut(field, invocationContext)); |
| break; |
| |
| case Opcodes.GETSTATIC: |
| updateConstraint(inliningConstraints.forStaticGet(field, invocationContext)); |
| break; |
| |
| case Opcodes.PUTSTATIC: |
| updateConstraint(inliningConstraints.forStaticPut(field, invocationContext)); |
| break; |
| |
| default: |
| throw new Unreachable("Unexpected opcode " + opcode); |
| } |
| } |
| |
| @Override |
| public void visitLdcInsn(Object cst) { |
| if (cst instanceof Type && ((Type) cst).getSort() != Type.METHOD) { |
| DexType type = application.getType((Type) cst); |
| updateConstraint(inliningConstraints.forConstClass(type, invocationContext)); |
| } else if (cst instanceof Handle) { |
| updateConstraint(inliningConstraints.forConstMethodHandle()); |
| } else { |
| updateConstraint(inliningConstraints.forConstInstruction()); |
| } |
| } |
| |
| @Override |
| public void visitInvokeDynamicInsn( |
| String name, |
| String descriptor, |
| Handle bootstrapMethodHandle, |
| Object... bootstrapMethodArguments) { |
| updateConstraint(inliningConstraints.forInvokeCustom()); |
| } |
| |
| @Override |
| public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { |
| DexType ownerType = application.getTypeFromName(owner); |
| DexMethod target = application.getMethod(ownerType, name, desc); |
| updateConstraint( |
| getConstraintForInvoke( |
| opcode, target, graphLense, appView, inliningConstraints, invocationContext)); |
| } |
| |
| public static ConstraintWithTarget getConstraintForInvoke( |
| int opcode, |
| DexMethod target, |
| GraphLense graphLense, |
| AppView<?> appView, |
| InliningConstraints inliningConstraints, |
| DexType invocationContext) { |
| // Find the DEX invocation type. |
| Invoke.Type type; |
| switch (opcode) { |
| case Opcodes.INVOKEINTERFACE: |
| // Could have changed to an invoke-virtual instruction due to vertical class merging |
| // (if an interface is merged into a class). |
| type = graphLense.lookupMethod(target, null, Invoke.Type.INTERFACE).getType(); |
| assert type == Invoke.Type.INTERFACE || type == Invoke.Type.VIRTUAL; |
| break; |
| |
| case Opcodes.INVOKESPECIAL: |
| if (appView.dexItemFactory().isConstructor(target)) { |
| type = Invoke.Type.DIRECT; |
| assert noNeedToUseGraphLense(target, type, graphLense); |
| } else if (target.holder == invocationContext) { |
| // The method could have been publicized. |
| type = graphLense.lookupMethod(target, null, Invoke.Type.DIRECT).getType(); |
| assert type == Invoke.Type.DIRECT || type == Invoke.Type.VIRTUAL; |
| } else { |
| // This is a super call. Note that the vertical class merger translates some invoke-super |
| // instructions to invoke-direct. However, when that happens, the invoke instruction and |
| // the target method end up being in the same class, and therefore, we will allow inlining |
| // it. The result of using type=SUPER below will be the same, since it leads to the |
| // inlining constraint SAMECLASS. |
| // TODO(christofferqa): Consider using graphLense.lookupMethod (to do this, we need the |
| // context for the graph lense, though). |
| type = Invoke.Type.SUPER; |
| assert noNeedToUseGraphLense(target, type, graphLense); |
| } |
| break; |
| |
| case Opcodes.INVOKESTATIC: { |
| // Static invokes may have changed as a result of horizontal class merging. |
| GraphLenseLookupResult lookup = graphLense.lookupMethod(target, null, Invoke.Type.STATIC); |
| target = lookup.getMethod(); |
| type = lookup.getType(); |
| break; |
| } |
| |
| case Opcodes.INVOKEVIRTUAL: { |
| type = Invoke.Type.VIRTUAL; |
| // Instructions that target a private method in the same class translates to |
| // invoke-direct. |
| if (target.holder == invocationContext) { |
| DexClass clazz = appView.definitionFor(target.holder); |
| if (clazz != null && clazz.lookupDirectMethod(target) != null) { |
| type = Invoke.Type.DIRECT; |
| } |
| } |
| |
| // Virtual invokes may have changed to interface invokes as a result of member rebinding. |
| GraphLenseLookupResult lookup = graphLense.lookupMethod(target, null, type); |
| target = lookup.getMethod(); |
| type = lookup.getType(); |
| break; |
| } |
| |
| default: |
| throw new Unreachable("Unexpected opcode " + opcode); |
| } |
| |
| return inliningConstraints.forInvoke(target, type, invocationContext); |
| } |
| |
| private static boolean noNeedToUseGraphLense( |
| DexMethod method, Invoke.Type type, GraphLense graphLense) { |
| assert graphLense.lookupMethod(method, null, type).getType() == type; |
| return true; |
| } |
| |
| @Override |
| public void visitInsn(int opcode) { |
| switch (opcode) { |
| case Opcodes.MONITORENTER: |
| case Opcodes.MONITOREXIT: |
| updateConstraint(inliningConstraints.forMonitor()); |
| break; |
| |
| default: |
| // All instructions here lead to the inlining constraint ALWAYS. |
| } |
| } |
| |
| @Override |
| public void visitMultiANewArrayInsn(String desc, int dims) { |
| DexType type = application.getTypeFromDescriptor(desc); |
| updateConstraint(inliningConstraints.forInvokeMultiNewArray(type, invocationContext)); |
| } |
| |
| @Override |
| public void visitTypeInsn(int opcode, String typeName) { |
| DexType type = application.getTypeFromName(typeName); |
| switch (opcode) { |
| case Opcodes.ANEWARRAY: |
| updateConstraint(inliningConstraints.forNewArrayEmpty(type, invocationContext)); |
| break; |
| |
| case Opcodes.CHECKCAST: |
| updateConstraint(inliningConstraints.forCheckCast(type, invocationContext)); |
| break; |
| |
| case Opcodes.INSTANCEOF: |
| updateConstraint(inliningConstraints.forInstanceOf(type, invocationContext)); |
| break; |
| |
| case Opcodes.NEW: |
| updateConstraint(inliningConstraints.forNewInstance(type, invocationContext)); |
| break; |
| |
| default: |
| throw new Unreachable("Unexpected opcode " + opcode); |
| } |
| } |
| } |