[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:
Evgeniy.Zhelenskiy
2022-11-13 21:57:15 +01:00
committed by Space Team
parent a3e396e7e4
commit 40f38c8adb
25 changed files with 726 additions and 2292 deletions
@@ -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
}
@@ -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)
@@ -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 ->
@@ -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 ->
@@ -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)