JVM KT-47851 fix redundant checkcast elimination

This commit is contained in:
Dmitry Petrov
2021-11-18 20:12:38 +03:00
committed by teamcityserver
parent 8b066fd345
commit e525e25518
12 changed files with 187 additions and 4 deletions
@@ -17,13 +17,17 @@
package org.jetbrains.kotlin.codegen.optimization
import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner
import org.jetbrains.kotlin.codegen.optimization.common.InstructionLivenessAnalyzer
import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter
import org.jetbrains.kotlin.codegen.optimization.fixStack.top
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
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 org.jetbrains.org.objectweb.asm.tree.TypeInsnNode
import org.jetbrains.org.objectweb.asm.tree.VarInsnNode
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
class RedundantCheckCastEliminationMethodTransformer : MethodTransformer() {
override fun transform(internalClassName: String, methodNode: MethodNode) {
@@ -31,9 +35,21 @@ class RedundantCheckCastEliminationMethodTransformer : MethodTransformer() {
if (!insns.any { it.opcode == Opcodes.CHECKCAST }) return
if (insns.any { ReifiedTypeInliner.isOperationReifiedMarker(it) }) return
val typeAdjustmentForALoads = getTypeAdjustmentForALoadInstructions(insns, methodNode)
val interpreter = object : OptimizationBasicInterpreter() {
override fun copyOperation(insn: AbstractInsnNode, value: BasicValue?): BasicValue {
val adjustedType = typeAdjustmentForALoads[insn]
return if (adjustedType != null)
newValue(adjustedType)
?: throw AssertionError("Local variable type can't be VOID: $adjustedType")
else
super.copyOperation(insn, value)
}
}
val redundantCheckCasts = ArrayList<TypeInsnNode>()
val frames = analyze(internalClassName, methodNode, OptimizationBasicInterpreter())
val frames = analyze(internalClassName, methodNode, interpreter)
for (i in insns.indices) {
val valueType = frames[i]?.top()?.type ?: continue
val insn = insns[i]
@@ -57,6 +73,29 @@ class RedundantCheckCastEliminationMethodTransformer : MethodTransformer() {
}
}
private fun getTypeAdjustmentForALoadInstructions(
insns: Array<AbstractInsnNode>,
methodNode: MethodNode
): Map<AbstractInsnNode, Type> {
val isNonHandler = InstructionLivenessAnalyzer(methodNode, visitExceptionHandlers = false).analyze()
val result = HashMap<AbstractInsnNode, Type>()
for (lv in methodNode.localVariables) {
val startIndex = methodNode.instructions.indexOf(lv.start)
val endIndex = methodNode.instructions.indexOf(lv.end)
for (i in startIndex until endIndex) {
val insn = insns[i]
// If we are in exception handler (or in dead code, but it really doesn't matter here, since dead code should not be seen
// by data flow analyzer), treat ALOAD instructions as producing a value of declared local variable type.
// Otherwise, resulting bytecode might fail verification on JDK 1.8+ because of inexact frames (see KT-47851).
if (insn.opcode == Opcodes.ALOAD && (insn as VarInsnNode).`var` == lv.index && !isNonHandler[i]) {
result[insn] = Type.getType(lv.desc)
}
}
}
return result
}
private fun isTrivialSubtype(superType: Type, subType: Type) =
superType == subType
@@ -37,7 +37,10 @@ package org.jetbrains.kotlin.codegen.optimization.common
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.tree.*
class InstructionLivenessAnalyzer(val method: MethodNode) {
class InstructionLivenessAnalyzer(
val method: MethodNode,
val visitExceptionHandlers: Boolean = true
) {
private val instructions = method.instructions
private val nInsns = instructions.size()
@@ -92,8 +95,10 @@ class InstructionLivenessAnalyzer(val method: MethodNode) {
}
}
handlers[insn]?.forEach { tcb ->
visitControlFlowEdge(tcb.handler.indexOf)
if (visitExceptionHandlers) {
handlers[insn]?.forEach { tcb ->
visitControlFlowEdge(tcb.handler.indexOf)
}
}
}
}
@@ -151,6 +156,7 @@ class InstructionLivenessAnalyzer(val method: MethodNode) {
}
private fun computeExceptionHandlersForEachInsn(m: MethodNode) {
if (!visitExceptionHandlers) return
for (tcb in m.tryCatchBlocks) {
val begin = tcb.start.indexOf
val end = tcb.end.indexOf