diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/inline/InlineCodegenUtil.java b/compiler/backend/src/org/jetbrains/jet/codegen/inline/InlineCodegenUtil.java index e7aed427b06..978c2c2bbbd 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/inline/InlineCodegenUtil.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/inline/InlineCodegenUtil.java @@ -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) { diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/inline/InternalFinallyBlockInliner.java b/compiler/backend/src/org/jetbrains/jet/codegen/inline/InternalFinallyBlockInliner.java index ffe2ad8738f..2c44d8f6b8a 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/inline/InternalFinallyBlockInliner.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/inline/InternalFinallyBlockInliner.java @@ -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() { diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/inline/MaxCalcNode.java b/compiler/backend/src/org/jetbrains/jet/codegen/inline/MaxCalcNode.java deleted file mode 100644 index 46920388b4a..00000000000 --- a/compiler/backend/src/org/jetbrains/jet/codegen/inline/MaxCalcNode.java +++ /dev/null @@ -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 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; - } -} diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/optimization/OptimizationMethodVisitor.java b/compiler/backend/src/org/jetbrains/jet/codegen/optimization/OptimizationMethodVisitor.java index 1b873550b73..a3e259684b0 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/optimization/OptimizationMethodVisitor.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/optimization/OptimizationMethodVisitor.java @@ -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; + } } diff --git a/compiler/backend/src/org/jetbrains/org/objectweb/asm/MaxLocalsCalculator.java b/compiler/backend/src/org/jetbrains/org/objectweb/asm/MaxLocalsCalculator.java new file mode 100644 index 00000000000..95ab68c61f4 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/org/objectweb/asm/MaxLocalsCalculator.java @@ -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; + } + } + +} diff --git a/compiler/backend/src/org/jetbrains/org/objectweb/asm/MaxStackFrameSizeAndLocalsCalculator.java b/compiler/backend/src/org/jetbrains/org/objectweb/asm/MaxStackFrameSizeAndLocalsCalculator.java new file mode 100644 index 00000000000..feb1cd0ae13 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/org/objectweb/asm/MaxStackFrameSizeAndLocalsCalculator.java @@ -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 stackSize. + */ + 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 stackSize. + */ + private int maxStackSize; + + /** + * Maximum stack size of this method. + */ + private int maxStack; + + + private Collection exceptionHandlers = new LinkedList(); + private Map labelWrappersMap = new HashMap(); + + 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 stack = new Stack(); + Set pushed = new HashSet(); + + 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 successors = new LinkedList(); + + 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() { + @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; + } + } +}