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 }