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.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) }
|
||||
}
|
||||
}
|
||||
|
||||
+12
-4
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user