Home | History | Annotate | Download | only in evaluation
      1 /*
      2  * ProGuard -- shrinking, optimization, obfuscation, and preverification
      3  *             of Java bytecode.
      4  *
      5  * Copyright (c) 2002-2014 Eric Lafortune (eric (at) graphics.cornell.edu)
      6  *
      7  * This program is free software; you can redistribute it and/or modify it
      8  * under the terms of the GNU General Public License as published by the Free
      9  * Software Foundation; either version 2 of the License, or (at your option)
     10  * any later version.
     11  *
     12  * This program is distributed in the hope that it will be useful, but WITHOUT
     13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     14  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
     15  * more details.
     16  *
     17  * You should have received a copy of the GNU General Public License along
     18  * with this program; if not, write to the Free Software Foundation, Inc.,
     19  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
     20  */
     21 package proguard.optimize.evaluation;
     22 
     23 import proguard.classfile.*;
     24 import proguard.classfile.attribute.*;
     25 import proguard.classfile.attribute.visitor.*;
     26 import proguard.classfile.editor.*;
     27 import proguard.classfile.util.*;
     28 import proguard.classfile.visitor.MemberVisitor;
     29 
     30 /**
     31  * This AttributeVisitor optimizes variable allocation based on their the liveness,
     32  * in the code attributes that it visits.
     33  *
     34  * @author Eric Lafortune
     35  */
     36 public class VariableOptimizer
     37 extends      SimplifiedVisitor
     38 implements   AttributeVisitor,
     39              LocalVariableInfoVisitor,
     40              LocalVariableTypeInfoVisitor
     41 {
     42     //*
     43     private static final boolean DEBUG = false;
     44     /*/
     45     private static       boolean DEBUG = true;
     46     //*/
     47 
     48     private static final int MAX_VARIABLES_SIZE = 64;
     49 
     50 
     51     private final boolean       reuseThis;
     52     private final MemberVisitor extraVariableMemberVisitor;
     53 
     54     private final LivenessAnalyzer livenessAnalyzer = new LivenessAnalyzer();
     55     private final VariableRemapper variableRemapper = new VariableRemapper();
     56     private       VariableCleaner  variableCleaner  = new VariableCleaner();
     57 
     58     private int[] variableMap = new int[ClassConstants.TYPICAL_VARIABLES_SIZE];
     59 
     60 
     61     /**
     62      * Creates a new VariableOptimizer.
     63      * @param reuseThis specifies whether the 'this' variable can be reused.
     64      *                  Many JVMs for JME and IBM's JVMs for JSE can't handle
     65      *                  such reuse.
     66      */
     67     public VariableOptimizer(boolean reuseThis)
     68     {
     69         this(reuseThis, null);
     70     }
     71 
     72 
     73     /**
     74      * Creates a new VariableOptimizer with an extra visitor.
     75      * @param reuseThis                  specifies whether the 'this' variable
     76      *                                   can be reused. Many JVMs for JME and
     77      *                                   IBM's JVMs for JSE can't handle such
     78      *                                   reuse.
     79      * @param extraVariableMemberVisitor an optional extra visitor for all
     80      *                                   removed variables.
     81      */
     82     public VariableOptimizer(boolean       reuseThis,
     83                              MemberVisitor extraVariableMemberVisitor)
     84     {
     85         this.reuseThis                  = reuseThis;
     86         this.extraVariableMemberVisitor = extraVariableMemberVisitor;
     87     }
     88 
     89 
     90     // Implementations for AttributeVisitor.
     91 
     92     public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
     93 
     94 
     95     public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
     96     {
     97 //        DEBUG =
     98 //            clazz.getName().equals("abc/Def") &&
     99 //            method.getName(clazz).equals("abc");
    100 
    101         // Initialize the global arrays.
    102         initializeArrays(codeAttribute);
    103 
    104         // Analyze the liveness of the variables in the code.
    105         livenessAnalyzer.visitCodeAttribute(clazz, method, codeAttribute);
    106 
    107         // Trim the variables in the local variable tables, because even
    108         // clipping the tables individually may leave some inconsistencies
    109         // between them.
    110         codeAttribute.attributesAccept(clazz, method, this);
    111 
    112         int startIndex =
    113             (method.getAccessFlags() & ClassConstants.ACC_STATIC) != 0 ||
    114             reuseThis ? 0 : 1;
    115 
    116         int parameterSize =
    117             ClassUtil.internalMethodParameterSize(method.getDescriptor(clazz),
    118                                                   method.getAccessFlags());
    119 
    120         int variableSize = codeAttribute.u2maxLocals;
    121         int codeLength   = codeAttribute.u4codeLength;
    122 
    123         boolean remapping = false;
    124 
    125         // Loop over all variables.
    126         for (int oldIndex = 0; oldIndex < variableSize; oldIndex++)
    127         {
    128             // By default, the variable will be mapped onto itself.
    129             variableMap[oldIndex] = oldIndex;
    130 
    131             // Only try remapping the variable if it's not a parameter.
    132             if (oldIndex >= parameterSize &&
    133                 oldIndex < MAX_VARIABLES_SIZE)
    134             {
    135                 // Try to remap the variable to a variable with a smaller index.
    136                 for (int newIndex = startIndex; newIndex < oldIndex; newIndex++)
    137                 {
    138                     if (areNonOverlapping(oldIndex, newIndex, codeLength))
    139                     {
    140                         variableMap[oldIndex] = newIndex;
    141 
    142                         updateLiveness(oldIndex, newIndex, codeLength);
    143 
    144                         remapping = true;
    145 
    146                         // This variable has been remapped. Go to the next one.
    147                         break;
    148                     }
    149                 }
    150             }
    151         }
    152 
    153         // Have we been able to remap any variables?
    154         if (remapping)
    155         {
    156             if (DEBUG)
    157             {
    158                 System.out.println("VariableOptimizer: "+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz));
    159                 for (int index= 0; index < variableSize; index++)
    160                 {
    161                     System.out.println("  v"+index+" -> "+variableMap[index]);
    162                 }
    163             }
    164 
    165             // Remap the variables.
    166             variableRemapper.setVariableMap(variableMap);
    167             variableRemapper.visitCodeAttribute(clazz, method, codeAttribute);
    168 
    169             // Visit the method, if required.
    170             if (extraVariableMemberVisitor != null)
    171             {
    172                 method.accept(clazz, extraVariableMemberVisitor);
    173             }
    174         }
    175         else
    176         {
    177             // Just clean up any empty variables.
    178             variableCleaner.visitCodeAttribute(clazz, method, codeAttribute);
    179         }
    180     }
    181 
    182 
    183     public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)
    184     {
    185         // Trim the variables in the local variable table.
    186         localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
    187     }
    188 
    189 
    190     public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)
    191     {
    192         // Trim the variables in the local variable type table.
    193         localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
    194     }
    195 
    196 
    197     // Implementations for LocalVariableInfoVisitor.
    198 
    199     public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo)
    200     {
    201         // Trim the local variable to the instructions at which it is alive.
    202         int variable = localVariableInfo.u2index;
    203         int startPC  = localVariableInfo.u2startPC;
    204         int endPC    = startPC + localVariableInfo.u2length;
    205 
    206         startPC = firstLiveness(startPC, endPC, variable);
    207         endPC   = lastLiveness(startPC, endPC, variable);
    208 
    209         // Leave the start address of unused variables unchanged.
    210         int length = endPC - startPC;
    211         if (length > 0)
    212         {
    213             localVariableInfo.u2startPC = startPC;
    214         }
    215 
    216         localVariableInfo.u2length  = length;
    217     }
    218 
    219 
    220     // Implementations for LocalVariableTypeInfoVisitor.
    221 
    222     public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo)
    223     {
    224         // Trim the local variable type to the instructions at which it is alive.
    225         int variable = localVariableTypeInfo.u2index;
    226         int startPC  = localVariableTypeInfo.u2startPC;
    227         int endPC    = startPC + localVariableTypeInfo.u2length;
    228 
    229         startPC = firstLiveness(startPC, endPC, variable);
    230         endPC   = lastLiveness(startPC, endPC, variable);
    231 
    232         // Leave the start address of unused variables unchanged.
    233         int length = endPC - startPC;
    234         if (length > 0)
    235         {
    236             localVariableTypeInfo.u2startPC = startPC;
    237         }
    238 
    239         localVariableTypeInfo.u2length  = length;
    240     }
    241 
    242 
    243     // Small utility methods.
    244 
    245     /**
    246      * Initializes the global arrays.
    247      */
    248     private void initializeArrays(CodeAttribute codeAttribute)
    249     {
    250         int codeLength = codeAttribute.u4codeLength;
    251 
    252         // Create new arrays for storing information at each instruction offset.
    253         if (variableMap.length < codeLength)
    254         {
    255             variableMap = new int[codeLength];
    256         }
    257     }
    258 
    259 
    260     /**
    261      * Returns whether the given variables are never alive at the same time.
    262      */
    263     private boolean areNonOverlapping(int variableIndex1,
    264                                       int variableIndex2,
    265                                       int codeLength)
    266     {
    267         // Loop over all instructions.
    268         for (int offset = 0; offset < codeLength; offset++)
    269         {
    270             if ((livenessAnalyzer.isAliveBefore(offset, variableIndex1) &&
    271                  livenessAnalyzer.isAliveBefore(offset, variableIndex2)) ||
    272 
    273                 (livenessAnalyzer.isAliveAfter(offset, variableIndex1) &&
    274                  livenessAnalyzer.isAliveAfter(offset, variableIndex2)) ||
    275 
    276                 // For now, exclude Category 2 variables.
    277                 livenessAnalyzer.isCategory2(offset, variableIndex1))
    278             {
    279                 return false;
    280             }
    281         }
    282 
    283         return true;
    284     }
    285 
    286 
    287     /**
    288      * Updates the liveness resulting from mapping the given old variable on
    289      * the given new variable.
    290      */
    291     private void updateLiveness(int oldVariableIndex,
    292                                 int newVariableIndex,
    293                                 int codeLength)
    294     {
    295         // Loop over all instructions.
    296         for (int offset = 0; offset < codeLength; offset++)
    297         {
    298             // Update the liveness before the instruction.
    299             if (livenessAnalyzer.isAliveBefore(offset, oldVariableIndex))
    300             {
    301                 livenessAnalyzer.setAliveBefore(offset, oldVariableIndex, false);
    302                 livenessAnalyzer.setAliveBefore(offset, newVariableIndex, true);
    303             }
    304 
    305             // Update the liveness after the instruction.
    306             if (livenessAnalyzer.isAliveAfter(offset, oldVariableIndex))
    307             {
    308                 livenessAnalyzer.setAliveAfter(offset, oldVariableIndex, false);
    309                 livenessAnalyzer.setAliveAfter(offset, newVariableIndex, true);
    310             }
    311         }
    312     }
    313 
    314 
    315     /**
    316      * Returns the first instruction offset between the given offsets at which
    317      * the given variable goes alive.
    318      */
    319     private int firstLiveness(int startOffset, int endOffset, int variableIndex)
    320     {
    321         for (int offset = startOffset; offset < endOffset; offset++)
    322         {
    323             if (livenessAnalyzer.isTraced(offset) &&
    324                 livenessAnalyzer.isAliveBefore(offset, variableIndex))
    325             {
    326                 return offset;
    327             }
    328         }
    329 
    330         return endOffset;
    331     }
    332 
    333 
    334     /**
    335      * Returns the last instruction offset between the given offsets before
    336      * which the given variable is still alive.
    337      */
    338     private int lastLiveness(int startOffset, int endOffset, int variableIndex)
    339     {
    340         int previousOffset = endOffset;
    341 
    342         for (int offset = endOffset-1; offset >= startOffset; offset--)
    343         {
    344             if (livenessAnalyzer.isTraced(offset))
    345             {
    346                 if (livenessAnalyzer.isAliveBefore(offset, variableIndex))
    347                 {
    348                     return previousOffset;
    349                 }
    350 
    351                 previousOffset = offset;
    352             }
    353         }
    354 
    355         return endOffset;
    356     }
    357 }
    358