diff options
Diffstat (limited to 'src/proguard/optimize/peephole/MethodInliner.java')
-rw-r--r-- | src/proguard/optimize/peephole/MethodInliner.java | 567 |
1 files changed, 567 insertions, 0 deletions
diff --git a/src/proguard/optimize/peephole/MethodInliner.java b/src/proguard/optimize/peephole/MethodInliner.java new file mode 100644 index 0000000..55f9ccb --- /dev/null +++ b/src/proguard/optimize/peephole/MethodInliner.java @@ -0,0 +1,567 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package proguard.optimize.peephole; + +import proguard.classfile.*; +import proguard.classfile.attribute.*; +import proguard.classfile.attribute.visitor.*; +import proguard.classfile.constant.*; +import proguard.classfile.constant.visitor.ConstantVisitor; +import proguard.classfile.editor.*; +import proguard.classfile.instruction.*; +import proguard.classfile.instruction.visitor.InstructionVisitor; +import proguard.classfile.util.*; +import proguard.classfile.visitor.MemberVisitor; +import proguard.optimize.info.*; + +import java.util.Stack; + +/** + * This AttributeVisitor inlines short methods or methods that are only invoked + * once, in the code attributes that it visits. + * + * @author Eric Lafortune + */ +public class MethodInliner +extends SimplifiedVisitor +implements AttributeVisitor, + InstructionVisitor, + ConstantVisitor, + MemberVisitor +{ + private static final int MAXIMUM_INLINED_CODE_LENGTH = Integer.parseInt(System.getProperty("maximum.inlined.code.length", "8")); + private static final int MAXIMUM_RESULTING_CODE_LENGTH_JSE = Integer.parseInt(System.getProperty("maximum.resulting.code.length", "8000")); + private static final int MAXIMUM_RESULTING_CODE_LENGTH_JME = Integer.parseInt(System.getProperty("maximum.resulting.code.length", "2000")); + private static final int MAXIMUM_CODE_EXPANSION = 2; + private static final int MAXIMUM_EXTRA_CODE_LENGTH = 128; + + //* + private static final boolean DEBUG = false; + /*/ + private static boolean DEBUG = true; + //*/ + + + private final boolean microEdition; + private final boolean allowAccessModification; + private final boolean inlineSingleInvocations; + private final InstructionVisitor extraInlinedInvocationVisitor; + + private final CodeAttributeComposer codeAttributeComposer = new CodeAttributeComposer(); + private final AccessMethodMarker accessMethodMarker = new AccessMethodMarker(); + private final CatchExceptionMarker catchExceptionMarker = new CatchExceptionMarker(); + private final StackSizeComputer stackSizeComputer = new StackSizeComputer(); + + private ProgramClass targetClass; + private ProgramMethod targetMethod; + private ConstantAdder constantAdder; + private ExceptionInfoAdder exceptionInfoAdder; + private int estimatedResultingCodeLength; + private boolean inlining; + private Stack inliningMethods = new Stack(); + private boolean emptyInvokingStack; + private int uninitializedObjectCount; + private int variableOffset; + private boolean inlined; + private boolean inlinedAny; + + + /** + * Creates a new MethodInliner. + * @param microEdition indicates whether the resulting code is + * targeted at Java Micro Edition. + * @param allowAccessModification indicates whether the access modifiers of + * classes and class members can be changed + * in order to inline methods. + * @param inlineSingleInvocations indicates whether the single invocations + * should be inlined, or, alternatively, + * short methods. + */ + public MethodInliner(boolean microEdition, + boolean allowAccessModification, + boolean inlineSingleInvocations) + { + this(microEdition, + allowAccessModification, + inlineSingleInvocations, + null); + } + + + /** + * Creates a new MethodInliner. + * @param microEdition indicates whether the resulting code is + * targeted at Java Micro Edition. + * @param allowAccessModification indicates whether the access modifiers of + * classes and class members can be changed + * in order to inline methods. + * @param inlineSingleInvocations indicates whether the single invocations + * should be inlined, or, alternatively, + * short methods. + * @param extraInlinedInvocationVisitor an optional extra visitor for all + * inlined invocation instructions. + */ + public MethodInliner(boolean microEdition, + boolean allowAccessModification, + boolean inlineSingleInvocations, + InstructionVisitor extraInlinedInvocationVisitor) + { + this.microEdition = microEdition; + this.allowAccessModification = allowAccessModification; + this.inlineSingleInvocations = inlineSingleInvocations; + this.extraInlinedInvocationVisitor = extraInlinedInvocationVisitor; + } + + + // Implementations for AttributeVisitor. + + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} + + + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) + { + if (!inlining) + { +// codeAttributeComposer.DEBUG = DEBUG = +// clazz.getName().equals("abc/Def") && +// method.getName(clazz).equals("abc"); + + targetClass = (ProgramClass)clazz; + targetMethod = (ProgramMethod)method; + constantAdder = new ConstantAdder(targetClass); + exceptionInfoAdder = new ExceptionInfoAdder(targetClass, codeAttributeComposer); + estimatedResultingCodeLength = codeAttribute.u4codeLength; + inliningMethods.clear(); + uninitializedObjectCount = method.getName(clazz).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT) ? 1 : 0; + inlinedAny = false; + codeAttributeComposer.reset(); + stackSizeComputer.visitCodeAttribute(clazz, method, codeAttribute); + + // Append the body of the code. + copyCode(clazz, method, codeAttribute); + + targetClass = null; + targetMethod = null; + constantAdder = null; + + // Update the code attribute if any code has been inlined. + if (inlinedAny) + { + codeAttributeComposer.visitCodeAttribute(clazz, method, codeAttribute); + + // Update the accessing flags. + codeAttribute.instructionsAccept(clazz, method, accessMethodMarker); + + // Update the exception catching flags. + catchExceptionMarker.visitCodeAttribute(clazz, method, codeAttribute); + } + } + + // Only inline the method if it is invoked once or if it is short. + else if ((inlineSingleInvocations ? + MethodInvocationMarker.getInvocationCount(method) == 1 : + codeAttribute.u4codeLength <= MAXIMUM_INLINED_CODE_LENGTH) && + estimatedResultingCodeLength + codeAttribute.u4codeLength < + (microEdition ? + MAXIMUM_RESULTING_CODE_LENGTH_JME : + MAXIMUM_RESULTING_CODE_LENGTH_JSE)) + { + if (DEBUG) + { + System.out.println("MethodInliner: inlining ["+ + clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"] in ["+ + targetClass.getName()+"."+targetMethod.getName(targetClass)+targetMethod.getDescriptor(targetClass)+"]"); + } + + // Ignore the removal of the original method invocation, + // the addition of the parameter setup, and + // the modification of a few inlined instructions. + estimatedResultingCodeLength += codeAttribute.u4codeLength; + + // Append instructions to store the parameters. + storeParameters(clazz, method); + + // Inline the body of the code. + copyCode(clazz, method, codeAttribute); + + inlined = true; + inlinedAny = true; + } + } + + + /** + * Appends instructions to pop the parameters for the given method, storing + * them in new local variables. + */ + private void storeParameters(Clazz clazz, Method method) + { + String descriptor = method.getDescriptor(clazz); + + boolean isStatic = + (method.getAccessFlags() & ClassConstants.INTERNAL_ACC_STATIC) != 0; + + // Count the number of parameters, taking into account their categories. + int parameterCount = ClassUtil.internalMethodParameterCount(descriptor); + int parameterSize = ClassUtil.internalMethodParameterSize(descriptor); + int parameterOffset = isStatic ? 0 : 1; + + // Store the parameter types. + String[] parameterTypes = new String[parameterSize]; + + InternalTypeEnumeration internalTypeEnumeration = + new InternalTypeEnumeration(descriptor); + + for (int parameterIndex = 0; parameterIndex < parameterSize; parameterIndex++) + { + String parameterType = internalTypeEnumeration.nextType(); + parameterTypes[parameterIndex] = parameterType; + if (ClassUtil.internalTypeSize(parameterType) == 2) + { + parameterIndex++; + } + } + + codeAttributeComposer.beginCodeFragment(parameterSize+1); + + // Go over the parameter types backward, storing the stack entries + // in their corresponding variables. + for (int parameterIndex = parameterSize-1; parameterIndex >= 0; parameterIndex--) + { + String parameterType = parameterTypes[parameterIndex]; + if (parameterType != null) + { + byte opcode; + switch (parameterType.charAt(0)) + { + case ClassConstants.INTERNAL_TYPE_BOOLEAN: + case ClassConstants.INTERNAL_TYPE_BYTE: + case ClassConstants.INTERNAL_TYPE_CHAR: + case ClassConstants.INTERNAL_TYPE_SHORT: + case ClassConstants.INTERNAL_TYPE_INT: + opcode = InstructionConstants.OP_ISTORE; + break; + + case ClassConstants.INTERNAL_TYPE_LONG: + opcode = InstructionConstants.OP_LSTORE; + break; + + case ClassConstants.INTERNAL_TYPE_FLOAT: + opcode = InstructionConstants.OP_FSTORE; + break; + + case ClassConstants.INTERNAL_TYPE_DOUBLE: + opcode = InstructionConstants.OP_DSTORE; + break; + + default: + opcode = InstructionConstants.OP_ASTORE; + break; + } + + codeAttributeComposer.appendInstruction(parameterSize-parameterIndex-1, + new VariableInstruction(opcode, variableOffset + parameterOffset + parameterIndex).shrink()); + } + } + + // Put the 'this' reference in variable 0 (plus offset). + if (!isStatic) + { + codeAttributeComposer.appendInstruction(parameterSize, + new VariableInstruction(InstructionConstants.OP_ASTORE, variableOffset).shrink()); + } + + codeAttributeComposer.endCodeFragment(); + } + + + /** + * Appends the code of the given code attribute. + */ + private void copyCode(Clazz clazz, Method method, CodeAttribute codeAttribute) + { + // The code may expand, due to expanding constant and variable + // instructions. + codeAttributeComposer.beginCodeFragment(codeAttribute.u4codeLength); + + // Copy the instructions. + codeAttribute.instructionsAccept(clazz, method, this); + + // Copy the exceptions. + codeAttribute.exceptionsAccept(clazz, method, exceptionInfoAdder); + + // Append a label just after the code. + codeAttributeComposer.appendLabel(codeAttribute.u4codeLength); + + codeAttributeComposer.endCodeFragment(); + } + + + // Implementations for InstructionVisitor. + + public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) + { + codeAttributeComposer.appendInstruction(offset, instruction.shrink()); + } + + + public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction) + { + // Are we inlining this instruction? + if (inlining) + { + // Replace any return instructions by branches to the end of the code. + switch (simpleInstruction.opcode) + { + case InstructionConstants.OP_IRETURN: + case InstructionConstants.OP_LRETURN: + case InstructionConstants.OP_FRETURN: + case InstructionConstants.OP_DRETURN: + case InstructionConstants.OP_ARETURN: + case InstructionConstants.OP_RETURN: + // Are we not at the last instruction? + if (offset < codeAttribute.u4codeLength-1) + { + // Replace the return instruction by a branch instruction. + Instruction branchInstruction = + new BranchInstruction(InstructionConstants.OP_GOTO_W, + codeAttribute.u4codeLength - offset); + + codeAttributeComposer.appendInstruction(offset, + branchInstruction.shrink()); + } + else + { + // Just leave out the instruction, but put in a label, + // for the sake of any other branch instructions. + codeAttributeComposer.appendLabel(offset); + } + + return; + } + } + + codeAttributeComposer.appendInstruction(offset, simpleInstruction.shrink()); + } + + + public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) + { + // Are we inlining this instruction? + if (inlining) + { + // Update the variable index. + variableInstruction.variableIndex += variableOffset; + } + + codeAttributeComposer.appendInstruction(offset, variableInstruction.shrink()); + } + + + public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) + { + // Is it a method invocation? + switch (constantInstruction.opcode) + { + case InstructionConstants.OP_NEW: + uninitializedObjectCount++; + break; + + case InstructionConstants.OP_INVOKEVIRTUAL: + case InstructionConstants.OP_INVOKESPECIAL: + case InstructionConstants.OP_INVOKESTATIC: + case InstructionConstants.OP_INVOKEINTERFACE: + // See if we can inline it. + inlined = false; + + // Append a label, in case the invocation will be inlined. + codeAttributeComposer.appendLabel(offset); + + emptyInvokingStack = + !inlining && + stackSizeComputer.isReachable(offset) && + stackSizeComputer.getStackSize(offset) == 0; + + variableOffset += codeAttribute.u2maxLocals; + + clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); + + variableOffset -= codeAttribute.u2maxLocals; + + // Was the method inlined? + if (inlined) + { + if (extraInlinedInvocationVisitor != null) + { + extraInlinedInvocationVisitor.visitConstantInstruction(clazz, method, codeAttribute, offset, constantInstruction); + } + + // The invocation itself is no longer necessary. + return; + } + + break; + } + + // Are we inlining this instruction? + if (inlining) + { + // Make sure the constant is present in the constant pool of the + // target class. + constantInstruction.constantIndex = + constantAdder.addConstant(clazz, constantInstruction.constantIndex); + } + + codeAttributeComposer.appendInstruction(offset, constantInstruction.shrink()); + } + + + // Implementations for ConstantVisitor. + + public void visitInterfaceMethodrefConstant(Clazz clazz, InterfaceMethodrefConstant interfaceMethodrefConstant) {} + + + public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant) + { + methodrefConstant.referencedMemberAccept(this); + } + + + // Implementations for MemberVisitor. + + public void visitAnyMember(Clazz Clazz, Member member) {} + + + public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) + { + int accessFlags = programMethod.getAccessFlags(); + + if (// Only inline the method if it is private, static, or final. + (accessFlags & (ClassConstants.INTERNAL_ACC_PRIVATE | + ClassConstants.INTERNAL_ACC_STATIC | + ClassConstants.INTERNAL_ACC_FINAL)) != 0 && + + // Only inline the method if it is not synchronized, etc. + (accessFlags & (ClassConstants.INTERNAL_ACC_SYNCHRONIZED | + ClassConstants.INTERNAL_ACC_NATIVE | + ClassConstants.INTERNAL_ACC_INTERFACE | + ClassConstants.INTERNAL_ACC_ABSTRACT)) == 0 && + + // Don't inline an <init> method, except in an <init> method in the + // same class. +// (!programMethod.getName(programClass).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT) || +// (programClass.equals(targetClass) && +// targetMethod.getName(targetClass).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT))) && + !programMethod.getName(programClass).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT) && + + // Don't inline a method into itself. + (!programMethod.equals(targetMethod) || + !programClass.equals(targetClass)) && + + // Only inline the method if it isn't recursing. + !inliningMethods.contains(programMethod) && + + // Only inline the method if its target class has at least the + // same version number as the source class, in order to avoid + // introducing incompatible constructs. + targetClass.u4version >= programClass.u4version && + + // Only inline the method if it doesn't invoke a super method, or if + // it is in the same class. + (!SuperInvocationMarker.invokesSuperMethods(programMethod) || + programClass.equals(targetClass)) && + + // Only inline the method if it doesn't branch backward while there + // are uninitialized objects. + (!BackwardBranchMarker.branchesBackward(programMethod) || + uninitializedObjectCount == 0) && + + // Only inline if the code access of the inlined method allows it. + (allowAccessModification || + ((!AccessMethodMarker.accessesPrivateCode(programMethod) || + programClass.equals(targetClass)) && + + (!AccessMethodMarker.accessesPackageCode(programMethod) || + ClassUtil.internalPackageName(programClass.getName()).equals( + ClassUtil.internalPackageName(targetClass.getName()))))) && + +// (!AccessMethodMarker.accessesProtectedCode(programMethod) || +// targetClass.extends_(programClass) || +// targetClass.implements_(programClass)) || + (!AccessMethodMarker.accessesProtectedCode(programMethod) || + programClass.equals(targetClass)) && + + // Only inline the method if it doesn't catch exceptions, or if it + // is invoked with an empty stack. + (!CatchExceptionMarker.catchesExceptions(programMethod) || + emptyInvokingStack) && + + // Only inline the method if it comes from the same class or from + // a class with a static initializer. + (programClass.equals(targetClass) || + programClass.findMethod(ClassConstants.INTERNAL_METHOD_NAME_CLINIT, + ClassConstants.INTERNAL_METHOD_TYPE_CLINIT) == null)) + { +// System.out.print("MethodInliner: inlining "); +// programMethod.accept(programClass, new SimpleClassPrinter(true)); +// System.out.print(" in "); +// targetMethod.accept(targetClass, new SimpleClassPrinter(true)); +// +// System.out.println(" Private: "+ +// (!AccessMethodMarker.accessesPrivateCode(programMethod) || +// programClass.equals(targetClass))); +// +// System.out.println(" Package: "+ +// (!AccessMethodMarker.accessesPackageCode(programMethod) || +// ClassUtil.internalPackageName(programClass.getName()).equals( +// ClassUtil.internalPackageName(targetClass.getName())))); +// +// System.out.println(" Protected: "+ +// ((!AccessMethodMarker.accessesProtectedCode(programMethod) || +// targetClass.extends_(programClass) || +// targetClass.implements_(programClass)) || +// ClassUtil.internalPackageName(programClass.getName()).equals( +// ClassUtil.internalPackageName(targetClass.getName())))); + + boolean oldInlining = inlining; + inlining = true; + inliningMethods.push(programMethod); + + // Inline the method body. + programMethod.attributesAccept(programClass, this); + + // Update the optimization information of the target method. + MethodOptimizationInfo info = + MethodOptimizationInfo.getMethodOptimizationInfo(targetMethod); + if (info != null) + { + info.merge(MethodOptimizationInfo.getMethodOptimizationInfo(programMethod)); + } + + inlining = oldInlining; + inliningMethods.pop(); + } + else if (programMethod.getName(programClass).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT)) + { + uninitializedObjectCount--; + } + } +} |