summaryrefslogtreecommitdiff
path: root/src/proguard/optimize/peephole/MethodInliner.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/proguard/optimize/peephole/MethodInliner.java')
-rw-r--r--src/proguard/optimize/peephole/MethodInliner.java567
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--;
+ }
+ }
+}