Merge pull request #506 from bintree/max-stack-calc-refining

Refining calculation of max stack size. #KT-5548 Fixed
This commit is contained in:
max-kammerer
2014-09-25 10:00:37 +04:00
6 changed files with 525 additions and 100 deletions
@@ -257,8 +257,8 @@ public class InlineCodegenUtil {
}
@NotNull
public static MaxCalcNode wrapWithMaxLocalCalc(@NotNull MethodNode methodNode) {
return new MaxCalcNode(methodNode);
public static MethodVisitor wrapWithMaxLocalCalc(@NotNull MethodNode methodNode) {
return new MaxStackFrameSizeAndLocalsCalculator(API, methodNode.access, methodNode.desc, methodNode);
}
private static boolean isInteger(@NotNull String string) {
@@ -24,9 +24,7 @@ import com.intellij.util.containers.Stack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.codegen.AsmUtil;
import org.jetbrains.org.objectweb.asm.Label;
import org.jetbrains.org.objectweb.asm.Opcodes;
import org.jetbrains.org.objectweb.asm.Type;
import org.jetbrains.org.objectweb.asm.*;
import org.jetbrains.org.objectweb.asm.tree.*;
import java.util.*;
@@ -79,9 +77,12 @@ public class InternalFinallyBlockInliner {
//sortTryCatchBlocks();/*TODO maybe remove*/
mapLabelsToTryCatchBlocks();
MaxCalcNode tempCalcNode = new MaxCalcNode(inlineFun.desc, (inlineFun.access & Opcodes.ACC_STATIC) != 0);
MaxLocalsCalculator tempCalcNode = new MaxLocalsCalculator(
InlineCodegenUtil.API,
inlineFun.access, inlineFun.desc, null
);
inlineFun.accept(tempCalcNode);
return tempCalcNode.getMaxLocal();
return tempCalcNode.getMaxLocals();
}
private void processInlineFunFinallyBlocks() {
@@ -1,90 +0,0 @@
/*
* Copyright 2010-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jet.codegen.inline;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.org.objectweb.asm.MethodVisitor;
import org.jetbrains.org.objectweb.asm.Opcodes;
import org.jetbrains.org.objectweb.asm.Type;
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode;
import org.jetbrains.org.objectweb.asm.tree.MethodNode;
import java.util.ListIterator;
public class MaxCalcNode extends MethodVisitor {
private int maxLocal;
private final MethodNode node;
public MaxCalcNode(@NotNull MethodNode node) {
this(node.desc, node, (node.access & Opcodes.ACC_STATIC) != 0);
}
public MaxCalcNode(@NotNull String desc, boolean isStatic) {
this(desc, null, isStatic);
}
private MaxCalcNode(@NotNull String desc, @Nullable MethodNode node, boolean isStatic) {
super(InlineCodegenUtil.API, node);
this.node = node;
maxLocal = (Type.getArgumentsAndReturnSizes(desc) >> 2) - (isStatic ? 1 : 0);
}
@Override
public void visitVarInsn(int opcode, int var) {
super.visitVarInsn(opcode, var);
int size = opcode == Opcodes.LLOAD || opcode == Opcodes.DLOAD || opcode == Opcodes.LSTORE || opcode == Opcodes.DSTORE ? 2 : 1;
updateMaxLocal(var, size);
}
@Override
public void visitIincInsn(int var, int increment) {
super.visitIincInsn(var, increment);
updateMaxLocal(var, 1);
}
private void updateMaxLocal(int index, int size) {
maxLocal = Math.max(maxLocal, index + size);
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
//NB: it's hack for fast maxStack calculation cause it performed only in MethodWriter
//temporary solution: maxStack = instruction size (without labels and line numbers) * 2 (cause 1 instruction could put value of size 2)
if (node != null) {
int size = 0;
ListIterator<AbstractInsnNode> iterator = node.instructions.iterator();
while (iterator.hasNext()) {
AbstractInsnNode next = iterator.next();
int type = next.getType();
if (type != AbstractInsnNode.LINE && type != AbstractInsnNode.LABEL) {
size++;
}
}
super.visitMaxs(Math.max(size * 2, maxStack), Math.max(maxLocals, this.maxLocal));
}
else {
super.visitMaxs(maxStack, maxLocals);
}
}
public int getMaxLocal() {
return maxLocal;
}
}
@@ -33,7 +33,7 @@ import java.util.ArrayList;
import java.util.List;
public class OptimizationMethodVisitor extends MethodVisitor {
private static final int MAX_INSTRUCTIONS_SIZE_TO_OPTIMIZE = 5000;
private static final int MEMORY_LIMIT_BY_METHOD_MB = 50;
private static final MethodTransformer MAIN_METHOD_TRANSFORMER = new RedundantNullCheckMethodTransformer(
new RedundantBoxingMethodTransformer(null)
);
@@ -65,8 +65,7 @@ public class OptimizationMethodVisitor extends MethodVisitor {
super.visitEnd();
if (methodNode.instructions.size() > 0 &&
methodNode.instructions.size() <= MAX_INSTRUCTIONS_SIZE_TO_OPTIMIZE) {
if (canBeAnalyzed(methodNode)) {
MAIN_METHOD_TRANSFORMER.transform("fake", methodNode);
}
@@ -113,4 +112,12 @@ public class OptimizationMethodVisitor extends MethodVisitor {
return traceMethodVisitor;
}
private static boolean canBeAnalyzed(@NotNull MethodNode node) {
int totalFramesSizeMb = node.instructions.size() *
(node.maxLocals + node.maxStack) / (1024 * 1024);
return node.instructions.size() > 0 &&
totalFramesSizeMb < MEMORY_LIMIT_BY_METHOD_MB;
}
}
@@ -0,0 +1,85 @@
/*
* Copyright 2010-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.org.objectweb.asm;
public class MaxLocalsCalculator extends MethodVisitor {
private int maxLocals;
public MaxLocalsCalculator(int api, int access, String descriptor, MethodVisitor mv) {
super(api, mv);
// updates maxLocals
int size = Type.getArgumentsAndReturnSizes(descriptor) >> 2;
if ((access & Opcodes.ACC_STATIC) != 0) {
--size;
}
maxLocals = size;
}
@Override
public void visitVarInsn(int opcode, int var) {
int n;
if (opcode == Opcodes.LLOAD || opcode == Opcodes.DLOAD ||
opcode == Opcodes.LSTORE || opcode == Opcodes.DSTORE) {
n = var + 2;
} else {
n = var + 1;
}
updateMaxLocals(n);
super.visitVarInsn(opcode, var);
}
@Override
public void visitIincInsn(int var, int increment) {
updateMaxLocals(var + 1);
super.visitIincInsn(var, increment);
}
@Override
public void visitLocalVariable(
String name, String desc,
String signature, Label start, Label end,
int index
) {
// updates max locals
char c = desc.charAt(0);
int n = index + (c == 'J' || c == 'D' ? 2 : 1);
updateMaxLocals(n);
super.visitLocalVariable(name, desc, signature, start, end, index);
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
super.visitMaxs(maxStack, this.maxLocals);
}
public int getMaxLocals() {
return maxLocals;
}
private void updateMaxLocals(int nextFreeSlotNumber) {
if (nextFreeSlotNumber > maxLocals) {
maxLocals = nextFreeSlotNumber;
}
}
}
@@ -0,0 +1,422 @@
/*
* Copyright 2010-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.org.objectweb.asm;
import com.intellij.openapi.util.Factory;
import com.intellij.util.containers.ContainerUtil;
import java.util.*;
public class MaxStackFrameSizeAndLocalsCalculator extends MaxLocalsCalculator {
private final LabelWrapper firstLabel;
private LabelWrapper currentBlock;
private LabelWrapper previousBlock;
/**
* The (relative) stack size after the last visited instruction. This size
* is relative to the beginning of the current basic block, i.e., the true
* stack size after the last visited instruction is equal to the
* {@link MaxStackFrameSizeAndLocalsCalculator.LabelWrapper#inputStackSize} of the current basic block
* plus <tt>stackSize</tt>.
*/
private int stackSize;
/**
* The (relative) maximum stack size after the last visited instruction.
* This size is relative to the beginning of the current basic block, i.e.,
* the true maximum stack size after the last visited instruction is equal
* to the {@link MaxStackFrameSizeAndLocalsCalculator.LabelWrapper#inputStackSize} of the current basic
* block plus <tt>stackSize</tt>.
*/
private int maxStackSize;
/**
* Maximum stack size of this method.
*/
private int maxStack;
private Collection<ExceptionHandler> exceptionHandlers = new LinkedList<ExceptionHandler>();
private Map<Label, LabelWrapper> labelWrappersMap = new HashMap<Label, LabelWrapper>();
public MaxStackFrameSizeAndLocalsCalculator(int api, int access, String descriptor, MethodVisitor mv) {
super(api, access, descriptor, mv);
firstLabel = getLabelWrapper(new Label());
processLabel(firstLabel.label);
}
@Override
public void visitFrame(int type, int nLocal,
Object[] local, int nStack, Object[] stack) {
throw new AssertionError("We don't support visitFrame because currently nobody needs");
}
@Override
public void visitInsn(int opcode) {
increaseStackSize(Frame.SIZE[opcode]);
// if opcode == ATHROW or xRETURN, ends current block (no successor)
if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) {
noSuccessor();
}
super.visitInsn(opcode);
}
@Override
public void visitIntInsn(int opcode, int operand) {
if (opcode != Opcodes.NEWARRAY) {
// updates current and max stack sizes only if it's not NEWARRAY
// (stack size variation is 0 for NEWARRAY and +1 BIPUSH or SIPUSH)
increaseStackSize(1);
}
super.visitIntInsn(opcode, operand);
}
@Override
public void visitVarInsn(int opcode, int var) {
increaseStackSize(Frame.SIZE[opcode]);
super.visitVarInsn(opcode, var);
}
@Override
public void visitTypeInsn(int opcode, String type) {
if (opcode == Opcodes.NEW) {
// updates current and max stack sizes only if opcode == NEW
// (no stack change for ANEWARRAY, CHECKCAST, INSTANCEOF)
increaseStackSize(1);
}
super.visitTypeInsn(opcode, type);
}
@Override
public void visitFieldInsn(int opcode, String owner,
String name, String desc) {
int stackSizeVariation;
// computes the stack size variation
char c = desc.charAt(0);
switch (opcode) {
case Opcodes.GETSTATIC:
stackSizeVariation = c == 'D' || c == 'J' ? 2 : 1;
break;
case Opcodes.PUTSTATIC:
stackSizeVariation = c == 'D' || c == 'J' ? -2 : -1;
break;
case Opcodes.GETFIELD:
stackSizeVariation = c == 'D' || c == 'J' ? 1 : 0;
break;
// case Constants.PUTFIELD:
default:
stackSizeVariation = c == 'D' || c == 'J' ? -3 : -2;
break;
}
increaseStackSize(stackSizeVariation);
super.visitFieldInsn(opcode, owner, name, desc);
}
@Override
public void visitMethodInsn(
int opcode, String owner,
String name, String desc, boolean itf
) {
int argSize = Type.getArgumentsAndReturnSizes(desc);
int sizeVariation;
if (opcode == Opcodes.INVOKESTATIC) {
sizeVariation = (argSize & 0x03) - (argSize >> 2) + 1;
} else {
sizeVariation = (argSize & 0x03) - (argSize >> 2);
}
increaseStackSize(sizeVariation);
super.visitMethodInsn(opcode, owner, name, desc, itf);
}
@Override
public void visitInvokeDynamicInsn(
String name, String desc,
Handle bsm, Object... bsmArgs
) {
int argSize = Type.getArgumentsAndReturnSizes(desc);
increaseStackSize((argSize & 0x03) - (argSize >> 2) + 1);
super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
}
@Override
public void visitJumpInsn(
int opcode, Label label
) {
if (currentBlock != null) {
// updates current stack size (max stack size unchanged
// because stack size variation always negative in this
// case)
stackSize += Frame.SIZE[opcode];
addSuccessor(getLabelWrapper(label), stackSize);
if (opcode == Opcodes.GOTO) {
noSuccessor();
}
}
super.visitJumpInsn(opcode, label);
}
@Override
public void visitLabel(Label label) {
processLabel(label);
super.visitLabel(label);
}
private void processLabel(Label label) {
LabelWrapper wrapper = getLabelWrapper(label);
if (currentBlock != null) {
// ends current block (with one new successor)
currentBlock.outputStackMax = maxStackSize;
addSuccessor(wrapper, stackSize);
}
// begins a new current block
currentBlock = wrapper;
// resets the relative current and max stack sizes
stackSize = 0;
maxStackSize = 0;
if (previousBlock != null) {
previousBlock.nextLabel = wrapper;
}
previousBlock = wrapper;
}
@Override
public void visitLdcInsn(Object cst) {
// computes the stack size variation
if (cst instanceof Long || cst instanceof Double) {
increaseStackSize(2);
} else {
increaseStackSize(1);
}
super.visitLdcInsn(cst);
}
@Override
public void visitTableSwitchInsn(int min, int max,
Label dflt, Label... labels) {
visitSwitchInsn(dflt, labels);
super.visitTableSwitchInsn(min, max, dflt, labels);
}
@Override
public void visitLookupSwitchInsn(Label dflt, int[] keys,
Label[] labels) {
visitSwitchInsn(dflt, labels);
super.visitLookupSwitchInsn(dflt, keys, labels);
}
private void visitSwitchInsn(Label dflt, Label[] labels) {
if (currentBlock != null) {
// updates current stack size (max stack size unchanged)
--stackSize;
// adds current block successors
addSuccessor(getLabelWrapper(dflt), stackSize);
for (int i = 0; i < labels.length; ++i) {
addSuccessor(getLabelWrapper(labels[i]), stackSize);
}
// ends current block
noSuccessor();
}
}
@Override
public void visitMultiANewArrayInsn(String desc, int dims) {
if (currentBlock != null) {
increaseStackSize(dims - 1);
}
super.visitMultiANewArrayInsn(desc, dims);
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
// completes the control flow graph with exception handler blocks
for (ExceptionHandler handler : exceptionHandlers) {
LabelWrapper l = handler.start;
LabelWrapper e = handler.end;
while (l != e) {
l.addSuccessor(handler.handlerLabel, 0, true);
l = l.nextLabel;
}
}
/*
* control flow analysis algorithm: while the block stack is not
* empty, pop a block from this stack, update the max stack size,
* compute the true (non relative) begin stack size of the
* successors of this block, and push these successors onto the
* stack (unless they have already been pushed onto the stack).
* Note: by hypothesis, the {@link LabelWrapper#inputStackSize} of the
* blocks in the block stack are the true (non relative) beginning
* stack sizes of these blocks.
*/
int max = 0;
Stack<LabelWrapper> stack = new Stack<LabelWrapper>();
Set<LabelWrapper> pushed = new HashSet<LabelWrapper>();
stack.push(firstLabel);
pushed.add(firstLabel);
while (!stack.empty()) {
LabelWrapper current = stack.pop();
int start = current.inputStackSize;
int blockMax = start + current.outputStackMax;
// updates the global max stack size
if (blockMax > max) {
max = blockMax;
}
// analyzes the successors of the block
for (ControlFlowEdge edge : current.successors) {
LabelWrapper successor = edge.successor;
if (!pushed.contains(successor)) {
// computes its true beginning stack size...
successor.inputStackSize = edge.isExceptional ? 1 : start + edge.outputStackSize;
// ...and pushes it onto the stack
pushed.add(successor);
stack.push(successor);
}
}
}
this.maxStack = Math.max(this.maxStack, Math.max(maxStack, max));
super.visitMaxs(this.maxStack, maxLocals);
}
@Override
public void visitTryCatchBlock(
Label start, Label end,
Label handler, String type
) {
ExceptionHandler exceptionHandler = new ExceptionHandler(
getLabelWrapper(start), getLabelWrapper(end), getLabelWrapper(handler)
);
exceptionHandlers.add(exceptionHandler);
super.visitTryCatchBlock(start, end, handler, type);
}
private static class ExceptionHandler {
private final LabelWrapper start;
private final LabelWrapper end;
private final LabelWrapper handlerLabel;
public ExceptionHandler(
LabelWrapper start,
LabelWrapper end,
LabelWrapper handlerLabel
) {
this.start = start;
this.end = end;
this.handlerLabel = handlerLabel;
}
}
private static class ControlFlowEdge {
private final LabelWrapper successor;
private final int outputStackSize;
private final boolean isExceptional;
public ControlFlowEdge(LabelWrapper successor, int outputStackSize, boolean isExceptional) {
this.successor = successor;
this.outputStackSize = outputStackSize;
this.isExceptional = isExceptional;
}
}
private static class LabelWrapper {
private final Label label;
private LabelWrapper nextLabel = null;
private final Collection<ControlFlowEdge> successors = new LinkedList<ControlFlowEdge>();
private int outputStackMax = 0;
private int inputStackSize = 0;
public LabelWrapper(Label label) {
this.label = label;
}
private void addSuccessor(LabelWrapper successor, int outputStackSize, boolean isExceptional) {
successors.add(new ControlFlowEdge(successor, outputStackSize, isExceptional));
}
}
// ------------------------------------------------------------------------
// Utility methods
// ------------------------------------------------------------------------
private LabelWrapper getLabelWrapper(final Label label) {
return ContainerUtil.getOrCreate(labelWrappersMap, label, new Factory<LabelWrapper>() {
@Override
public LabelWrapper create() {
return new LabelWrapper(label);
}
});
}
private void increaseStackSize(int variation) {
updateStackSize(stackSize + variation);
}
private void updateStackSize(int size) {
if (size > maxStackSize) {
maxStackSize = size;
}
stackSize = size;
}
private void addSuccessor(LabelWrapper successor, int outputStackSize) {
currentBlock.addSuccessor(successor, outputStackSize, false);
}
/**
* Ends the current basic block. This method must be used in the case where
* the current basic block does not have any successor.
*/
private void noSuccessor() {
if (currentBlock != null) {
currentBlock.outputStackMax = maxStackSize;
currentBlock = null;
}
}
}