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.inline;
018    
019    import com.intellij.openapi.util.Factory;
020    import com.intellij.util.containers.ContainerUtil;
021    import org.jetbrains.annotations.NotNull;
022    import org.jetbrains.org.objectweb.asm.*;
023    
024    import java.util.*;
025    
026    public class MaxStackFrameSizeAndLocalsCalculator extends MaxLocalsCalculator {
027        private static final int[] FRAME_SIZE_CHANGE_BY_OPCODE;
028        static {
029            // copy-pasted from org.jetbrains.org.objectweb.asm.Frame
030            int i;
031            int[] b = new int[202];
032            String s = "EFFFFFFFFGGFFFGGFFFEEFGFGFEEEEEEEEEEEEEEEEEEEEDEDEDDDDD"
033                       + "CDCDEEEEEEEEEEEEEEEEEEEEBABABBBBDCFFFGGGEDCDCDCDCDCDCDCDCD"
034                       + "CDCEEEEDDDDDDDCDCDCEFEFDDEEFFDEDEEEBDDBBDDDDDDCCCCCCCCEFED"
035                       + "DDCDCDEEEEEEEEEEFEEEEEEDDEEDDEE";
036            for (i = 0; i < b.length; ++i) {
037                b[i] = s.charAt(i) - 'E';
038            }
039            
040            FRAME_SIZE_CHANGE_BY_OPCODE = b;
041        }
042    
043        private final LabelWrapper firstLabel;
044    
045        private LabelWrapper currentBlock;
046        private LabelWrapper previousBlock;
047    
048        /**
049         * The (relative) stack size after the last visited instruction. This size
050         * is relative to the beginning of the current basic block, i.e., the true
051         * stack size after the last visited instruction is equal to the
052         * {@link MaxStackFrameSizeAndLocalsCalculator.LabelWrapper#inputStackSize} of the current basic block
053         * plus <tt>stackSize</tt>.
054         */
055        private int stackSize;
056    
057        /**
058         * The (relative) maximum stack size after the last visited instruction.
059         * This size is relative to the beginning of the current basic block, i.e.,
060         * the true maximum stack size after the last visited instruction is equal
061         * to the {@link MaxStackFrameSizeAndLocalsCalculator.LabelWrapper#inputStackSize} of the current basic
062         * block plus <tt>stackSize</tt>.
063         */
064        private int maxStackSize;
065    
066        /**
067         * Maximum stack size of this method.
068         */
069        private int maxStack;
070    
071    
072        private final Collection<ExceptionHandler> exceptionHandlers = new LinkedList<ExceptionHandler>();
073        private final Map<Label, LabelWrapper> labelWrappersMap = new HashMap<Label, LabelWrapper>();
074    
075        public MaxStackFrameSizeAndLocalsCalculator(int api, int access, String descriptor, MethodVisitor mv) {
076            super(api, access, descriptor, mv);
077    
078            firstLabel = getLabelWrapper(new Label());
079            processLabel(firstLabel.label);
080        }
081    
082        @Override
083        public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
084            throw new AssertionError("We don't support visitFrame because currently nobody needs");
085        }
086    
087        @Override
088        public void visitInsn(int opcode) {
089            increaseStackSize(FRAME_SIZE_CHANGE_BY_OPCODE[opcode]);
090    
091            // if opcode == ATHROW or xRETURN, ends current block (no successor)
092            if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) {
093                noSuccessor();
094            }
095    
096            super.visitInsn(opcode);
097        }
098    
099        @Override
100        public void visitIntInsn(int opcode, int operand) {
101            if (opcode != Opcodes.NEWARRAY) {
102                // updates current and max stack sizes only if it's not NEWARRAY
103                // (stack size variation is 0 for NEWARRAY and +1 BIPUSH or SIPUSH)
104                increaseStackSize(1);
105            }
106    
107            super.visitIntInsn(opcode, operand);
108        }
109    
110        @Override
111        public void visitVarInsn(int opcode, int var) {
112            increaseStackSize(FRAME_SIZE_CHANGE_BY_OPCODE[opcode]);
113    
114            super.visitVarInsn(opcode, var);
115        }
116    
117        @Override
118        public void visitTypeInsn(int opcode, @NotNull String type) {
119            if (opcode == Opcodes.NEW) {
120                // updates current and max stack sizes only if opcode == NEW
121                // (no stack change for ANEWARRAY, CHECKCAST, INSTANCEOF)
122                increaseStackSize(1);
123            }
124    
125            super.visitTypeInsn(opcode, type);
126        }
127    
128        @Override
129        public void visitFieldInsn(int opcode, @NotNull String owner, @NotNull String name, @NotNull String desc) {
130            int stackSizeVariation;
131    
132            // computes the stack size variation
133            char c = desc.charAt(0);
134            switch (opcode) {
135                case Opcodes.GETSTATIC:
136                    stackSizeVariation = c == 'D' || c == 'J' ? 2 : 1;
137                    break;
138                case Opcodes.PUTSTATIC:
139                    stackSizeVariation = c == 'D' || c == 'J' ? -2 : -1;
140                    break;
141                case Opcodes.GETFIELD:
142                    stackSizeVariation = c == 'D' || c == 'J' ? 1 : 0;
143                    break;
144                // case Constants.PUTFIELD:
145                default:
146                    stackSizeVariation = c == 'D' || c == 'J' ? -3 : -2;
147                    break;
148            }
149    
150            increaseStackSize(stackSizeVariation);
151    
152            super.visitFieldInsn(opcode, owner, name, desc);
153        }
154    
155        @Override
156        public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
157            int argSize = Type.getArgumentsAndReturnSizes(desc);
158            int sizeVariation;
159            if (opcode == Opcodes.INVOKESTATIC) {
160                sizeVariation = (argSize & 0x03) - (argSize >> 2) + 1;
161            }
162            else {
163                sizeVariation = (argSize & 0x03) - (argSize >> 2);
164            }
165    
166            increaseStackSize(sizeVariation);
167    
168            super.visitMethodInsn(opcode, owner, name, desc, itf);
169        }
170    
171        @Override
172        public void visitInvokeDynamicInsn(@NotNull String name, @NotNull String desc, @NotNull Handle bsm, @NotNull Object... bsmArgs) {
173            int argSize = Type.getArgumentsAndReturnSizes(desc);
174            increaseStackSize((argSize & 0x03) - (argSize >> 2) + 1);
175    
176            super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
177        }
178    
179        @Override
180        public void visitJumpInsn(int opcode, @NotNull Label label) {
181            if (currentBlock != null) {
182                // updates current stack size (max stack size unchanged
183                // because stack size variation always negative in this
184                // case)
185                stackSize += FRAME_SIZE_CHANGE_BY_OPCODE[opcode];
186                addSuccessor(getLabelWrapper(label), stackSize);
187    
188                if (opcode == Opcodes.GOTO) {
189                    noSuccessor();
190                }
191            }
192    
193            super.visitJumpInsn(opcode, label);
194        }
195    
196        @Override
197        public void visitLabel(@NotNull Label label) {
198            processLabel(label);
199            super.visitLabel(label);
200        }
201    
202        private void processLabel(Label label) {
203            LabelWrapper wrapper = getLabelWrapper(label);
204    
205            if (currentBlock != null) {
206                // ends current block (with one new successor)
207                currentBlock.outputStackMax = maxStackSize;
208                addSuccessor(wrapper, stackSize);
209            }
210    
211            // begins a new current block
212            currentBlock = wrapper;
213            // resets the relative current and max stack sizes
214            stackSize = 0;
215            maxStackSize = 0;
216    
217            if (previousBlock != null) {
218                previousBlock.nextLabel = wrapper;
219            }
220    
221            previousBlock = wrapper;
222        }
223    
224        @Override
225        public void visitLdcInsn(@NotNull Object cst) {
226            // computes the stack size variation
227            if (cst instanceof Long || cst instanceof Double) {
228                increaseStackSize(2);
229            }
230            else {
231                increaseStackSize(1);
232            }
233    
234            super.visitLdcInsn(cst);
235        }
236    
237        @Override
238        public void visitTableSwitchInsn(int min, int max, @NotNull Label dflt, @NotNull Label... labels) {
239            visitSwitchInsn(dflt, labels);
240    
241            super.visitTableSwitchInsn(min, max, dflt, labels);
242        }
243    
244        @Override
245        public void visitLookupSwitchInsn(@NotNull Label dflt, @NotNull int[] keys, @NotNull Label[] labels) {
246            visitSwitchInsn(dflt, labels);
247    
248            super.visitLookupSwitchInsn(dflt, keys, labels);
249        }
250    
251        private void visitSwitchInsn(Label dflt, Label[] labels) {
252            if (currentBlock != null) {
253                // updates current stack size (max stack size unchanged)
254                --stackSize;
255                // adds current block successors
256                addSuccessor(getLabelWrapper(dflt), stackSize);
257                for (Label label : labels) {
258                    addSuccessor(getLabelWrapper(label), stackSize);
259                }
260                // ends current block
261                noSuccessor();
262            }
263        }
264    
265        @Override
266        public void visitMultiANewArrayInsn(@NotNull String desc, int dims) {
267            if (currentBlock != null) {
268                increaseStackSize(dims - 1);
269            }
270    
271            super.visitMultiANewArrayInsn(desc, dims);
272        }
273    
274        @Override
275        public void visitMaxs(int maxStack, int maxLocals) {
276            // completes the control flow graph with exception handler blocks
277            for (ExceptionHandler handler : exceptionHandlers) {
278                LabelWrapper l = handler.start;
279                LabelWrapper e = handler.end;
280    
281                while (l != e) {
282                    l.addSuccessor(handler.handlerLabel, 0, true);
283                    l = l.nextLabel;
284                }
285            }
286    
287            /*
288             * control flow analysis algorithm: while the block stack is not
289             * empty, pop a block from this stack, update the max stack size,
290             * compute the true (non relative) begin stack size of the
291             * successors of this block, and push these successors onto the
292             * stack (unless they have already been pushed onto the stack).
293             * Note: by hypothesis, the {@link LabelWrapper#inputStackSize} of the
294             * blocks in the block stack are the true (non relative) beginning
295             * stack sizes of these blocks.
296             */
297            int max = 0;
298            Stack<LabelWrapper> stack = new Stack<LabelWrapper>();
299            Set<LabelWrapper> pushed = new HashSet<LabelWrapper>();
300    
301            stack.push(firstLabel);
302            pushed.add(firstLabel);
303    
304            while (!stack.empty()) {
305                LabelWrapper current = stack.pop();
306                int start = current.inputStackSize;
307                int blockMax = start + current.outputStackMax;
308    
309                // updates the global max stack size
310                if (blockMax > max) {
311                    max = blockMax;
312                }
313    
314                // analyzes the successors of the block
315                for (ControlFlowEdge edge : current.successors) {
316                    LabelWrapper successor = edge.successor;
317    
318                    if (!pushed.contains(successor)) {
319                        // computes its true beginning stack size...
320                        successor.inputStackSize = edge.isExceptional ? 1 : start + edge.outputStackSize;
321                        // ...and pushes it onto the stack
322                        pushed.add(successor);
323                        stack.push(successor);
324                    }
325                }
326            }
327    
328            this.maxStack = Math.max(this.maxStack, Math.max(maxStack, max));
329    
330            super.visitMaxs(this.maxStack, maxLocals);
331        }
332    
333        @Override
334        public void visitTryCatchBlock(
335                @NotNull Label start, @NotNull Label end,
336                @NotNull Label handler, String type
337        ) {
338            ExceptionHandler exceptionHandler = new ExceptionHandler(
339                getLabelWrapper(start), getLabelWrapper(end), getLabelWrapper(handler)
340            );
341    
342            exceptionHandlers.add(exceptionHandler);
343    
344            super.visitTryCatchBlock(start, end, handler, type);
345        }
346    
347        private static class ExceptionHandler {
348            private final LabelWrapper start;
349            private final LabelWrapper end;
350            private final LabelWrapper handlerLabel;
351    
352            public ExceptionHandler(
353                    LabelWrapper start,
354                    LabelWrapper end,
355                    LabelWrapper handlerLabel
356            ) {
357                this.start = start;
358                this.end = end;
359                this.handlerLabel = handlerLabel;
360            }
361        }
362    
363        private static class ControlFlowEdge {
364            private final LabelWrapper successor;
365            private final int outputStackSize;
366            private final boolean isExceptional;
367    
368            public ControlFlowEdge(LabelWrapper successor, int outputStackSize, boolean isExceptional) {
369                this.successor = successor;
370                this.outputStackSize = outputStackSize;
371                this.isExceptional = isExceptional;
372            }
373        }
374    
375        private static class LabelWrapper {
376            private final Label label;
377            private LabelWrapper nextLabel = null;
378            private final Collection<ControlFlowEdge> successors = new LinkedList<ControlFlowEdge>();
379    
380            private int outputStackMax = 0;
381            private int inputStackSize = 0;
382            public LabelWrapper(Label label) {
383                this.label = label;
384            }
385    
386            private void addSuccessor(LabelWrapper successor, int outputStackSize, boolean isExceptional) {
387                successors.add(new ControlFlowEdge(successor, outputStackSize, isExceptional));
388            }
389        }
390    
391        // ------------------------------------------------------------------------
392        // Utility methods
393        // ------------------------------------------------------------------------
394    
395        private LabelWrapper getLabelWrapper(final Label label) {
396            return ContainerUtil.getOrCreate(labelWrappersMap, label, new Factory<LabelWrapper>() {
397                @Override
398                public LabelWrapper create() {
399                    return new LabelWrapper(label);
400                }
401            });
402        }
403    
404        private void increaseStackSize(int variation) {
405            updateStackSize(stackSize + variation);
406        }
407    
408        private void updateStackSize(int size) {
409            if (size > maxStackSize) {
410                maxStackSize = size;
411            }
412    
413            stackSize = size;
414        }
415    
416        private void addSuccessor(LabelWrapper successor, int outputStackSize) {
417            currentBlock.addSuccessor(successor, outputStackSize, false);
418        }
419    
420        /**
421         * Ends the current basic block. This method must be used in the case where
422         * the current basic block does not have any successor.
423         */
424        private void noSuccessor() {
425            if (currentBlock != null) {
426                currentBlock.outputStackMax = maxStackSize;
427                currentBlock = null;
428            }
429        }
430    }