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