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:
pyos
2022-04-07 09:44:46 +02:00
committed by max-kammerer
parent db62640ae2
commit 9d3a5e93d4
2 changed files with 33 additions and 7 deletions
@@ -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) }
} }
} }
@@ -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