[IR] Eliminate redundant boxing/unboxing of MFVC after inlining
Signed-off-by: Evgeniy.Zhelenskiy <Evgeniy.Zhelenskiy@jetbrains.com> #KT-1179
This commit is contained in:
committed by
Space Team
parent
a3e396e7e4
commit
40f38c8adb
+70
-11
@@ -20,10 +20,16 @@ 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.codegen.state.GenerationState.MultiFieldValueClassUnboxInfo
|
||||
import org.jetbrains.kotlin.resolve.isInlineClass
|
||||
import org.jetbrains.kotlin.resolve.isMultiFieldValueClass
|
||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||
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.InsnList
|
||||
import org.jetbrains.org.objectweb.asm.tree.InsnNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode
|
||||
|
||||
abstract class BoxedBasicValue(type: Type) : StrictBasicValue(type) {
|
||||
abstract val descriptor: BoxedValueDescriptor
|
||||
@@ -55,22 +61,33 @@ class TaintedBoxedValue(private val boxedBasicValue: CleanBoxedValue) : BoxedBas
|
||||
|
||||
|
||||
class BoxedValueDescriptor(
|
||||
boxedType: Type,
|
||||
val boxedType: Type,
|
||||
val boxingInsn: AbstractInsnNode,
|
||||
val progressionIterator: ProgressionIteratorBasicValue?,
|
||||
val generationState: GenerationState
|
||||
) {
|
||||
private val associatedInsns = HashSet<AbstractInsnNode>()
|
||||
private val unboxingWithCastInsns = HashSet<Pair<AbstractInsnNode, Type>>()
|
||||
private val associatedInsns = LinkedHashSet<AbstractInsnNode>()
|
||||
private val unboxingWithCastInsns = LinkedHashSet<Pair<AbstractInsnNode, Type>>()
|
||||
private val associatedVariables = HashSet<Int>()
|
||||
private val mergedWith = HashSet<BoxedValueDescriptor>()
|
||||
|
||||
var isSafeToRemove = true; private set
|
||||
val unboxedType: Type = getUnboxedType(boxedType, generationState)
|
||||
val isInlineClassValue = isInlineClassValue(boxedType)
|
||||
val multiFieldValueClassUnboxInfo = getMultiFieldValueClassUnboxInfo(boxedType, generationState)
|
||||
val unboxedTypes: List<Type> = getUnboxedTypes(boxedType, generationState, multiFieldValueClassUnboxInfo)
|
||||
|
||||
fun getUnboxTypeOrOtherwiseMethodReturnType(methodInsnNode: MethodInsnNode?) =
|
||||
unboxedTypes.singleOrNull() ?: Type.getReturnType(methodInsnNode!!.desc)
|
||||
|
||||
val isValueClassValue = isValueClassValue(boxedType)
|
||||
|
||||
fun getAssociatedInsns() = associatedInsns.toList()
|
||||
|
||||
fun sortAssociatedInsns(indexes: Map<AbstractInsnNode, Int>) {
|
||||
val newOrder = associatedInsns.sortedBy { indexes[it]!! }
|
||||
associatedInsns.clear()
|
||||
associatedInsns.addAll(newOrder)
|
||||
}
|
||||
|
||||
fun addInsn(insnNode: AbstractInsnNode) {
|
||||
associatedInsns.add(insnNode)
|
||||
}
|
||||
@@ -93,7 +110,7 @@ class BoxedValueDescriptor(
|
||||
isSafeToRemove = false
|
||||
}
|
||||
|
||||
fun isDoubleSize() = unboxedType.size == 2
|
||||
fun getTotalUnboxSize() = unboxedTypes.sumOf { it.size }
|
||||
|
||||
fun isFromProgressionIterator() = progressionIterator != null
|
||||
|
||||
@@ -103,16 +120,51 @@ class BoxedValueDescriptor(
|
||||
|
||||
fun getUnboxingWithCastInsns(): Set<Pair<AbstractInsnNode, Type>> =
|
||||
unboxingWithCastInsns
|
||||
|
||||
fun sortUnboxingWithCastInsns(indexes: Map<AbstractInsnNode, Int>) {
|
||||
val newInsnsAndResultTypes = unboxingWithCastInsns.sortedBy { insnAndResultType -> indexes[insnAndResultType.first]!! }
|
||||
unboxingWithCastInsns.clear()
|
||||
unboxingWithCastInsns.addAll(newInsnsAndResultTypes)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun makePops(unboxedTypes: List<Type>) = InsnList().apply {
|
||||
var restSingleSize = false
|
||||
for (unboxType in unboxedTypes.asReversed()) {
|
||||
restSingleSize = when (unboxType.size) {
|
||||
1 -> {
|
||||
if (restSingleSize) add(InsnNode(Opcodes.POP2))
|
||||
!restSingleSize
|
||||
}
|
||||
|
||||
2 -> {
|
||||
if (restSingleSize) add(InsnNode(Opcodes.POP))
|
||||
add(InsnNode(Opcodes.POP2))
|
||||
false
|
||||
}
|
||||
|
||||
else -> error("Illegal type size: ${unboxType.size}")
|
||||
}
|
||||
}
|
||||
|
||||
if (restSingleSize) {
|
||||
add(InsnNode(Opcodes.POP))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun getUnboxedType(boxedType: Type, state: GenerationState): Type {
|
||||
fun getUnboxedTypes(
|
||||
boxedType: Type,
|
||||
state: GenerationState,
|
||||
multiFieldValueClassUnboxInfo: MultiFieldValueClassUnboxInfo?
|
||||
): List<Type> {
|
||||
val primitiveType = AsmUtil.unboxPrimitiveTypeOrNull(boxedType)
|
||||
if (primitiveType != null) return primitiveType
|
||||
if (primitiveType != null) return listOf(primitiveType)
|
||||
|
||||
if (boxedType == AsmTypes.K_CLASS_TYPE) return AsmTypes.JAVA_CLASS_TYPE
|
||||
if (boxedType == AsmTypes.K_CLASS_TYPE) return listOf(AsmTypes.JAVA_CLASS_TYPE)
|
||||
|
||||
unboxedTypeOfInlineClass(boxedType, state)?.let { return it }
|
||||
unboxedTypeOfInlineClass(boxedType, state)?.let { return listOf(it) }
|
||||
multiFieldValueClassUnboxInfo?.let { return it.unboxedTypes }
|
||||
|
||||
throw IllegalArgumentException("Expected primitive type wrapper or KClass or inline class wrapper, got: $boxedType")
|
||||
}
|
||||
@@ -123,6 +175,13 @@ fun unboxedTypeOfInlineClass(boxedType: Type, state: GenerationState): Type? {
|
||||
return state.mapInlineClass(descriptor)
|
||||
}
|
||||
|
||||
private fun isInlineClassValue(boxedType: Type): Boolean {
|
||||
fun getMultiFieldValueClassUnboxInfo(boxedType: Type, state: GenerationState): MultiFieldValueClassUnboxInfo? {
|
||||
val descriptor =
|
||||
state.jvmBackendClassResolver.resolveToClassDescriptors(boxedType).singleOrNull()?.takeIf { it.isMultiFieldValueClass() }
|
||||
?: return null
|
||||
return state.multiFieldValueClassUnboxInfo(descriptor)
|
||||
}
|
||||
|
||||
private fun isValueClassValue(boxedType: Type): Boolean {
|
||||
return !AsmUtil.isBoxedPrimitiveType(boxedType) && boxedType != AsmTypes.K_CLASS_TYPE
|
||||
}
|
||||
|
||||
+35
-7
@@ -180,7 +180,7 @@ open class BoxingInterpreter(
|
||||
v is TaintedBoxedValue -> v
|
||||
w is TaintedBoxedValue -> w
|
||||
v.type != w.type -> mergeBoxedHazardous(v, w, isLocalVariable)
|
||||
else -> v
|
||||
else -> v // two clean boxed values with the same type are equal
|
||||
}
|
||||
}
|
||||
v is BoxedBasicValue ->
|
||||
@@ -222,10 +222,10 @@ private val KCLASS_TO_JLCLASS = Type.getMethodDescriptor(AsmTypes.JAVA_CLASS_TYP
|
||||
private val JLCLASS_TO_KCLASS = Type.getMethodDescriptor(AsmTypes.K_CLASS_TYPE, AsmTypes.JAVA_CLASS_TYPE)
|
||||
|
||||
fun AbstractInsnNode.isUnboxing(state: GenerationState) =
|
||||
isPrimitiveUnboxing() || isJavaLangClassUnboxing() || isInlineClassUnboxing(state)
|
||||
isPrimitiveUnboxing() || isJavaLangClassUnboxing() || isInlineClassUnboxing(state) || isMultiFieldValueClassUnboxing(state)
|
||||
|
||||
fun AbstractInsnNode.isBoxing(state: GenerationState) =
|
||||
isPrimitiveBoxing() || isJavaLangClassBoxing() || isInlineClassBoxing(state) || isCoroutinePrimitiveBoxing()
|
||||
isPrimitiveBoxing() || isJavaLangClassBoxing() || isInlineClassBoxing(state) || isCoroutinePrimitiveBoxing() || isMultiFieldValueClassBoxing(state)
|
||||
|
||||
fun AbstractInsnNode.isPrimitiveUnboxing() =
|
||||
isMethodInsnWith(Opcodes.INVOKEVIRTUAL) {
|
||||
@@ -292,11 +292,21 @@ private fun AbstractInsnNode.isInlineClassBoxing(state: GenerationState) =
|
||||
isInlineClassBoxingMethodDescriptor(state)
|
||||
}
|
||||
|
||||
private fun AbstractInsnNode.isMultiFieldValueClassBoxing(state: GenerationState) =
|
||||
isMethodInsnWith(Opcodes.INVOKESTATIC) {
|
||||
isMultiFieldValueClassBoxingMethodDescriptor(state)
|
||||
}
|
||||
|
||||
private fun AbstractInsnNode.isInlineClassUnboxing(state: GenerationState) =
|
||||
isMethodInsnWith(Opcodes.INVOKEVIRTUAL) {
|
||||
isInlineClassUnboxingMethodDescriptor(state)
|
||||
}
|
||||
|
||||
private fun AbstractInsnNode.isMultiFieldValueClassUnboxing(state: GenerationState) =
|
||||
isMethodInsnWith(Opcodes.INVOKEVIRTUAL) {
|
||||
isMultiFieldValueClassUnboxingMethodDescriptor(state)
|
||||
}
|
||||
|
||||
private fun MethodInsnNode.isInlineClassBoxingMethodDescriptor(state: GenerationState): Boolean {
|
||||
if (name != KotlinTypeMapper.BOX_JVM_METHOD_NAME) return false
|
||||
|
||||
@@ -305,6 +315,14 @@ private fun MethodInsnNode.isInlineClassBoxingMethodDescriptor(state: Generation
|
||||
return desc == Type.getMethodDescriptor(ownerType, unboxedType)
|
||||
}
|
||||
|
||||
private fun MethodInsnNode.isMultiFieldValueClassBoxingMethodDescriptor(state: GenerationState): Boolean {
|
||||
if (name != KotlinTypeMapper.BOX_JVM_METHOD_NAME) return false
|
||||
|
||||
val ownerType = Type.getObjectType(owner)
|
||||
val multiFieldValueClassUnboxInfo = getMultiFieldValueClassUnboxInfo(ownerType, state) ?: return false
|
||||
return desc == Type.getMethodDescriptor(ownerType, *multiFieldValueClassUnboxInfo.unboxedTypes.toTypedArray())
|
||||
}
|
||||
|
||||
private fun MethodInsnNode.isInlineClassUnboxingMethodDescriptor(state: GenerationState): Boolean {
|
||||
if (name != KotlinTypeMapper.UNBOX_JVM_METHOD_NAME) return false
|
||||
|
||||
@@ -313,6 +331,14 @@ private fun MethodInsnNode.isInlineClassUnboxingMethodDescriptor(state: Generati
|
||||
return desc == Type.getMethodDescriptor(unboxedType)
|
||||
}
|
||||
|
||||
private fun MethodInsnNode.isMultiFieldValueClassUnboxingMethodDescriptor(state: GenerationState): Boolean {
|
||||
val ownerType = Type.getObjectType(owner)
|
||||
val multiFieldValueClassUnboxInfo = getMultiFieldValueClassUnboxInfo(ownerType, state) ?: return false
|
||||
return multiFieldValueClassUnboxInfo.unboxedTypesAndMethodNamesAndFieldNames.any { (type, methodName) ->
|
||||
name == methodName && desc == Type.getMethodDescriptor(type)
|
||||
}
|
||||
}
|
||||
|
||||
fun AbstractInsnNode.isNextMethodCallOfProgressionIterator(values: List<BasicValue>) =
|
||||
values.firstOrNull() is ProgressionIteratorBasicValue &&
|
||||
isMethodInsnWith(Opcodes.INVOKEINTERFACE) {
|
||||
@@ -349,8 +375,8 @@ fun areSameTypedPrimitiveBoxedValues(values: List<BasicValue>): Boolean {
|
||||
val (v1, v2) = values
|
||||
return v1 is BoxedBasicValue &&
|
||||
v2 is BoxedBasicValue &&
|
||||
v1.descriptor.unboxedType == v2.descriptor.unboxedType &&
|
||||
!v1.descriptor.isInlineClassValue && !v2.descriptor.isInlineClassValue
|
||||
!v1.descriptor.isValueClassValue && !v2.descriptor.isValueClassValue &&
|
||||
v1.descriptor.unboxedTypes.single() == v2.descriptor.unboxedTypes.single()
|
||||
}
|
||||
|
||||
fun AbstractInsnNode.isAreEqualIntrinsic() =
|
||||
@@ -362,8 +388,10 @@ fun AbstractInsnNode.isAreEqualIntrinsic() =
|
||||
|
||||
private val shouldUseEqualsForWrappers = setOf(Type.DOUBLE_TYPE, Type.FLOAT_TYPE, AsmTypes.JAVA_CLASS_TYPE)
|
||||
|
||||
fun canValuesBeUnboxedForAreEqual(values: List<BasicValue>, generationState: GenerationState): Boolean =
|
||||
values.none { getUnboxedType(it.type, generationState) in shouldUseEqualsForWrappers }
|
||||
fun canValuesBeUnboxedForAreEqual(values: List<BasicValue>, generationState: GenerationState): Boolean = values.none {
|
||||
val unboxedType = getUnboxedTypes(it.type, generationState, getMultiFieldValueClassUnboxInfo(it.type, generationState)).singleOrNull()
|
||||
unboxedType == null || unboxedType in shouldUseEqualsForWrappers
|
||||
}
|
||||
|
||||
fun AbstractInsnNode.isJavaLangComparableCompareToForSameTypedBoxedValues(values: List<BasicValue>) =
|
||||
isJavaLangComparableCompareTo() && areSameTypedPrimitiveBoxedValues(values)
|
||||
|
||||
+5
-7
@@ -20,16 +20,13 @@ 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
|
||||
import org.jetbrains.org.objectweb.asm.tree.InsnList
|
||||
import org.jetbrains.org.objectweb.asm.tree.TypeInsnNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.VarInsnNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.*
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
|
||||
|
||||
internal class RedundantBoxingInterpreter(
|
||||
insnList: InsnList,
|
||||
methodNode: MethodNode,
|
||||
generationState: GenerationState
|
||||
) : BoxingInterpreter(insnList, generationState) {
|
||||
) : BoxingInterpreter(methodNode.instructions, generationState) {
|
||||
|
||||
val candidatesBoxedValues = RedundantBoxedValuesCollection()
|
||||
|
||||
@@ -81,6 +78,7 @@ internal class RedundantBoxingInterpreter(
|
||||
|
||||
override fun onUnboxing(insn: AbstractInsnNode, value: BoxedBasicValue, resultType: Type) {
|
||||
value.descriptor.run {
|
||||
val unboxedType = getUnboxTypeOrOtherwiseMethodReturnType(insn as? MethodInsnNode)
|
||||
if (unboxedType == resultType)
|
||||
addAssociatedInsn(value, insn)
|
||||
else
|
||||
@@ -142,7 +140,7 @@ internal class RedundantBoxingInterpreter(
|
||||
Type.getInternalName(Any::class.java) ->
|
||||
true
|
||||
Type.getInternalName(Number::class.java) ->
|
||||
PRIMITIVE_TYPES_SORTS_WITH_WRAPPER_EXTENDS_NUMBER.contains(value.descriptor.unboxedType.sort)
|
||||
value.descriptor.unboxedTypes.singleOrNull()?.sort?.let { PRIMITIVE_TYPES_SORTS_WITH_WRAPPER_EXTENDS_NUMBER.contains(it) } == true
|
||||
"java/lang/Comparable" ->
|
||||
true
|
||||
else ->
|
||||
|
||||
+175
-36
@@ -41,7 +41,7 @@ class RedundantBoxingMethodTransformer(private val generationState: GenerationSt
|
||||
if (insns.none { it.isBoxing(generationState) || it.isMethodInsnWith(Opcodes.INVOKEINTERFACE) { name == "next" } })
|
||||
return
|
||||
|
||||
val interpreter = RedundantBoxingInterpreter(node.instructions, generationState)
|
||||
val interpreter = RedundantBoxingInterpreter(node, generationState)
|
||||
val frames = BoxingAnalyzer(internalClassName, node, interpreter).analyze()
|
||||
|
||||
interpretPopInstructionsForBoxedValues(interpreter, node, frames)
|
||||
@@ -55,14 +55,34 @@ class RedundantBoxingMethodTransformer(private val generationState: GenerationSt
|
||||
// has side effect on valuesToOptimize and frames, containing BoxedBasicValues that are unsafe to remove
|
||||
removeValuesClashingWithVariables(valuesToOptimize, node, frames)
|
||||
|
||||
adaptLocalVariableTableForBoxedValues(node, frames)
|
||||
// cannot replace them inplace because replaced variables indexes are known after remapping
|
||||
val variablesForReplacement = adaptLocalSingleVariableTableForBoxedValuesAndPrepareMultiVariables(node, frames)
|
||||
|
||||
node.remapLocalVariables(buildVariablesRemapping(valuesToOptimize, node))
|
||||
|
||||
replaceVariables(node, variablesForReplacement)
|
||||
|
||||
sortAdaptableInstructionsForBoxedValues(node, valuesToOptimize)
|
||||
|
||||
adaptInstructionsForBoxedValues(node, valuesToOptimize)
|
||||
}
|
||||
}
|
||||
|
||||
private fun sortAdaptableInstructionsForBoxedValues(node: MethodNode, valuesToOptimize: RedundantBoxedValuesCollection) {
|
||||
val indexes = node.instructions.withIndex().associate { (index, insn) -> insn to index }
|
||||
for (value in valuesToOptimize) {
|
||||
value.sortAssociatedInsns(indexes)
|
||||
value.sortUnboxingWithCastInsns(indexes)
|
||||
}
|
||||
}
|
||||
|
||||
private fun replaceVariables(node: MethodNode, variablesForReplacement: Map<LocalVariableNode, List<LocalVariableNode>>) {
|
||||
if (variablesForReplacement.isEmpty()) return
|
||||
node.localVariables = node.localVariables.flatMap { oldVar ->
|
||||
variablesForReplacement[oldVar]?.also { newVars -> for (newVar in newVars) newVar.index += oldVar.index } ?: listOf(oldVar)
|
||||
}.toMutableList()
|
||||
}
|
||||
|
||||
private fun interpretPopInstructionsForBoxedValues(
|
||||
interpreter: RedundantBoxingInterpreter,
|
||||
node: MethodNode,
|
||||
@@ -114,7 +134,7 @@ class RedundantBoxingMethodTransformer(private val generationState: GenerationSt
|
||||
if (boxed.isEmpty()) continue
|
||||
|
||||
val firstBoxed = boxed.first().descriptor
|
||||
if (isUnsafeToRemoveBoxingForConnectedValues(variableValues, firstBoxed.unboxedType)) {
|
||||
if (isUnsafeToRemoveBoxingForConnectedValues(variableValues, firstBoxed.unboxedTypes)) {
|
||||
for (value in boxed) {
|
||||
val descriptor = value.descriptor
|
||||
if (descriptor.isSafeToRemove) {
|
||||
@@ -137,16 +157,19 @@ class RedundantBoxingMethodTransformer(private val generationState: GenerationSt
|
||||
}
|
||||
}
|
||||
|
||||
private fun isUnsafeToRemoveBoxingForConnectedValues(usedValues: List<BasicValue>, unboxedType: Type): Boolean =
|
||||
private fun isUnsafeToRemoveBoxingForConnectedValues(usedValues: List<BasicValue>, unboxedTypes: List<Type>): Boolean =
|
||||
usedValues.any { input ->
|
||||
if (input === StrictBasicValue.UNINITIALIZED_VALUE) return@any false
|
||||
if (input !is CleanBoxedValue) return@any true
|
||||
|
||||
val descriptor = input.descriptor
|
||||
!descriptor.isSafeToRemove || descriptor.unboxedType != unboxedType
|
||||
!descriptor.isSafeToRemove || descriptor.unboxedTypes != unboxedTypes
|
||||
}
|
||||
|
||||
private fun adaptLocalVariableTableForBoxedValues(node: MethodNode, frames: Array<Frame<BasicValue>?>) {
|
||||
private fun adaptLocalSingleVariableTableForBoxedValuesAndPrepareMultiVariables(
|
||||
node: MethodNode, frames: Array<Frame<BasicValue>?>
|
||||
): Map<LocalVariableNode, List<LocalVariableNode>> {
|
||||
val localVariablesReplacement = mutableMapOf<LocalVariableNode, List<LocalVariableNode>>()
|
||||
for (localVariableNode in node.localVariables) {
|
||||
if (Type.getType(localVariableNode.desc).sort != Type.OBJECT) {
|
||||
continue
|
||||
@@ -157,9 +180,24 @@ class RedundantBoxingMethodTransformer(private val generationState: GenerationSt
|
||||
|
||||
val descriptor = value.descriptor
|
||||
if (!descriptor.isSafeToRemove) continue
|
||||
localVariableNode.desc = descriptor.unboxedType.descriptor
|
||||
val unboxedType = descriptor.unboxedTypes.singleOrNull()
|
||||
if (unboxedType == null) {
|
||||
var offset = 0
|
||||
localVariablesReplacement[localVariableNode] =
|
||||
descriptor.multiFieldValueClassUnboxInfo!!.unboxedTypesAndMethodNamesAndFieldNames.map { (type, _, fieldName) ->
|
||||
val newVarName = "${localVariableNode.name}-$fieldName"
|
||||
val newStart = localVariableNode.start
|
||||
val newEnd = localVariableNode.end
|
||||
val newOffset = offset
|
||||
offset += type.size
|
||||
LocalVariableNode(newVarName, type.descriptor, null, newStart, newEnd, newOffset)
|
||||
}
|
||||
} else {
|
||||
localVariableNode.desc = unboxedType.descriptor
|
||||
}
|
||||
}
|
||||
}
|
||||
return localVariablesReplacement
|
||||
}
|
||||
|
||||
private fun getValuesStoredOrLoadedToVariable(
|
||||
@@ -197,22 +235,24 @@ class RedundantBoxingMethodTransformer(private val generationState: GenerationSt
|
||||
}
|
||||
|
||||
private fun buildVariablesRemapping(values: RedundantBoxedValuesCollection, node: MethodNode): IntArray {
|
||||
val doubleSizedVars = HashSet<Int>()
|
||||
val wideVars2SizeMinusOne = HashMap<Int, Int>()
|
||||
for (valueDescriptor in values) {
|
||||
if (valueDescriptor.isDoubleSize()) {
|
||||
doubleSizedVars.addAll(valueDescriptor.getVariablesIndexes())
|
||||
val size = valueDescriptor.getTotalUnboxSize()
|
||||
if (size < 2) continue
|
||||
for (index in valueDescriptor.getVariablesIndexes()) {
|
||||
wideVars2SizeMinusOne.merge(index, size - 1, ::maxOf)
|
||||
}
|
||||
}
|
||||
|
||||
node.maxLocals += doubleSizedVars.size
|
||||
node.maxLocals += wideVars2SizeMinusOne.values.sum()
|
||||
val remapping = IntArray(node.maxLocals)
|
||||
for (i in remapping.indices) {
|
||||
remapping[i] = i
|
||||
}
|
||||
|
||||
for (varIndex in doubleSizedVars) {
|
||||
for ((varIndex, shift) in wideVars2SizeMinusOne) {
|
||||
for (i in varIndex + 1..remapping.lastIndex) {
|
||||
remapping[i]++
|
||||
remapping[i] += shift
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,9 +275,11 @@ class RedundantBoxingMethodTransformer(private val generationState: GenerationSt
|
||||
adaptCastInstruction(node, value, cast)
|
||||
}
|
||||
|
||||
var extraSlotsUsed = 0
|
||||
for (insn in value.getAssociatedInsns()) {
|
||||
adaptInstruction(node, insn, value)
|
||||
extraSlotsUsed = maxOf(extraSlotsUsed, adaptInstruction(node, insn, value))
|
||||
}
|
||||
node.maxLocals += extraSlotsUsed
|
||||
}
|
||||
|
||||
private fun adaptBoxingInstruction(node: MethodNode, value: BoxedValueDescriptor) {
|
||||
@@ -268,7 +310,9 @@ class RedundantBoxingMethodTransformer(private val generationState: GenerationSt
|
||||
) {
|
||||
val castInsn = castWithType.getFirst()
|
||||
val castInsnsListener = MethodNode(Opcodes.API_VERSION)
|
||||
InstructionAdapter(castInsnsListener).cast(value.unboxedType, castWithType.getSecond())
|
||||
InstructionAdapter(castInsnsListener)
|
||||
.cast(value.getUnboxTypeOrOtherwiseMethodReturnType(castInsn as? MethodInsnNode), castWithType.getSecond())
|
||||
|
||||
|
||||
for (insn in castInsnsListener.instructions.toArray()) {
|
||||
node.instructions.insertBefore(castInsn, insn)
|
||||
@@ -279,30 +323,75 @@ class RedundantBoxingMethodTransformer(private val generationState: GenerationSt
|
||||
|
||||
private fun adaptInstruction(
|
||||
node: MethodNode, insn: AbstractInsnNode, value: BoxedValueDescriptor
|
||||
) {
|
||||
val isDoubleSize = value.isDoubleSize()
|
||||
): Int {
|
||||
var usedExtraSlots = 0
|
||||
|
||||
when (insn.opcode) {
|
||||
Opcodes.POP ->
|
||||
if (isDoubleSize) {
|
||||
node.instructions.set(insn, InsnNode(Opcodes.POP2))
|
||||
}
|
||||
Opcodes.POP -> {
|
||||
val newPops = makePops(value.unboxedTypes)
|
||||
node.instructions.insert(insn, newPops)
|
||||
node.instructions.remove(insn)
|
||||
}
|
||||
|
||||
Opcodes.DUP ->
|
||||
if (isDoubleSize) {
|
||||
node.instructions.set(insn, InsnNode(Opcodes.DUP2))
|
||||
Opcodes.DUP -> when (value.getTotalUnboxSize()) {
|
||||
1 -> Unit
|
||||
2 -> node.instructions.set(insn, InsnNode(Opcodes.DUP2))
|
||||
else -> {
|
||||
usedExtraSlots = value.getTotalUnboxSize()
|
||||
var currentSlot = node.maxLocals
|
||||
val slotIndices = value.unboxedTypes.map { type -> currentSlot.also { currentSlot += type.size } }
|
||||
for ((type, index) in (value.unboxedTypes zip slotIndices).asReversed()) {
|
||||
node.instructions.insertBefore(insn, VarInsnNode(type.getOpcode(Opcodes.ISTORE), index))
|
||||
}
|
||||
repeat(2) {
|
||||
for ((type, index) in (value.unboxedTypes zip slotIndices)) {
|
||||
node.instructions.insertBefore(insn, VarInsnNode(type.getOpcode(Opcodes.ILOAD), index))
|
||||
}
|
||||
}
|
||||
node.instructions.remove(insn)
|
||||
}
|
||||
}
|
||||
|
||||
Opcodes.ASTORE, Opcodes.ALOAD -> {
|
||||
val storeOpcode = value.unboxedType.getOpcode(if (insn.opcode == Opcodes.ASTORE) Opcodes.ISTORE else Opcodes.ILOAD)
|
||||
node.instructions.set(insn, VarInsnNode(storeOpcode, (insn as VarInsnNode).`var`))
|
||||
val isStore = insn.opcode == Opcodes.ASTORE
|
||||
val singleUnboxedType = value.unboxedTypes.singleOrNull()
|
||||
if (singleUnboxedType == null) {
|
||||
val newInstructions = mutableListOf<VarInsnNode>()
|
||||
var offset = 0
|
||||
for (unboxedType in value.unboxedTypes) {
|
||||
val opcode = unboxedType.getOpcode(if (isStore) Opcodes.ISTORE else Opcodes.ILOAD)
|
||||
val newIndex = (insn as VarInsnNode).`var` + offset
|
||||
newInstructions.add(VarInsnNode(opcode, newIndex))
|
||||
offset += unboxedType.size
|
||||
}
|
||||
if (isStore) {
|
||||
val previousInstructions = generateSequence(insn.previous) { it.previous }
|
||||
.take(value.unboxedTypes.size).toList().asReversed()
|
||||
if (value.unboxedTypes.map { it.getOpcode(Opcodes.ILOAD) } == previousInstructions.map { it.opcode }) {
|
||||
// help optimizer and put each xSTORE after the corresponding xLOAD
|
||||
for ((load, store) in previousInstructions zip newInstructions) {
|
||||
newInstructions.remove(store)
|
||||
node.instructions.insert(load, store)
|
||||
}
|
||||
} else {
|
||||
for (newInstruction in newInstructions.asReversed()) {
|
||||
node.instructions.insertBefore(insn, newInstruction)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (newInstruction in newInstructions) {
|
||||
node.instructions.insertBefore(insn, newInstruction)
|
||||
}
|
||||
}
|
||||
node.instructions.remove(insn)
|
||||
} else {
|
||||
val storeOpcode = singleUnboxedType.getOpcode(if (isStore) Opcodes.ISTORE else Opcodes.ILOAD)
|
||||
node.instructions.set(insn, VarInsnNode(storeOpcode, (insn as VarInsnNode).`var`))
|
||||
}
|
||||
}
|
||||
|
||||
Opcodes.INSTANCEOF -> {
|
||||
node.instructions.insertBefore(
|
||||
insn,
|
||||
InsnNode(if (isDoubleSize) Opcodes.POP2 else Opcodes.POP)
|
||||
)
|
||||
node.instructions.insertBefore(insn, makePops(value.unboxedTypes))
|
||||
node.instructions.set(insn, InsnNode(Opcodes.ICONST_1))
|
||||
}
|
||||
|
||||
@@ -328,13 +417,63 @@ class RedundantBoxingMethodTransformer(private val generationState: GenerationSt
|
||||
}
|
||||
}
|
||||
|
||||
Opcodes.CHECKCAST,
|
||||
Opcodes.INVOKEVIRTUAL ->
|
||||
Opcodes.CHECKCAST -> node.instructions.remove(insn)
|
||||
Opcodes.INVOKEVIRTUAL -> {
|
||||
if (value.unboxedTypes.size != 1) {
|
||||
val unboxMethodCall = insn as MethodInsnNode
|
||||
val unboxMethodIndex = value.multiFieldValueClassUnboxInfo!!.unboxedMethodNames.indexOf(unboxMethodCall.name)
|
||||
val unboxedType = value.unboxedTypes[unboxMethodIndex]
|
||||
|
||||
var canRemoveInsns = true
|
||||
var savedToVariable = false
|
||||
for ((i, type) in value.unboxedTypes.withIndex().toList().asReversed()) {
|
||||
fun canRemoveInsn(includeDup: Boolean): Boolean {
|
||||
if (!canRemoveInsns) return false
|
||||
val insnToCheck = if (i < unboxMethodIndex) unboxMethodCall.previous.previous else unboxMethodCall.previous
|
||||
val result = when (insnToCheck.opcode) {
|
||||
type.getOpcode(Opcodes.ILOAD) -> true
|
||||
Opcodes.DUP2 -> includeDup && type.size == 2
|
||||
Opcodes.DUP -> includeDup && type.size == 1
|
||||
else -> false
|
||||
}
|
||||
|
||||
canRemoveInsns = result
|
||||
return result
|
||||
}
|
||||
|
||||
fun insertPopInstruction() =
|
||||
node.instructions.insertBefore(unboxMethodCall, InsnNode(if (type.size == 2) Opcodes.POP2 else Opcodes.POP))
|
||||
|
||||
fun saveToVariableIfNecessary() {
|
||||
if (savedToVariable) return
|
||||
if (i > unboxMethodIndex) return
|
||||
savedToVariable = true
|
||||
usedExtraSlots = unboxedType.size
|
||||
node.instructions.insertBefore(insn, VarInsnNode(unboxedType.getOpcode(Opcodes.ISTORE), node.maxLocals))
|
||||
}
|
||||
|
||||
if (i == unboxMethodIndex) {
|
||||
if (unboxMethodIndex > 0 && !canRemoveInsn(includeDup = false)) {
|
||||
saveToVariableIfNecessary()
|
||||
}
|
||||
} else if (canRemoveInsn(includeDup = i > unboxMethodIndex)) {
|
||||
node.instructions.remove(if (i < unboxMethodIndex) unboxMethodCall.previous.previous else unboxMethodCall.previous)
|
||||
} else {
|
||||
saveToVariableIfNecessary()
|
||||
insertPopInstruction()
|
||||
}
|
||||
}
|
||||
if (savedToVariable) {
|
||||
node.instructions.insertBefore(insn, VarInsnNode(unboxedType.getOpcode(Opcodes.ILOAD), node.maxLocals))
|
||||
}
|
||||
}
|
||||
node.instructions.remove(insn)
|
||||
}
|
||||
|
||||
else ->
|
||||
throwCannotAdaptInstruction(insn)
|
||||
}
|
||||
return usedExtraSlots
|
||||
}
|
||||
|
||||
private fun throwCannotAdaptInstruction(insn: AbstractInsnNode): Nothing =
|
||||
@@ -345,14 +484,14 @@ class RedundantBoxingMethodTransformer(private val generationState: GenerationSt
|
||||
insn: AbstractInsnNode,
|
||||
value: BoxedValueDescriptor
|
||||
) {
|
||||
val unboxedType = value.unboxedType
|
||||
val unboxedType = value.unboxedTypes.singleOrNull()
|
||||
|
||||
when (unboxedType.sort) {
|
||||
when (unboxedType?.sort) {
|
||||
Type.BOOLEAN, Type.BYTE, Type.SHORT, Type.INT, Type.CHAR ->
|
||||
adaptAreEqualIntrinsicForInt(node, insn)
|
||||
Type.LONG ->
|
||||
adaptAreEqualIntrinsicForLong(node, insn)
|
||||
Type.OBJECT -> {
|
||||
Type.OBJECT, null -> {
|
||||
}
|
||||
else ->
|
||||
throw AssertionError("Unexpected unboxed type kind: $unboxedType")
|
||||
@@ -427,7 +566,7 @@ class RedundantBoxingMethodTransformer(private val generationState: GenerationSt
|
||||
insn: AbstractInsnNode,
|
||||
value: BoxedValueDescriptor
|
||||
) {
|
||||
val unboxedType = value.unboxedType
|
||||
val unboxedType = value.unboxedTypes.single()
|
||||
|
||||
when (unboxedType.sort) {
|
||||
Type.BOOLEAN, Type.BYTE, Type.SHORT, Type.INT, Type.CHAR ->
|
||||
|
||||
+5
@@ -236,6 +236,11 @@ open class FastMethodAnalyzer<V : Value>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates frame at the index [dest] with its old value if provided and previous control flow node frame [frame].
|
||||
* Reuses old frame when possible and when [canReuse] is true.
|
||||
* If updated, adds the frame to the queue
|
||||
*/
|
||||
private fun mergeControlFlowEdge(dest: Int, frame: Frame<V>, canReuse: Boolean = false) {
|
||||
val oldFrame = frames[dest]
|
||||
val changes = when {
|
||||
|
||||
@@ -9,7 +9,6 @@ import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.ModificationTracker
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.codegen.*
|
||||
import org.jetbrains.kotlin.codegen.`when`.MappingsClassesForWhenByEnum
|
||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
|
||||
import org.jetbrains.kotlin.codegen.context.CodegenContext
|
||||
import org.jetbrains.kotlin.codegen.context.RootContext
|
||||
@@ -20,9 +19,13 @@ import org.jetbrains.kotlin.codegen.inline.InlineCache
|
||||
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods
|
||||
import org.jetbrains.kotlin.codegen.optimization.OptimizationClassBuilderFactory
|
||||
import org.jetbrains.kotlin.codegen.serialization.JvmSerializationBindings
|
||||
import org.jetbrains.kotlin.codegen.`when`.MappingsClassesForWhenByEnum
|
||||
import org.jetbrains.kotlin.config.*
|
||||
import org.jetbrains.kotlin.config.LanguageVersion.*
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticReporterFactory
|
||||
@@ -354,6 +357,14 @@ class GenerationState private constructor(
|
||||
val globalSerializationBindings = JvmSerializationBindings()
|
||||
var mapInlineClass: (ClassDescriptor) -> Type = { descriptor -> typeMapper.mapType(descriptor.defaultType) }
|
||||
|
||||
class MultiFieldValueClassUnboxInfo(val unboxedTypesAndMethodNamesAndFieldNames: List<Triple<Type, String, String>>) {
|
||||
val unboxedTypes = unboxedTypesAndMethodNamesAndFieldNames.map { (type, _, _) -> type }
|
||||
val unboxedMethodNames = unboxedTypesAndMethodNamesAndFieldNames.map { (_, methodName, _) -> methodName }
|
||||
val unboxedFieldNames = unboxedTypesAndMethodNamesAndFieldNames.map { (_, _, fieldName) -> fieldName }
|
||||
}
|
||||
|
||||
var multiFieldValueClassUnboxInfo: (ClassDescriptor) -> MultiFieldValueClassUnboxInfo? = { null }
|
||||
|
||||
val typeApproximator: TypeApproximator? =
|
||||
if (languageVersionSettings.supportsFeature(LanguageFeature.NewInference))
|
||||
TypeApproximator(module.builtIns, languageVersionSettings)
|
||||
|
||||
Reference in New Issue
Block a user