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 }