001    /*
002     * Copyright 2010-2014 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.jet.codegen.optimization.boxing;
018    
019    import com.google.common.base.Predicate;
020    import com.google.common.collect.Collections2;
021    import com.intellij.openapi.util.Pair;
022    import org.jetbrains.annotations.NotNull;
023    import org.jetbrains.jet.codegen.optimization.transformer.MethodTransformer;
024    import org.jetbrains.org.objectweb.asm.Opcodes;
025    import org.jetbrains.org.objectweb.asm.Type;
026    import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter;
027    import org.jetbrains.org.objectweb.asm.tree.*;
028    import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue;
029    import org.jetbrains.org.objectweb.asm.tree.analysis.Frame;
030    
031    import java.util.*;
032    
033    public class RedundantBoxingMethodTransformer extends MethodTransformer {
034    
035        @Override
036        public void transform(@NotNull String internalClassName, @NotNull MethodNode node) {
037            RedundantBoxingInterpreter interpreter = new RedundantBoxingInterpreter(node.instructions);
038            Frame<BasicValue>[] frames = analyze(
039                    internalClassName, node, interpreter
040            );
041            interpretPopInstructionsForBoxedValues(interpreter, node, frames);
042    
043            RedundantBoxedValuesCollection valuesToOptimize = interpreter.getCandidatesBoxedValues();
044    
045            if (!valuesToOptimize.isEmpty()) {
046                // has side effect on valuesToOptimize and frames, containing BoxedBasicValues that are unsafe to remove
047                removeValuesClashingWithVariables(valuesToOptimize, node, frames);
048    
049                adaptLocalVariableTableForBoxedValues(node, frames);
050    
051                applyVariablesRemapping(node, buildVariablesRemapping(valuesToOptimize, node));
052    
053                adaptInstructionsForBoxedValues(node, valuesToOptimize);
054            }
055        }
056    
057        private static void interpretPopInstructionsForBoxedValues(
058                @NotNull RedundantBoxingInterpreter interpreter,
059                @NotNull MethodNode node,
060                @NotNull Frame<BasicValue>[] frames
061        ) {
062            for (int i = 0; i < node.instructions.size(); i++) {
063                AbstractInsnNode insn = node.instructions.get(i);
064                if ((insn.getOpcode() != Opcodes.POP && insn.getOpcode() != Opcodes.POP2) || frames[i] == null) {
065                    continue;
066                }
067    
068                BasicValue top = frames[i].getStack(frames[i].getStackSize() - 1);
069                interpreter.processPopInstruction(insn, top);
070    
071                if (top.getSize() == 1 && insn.getOpcode() == Opcodes.POP2) {
072                    interpreter.processPopInstruction(insn, frames[i].getStack(frames[i].getStackSize() - 2));
073                }
074            }
075        }
076    
077        private static void removeValuesClashingWithVariables(
078                @NotNull RedundantBoxedValuesCollection values,
079                @NotNull MethodNode node,
080                @NotNull Frame<BasicValue>[] frames
081        ) {
082            while (removeValuesClashingWithVariablesPass(values, node, frames)) {
083                // do nothing
084            }
085        }
086    
087        private static boolean removeValuesClashingWithVariablesPass(
088                @NotNull RedundantBoxedValuesCollection values,
089                @NotNull MethodNode node,
090                @NotNull Frame<BasicValue>[] frames
091        ) {
092            boolean needToRepeat = false;
093    
094            for (LocalVariableNode localVariableNode : node.localVariables) {
095                if (Type.getType(localVariableNode.desc).getSort() != Type.OBJECT) {
096                    continue;
097                }
098    
099                List<BasicValue> usedValues = getValuesStoredOrLoadedToVariable(localVariableNode, node, frames);
100    
101                Collection<BasicValue> boxed = Collections2.filter(usedValues, new Predicate<BasicValue>() {
102                    @Override
103                    public boolean apply(BasicValue input) {
104                        return input instanceof BoxedBasicValue;
105                    }
106                });
107    
108                if (boxed.isEmpty()) continue;
109    
110                final BoxedBasicValue firstBoxed = (BoxedBasicValue) boxed.iterator().next();
111    
112                if (!Collections2.filter(usedValues, new Predicate<BasicValue>() {
113                    @Override
114                    public boolean apply(BasicValue input) {
115                        return input == null ||
116                               !(input instanceof BoxedBasicValue) ||
117                               !((BoxedBasicValue) input).isSafeToRemove() ||
118                               !((BoxedBasicValue) input).getPrimitiveType().equals(firstBoxed.getPrimitiveType());
119                    }
120                }).isEmpty()) {
121                    for (BasicValue value : usedValues) {
122                        if (value instanceof BoxedBasicValue && ((BoxedBasicValue) value).isSafeToRemove()) {
123                            values.remove((BoxedBasicValue) value);
124                            needToRepeat = true;
125                        }
126                    }
127                }
128            }
129    
130            return needToRepeat;
131        }
132    
133        private static void adaptLocalVariableTableForBoxedValues(@NotNull MethodNode node, @NotNull Frame<BasicValue>[] frames) {
134            for (LocalVariableNode localVariableNode : node.localVariables) {
135                if (Type.getType(localVariableNode.desc).getSort() != Type.OBJECT) {
136                    continue;
137                }
138    
139                for (BasicValue value : getValuesStoredOrLoadedToVariable(localVariableNode, node, frames)) {
140                    if (value == null || !(value instanceof BoxedBasicValue) || !((BoxedBasicValue) value).isSafeToRemove()) continue;
141                    localVariableNode.desc = ((BoxedBasicValue) value).getPrimitiveType().getDescriptor();
142                }
143            }
144        }
145    
146        @NotNull
147        private static List<BasicValue> getValuesStoredOrLoadedToVariable(
148                @NotNull LocalVariableNode localVariableNode,
149                @NotNull MethodNode node,
150                @NotNull Frame<BasicValue>[] frames
151        ) {
152            List<BasicValue> values = new ArrayList<BasicValue>();
153            InsnList insnList = node.instructions;
154            int from = insnList.indexOf(localVariableNode.start) + 1;
155            int to = insnList.indexOf(localVariableNode.end) - 1;
156    
157            for (int i = from; i <= to; i++) {
158                if (i < 0 || i >= insnList.size()) continue;
159    
160                AbstractInsnNode insn = insnList.get(i);
161                if ((insn.getOpcode() == Opcodes.ASTORE || insn.getOpcode() == Opcodes.ALOAD) &&
162                    ((VarInsnNode) insn).var == localVariableNode.index) {
163    
164                    // frames[i] can be null in case of exception handlers
165                    if (frames[i] == null) {
166                        values.add(null);
167                        continue;
168                    }
169    
170                    if (insn.getOpcode() == Opcodes.ASTORE) {
171                        values.add(frames[i].getStack(frames[i].getStackSize() - 1));
172                    }
173                    else {
174                        values.add(frames[i].getLocal(((VarInsnNode) insn).var));
175                    }
176                }
177            }
178    
179            return values;
180        }
181    
182        @NotNull
183        private static int[] buildVariablesRemapping(@NotNull RedundantBoxedValuesCollection values, @NotNull MethodNode node) {
184            Set<Integer> doubleSizedVars = new HashSet<Integer>();
185            for (BoxedBasicValue value : values) {
186                if (value.getPrimitiveType().getSize() == 2) {
187                    doubleSizedVars.addAll(value.getVariablesIndexes());
188                }
189            }
190    
191            node.maxLocals += doubleSizedVars.size();
192            int[] remapping = new int[node.maxLocals];
193            for (int i = 0; i < remapping.length; i++) {
194                remapping[i] = i;
195            }
196    
197            for (int varIndex : doubleSizedVars) {
198                for (int i = varIndex + 1; i < remapping.length; i++) {
199                    remapping[i]++;
200                }
201            }
202    
203            return remapping;
204        }
205    
206        private static void applyVariablesRemapping(@NotNull MethodNode node, @NotNull int[] remapping) {
207            for (AbstractInsnNode insn : node.instructions.toArray()) {
208                if (insn instanceof VarInsnNode) {
209                    ((VarInsnNode) insn).var = remapping[((VarInsnNode) insn).var];
210                }
211                if (insn instanceof IincInsnNode) {
212                    ((IincInsnNode) insn).var = remapping[((IincInsnNode) insn).var];
213                }
214            }
215    
216            for (LocalVariableNode localVariableNode : node.localVariables) {
217                localVariableNode.index = remapping[localVariableNode.index];
218            }
219        }
220    
221        private static void adaptInstructionsForBoxedValues(
222                @NotNull MethodNode node,
223                @NotNull RedundantBoxedValuesCollection values
224        ) {
225            for (BoxedBasicValue value : values) {
226                adaptInstructionsForBoxedValue(node, value);
227            }
228        }
229    
230        private static void adaptInstructionsForBoxedValue(@NotNull MethodNode node, @NotNull BoxedBasicValue value) {
231            adaptBoxingInstruction(node, value);
232    
233            for (Pair<AbstractInsnNode, Type> cast : value.getUnboxingWithCastInsns()) {
234                adaptCastInstruction(node, value, cast);
235            }
236    
237            for (AbstractInsnNode insn : value.getAssociatedInsns()) {
238                adaptInstruction(node, insn, value);
239            }
240        }
241    
242        private static void adaptBoxingInstruction(@NotNull MethodNode node, @NotNull BoxedBasicValue value) {
243            if (!value.isFromProgressionIterator()) {
244                node.instructions.remove(value.getBoxingInsn());
245            }
246            else {
247                ProgressionIteratorBasicValue iterator = value.getProgressionIterator();
248                assert iterator != null : "iterator should not be null because isFromProgressionIterator returns true";
249    
250                //add checkcast to kotlin/<T>Iterator before next() call
251                node.instructions.insertBefore(
252                        value.getBoxingInsn(),
253                        new TypeInsnNode(Opcodes.CHECKCAST, iterator.getType().getInternalName())
254                );
255    
256                //invoke concrete method (kotlin/<T>iteraror.next<T>())
257                node.instructions.set(
258                        value.getBoxingInsn(),
259                        new MethodInsnNode(
260                                Opcodes.INVOKEVIRTUAL,
261                                iterator.getType().getInternalName(),
262                                iterator.getNextMethodName(),
263                                iterator.getNextMethodDesc(),
264                                false
265                        )
266                );
267            }
268        }
269    
270        private static void adaptCastInstruction(
271                @NotNull MethodNode node,
272                @NotNull BoxedBasicValue value,
273                @NotNull Pair<AbstractInsnNode, Type> castWithType
274        ) {
275            AbstractInsnNode castInsn = castWithType.getFirst();
276            MethodNode castInsnsListener = new MethodNode(Opcodes.ASM5);
277            new InstructionAdapter(castInsnsListener).cast(value.getPrimitiveType(), castWithType.getSecond());
278    
279            for (AbstractInsnNode insn : castInsnsListener.instructions.toArray()) {
280                node.instructions.insertBefore(castInsn, insn);
281            }
282    
283            node.instructions.remove(castInsn);
284        }
285    
286        private static void adaptInstruction(
287                @NotNull MethodNode node, @NotNull AbstractInsnNode insn, @NotNull BoxedBasicValue value
288        ) {
289            boolean isDoubleSize = value.isDoubleSize();
290    
291            switch (insn.getOpcode()) {
292                case Opcodes.POP:
293                    if (isDoubleSize) {
294                        node.instructions.set(
295                                insn,
296                                new InsnNode(Opcodes.POP2)
297                        );
298                    }
299                    break;
300                case Opcodes.DUP:
301                    if (isDoubleSize) {
302                        node.instructions.set(
303                                insn,
304                                new InsnNode(Opcodes.DUP2)
305                        );
306                    }
307                    break;
308                case Opcodes.ASTORE:
309                case Opcodes.ALOAD:
310                    int intVarOpcode = insn.getOpcode() == Opcodes.ASTORE ? Opcodes.ISTORE : Opcodes.ILOAD;
311                    node.instructions.set(
312                            insn,
313                            new VarInsnNode(
314                                    value.getPrimitiveType().getOpcode(intVarOpcode),
315                                    ((VarInsnNode) insn).var
316                            )
317                    );
318                    break;
319                case Opcodes.INSTANCEOF:
320                    node.instructions.insertBefore(
321                            insn,
322                            new InsnNode(isDoubleSize ? Opcodes.POP2 : Opcodes.POP)
323                    );
324                    node.instructions.set(insn, new InsnNode(Opcodes.ICONST_1));
325                    break;
326                default:
327                    // CHECKCAST or unboxing-method call
328                    node.instructions.remove(insn);
329            }
330        }
331    }