Perform bytecode optimisations for inline classes
#KT-23742 Fixed
This commit is contained in:
+19
-19
@@ -39,6 +39,25 @@ class OptimizationMethodVisitor(
|
||||
private val constructorCallNormalizationTransformer =
|
||||
UninitializedStoresMethodTransformer(generationState.constructorCallNormalizationMode)
|
||||
|
||||
val normalizationMethodTransformer = CompositeMethodTransformer(
|
||||
FixStackWithLabelNormalizationMethodTransformer(),
|
||||
MethodVerifier("AFTER mandatory stack transformations")
|
||||
)
|
||||
|
||||
val optimizationTransformer = CompositeMethodTransformer(
|
||||
CapturedVarsOptimizationMethodTransformer(),
|
||||
RedundantNullCheckMethodTransformer(generationState),
|
||||
RedundantCheckCastEliminationMethodTransformer(),
|
||||
ConstantConditionEliminationMethodTransformer(),
|
||||
RedundantBoxingMethodTransformer(generationState),
|
||||
StackPeepholeOptimizationsTransformer(),
|
||||
PopBackwardPropagationTransformer(),
|
||||
DeadCodeEliminationMethodTransformer(),
|
||||
RedundantGotoMethodTransformer(),
|
||||
RedundantNopsCleanupMethodTransformer(),
|
||||
MethodVerifier("AFTER optimizations")
|
||||
)
|
||||
|
||||
override fun performTransformations(methodNode: MethodNode) {
|
||||
normalizationMethodTransformer.transform("fake", methodNode)
|
||||
constructorCallNormalizationTransformer.transform("fake", methodNode)
|
||||
@@ -53,25 +72,6 @@ class OptimizationMethodVisitor(
|
||||
companion object {
|
||||
private val MEMORY_LIMIT_BY_METHOD_MB = 50
|
||||
|
||||
val normalizationMethodTransformer = CompositeMethodTransformer(
|
||||
FixStackWithLabelNormalizationMethodTransformer(),
|
||||
MethodVerifier("AFTER mandatory stack transformations")
|
||||
)
|
||||
|
||||
val optimizationTransformer = CompositeMethodTransformer(
|
||||
CapturedVarsOptimizationMethodTransformer(),
|
||||
RedundantNullCheckMethodTransformer(),
|
||||
RedundantCheckCastEliminationMethodTransformer(),
|
||||
ConstantConditionEliminationMethodTransformer(),
|
||||
RedundantBoxingMethodTransformer(),
|
||||
StackPeepholeOptimizationsTransformer(),
|
||||
PopBackwardPropagationTransformer(),
|
||||
DeadCodeEliminationMethodTransformer(),
|
||||
RedundantGotoMethodTransformer(),
|
||||
RedundantNopsCleanupMethodTransformer(),
|
||||
MethodVerifier("AFTER optimizations")
|
||||
)
|
||||
|
||||
fun canBeOptimized(node: MethodNode): Boolean {
|
||||
val totalFramesSizeMb = node.instructions.size() * (node.maxLocals + node.maxStack) / (1024 * 1024)
|
||||
return totalFramesSizeMb < MEMORY_LIMIT_BY_METHOD_MB
|
||||
|
||||
+16
-6
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.codegen.optimization.boxing
|
||||
import com.intellij.openapi.util.Pair
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.StrictBasicValue
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
|
||||
@@ -36,9 +37,10 @@ abstract class BoxedBasicValue(type: Type) : StrictBasicValue(type) {
|
||||
class CleanBoxedValue(
|
||||
boxedType: Type,
|
||||
boxingInsn: AbstractInsnNode,
|
||||
progressionIterator: ProgressionIteratorBasicValue?
|
||||
progressionIterator: ProgressionIteratorBasicValue?,
|
||||
val generationState: GenerationState
|
||||
) : BoxedBasicValue(boxedType) {
|
||||
override val descriptor = BoxedValueDescriptor(boxedType, boxingInsn, progressionIterator)
|
||||
override val descriptor = BoxedValueDescriptor(boxedType, boxingInsn, progressionIterator, generationState)
|
||||
|
||||
private var tainted: TaintedBoxedValue? = null
|
||||
override fun taint(): BoxedBasicValue = tainted ?: TaintedBoxedValue(this).also { tainted = it }
|
||||
@@ -55,7 +57,8 @@ class TaintedBoxedValue(private val boxedBasicValue: CleanBoxedValue) : BoxedBas
|
||||
class BoxedValueDescriptor(
|
||||
private val boxedType: Type,
|
||||
val boxingInsn: AbstractInsnNode,
|
||||
val progressionIterator: ProgressionIteratorBasicValue?
|
||||
val progressionIterator: ProgressionIteratorBasicValue?,
|
||||
val generationState: GenerationState
|
||||
) {
|
||||
private val associatedInsns = HashSet<AbstractInsnNode>()
|
||||
private val unboxingWithCastInsns = HashSet<Pair<AbstractInsnNode, Type>>()
|
||||
@@ -63,7 +66,7 @@ class BoxedValueDescriptor(
|
||||
private val mergedWith = HashSet<BoxedValueDescriptor>()
|
||||
|
||||
var isSafeToRemove = true; private set
|
||||
val unboxedType: Type = getUnboxedType(boxedType)
|
||||
val unboxedType: Type = getUnboxedType(boxedType, generationState)
|
||||
|
||||
fun getAssociatedInsns() = associatedInsns.toList()
|
||||
|
||||
@@ -102,11 +105,18 @@ class BoxedValueDescriptor(
|
||||
}
|
||||
|
||||
|
||||
fun getUnboxedType(boxedType: Type): Type {
|
||||
fun getUnboxedType(boxedType: Type, state: GenerationState): Type {
|
||||
val primitiveType = AsmUtil.unboxPrimitiveTypeOrNull(boxedType)
|
||||
if (primitiveType != null) return primitiveType
|
||||
|
||||
if (boxedType == AsmTypes.K_CLASS_TYPE) return AsmTypes.JAVA_CLASS_TYPE
|
||||
|
||||
throw IllegalArgumentException("Expected primitive type wrapper or KClass, got: $boxedType")
|
||||
unboxedTypeOfInlineClass(boxedType, state)?.let { return it }
|
||||
|
||||
throw IllegalArgumentException("Expected primitive type wrapper or KClass or inline class wrapper, got: $boxedType")
|
||||
}
|
||||
|
||||
fun unboxedTypeOfInlineClass(boxedType: Type, state: GenerationState): Type? {
|
||||
val descriptor = state.jvmBackendClassResolver.resolveToClassDescriptors(boxedType).singleOrNull() ?: return null
|
||||
return state.typeMapper.mapType(descriptor.defaultType)
|
||||
}
|
||||
|
||||
+50
-11
@@ -22,7 +22,10 @@ import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods
|
||||
import org.jetbrains.kotlin.codegen.isRangeOrProgression
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.StrictBasicValue
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.resolve.InlineClassDescriptorResolver
|
||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
@@ -33,7 +36,10 @@ import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
|
||||
import java.util.*
|
||||
|
||||
open class BoxingInterpreter(private val insnList: InsnList) : OptimizationBasicInterpreter() {
|
||||
open class BoxingInterpreter(
|
||||
private val insnList: InsnList,
|
||||
private val generationState: GenerationState
|
||||
) : OptimizationBasicInterpreter() {
|
||||
private val boxingPlaces = HashMap<Int, BoxedBasicValue>()
|
||||
|
||||
protected open fun createNewBoxing(
|
||||
@@ -42,7 +48,7 @@ open class BoxingInterpreter(private val insnList: InsnList) : OptimizationBasic
|
||||
progressionIterator: ProgressionIteratorBasicValue?
|
||||
): BasicValue =
|
||||
boxingPlaces.getOrPut(insnList.indexOf(insn)) {
|
||||
val boxedBasicValue = CleanBoxedValue(type, insn, progressionIterator)
|
||||
val boxedBasicValue = CleanBoxedValue(type, insn, progressionIterator, generationState)
|
||||
onNewBoxedValue(boxedBasicValue)
|
||||
boxedBasicValue
|
||||
}
|
||||
@@ -62,10 +68,10 @@ open class BoxingInterpreter(private val insnList: InsnList) : OptimizationBasic
|
||||
val firstArg = values.firstOrNull() ?: return value
|
||||
|
||||
return when {
|
||||
insn.isBoxing() -> {
|
||||
insn.isBoxing(generationState) -> {
|
||||
createNewBoxing(insn, value.type, null)
|
||||
}
|
||||
insn.isUnboxing() && firstArg is BoxedBasicValue -> {
|
||||
insn.isUnboxing(generationState) && firstArg is BoxedBasicValue -> {
|
||||
onUnboxing(insn, firstArg, value.type)
|
||||
value
|
||||
}
|
||||
@@ -76,7 +82,7 @@ open class BoxingInterpreter(private val insnList: InsnList) : OptimizationBasic
|
||||
?: throw AssertionError("firstArg should be progression iterator")
|
||||
createNewBoxing(insn, AsmUtil.boxType(progressionIterator.valuesPrimitiveType), progressionIterator)
|
||||
}
|
||||
insn.isAreEqualIntrinsicForSameTypedBoxedValues(values) && canValuesBeUnboxedForAreEqual(values) -> {
|
||||
insn.isAreEqualIntrinsicForSameTypedBoxedValues(values) && canValuesBeUnboxedForAreEqual(values, generationState) -> {
|
||||
onAreEqual(insn, values[0] as BoxedBasicValue, values[1] as BoxedBasicValue)
|
||||
value
|
||||
}
|
||||
@@ -149,11 +155,11 @@ private val UNBOXING_METHOD_NAMES =
|
||||
private val KCLASS_TO_JLCLASS = Type.getMethodDescriptor(AsmTypes.JAVA_CLASS_TYPE, AsmTypes.K_CLASS_TYPE)
|
||||
private val JLCLASS_TO_KCLASS = Type.getMethodDescriptor(AsmTypes.K_CLASS_TYPE, AsmTypes.JAVA_CLASS_TYPE)
|
||||
|
||||
fun AbstractInsnNode.isUnboxing() =
|
||||
isPrimitiveUnboxing() || isJavaLangClassUnboxing()
|
||||
fun AbstractInsnNode.isUnboxing(state: GenerationState) =
|
||||
isPrimitiveUnboxing() || isJavaLangClassUnboxing() || isInlineClassUnboxing(state)
|
||||
|
||||
fun AbstractInsnNode.isBoxing() =
|
||||
isPrimitiveBoxing() || isJavaLangClassBoxing()
|
||||
fun AbstractInsnNode.isBoxing(state: GenerationState) =
|
||||
isPrimitiveBoxing() || isJavaLangClassBoxing() || isInlineClassBoxing(state)
|
||||
|
||||
fun AbstractInsnNode.isPrimitiveUnboxing() =
|
||||
isMethodInsnWith(Opcodes.INVOKEVIRTUAL) {
|
||||
@@ -202,6 +208,39 @@ fun AbstractInsnNode.isJavaLangClassBoxing() =
|
||||
desc == JLCLASS_TO_KCLASS
|
||||
}
|
||||
|
||||
private fun AbstractInsnNode.isInlineClassBoxing(state: GenerationState) =
|
||||
isMethodInsnWith(Opcodes.INVOKESTATIC) {
|
||||
isInlineClassBoxingMethodDescriptor(state)
|
||||
}
|
||||
|
||||
private fun AbstractInsnNode.isInlineClassUnboxing(state: GenerationState) =
|
||||
isMethodInsnWith(Opcodes.INVOKEVIRTUAL) {
|
||||
isInlineClassUnboxingMethodDescriptor(state)
|
||||
}
|
||||
|
||||
private fun MethodInsnNode.isInlineClassBoxingMethodDescriptor(state: GenerationState): Boolean {
|
||||
if (name != InlineClassDescriptorResolver.BOX_METHOD_NAME.asString()) return false
|
||||
if (!owner.endsWith(JvmAbi.ERASED_INLINE_CLASS_SUFFIX)) return false
|
||||
|
||||
val ownerType = Type.getObjectType(owner.removeSuffix(JvmAbi.ERASED_INLINE_CLASS_SUFFIX))
|
||||
val descriptor = state.jvmBackendClassResolver.resolveToClassDescriptors(ownerType).singleOrNull() ?: return false
|
||||
|
||||
if (!descriptor.isInline) return false
|
||||
|
||||
return desc == Type.getMethodDescriptor(ownerType, state.typeMapper.mapType(descriptor.defaultType))
|
||||
}
|
||||
|
||||
private fun MethodInsnNode.isInlineClassUnboxingMethodDescriptor(state: GenerationState): Boolean {
|
||||
if (name != InlineClassDescriptorResolver.UNBOX_METHOD_NAME.asString()) return false
|
||||
|
||||
val ownerType = Type.getObjectType(owner)
|
||||
val descriptor = state.jvmBackendClassResolver.resolveToClassDescriptors(ownerType).singleOrNull() ?: return false
|
||||
|
||||
if (!descriptor.isInline) return false
|
||||
|
||||
return desc == Type.getMethodDescriptor(state.typeMapper.mapType(descriptor.defaultType))
|
||||
}
|
||||
|
||||
fun AbstractInsnNode.isNextMethodCallOfProgressionIterator(values: List<BasicValue>) =
|
||||
values.firstOrNull() is ProgressionIteratorBasicValue &&
|
||||
isMethodInsnWith(Opcodes.INVOKEINTERFACE) {
|
||||
@@ -239,8 +278,8 @@ fun AbstractInsnNode.isAreEqualIntrinsic() =
|
||||
|
||||
private val shouldUseEqualsForWrappers = setOf(Type.DOUBLE_TYPE, Type.FLOAT_TYPE, AsmTypes.JAVA_CLASS_TYPE)
|
||||
|
||||
fun canValuesBeUnboxedForAreEqual(values: List<BasicValue>): Boolean =
|
||||
values.none { getUnboxedType(it.type) in shouldUseEqualsForWrappers }
|
||||
fun canValuesBeUnboxedForAreEqual(values: List<BasicValue>, generationState: GenerationState): Boolean =
|
||||
values.none { getUnboxedType(it.type, generationState) in shouldUseEqualsForWrappers }
|
||||
|
||||
fun AbstractInsnNode.isJavaLangComparableCompareToForSameTypedBoxedValues(values: List<BasicValue>) =
|
||||
isJavaLangComparableCompareTo() && areSameTypedBoxedValues(values)
|
||||
|
||||
+5
-1
@@ -17,6 +17,7 @@
|
||||
package org.jetbrains.kotlin.codegen.optimization.boxing
|
||||
|
||||
import com.google.common.collect.ImmutableSet
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
|
||||
@@ -25,7 +26,10 @@ 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
|
||||
|
||||
internal class RedundantBoxingInterpreter(insnList: InsnList) : BoxingInterpreter(insnList) {
|
||||
internal class RedundantBoxingInterpreter(
|
||||
insnList: InsnList,
|
||||
generationState: GenerationState
|
||||
) : BoxingInterpreter(insnList, generationState) {
|
||||
|
||||
val candidatesBoxedValues = RedundantBoxedValuesCollection()
|
||||
|
||||
|
||||
+3
-2
@@ -25,6 +25,7 @@ import org.jetbrains.kotlin.codegen.optimization.common.remapLocalVariables
|
||||
import org.jetbrains.kotlin.codegen.optimization.fixStack.peek
|
||||
import org.jetbrains.kotlin.codegen.optimization.fixStack.top
|
||||
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||
import org.jetbrains.org.objectweb.asm.Label
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
@@ -34,10 +35,10 @@ import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.Frame
|
||||
import java.util.*
|
||||
|
||||
class RedundantBoxingMethodTransformer : MethodTransformer() {
|
||||
class RedundantBoxingMethodTransformer(private val generationState: GenerationState) : MethodTransformer() {
|
||||
|
||||
override fun transform(internalClassName: String, node: MethodNode) {
|
||||
val interpreter = RedundantBoxingInterpreter(node.instructions)
|
||||
val interpreter = RedundantBoxingInterpreter(node.instructions, generationState)
|
||||
val frames = MethodTransformer.analyze(internalClassName, node, interpreter)
|
||||
|
||||
interpretPopInstructionsForBoxedValues(interpreter, node, frames)
|
||||
|
||||
+3
-2
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpr
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.StrictBasicValue
|
||||
import org.jetbrains.kotlin.codegen.pseudoInsns.PseudoInsn
|
||||
import org.jetbrains.kotlin.codegen.pseudoInsns.isPseudo
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
|
||||
@@ -30,7 +31,7 @@ import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.TypeInsnNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
|
||||
|
||||
class NullabilityInterpreter : OptimizationBasicInterpreter() {
|
||||
class NullabilityInterpreter(private val generationState: GenerationState) : OptimizationBasicInterpreter() {
|
||||
override fun newOperation(insn: AbstractInsnNode): BasicValue? {
|
||||
val defaultResult = super.newOperation(insn)
|
||||
val resultType = defaultResult?.type
|
||||
@@ -80,7 +81,7 @@ class NullabilityInterpreter : OptimizationBasicInterpreter() {
|
||||
val resultType = defaultResult?.type
|
||||
|
||||
return when {
|
||||
insn.isBoxing() ->
|
||||
insn.isBoxing(generationState) ->
|
||||
NotNullBasicValue(resultType)
|
||||
insn.isIteratorMethodCallOfProgression(values) ->
|
||||
ProgressionIteratorBasicValue.byProgressionClassType(values[0].type)
|
||||
|
||||
+5
-4
@@ -28,6 +28,7 @@ import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
|
||||
import org.jetbrains.kotlin.codegen.pseudoInsns.PseudoInsn
|
||||
import org.jetbrains.kotlin.codegen.pseudoInsns.asNotNull
|
||||
import org.jetbrains.kotlin.codegen.pseudoInsns.isPseudo
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||
import org.jetbrains.kotlin.utils.SmartList
|
||||
import org.jetbrains.org.objectweb.asm.Label
|
||||
@@ -35,13 +36,13 @@ import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
|
||||
import org.jetbrains.org.objectweb.asm.tree.*
|
||||
|
||||
class RedundantNullCheckMethodTransformer : MethodTransformer() {
|
||||
class RedundantNullCheckMethodTransformer(private val generationState: GenerationState) : MethodTransformer() {
|
||||
override fun transform(internalClassName: String, methodNode: MethodNode) {
|
||||
while (TransformerPass(internalClassName, methodNode).run()) {
|
||||
while (TransformerPass(internalClassName, methodNode, generationState).run()) {
|
||||
}
|
||||
}
|
||||
|
||||
private class TransformerPass(val internalClassName: String, val methodNode: MethodNode) {
|
||||
private class TransformerPass(val internalClassName: String, val methodNode: MethodNode, val generationState: GenerationState) {
|
||||
private var changes = false
|
||||
|
||||
fun run(): Boolean {
|
||||
@@ -59,7 +60,7 @@ class RedundantNullCheckMethodTransformer : MethodTransformer() {
|
||||
}
|
||||
|
||||
private fun analyzeNullabilities(): Map<AbstractInsnNode, StrictBasicValue> {
|
||||
val frames = analyze(internalClassName, methodNode, NullabilityInterpreter())
|
||||
val frames = analyze(internalClassName, methodNode, NullabilityInterpreter(generationState))
|
||||
val insns = methodNode.instructions.toArray()
|
||||
val nullabilityMap = LinkedHashMap<AbstractInsnNode, StrictBasicValue>()
|
||||
for (i in insns.indices) {
|
||||
|
||||
Reference in New Issue
Block a user