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.state.GenerationState
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
class OptimizationMethodVisitor(
@@ -83,11 +85,11 @@ class OptimizationMethodVisitor(
if (getTotalFramesWeight(getTotalTcbSize(node), node) > MEMORY_LIMIT_BY_METHOD_MB)
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 {
val methodSize = node.instructions.size()
val methodSize = node.instructions.first.countInsnsWithFramesUntil(null)
if (node.tryCatchBlocks.size > TRY_CATCH_BLOCKS_SOFT_LIMIT) {
if (getTotalFramesWeight(getTotalTcbSize(node) * methodSize, node) > MEMORY_LIMIT_BY_METHOD_MB)
return false
@@ -95,10 +97,26 @@ class OptimizationMethodVisitor(
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) =
size.toLong() * (node.maxLocals + node.maxStack) / (1024 * 1024)
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 insnType = insnNode.type
if (insnType == AbstractInsnNode.LABEL || insnType == AbstractInsnNode.LINE || insnType == AbstractInsnNode.FRAME) {
mergeControlFlowEdge(insn + 1, f)
if (insnType == AbstractInsnNode.LABEL ||
insnType == AbstractInsnNode.LINE ||
insnType == AbstractInsnNode.FRAME ||
insnOpcode == Opcodes.NOP
) {
mergeControlFlowEdge(insn + 1, f, canReuse = true)
} else {
current.init(f).execute(insnNode, interpreter)
when {
@@ -236,10 +240,10 @@ open class FastMethodAnalyzer<V : Value>
}
private fun visitJumpInsnNode(insnNode: JumpInsnNode, current: Frame<V>, insn: Int, insnOpcode: Int) {
mergeControlFlowEdge(insnNode.label.indexOf(), current)
if (insnOpcode != Opcodes.GOTO) {
mergeControlFlowEdge(insn + 1, current)
}
mergeControlFlowEdge(insnNode.label.indexOf(), current)
}
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 changes = when {
canReuse && !isMergeNode[dest] -> {
frames[dest] = frame
true
}
oldFrame == null -> {
frames[dest] = newFrame(frame.locals, frame.maxStackSize).apply { init(frame) }
true