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