JVM: reuse Frames of instructions that do nothing
This saves some RAM when optimizing functions with lots of line numbers.
This commit is contained in:
+21
-3
@@ -28,6 +28,8 @@ import org.jetbrains.kotlin.codegen.optimization.temporaryVals.TemporaryVariable
|
|||||||
import org.jetbrains.kotlin.codegen.optimization.transformer.CompositeMethodTransformer
|
import org.jetbrains.kotlin.codegen.optimization.transformer.CompositeMethodTransformer
|
||||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||||
import org.jetbrains.org.objectweb.asm.MethodVisitor
|
import org.jetbrains.org.objectweb.asm.MethodVisitor
|
||||||
|
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||||
|
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
|
||||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
||||||
|
|
||||||
class OptimizationMethodVisitor(
|
class OptimizationMethodVisitor(
|
||||||
@@ -83,11 +85,11 @@ class OptimizationMethodVisitor(
|
|||||||
if (getTotalFramesWeight(getTotalTcbSize(node), node) > MEMORY_LIMIT_BY_METHOD_MB)
|
if (getTotalFramesWeight(getTotalTcbSize(node), node) > MEMORY_LIMIT_BY_METHOD_MB)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return getTotalFramesWeight(node.instructions.size(), node) < MEMORY_LIMIT_BY_METHOD_MB
|
return getTotalFramesWeight(node.instructions.first.countInsnsWithFramesUntil(null), node) < MEMORY_LIMIT_BY_METHOD_MB
|
||||||
}
|
}
|
||||||
|
|
||||||
fun canBeOptimizedUsingSourceInterpreter(node: MethodNode): Boolean {
|
fun canBeOptimizedUsingSourceInterpreter(node: MethodNode): Boolean {
|
||||||
val methodSize = node.instructions.size()
|
val methodSize = node.instructions.first.countInsnsWithFramesUntil(null)
|
||||||
if (node.tryCatchBlocks.size > TRY_CATCH_BLOCKS_SOFT_LIMIT) {
|
if (node.tryCatchBlocks.size > TRY_CATCH_BLOCKS_SOFT_LIMIT) {
|
||||||
if (getTotalFramesWeight(getTotalTcbSize(node) * methodSize, node) > MEMORY_LIMIT_BY_METHOD_MB)
|
if (getTotalFramesWeight(getTotalTcbSize(node) * methodSize, node) > MEMORY_LIMIT_BY_METHOD_MB)
|
||||||
return false
|
return false
|
||||||
@@ -95,10 +97,26 @@ class OptimizationMethodVisitor(
|
|||||||
return getTotalFramesWeight(methodSize * methodSize, node) < MEMORY_LIMIT_BY_METHOD_MB
|
return getTotalFramesWeight(methodSize * methodSize, node) < MEMORY_LIMIT_BY_METHOD_MB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun AbstractInsnNode?.countInsnsWithFramesUntil(end: AbstractInsnNode?): Int {
|
||||||
|
var it = this
|
||||||
|
var result = 0
|
||||||
|
while (it != end && it != null) {
|
||||||
|
// FastMethodAnalyzer will reuse the Frame instance when a pseudo-instruction or a NOP is followed
|
||||||
|
// by anything other than a jump-targeted label, so those instructions consume no extra memory.
|
||||||
|
// To avoid checking all jumps, here we assume all labels are potentially targeted.
|
||||||
|
// (Effectively, all of this means that adding line numbers should not inhibit optimization.)
|
||||||
|
if ((it.type != AbstractInsnNode.LINE && it.type != AbstractInsnNode.FRAME && it.type != AbstractInsnNode.LABEL &&
|
||||||
|
it.opcode != Opcodes.NOP) || it.next?.type == AbstractInsnNode.LABEL
|
||||||
|
) result++
|
||||||
|
it = it.next
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
private fun getTotalFramesWeight(size: Int, node: MethodNode) =
|
private fun getTotalFramesWeight(size: Int, node: MethodNode) =
|
||||||
size.toLong() * (node.maxLocals + node.maxStack) / (1024 * 1024)
|
size.toLong() * (node.maxLocals + node.maxStack) / (1024 * 1024)
|
||||||
|
|
||||||
private fun getTotalTcbSize(node: MethodNode) =
|
private fun getTotalTcbSize(node: MethodNode) =
|
||||||
node.tryCatchBlocks.sumOf { node.instructions.indexOf(it.end) - node.instructions.indexOf(it.start) }
|
node.tryCatchBlocks.sumOf { it.start.countInsnsWithFramesUntil(it.end) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-4
@@ -96,8 +96,12 @@ open class FastMethodAnalyzer<V : Value>
|
|||||||
val insnOpcode = insnNode.opcode
|
val insnOpcode = insnNode.opcode
|
||||||
val insnType = insnNode.type
|
val insnType = insnNode.type
|
||||||
|
|
||||||
if (insnType == AbstractInsnNode.LABEL || insnType == AbstractInsnNode.LINE || insnType == AbstractInsnNode.FRAME) {
|
if (insnType == AbstractInsnNode.LABEL ||
|
||||||
mergeControlFlowEdge(insn + 1, f)
|
insnType == AbstractInsnNode.LINE ||
|
||||||
|
insnType == AbstractInsnNode.FRAME ||
|
||||||
|
insnOpcode == Opcodes.NOP
|
||||||
|
) {
|
||||||
|
mergeControlFlowEdge(insn + 1, f, canReuse = true)
|
||||||
} else {
|
} else {
|
||||||
current.init(f).execute(insnNode, interpreter)
|
current.init(f).execute(insnNode, interpreter)
|
||||||
when {
|
when {
|
||||||
@@ -236,10 +240,10 @@ open class FastMethodAnalyzer<V : Value>
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun visitJumpInsnNode(insnNode: JumpInsnNode, current: Frame<V>, insn: Int, insnOpcode: Int) {
|
private fun visitJumpInsnNode(insnNode: JumpInsnNode, current: Frame<V>, insn: Int, insnOpcode: Int) {
|
||||||
|
mergeControlFlowEdge(insnNode.label.indexOf(), current)
|
||||||
if (insnOpcode != Opcodes.GOTO) {
|
if (insnOpcode != Opcodes.GOTO) {
|
||||||
mergeControlFlowEdge(insn + 1, current)
|
mergeControlFlowEdge(insn + 1, current)
|
||||||
}
|
}
|
||||||
mergeControlFlowEdge(insnNode.label.indexOf(), current)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun computeExceptionHandlersForEachInsn(m: MethodNode) {
|
private fun computeExceptionHandlersForEachInsn(m: MethodNode) {
|
||||||
@@ -258,9 +262,13 @@ open class FastMethodAnalyzer<V : Value>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mergeControlFlowEdge(dest: Int, frame: Frame<V>) {
|
private fun mergeControlFlowEdge(dest: Int, frame: Frame<V>, canReuse: Boolean = false) {
|
||||||
val oldFrame = frames[dest]
|
val oldFrame = frames[dest]
|
||||||
val changes = when {
|
val changes = when {
|
||||||
|
canReuse && !isMergeNode[dest] -> {
|
||||||
|
frames[dest] = frame
|
||||||
|
true
|
||||||
|
}
|
||||||
oldFrame == null -> {
|
oldFrame == null -> {
|
||||||
frames[dest] = newFrame(frame.locals, frame.maxStackSize).apply { init(frame) }
|
frames[dest] = newFrame(frame.locals, frame.maxStackSize).apply { init(frame) }
|
||||||
true
|
true
|
||||||
|
|||||||
Reference in New Issue
Block a user