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     * ASM: a very small and fast Java bytecode manipulation framework
018     * Copyright (c) 2000-2011 INRIA, France Telecom
019     * All rights reserved.
020     *
021     * Redistribution and use in source and binary forms, with or without
022     * modification, are permitted provided that the following conditions
023     * are met:
024     * 1. Redistributions of source code must retain the above copyright
025     *    notice, this list of conditions and the following disclaimer.
026     * 2. Redistributions in binary form must reproduce the above copyright
027     *    notice, this list of conditions and the following disclaimer in the
028     *    documentation and/or other materials provided with the distribution.
029     * 3. Neither the name of the copyright holders nor the names of its
030     *    contributors may be used to endorse or promote products derived from
031     *    this software without specific prior written permission.
032     *
033     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
034     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
035     * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
036     * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
037     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
038     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
039     * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
040     * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
041     * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
042     * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
043     * THE POSSIBILITY OF SUCH DAMAGE.
044     *
045     */
046    
047    package org.jetbrains.kotlin.codegen.inline;
048    
049    import com.intellij.openapi.util.Factory;
050    import com.intellij.util.containers.ContainerUtil;
051    import org.jetbrains.annotations.NotNull;
052    import org.jetbrains.org.objectweb.asm.*;
053    
054    import java.util.*;
055    
056    /**
057     * This class is based on `org.objectweb.asm.MethodWriter`
058     *
059     * @author Eric Bruneton
060     * @author Eugene Kuleshov
061     * @author Denis Zharkov
062     */
063    public class MaxStackFrameSizeAndLocalsCalculator extends MaxLocalsCalculator {
064        private static final int[] FRAME_SIZE_CHANGE_BY_OPCODE;
065        static {
066            // copy-pasted from org.jetbrains.org.objectweb.asm.Frame
067            int i;
068            int[] b = new int[202];
069            String s = "EFFFFFFFFGGFFFGGFFFEEFGFGFEEEEEEEEEEEEEEEEEEEEDEDEDDDDD"
070                       + "CDCDEEEEEEEEEEEEEEEEEEEEBABABBBBDCFFFGGGEDCDCDCDCDCDCDCDCD"
071                       + "CDCEEEEDDDDDDDCDCDCEFEFDDEEFFDEDEEEBDDBBDDDDDDCCCCCCCCEFED"
072                       + "DDCDCDEEEEEEEEEEFEEEEEEDDEEDDEE";
073            for (i = 0; i < b.length; ++i) {
074                b[i] = s.charAt(i) - 'E';
075            }
076    
077            FRAME_SIZE_CHANGE_BY_OPCODE = b;
078        }
079    
080        private final LabelWrapper firstLabel;
081    
082        private LabelWrapper currentBlock;
083        private LabelWrapper previousBlock;
084    
085        /**
086         * The (relative) stack size after the last visited instruction. This size
087         * is relative to the beginning of the current basic block, i.e., the true
088         * stack size after the last visited instruction is equal to the
089         * {@link MaxStackFrameSizeAndLocalsCalculator.LabelWrapper#inputStackSize} of the current basic block
090         * plus <tt>stackSize</tt>.
091         */
092        private int stackSize;
093    
094        /**
095         * The (relative) maximum stack size after the last visited instruction.
096         * This size is relative to the beginning of the current basic block, i.e.,
097         * the true maximum stack size after the last visited instruction is equal
098         * to the {@link MaxStackFrameSizeAndLocalsCalculator.LabelWrapper#inputStackSize} of the current basic
099         * block plus <tt>stackSize</tt>.
100         */
101        private int maxStackSize;
102    
103        /**
104         * Maximum stack size of this method.
105         */
106        private int maxStack;
107    
108    
109        private final Collection<ExceptionHandler> exceptionHandlers = new LinkedList<ExceptionHandler>();
110        private final Map<Label, LabelWrapper> labelWrappersMap = new HashMap<Label, LabelWrapper>();
111    
112        public MaxStackFrameSizeAndLocalsCalculator(int api, int access, String descriptor, MethodVisitor mv) {
113            super(api, access, descriptor, mv);
114    
115            firstLabel = getLabelWrapper(new Label());
116            processLabel(firstLabel.label);
117        }
118    
119        @Override
120        public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
121            throw new AssertionError("We don't support visitFrame because currently nobody needs");
122        }
123    
124        @Override
125        public void visitInsn(int opcode) {
126            increaseStackSize(FRAME_SIZE_CHANGE_BY_OPCODE[opcode]);
127    
128            // if opcode == ATHROW or xRETURN, ends current block (no successor)
129            if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) {
130                noSuccessor();
131            }
132    
133            super.visitInsn(opcode);
134        }
135    
136        @Override
137        public void visitIntInsn(int opcode, int operand) {
138            if (opcode != Opcodes.NEWARRAY) {
139                // updates current and max stack sizes only if it's not NEWARRAY
140                // (stack size variation is 0 for NEWARRAY and +1 BIPUSH or SIPUSH)
141                increaseStackSize(1);
142            }
143    
144            super.visitIntInsn(opcode, operand);
145        }
146    
147        @Override
148        public void visitVarInsn(int opcode, int var) {
149            increaseStackSize(FRAME_SIZE_CHANGE_BY_OPCODE[opcode]);
150    
151            super.visitVarInsn(opcode, var);
152        }
153    
154        @Override
155        public void visitTypeInsn(int opcode, @NotNull String type) {
156            if (opcode == Opcodes.NEW) {
157                // updates current and max stack sizes only if opcode == NEW
158                // (no stack change for ANEWARRAY, CHECKCAST, INSTANCEOF)
159                increaseStackSize(1);
160            }
161    
162            super.visitTypeInsn(opcode, type);
163        }
164    
165        @Override
166        public void visitFieldInsn(int opcode, @NotNull String owner, @NotNull String name, @NotNull String desc) {
167            int stackSizeVariation;
168    
169            // computes the stack size variation
170            char c = desc.charAt(0);
171            switch (opcode) {
172                case Opcodes.GETSTATIC:
173                    stackSizeVariation = c == 'D' || c == 'J' ? 2 : 1;
174                    break;
175                case Opcodes.PUTSTATIC:
176                    stackSizeVariation = c == 'D' || c == 'J' ? -2 : -1;
177                    break;
178                case Opcodes.GETFIELD:
179                    stackSizeVariation = c == 'D' || c == 'J' ? 1 : 0;
180                    break;
181                // case Constants.PUTFIELD:
182                default:
183                    stackSizeVariation = c == 'D' || c == 'J' ? -3 : -2;
184                    break;
185            }
186    
187            increaseStackSize(stackSizeVariation);
188    
189            super.visitFieldInsn(opcode, owner, name, desc);
190        }
191    
192        @Override
193        public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
194            int argSize = Type.getArgumentsAndReturnSizes(desc);
195            int sizeVariation;
196            if (opcode == Opcodes.INVOKESTATIC) {
197                sizeVariation = (argSize & 0x03) - (argSize >> 2) + 1;
198            }
199            else {
200                sizeVariation = (argSize & 0x03) - (argSize >> 2);
201            }
202    
203            increaseStackSize(sizeVariation);
204    
205            super.visitMethodInsn(opcode, owner, name, desc, itf);
206        }
207    
208        @Override
209        public void visitInvokeDynamicInsn(@NotNull String name, @NotNull String desc, @NotNull Handle bsm, @NotNull Object... bsmArgs) {
210            int argSize = Type.getArgumentsAndReturnSizes(desc);
211            increaseStackSize((argSize & 0x03) - (argSize >> 2) + 1);
212    
213            super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
214        }
215    
216        @Override
217        public void visitJumpInsn(int opcode, @NotNull Label label) {
218            if (currentBlock != null) {
219                // updates current stack size (max stack size unchanged
220                // because stack size variation always negative in this
221                // case)
222                stackSize += FRAME_SIZE_CHANGE_BY_OPCODE[opcode];
223                addSuccessor(getLabelWrapper(label), stackSize);
224    
225                if (opcode == Opcodes.GOTO) {
226                    noSuccessor();
227                }
228            }
229    
230            super.visitJumpInsn(opcode, label);
231        }
232    
233        @Override
234        public void visitLabel(@NotNull Label label) {
235            processLabel(label);
236            super.visitLabel(label);
237        }
238    
239        private void processLabel(Label label) {
240            LabelWrapper wrapper = getLabelWrapper(label);
241    
242            if (currentBlock != null) {
243                // ends current block (with one new successor)
244                currentBlock.outputStackMax = maxStackSize;
245                addSuccessor(wrapper, stackSize);
246            }
247    
248            // begins a new current block
249            currentBlock = wrapper;
250            // resets the relative current and max stack sizes
251            stackSize = 0;
252            maxStackSize = 0;
253    
254            if (previousBlock != null) {
255                previousBlock.nextLabel = wrapper;
256            }
257    
258            previousBlock = wrapper;
259        }
260    
261        @Override
262        public void visitLdcInsn(@NotNull Object cst) {
263            // computes the stack size variation
264            if (cst instanceof Long || cst instanceof Double) {
265                increaseStackSize(2);
266            }
267            else {
268                increaseStackSize(1);
269            }
270    
271            super.visitLdcInsn(cst);
272        }
273    
274        @Override
275        public void visitTableSwitchInsn(int min, int max, @NotNull Label dflt, @NotNull Label... labels) {
276            visitSwitchInsn(dflt, labels);
277    
278            super.visitTableSwitchInsn(min, max, dflt, labels);
279        }
280    
281        @Override
282        public void visitLookupSwitchInsn(@NotNull Label dflt, @NotNull int[] keys, @NotNull Label[] labels) {
283            visitSwitchInsn(dflt, labels);
284    
285            super.visitLookupSwitchInsn(dflt, keys, labels);
286        }
287    
288        private void visitSwitchInsn(Label dflt, Label[] labels) {
289            if (currentBlock != null) {
290                // updates current stack size (max stack size unchanged)
291                --stackSize;
292                // adds current block successors
293                addSuccessor(getLabelWrapper(dflt), stackSize);
294                for (Label label : labels) {
295                    addSuccessor(getLabelWrapper(label), stackSize);
296                }
297                // ends current block
298                noSuccessor();
299            }
300        }
301    
302        @Override
303        public void visitMultiANewArrayInsn(@NotNull String desc, int dims) {
304            if (currentBlock != null) {
305                increaseStackSize(dims - 1);
306            }
307    
308            super.visitMultiANewArrayInsn(desc, dims);
309        }
310    
311        @Override
312        public void visitMaxs(int maxStack, int maxLocals) {
313            // completes the control flow graph with exception handler blocks
314            for (ExceptionHandler handler : exceptionHandlers) {
315                LabelWrapper l = handler.start;
316                LabelWrapper e = handler.end;
317    
318                while (l != e) {
319                    l.addSuccessor(handler.handlerLabel, 0, true);
320                    l = l.nextLabel;
321                }
322            }
323    
324            /*
325             * control flow analysis algorithm: while the block stack is not
326             * empty, pop a block from this stack, update the max stack size,
327             * compute the true (non relative) begin stack size of the
328             * successors of this block, and push these successors onto the
329             * stack (unless they have already been pushed onto the stack).
330             * Note: by hypothesis, the {@link LabelWrapper#inputStackSize} of the
331             * blocks in the block stack are the true (non relative) beginning
332             * stack sizes of these blocks.
333             */
334            int max = 0;
335            Stack<LabelWrapper> stack = new Stack<LabelWrapper>();
336            Set<LabelWrapper> pushed = new HashSet<LabelWrapper>();
337    
338            stack.push(firstLabel);
339            pushed.add(firstLabel);
340    
341            while (!stack.empty()) {
342                LabelWrapper current = stack.pop();
343                int start = current.inputStackSize;
344                int blockMax = start + current.outputStackMax;
345    
346                // updates the global max stack size
347                if (blockMax > max) {
348                    max = blockMax;
349                }
350    
351                // analyzes the successors of the block
352                for (ControlFlowEdge edge : current.successors) {
353                    LabelWrapper successor = edge.successor;
354    
355                    if (!pushed.contains(successor)) {
356                        // computes its true beginning stack size...
357                        successor.inputStackSize = edge.isExceptional ? 1 : start + edge.outputStackSize;
358                        // ...and pushes it onto the stack
359                        pushed.add(successor);
360                        stack.push(successor);
361                    }
362                }
363            }
364    
365            this.maxStack = Math.max(this.maxStack, Math.max(maxStack, max));
366    
367            super.visitMaxs(this.maxStack, maxLocals);
368        }
369    
370        @Override
371        public void visitTryCatchBlock(
372                @NotNull Label start, @NotNull Label end,
373                @NotNull Label handler, String type
374        ) {
375            ExceptionHandler exceptionHandler = new ExceptionHandler(
376                getLabelWrapper(start), getLabelWrapper(end), getLabelWrapper(handler)
377            );
378    
379            exceptionHandlers.add(exceptionHandler);
380    
381            super.visitTryCatchBlock(start, end, handler, type);
382        }
383    
384        private static class ExceptionHandler {
385            private final LabelWrapper start;
386            private final LabelWrapper end;
387            private final LabelWrapper handlerLabel;
388    
389            public ExceptionHandler(
390                    LabelWrapper start,
391                    LabelWrapper end,
392                    LabelWrapper handlerLabel
393            ) {
394                this.start = start;
395                this.end = end;
396                this.handlerLabel = handlerLabel;
397            }
398        }
399    
400        private static class ControlFlowEdge {
401            private final LabelWrapper successor;
402            private final int outputStackSize;
403            private final boolean isExceptional;
404    
405            public ControlFlowEdge(LabelWrapper successor, int outputStackSize, boolean isExceptional) {
406                this.successor = successor;
407                this.outputStackSize = outputStackSize;
408                this.isExceptional = isExceptional;
409            }
410        }
411    
412        private static class LabelWrapper {
413            private final Label label;
414            private LabelWrapper nextLabel = null;
415            private final Collection<ControlFlowEdge> successors = new LinkedList<ControlFlowEdge>();
416    
417            private int outputStackMax = 0;
418            private int inputStackSize = 0;
419            public LabelWrapper(Label label) {
420                this.label = label;
421            }
422    
423            private void addSuccessor(LabelWrapper successor, int outputStackSize, boolean isExceptional) {
424                successors.add(new ControlFlowEdge(successor, outputStackSize, isExceptional));
425            }
426        }
427    
428        // ------------------------------------------------------------------------
429        // Utility methods
430        // ------------------------------------------------------------------------
431    
432        private LabelWrapper getLabelWrapper(final Label label) {
433            return ContainerUtil.getOrCreate(labelWrappersMap, label, new Factory<LabelWrapper>() {
434                @Override
435                public LabelWrapper create() {
436                    return new LabelWrapper(label);
437                }
438            });
439        }
440    
441        private void increaseStackSize(int variation) {
442            updateStackSize(stackSize + variation);
443        }
444    
445        private void updateStackSize(int size) {
446            if (size > maxStackSize) {
447                maxStackSize = size;
448            }
449    
450            stackSize = size;
451        }
452    
453        private void addSuccessor(LabelWrapper successor, int outputStackSize) {
454            currentBlock.addSuccessor(successor, outputStackSize, false);
455        }
456    
457        /**
458         * Ends the current basic block. This method must be used in the case where
459         * the current basic block does not have any successor.
460         */
461        private void noSuccessor() {
462            if (currentBlock != null) {
463                currentBlock.outputStackMax = maxStackSize;
464                currentBlock = null;
465            }
466        }
467    }