| /* |
| * ProGuard -- shrinking, optimization, obfuscation, and preverification |
| * of Java bytecode. |
| * |
| * Copyright (c) 2002-2014 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.evaluation; |
| |
| import proguard.classfile.*; |
| import proguard.classfile.attribute.*; |
| import proguard.classfile.attribute.visitor.*; |
| import proguard.classfile.editor.*; |
| import proguard.classfile.util.*; |
| import proguard.classfile.visitor.MemberVisitor; |
| |
| /** |
| * This AttributeVisitor optimizes variable allocation based on their the liveness, |
| * in the code attributes that it visits. |
| * |
| * @author Eric Lafortune |
| */ |
| public class VariableOptimizer |
| extends SimplifiedVisitor |
| implements AttributeVisitor, |
| LocalVariableInfoVisitor, |
| LocalVariableTypeInfoVisitor |
| { |
| //* |
| private static final boolean DEBUG = false; |
| /*/ |
| private static boolean DEBUG = true; |
| //*/ |
| |
| private static final int MAX_VARIABLES_SIZE = 64; |
| |
| |
| private final boolean reuseThis; |
| private final MemberVisitor extraVariableMemberVisitor; |
| |
| private final LivenessAnalyzer livenessAnalyzer = new LivenessAnalyzer(); |
| private final VariableRemapper variableRemapper = new VariableRemapper(); |
| private VariableCleaner variableCleaner = new VariableCleaner(); |
| |
| private int[] variableMap = new int[ClassConstants.TYPICAL_VARIABLES_SIZE]; |
| |
| |
| /** |
| * Creates a new VariableOptimizer. |
| * @param reuseThis specifies whether the 'this' variable can be reused. |
| * Many JVMs for JME and IBM's JVMs for JSE can't handle |
| * such reuse. |
| */ |
| public VariableOptimizer(boolean reuseThis) |
| { |
| this(reuseThis, null); |
| } |
| |
| |
| /** |
| * Creates a new VariableOptimizer with an extra visitor. |
| * @param reuseThis specifies whether the 'this' variable |
| * can be reused. Many JVMs for JME and |
| * IBM's JVMs for JSE can't handle such |
| * reuse. |
| * @param extraVariableMemberVisitor an optional extra visitor for all |
| * removed variables. |
| */ |
| public VariableOptimizer(boolean reuseThis, |
| MemberVisitor extraVariableMemberVisitor) |
| { |
| this.reuseThis = reuseThis; |
| this.extraVariableMemberVisitor = extraVariableMemberVisitor; |
| } |
| |
| |
| // Implementations for AttributeVisitor. |
| |
| public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} |
| |
| |
| public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) |
| { |
| // DEBUG = |
| // clazz.getName().equals("abc/Def") && |
| // method.getName(clazz).equals("abc"); |
| |
| // Initialize the global arrays. |
| initializeArrays(codeAttribute); |
| |
| // Analyze the liveness of the variables in the code. |
| livenessAnalyzer.visitCodeAttribute(clazz, method, codeAttribute); |
| |
| // Trim the variables in the local variable tables, because even |
| // clipping the tables individually may leave some inconsistencies |
| // between them. |
| codeAttribute.attributesAccept(clazz, method, this); |
| |
| int startIndex = |
| (method.getAccessFlags() & ClassConstants.ACC_STATIC) != 0 || |
| reuseThis ? 0 : 1; |
| |
| int parameterSize = |
| ClassUtil.internalMethodParameterSize(method.getDescriptor(clazz), |
| method.getAccessFlags()); |
| |
| int variableSize = codeAttribute.u2maxLocals; |
| int codeLength = codeAttribute.u4codeLength; |
| |
| boolean remapping = false; |
| |
| // Loop over all variables. |
| for (int oldIndex = 0; oldIndex < variableSize; oldIndex++) |
| { |
| // By default, the variable will be mapped onto itself. |
| variableMap[oldIndex] = oldIndex; |
| |
| // Only try remapping the variable if it's not a parameter. |
| if (oldIndex >= parameterSize && |
| oldIndex < MAX_VARIABLES_SIZE) |
| { |
| // Try to remap the variable to a variable with a smaller index. |
| for (int newIndex = startIndex; newIndex < oldIndex; newIndex++) |
| { |
| if (areNonOverlapping(oldIndex, newIndex, codeLength)) |
| { |
| variableMap[oldIndex] = newIndex; |
| |
| updateLiveness(oldIndex, newIndex, codeLength); |
| |
| remapping = true; |
| |
| // This variable has been remapped. Go to the next one. |
| break; |
| } |
| } |
| } |
| } |
| |
| // Have we been able to remap any variables? |
| if (remapping) |
| { |
| if (DEBUG) |
| { |
| System.out.println("VariableOptimizer: "+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)); |
| for (int index= 0; index < variableSize; index++) |
| { |
| System.out.println(" v"+index+" -> "+variableMap[index]); |
| } |
| } |
| |
| // Remap the variables. |
| variableRemapper.setVariableMap(variableMap); |
| variableRemapper.visitCodeAttribute(clazz, method, codeAttribute); |
| |
| // Visit the method, if required. |
| if (extraVariableMemberVisitor != null) |
| { |
| method.accept(clazz, extraVariableMemberVisitor); |
| } |
| } |
| else |
| { |
| // Just clean up any empty variables. |
| variableCleaner.visitCodeAttribute(clazz, method, codeAttribute); |
| } |
| } |
| |
| |
| public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute) |
| { |
| // Trim the variables in the local variable table. |
| localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); |
| } |
| |
| |
| public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute) |
| { |
| // Trim the variables in the local variable type table. |
| localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this); |
| } |
| |
| |
| // Implementations for LocalVariableInfoVisitor. |
| |
| public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo) |
| { |
| // Trim the local variable to the instructions at which it is alive. |
| int variable = localVariableInfo.u2index; |
| int startPC = localVariableInfo.u2startPC; |
| int endPC = startPC + localVariableInfo.u2length; |
| |
| startPC = firstLiveness(startPC, endPC, variable); |
| endPC = lastLiveness(startPC, endPC, variable); |
| |
| // Leave the start address of unused variables unchanged. |
| int length = endPC - startPC; |
| if (length > 0) |
| { |
| localVariableInfo.u2startPC = startPC; |
| } |
| |
| localVariableInfo.u2length = length; |
| } |
| |
| |
| // Implementations for LocalVariableTypeInfoVisitor. |
| |
| public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo) |
| { |
| // Trim the local variable type to the instructions at which it is alive. |
| int variable = localVariableTypeInfo.u2index; |
| int startPC = localVariableTypeInfo.u2startPC; |
| int endPC = startPC + localVariableTypeInfo.u2length; |
| |
| startPC = firstLiveness(startPC, endPC, variable); |
| endPC = lastLiveness(startPC, endPC, variable); |
| |
| // Leave the start address of unused variables unchanged. |
| int length = endPC - startPC; |
| if (length > 0) |
| { |
| localVariableTypeInfo.u2startPC = startPC; |
| } |
| |
| localVariableTypeInfo.u2length = length; |
| } |
| |
| |
| // Small utility methods. |
| |
| /** |
| * Initializes the global arrays. |
| */ |
| private void initializeArrays(CodeAttribute codeAttribute) |
| { |
| int codeLength = codeAttribute.u4codeLength; |
| |
| // Create new arrays for storing information at each instruction offset. |
| if (variableMap.length < codeLength) |
| { |
| variableMap = new int[codeLength]; |
| } |
| } |
| |
| |
| /** |
| * Returns whether the given variables are never alive at the same time. |
| */ |
| private boolean areNonOverlapping(int variableIndex1, |
| int variableIndex2, |
| int codeLength) |
| { |
| // Loop over all instructions. |
| for (int offset = 0; offset < codeLength; offset++) |
| { |
| if ((livenessAnalyzer.isAliveBefore(offset, variableIndex1) && |
| livenessAnalyzer.isAliveBefore(offset, variableIndex2)) || |
| |
| (livenessAnalyzer.isAliveAfter(offset, variableIndex1) && |
| livenessAnalyzer.isAliveAfter(offset, variableIndex2)) || |
| |
| // For now, exclude Category 2 variables. |
| livenessAnalyzer.isCategory2(offset, variableIndex1)) |
| { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| |
| /** |
| * Updates the liveness resulting from mapping the given old variable on |
| * the given new variable. |
| */ |
| private void updateLiveness(int oldVariableIndex, |
| int newVariableIndex, |
| int codeLength) |
| { |
| // Loop over all instructions. |
| for (int offset = 0; offset < codeLength; offset++) |
| { |
| // Update the liveness before the instruction. |
| if (livenessAnalyzer.isAliveBefore(offset, oldVariableIndex)) |
| { |
| livenessAnalyzer.setAliveBefore(offset, oldVariableIndex, false); |
| livenessAnalyzer.setAliveBefore(offset, newVariableIndex, true); |
| } |
| |
| // Update the liveness after the instruction. |
| if (livenessAnalyzer.isAliveAfter(offset, oldVariableIndex)) |
| { |
| livenessAnalyzer.setAliveAfter(offset, oldVariableIndex, false); |
| livenessAnalyzer.setAliveAfter(offset, newVariableIndex, true); |
| } |
| } |
| } |
| |
| |
| /** |
| * Returns the first instruction offset between the given offsets at which |
| * the given variable goes alive. |
| */ |
| private int firstLiveness(int startOffset, int endOffset, int variableIndex) |
| { |
| for (int offset = startOffset; offset < endOffset; offset++) |
| { |
| if (livenessAnalyzer.isTraced(offset) && |
| livenessAnalyzer.isAliveBefore(offset, variableIndex)) |
| { |
| return offset; |
| } |
| } |
| |
| return endOffset; |
| } |
| |
| |
| /** |
| * Returns the last instruction offset between the given offsets before |
| * which the given variable is still alive. |
| */ |
| private int lastLiveness(int startOffset, int endOffset, int variableIndex) |
| { |
| int previousOffset = endOffset; |
| |
| for (int offset = endOffset-1; offset >= startOffset; offset--) |
| { |
| if (livenessAnalyzer.isTraced(offset)) |
| { |
| if (livenessAnalyzer.isAliveBefore(offset, variableIndex)) |
| { |
| return previousOffset; |
| } |
| |
| previousOffset = offset; |
| } |
| } |
| |
| return endOffset; |
| } |
| } |