TailRecursionSimplifier.java revision db267bc191f906f55eaef21a27110cce2ec57fdf
1/*
2 * ProGuard -- shrinking, optimization, obfuscation, and preverification
3 *             of Java bytecode.
4 *
5 * Copyright (c) 2002-2009 Eric Lafortune (eric@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 */
21package proguard.optimize;
22
23import proguard.classfile.*;
24import proguard.classfile.attribute.*;
25import proguard.classfile.attribute.visitor.*;
26import proguard.classfile.constant.MethodrefConstant;
27import proguard.classfile.constant.visitor.ConstantVisitor;
28import proguard.classfile.editor.CodeAttributeComposer;
29import proguard.classfile.instruction.*;
30import proguard.classfile.instruction.visitor.InstructionVisitor;
31import proguard.classfile.util.*;
32
33/**
34 * This MemberVisitor simplifies tail recursion calls in  all methods that it
35 * visits.
36 *
37 * @author Eric Lafortune
38 */
39public class TailRecursionSimplifier
40extends      SimplifiedVisitor
41implements   AttributeVisitor,
42             InstructionVisitor,
43             ConstantVisitor,
44             ExceptionInfoVisitor
45{
46    //*
47    private static final boolean DEBUG = false;
48    /*/
49    private static       boolean DEBUG = true;
50    //*/
51
52
53    private final InstructionVisitor extraTailRecursionVisitor;
54
55
56    private final CodeAttributeComposer codeAttributeComposer = new CodeAttributeComposer();
57
58    private Method  targetMethod;
59    private boolean recursive;
60    private boolean inlinedAny;
61
62
63
64    /**
65     * Creates a new TailRecursionSimplifier.
66     */
67    public TailRecursionSimplifier()
68    {
69        this(null);
70    }
71
72
73    /**
74     * Creates a new TailRecursionSimplifier with an extra visitor.
75     * @param extraTailRecursionVisitor an optional extra visitor for all
76     *                                  simplified tail recursions.
77     */
78    public TailRecursionSimplifier(InstructionVisitor extraTailRecursionVisitor)
79    {
80        this.extraTailRecursionVisitor = extraTailRecursionVisitor;
81    }
82
83
84    // Implementations for AttributeVisitor.
85
86    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
87
88
89    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
90    {
91        int accessFlags = method.getAccessFlags();
92
93        if (// Only check the method if it is private, static, or final.
94            (accessFlags & (ClassConstants.INTERNAL_ACC_PRIVATE |
95                            ClassConstants.INTERNAL_ACC_STATIC  |
96                            ClassConstants.INTERNAL_ACC_FINAL)) != 0 &&
97
98            // Only check the method if it is not synchronized, etc.
99            (accessFlags & (ClassConstants.INTERNAL_ACC_SYNCHRONIZED |
100                            ClassConstants.INTERNAL_ACC_NATIVE       |
101                            ClassConstants.INTERNAL_ACC_INTERFACE    |
102                            ClassConstants.INTERNAL_ACC_ABSTRACT)) == 0)
103        {
104//            codeAttributeComposer.DEBUG = DEBUG =
105//                clazz.getName().equals("abc/Def") &&
106//                method.getName(clazz).equals("abc");
107
108            targetMethod    = method;
109            inlinedAny      = false;
110            codeAttributeComposer.reset();
111
112            // Append the body of the code.
113            copyCode(clazz, method, codeAttribute);
114
115            // Update the code attribute if any code has been inlined.
116            if (inlinedAny)
117            {
118                codeAttributeComposer.visitCodeAttribute(clazz, method, codeAttribute);
119            }
120        }
121    }
122
123
124    /**
125     * Appends the code of the given code attribute.
126     */
127    private void copyCode(Clazz clazz, Method method, CodeAttribute codeAttribute)
128    {
129        // The code may expand, due to expanding constant and variable
130        // instructions.
131        codeAttributeComposer.beginCodeFragment(codeAttribute.u4codeLength);
132
133        // Copy the instructions.
134        codeAttribute.instructionsAccept(clazz, method, this);
135
136        // Append a label just after the code.
137        codeAttributeComposer.appendLabel(codeAttribute.u4codeLength);
138
139        codeAttributeComposer.endCodeFragment();
140    }
141
142
143    // Implementations for InstructionVisitor.
144
145    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction)
146    {
147        // Copy the instruction.
148        codeAttributeComposer.appendInstruction(offset, instruction.shrink());
149    }
150
151
152    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
153    {
154        // Is it a method invocation?
155        switch (constantInstruction.opcode)
156        {
157            case InstructionConstants.OP_INVOKEVIRTUAL:
158            case InstructionConstants.OP_INVOKESPECIAL:
159            case InstructionConstants.OP_INVOKESTATIC:
160            {
161                // Is it a recursive call?
162                clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
163
164                if (recursive)
165                {
166                    // Is the next instruction a return?
167                    int nextOffset =
168                        offset + constantInstruction.length(offset);
169
170                    Instruction nextInstruction =
171                        InstructionFactory.create(codeAttribute.code, nextOffset);
172
173                    switch (nextInstruction.opcode)
174                    {
175                        case InstructionConstants.OP_IRETURN:
176                        case InstructionConstants.OP_LRETURN:
177                        case InstructionConstants.OP_FRETURN:
178                        case InstructionConstants.OP_DRETURN:
179                        case InstructionConstants.OP_ARETURN:
180                        case InstructionConstants.OP_RETURN:
181                        {
182                            // Isn't the recursive call inside a try/catch block?
183                            codeAttribute.exceptionsAccept(clazz, method, offset, this);
184
185                            if (recursive)
186                            {
187                                if (DEBUG)
188                                {
189                                    System.out.println("TailRecursionSimplifier.visitConstantInstruction: ["+
190                                                       clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"], inlining "+constantInstruction.toString(offset));
191                                }
192
193                                // Append a label.
194                                codeAttributeComposer.appendLabel(offset);
195
196                                storeParameters(clazz, method);
197
198                                // Branch back to the start of the method.
199                                int gotoOffset = offset + 1;
200                                codeAttributeComposer.appendInstruction(gotoOffset,
201                                                                        new BranchInstruction(InstructionConstants.OP_GOTO, -gotoOffset));
202
203                                // The original return instruction will be
204                                // removed elsewhere, if possible.
205
206                                // Remember that the code has changed.
207                                inlinedAny = true;
208
209                                if (extraTailRecursionVisitor != null)
210                                {
211                                    extraTailRecursionVisitor.visitConstantInstruction(clazz, method, codeAttribute, offset, constantInstruction);
212                                }
213
214                                // The invocation itself is no longer necessary.
215                                return;
216                            }
217                        }
218                    }
219                }
220
221                break;
222            }
223        }
224
225        // Copy the instruction.
226        codeAttributeComposer.appendInstruction(offset, constantInstruction.shrink());
227    }
228
229
230    // Implementations for ConstantVisitor.
231
232    public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant)
233    {
234        recursive = targetMethod.equals(methodrefConstant.referencedMember);
235    }
236
237
238    // Implementations for ExceptionInfoVisitor.
239
240    public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
241    {
242        recursive = false;
243    }
244
245
246    // Small utility methods.
247
248    /**
249     * Appends instructions to pop the parameters for the given method, storing
250     * them in new local variables.
251     */
252    private void storeParameters(Clazz clazz, Method method)
253    {
254        String descriptor = method.getDescriptor(clazz);
255
256        boolean isStatic =
257            (method.getAccessFlags() & ClassConstants.INTERNAL_ACC_STATIC) != 0;
258
259        // Count the number of parameters, taking into account their categories.
260        int parameterCount  = ClassUtil.internalMethodParameterCount(descriptor);
261        int parameterSize   = ClassUtil.internalMethodParameterSize(descriptor);
262        int parameterOffset = isStatic ? 0 : 1;
263
264        // Store the parameter types.
265        String[] parameterTypes = new String[parameterSize];
266
267        InternalTypeEnumeration internalTypeEnumeration =
268            new InternalTypeEnumeration(descriptor);
269
270        for (int parameterIndex = 0; parameterIndex < parameterSize; parameterIndex++)
271        {
272            String parameterType = internalTypeEnumeration.nextType();
273            parameterTypes[parameterIndex] = parameterType;
274            if (ClassUtil.internalTypeSize(parameterType) == 2)
275            {
276                parameterIndex++;
277            }
278        }
279
280        codeAttributeComposer.beginCodeFragment(parameterSize + 1);
281
282        // Go over the parameter types backward, storing the stack entries
283        // in their corresponding variables.
284        for (int parameterIndex = parameterSize-1; parameterIndex >= 0; parameterIndex--)
285        {
286            String parameterType = parameterTypes[parameterIndex];
287            if (parameterType != null)
288            {
289                byte opcode;
290                switch (parameterType.charAt(0))
291                {
292                    case ClassConstants.INTERNAL_TYPE_BOOLEAN:
293                    case ClassConstants.INTERNAL_TYPE_BYTE:
294                    case ClassConstants.INTERNAL_TYPE_CHAR:
295                    case ClassConstants.INTERNAL_TYPE_SHORT:
296                    case ClassConstants.INTERNAL_TYPE_INT:
297                        opcode = InstructionConstants.OP_ISTORE;
298                        break;
299
300                    case ClassConstants.INTERNAL_TYPE_LONG:
301                        opcode = InstructionConstants.OP_LSTORE;
302                        break;
303
304                    case ClassConstants.INTERNAL_TYPE_FLOAT:
305                        opcode = InstructionConstants.OP_FSTORE;
306                        break;
307
308                    case ClassConstants.INTERNAL_TYPE_DOUBLE:
309                        opcode = InstructionConstants.OP_DSTORE;
310                        break;
311
312                    default:
313                        opcode = InstructionConstants.OP_ASTORE;
314                        break;
315                }
316
317                codeAttributeComposer.appendInstruction(parameterSize-parameterIndex-1,
318                                                        new VariableInstruction(opcode, parameterOffset + parameterIndex).shrink());
319            }
320        }
321
322        // Put the 'this' reference in variable 0.
323        if (!isStatic)
324        {
325            codeAttributeComposer.appendInstruction(parameterSize,
326                                                    new VariableInstruction(InstructionConstants.OP_ASTORE, 0).shrink());
327        }
328
329        codeAttributeComposer.endCodeFragment();
330    }
331}