KT-9670: optimize Class <-> KClass wrapping/unwrapping as a special case of boxing/unboxing.
NB doesn't work for arrays of classes.
This commit is contained in:
@@ -120,13 +120,18 @@ public class AsmUtil {
|
|||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static Type unboxType(@NotNull Type boxedType) {
|
public static Type unboxType(@NotNull Type boxedType) {
|
||||||
Type primitiveType = primitiveTypeByBoxedType.get(boxedType);
|
Type primitiveType = unboxPrimitiveTypeOrNull(boxedType);
|
||||||
if (primitiveType == null) {
|
if (primitiveType == null) {
|
||||||
throw new UnsupportedOperationException("Unboxing: " + boxedType);
|
throw new UnsupportedOperationException("Unboxing: " + boxedType);
|
||||||
}
|
}
|
||||||
return primitiveType;
|
return primitiveType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static Type unboxPrimitiveTypeOrNull(@NotNull Type boxedType) {
|
||||||
|
return primitiveTypeByBoxedType.get(boxedType);
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isIntPrimitive(Type type) {
|
public static boolean isIntPrimitive(Type type) {
|
||||||
return type == Type.INT_TYPE || type == Type.SHORT_TYPE || type == Type.BYTE_TYPE || type == Type.CHAR_TYPE;
|
return type == Type.INT_TYPE || type == Type.SHORT_TYPE || type == Type.BYTE_TYPE || type == Type.CHAR_TYPE;
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-1
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.codegen.optimization.boxing
|
|||||||
|
|
||||||
import com.intellij.openapi.util.Pair
|
import com.intellij.openapi.util.Pair
|
||||||
import org.jetbrains.kotlin.codegen.AsmUtil
|
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||||
|
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||||
import org.jetbrains.org.objectweb.asm.Type
|
import org.jetbrains.org.objectweb.asm.Type
|
||||||
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
|
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
|
||||||
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
|
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
|
||||||
@@ -35,7 +36,7 @@ class BoxedBasicValue(
|
|||||||
private val associatedVariables = HashSet<Int>()
|
private val associatedVariables = HashSet<Int>()
|
||||||
private val mergedWith = HashSet<BoxedBasicValue>()
|
private val mergedWith = HashSet<BoxedBasicValue>()
|
||||||
|
|
||||||
val primitiveType: Type = AsmUtil.unboxType(boxedType)
|
val primitiveType: Type = unboxType(boxedType)
|
||||||
var isSafeToRemove = true; private set
|
var isSafeToRemove = true; private set
|
||||||
|
|
||||||
override fun equals(other: Any?) =
|
override fun equals(other: Any?) =
|
||||||
@@ -84,4 +85,15 @@ class BoxedBasicValue(
|
|||||||
|
|
||||||
fun getUnboxingWithCastInsns(): Set<Pair<AbstractInsnNode, Type>> =
|
fun getUnboxingWithCastInsns(): Set<Pair<AbstractInsnNode, Type>> =
|
||||||
unboxingWithCastInsns
|
unboxingWithCastInsns
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private fun unboxType(boxedType: Type): 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")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+59
-23
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.codegen.AsmUtil
|
|||||||
import org.jetbrains.kotlin.codegen.RangeCodegenUtil
|
import org.jetbrains.kotlin.codegen.RangeCodegenUtil
|
||||||
import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter
|
import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||||
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType
|
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType
|
||||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||||
import org.jetbrains.org.objectweb.asm.Type
|
import org.jetbrains.org.objectweb.asm.Type
|
||||||
@@ -49,17 +50,17 @@ open class BoxingInterpreter(private val insnList: InsnList) : OptimizationBasic
|
|||||||
val firstArg = values.firstOrNull() ?: return value
|
val firstArg = values.firstOrNull() ?: return value
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
isBoxing(insn) -> {
|
insn.isBoxing() -> {
|
||||||
createNewBoxing(insn, value.type, null)
|
createNewBoxing(insn, value.type, null)
|
||||||
}
|
}
|
||||||
isUnboxing(insn) && firstArg is BoxedBasicValue -> {
|
insn.isUnboxing() && firstArg is BoxedBasicValue -> {
|
||||||
onUnboxing(insn, firstArg, value.type)
|
onUnboxing(insn, firstArg, value.type)
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
isIteratorMethodCallOfProgression(insn, values) -> {
|
insn.isIteratorMethodCallOfProgression(values) -> {
|
||||||
ProgressionIteratorBasicValue(getValuesTypeOfProgressionClass(firstArg.type.internalName))
|
ProgressionIteratorBasicValue(getValuesTypeOfProgressionClass(firstArg.type.internalName))
|
||||||
}
|
}
|
||||||
isNextMethodCallOfProgressionIterator(insn, values) -> {
|
insn.isNextMethodCallOfProgressionIterator(values) -> {
|
||||||
val progressionIterator = firstArg as? ProgressionIteratorBasicValue
|
val progressionIterator = firstArg as? ProgressionIteratorBasicValue
|
||||||
?: throw AssertionError("firstArg should be progression iterator")
|
?: throw AssertionError("firstArg should be progression iterator")
|
||||||
createNewBoxing(insn, AsmUtil.boxType(progressionIterator.valuesPrimitiveType), progressionIterator)
|
createNewBoxing(insn, AsmUtil.boxType(progressionIterator.valuesPrimitiveType), progressionIterator)
|
||||||
@@ -120,6 +121,9 @@ open class BoxingInterpreter(private val insnList: InsnList) : OptimizationBasic
|
|||||||
private val UNBOXING_METHOD_NAMES =
|
private val UNBOXING_METHOD_NAMES =
|
||||||
ImmutableSet.of("booleanValue", "charValue", "byteValue", "shortValue", "intValue", "floatValue", "longValue", "doubleValue")
|
ImmutableSet.of("booleanValue", "charValue", "byteValue", "shortValue", "intValue", "floatValue", "longValue", "doubleValue")
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
private fun isWrapperClassNameOrNumber(internalClassName: String) =
|
private fun isWrapperClassNameOrNumber(internalClassName: String) =
|
||||||
isWrapperClassName(internalClassName) || internalClassName == Type.getInternalName(Number::class.java)
|
isWrapperClassName(internalClassName) || internalClassName == Type.getInternalName(Number::class.java)
|
||||||
|
|
||||||
@@ -129,40 +133,72 @@ open class BoxingInterpreter(private val insnList: InsnList) : OptimizationBasic
|
|||||||
private fun buildFqNameByInternal(internalClassName: String) =
|
private fun buildFqNameByInternal(internalClassName: String) =
|
||||||
FqName(Type.getObjectType(internalClassName).className)
|
FqName(Type.getObjectType(internalClassName).className)
|
||||||
|
|
||||||
private fun isUnboxing(insn: AbstractInsnNode) =
|
private fun AbstractInsnNode.isUnboxing() =
|
||||||
insn.opcode == Opcodes.INVOKEVIRTUAL && run {
|
isPrimitiveUnboxing() || isJavaLangClassUnboxing()
|
||||||
val methodInsn = insn as MethodInsnNode
|
|
||||||
isWrapperClassNameOrNumber(methodInsn.owner) && isUnboxingMethodName(methodInsn.name)
|
private inline fun AbstractInsnNode.isMethodInsnWith(opcode: Int, condition: MethodInsnNode.() -> Boolean): Boolean =
|
||||||
|
if (this.opcode == opcode && this is MethodInsnNode)
|
||||||
|
this.condition()
|
||||||
|
else
|
||||||
|
false
|
||||||
|
|
||||||
|
private fun AbstractInsnNode.isPrimitiveUnboxing() =
|
||||||
|
isMethodInsnWith(Opcodes.INVOKEVIRTUAL) {
|
||||||
|
isWrapperClassNameOrNumber(owner) && isUnboxingMethodName(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun AbstractInsnNode.isJavaLangClassUnboxing() =
|
||||||
|
isMethodInsnWith(Opcodes.INVOKESTATIC) {
|
||||||
|
owner == "kotlin/jvm/JvmClassMappingKt" &&
|
||||||
|
name == "getJavaClass" &&
|
||||||
|
desc == KCLASS_TO_JLCLASS
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isUnboxingMethodName(name: String) =
|
private fun isUnboxingMethodName(name: String) =
|
||||||
UNBOXING_METHOD_NAMES.contains(name)
|
UNBOXING_METHOD_NAMES.contains(name)
|
||||||
|
|
||||||
private fun isBoxing(insn: AbstractInsnNode) =
|
private fun AbstractInsnNode.isBoxing() =
|
||||||
insn.opcode == Opcodes.INVOKESTATIC && run {
|
this.isPrimitiveBoxing() || this.isJavaLangClassBoxing()
|
||||||
val methodInsn = insn as MethodInsnNode
|
|
||||||
isWrapperClassName(methodInsn.owner) && methodInsn.name == "valueOf" && run {
|
private fun AbstractInsnNode.isPrimitiveBoxing() =
|
||||||
val ownerType = Type.getObjectType(methodInsn.owner)
|
isMethodInsnWith(Opcodes.INVOKESTATIC) {
|
||||||
methodInsn.desc == Type.getMethodDescriptor(ownerType, AsmUtil.unboxType(ownerType))
|
isWrapperClassName(owner) &&
|
||||||
}
|
name == "valueOf" &&
|
||||||
|
isBoxingMethodDescriptor()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isNextMethodCallOfProgressionIterator(insn: AbstractInsnNode, values: kotlin.collections.List<BasicValue>) =
|
private fun MethodInsnNode.isBoxingMethodDescriptor(): Boolean {
|
||||||
insn.opcode == Opcodes.INVOKEINTERFACE &&
|
val ownerType = Type.getObjectType(owner)
|
||||||
values[0] is ProgressionIteratorBasicValue &&
|
return desc == Type.getMethodDescriptor(ownerType, AsmUtil.unboxType(ownerType))
|
||||||
(insn as MethodInsnNode).name == "next"
|
}
|
||||||
|
|
||||||
private fun isIteratorMethodCallOfProgression(insn: AbstractInsnNode, values: kotlin.collections.List<BasicValue>) =
|
private fun AbstractInsnNode.isJavaLangClassBoxing() =
|
||||||
insn.opcode == Opcodes.INVOKEINTERFACE && run {
|
isMethodInsnWith(Opcodes.INVOKESTATIC) {
|
||||||
|
owner == AsmTypes.REFLECTION &&
|
||||||
|
name == "getOrCreateKotlinClass" &&
|
||||||
|
desc == JLCLASS_TO_KCLASS
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun AbstractInsnNode.isNextMethodCallOfProgressionIterator(values: List<BasicValue>) =
|
||||||
|
values[0] is ProgressionIteratorBasicValue &&
|
||||||
|
isMethodInsnWith(INVOKEINTERFACE) {
|
||||||
|
name == "next"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun AbstractInsnNode.isIteratorMethodCallOfProgression(values: List<BasicValue>) =
|
||||||
|
isMethodInsnWith(INVOKEINTERFACE) {
|
||||||
val firstArgType = values[0].type
|
val firstArgType = values[0].type
|
||||||
firstArgType != null && isProgressionClass(firstArgType.internalName) && "iterator" == (insn as MethodInsnNode).name
|
firstArgType != null &&
|
||||||
|
isProgressionClass(firstArgType.internalName) &&
|
||||||
|
name == "iterator"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isProgressionClass(internalClassName: String) =
|
private fun isProgressionClass(internalClassName: String) =
|
||||||
RangeCodegenUtil.isRangeOrProgression(buildFqNameByInternal(internalClassName))
|
RangeCodegenUtil.isRangeOrProgression(buildFqNameByInternal(internalClassName))
|
||||||
|
|
||||||
private fun getValuesTypeOfProgressionClass(progressionClassInternalName: String) =
|
private fun getValuesTypeOfProgressionClass(progressionClassInternalName: String) =
|
||||||
RangeCodegenUtil.getPrimitiveRangeOrProgressionElementType(buildFqNameByInternal(progressionClassInternalName))?.let { type ->
|
RangeCodegenUtil.getPrimitiveRangeOrProgressionElementType(buildFqNameByInternal(progressionClassInternalName))?.let {
|
||||||
|
type ->
|
||||||
type.typeName.asString()
|
type.typeName.asString()
|
||||||
} ?: error("type should be not null")
|
} ?: error("type should be not null")
|
||||||
}
|
}
|
||||||
|
|||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
annotation class Ann(val arg: KClass<*>)
|
||||||
|
|
||||||
|
class OK
|
||||||
|
|
||||||
|
@Ann(OK::class) class MyClass
|
||||||
|
|
||||||
|
fun test(): String {
|
||||||
|
val arg = MyClass::class.java.getAnnotation(Ann::class.java).arg.java
|
||||||
|
return arg.getSimpleName()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0 INVOKESTATIC kotlin/jvm/internal/Reflection\.getOrCreateKotlinClass
|
||||||
|
// 0 INVOKESTATIC kotlin/jvm/JvmClassMappingKt\.getJavaClass
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
annotation class Ann(val arg: KClass<*>)
|
||||||
|
|
||||||
|
class OK
|
||||||
|
|
||||||
|
@Ann(OK::class) class MyClass
|
||||||
|
|
||||||
|
var escape: KClass<*>? = null
|
||||||
|
|
||||||
|
fun test1(): String {
|
||||||
|
val arg = MyClass::class.java.getAnnotation(Ann::class.java).arg
|
||||||
|
escape = arg
|
||||||
|
val argSimpleName = arg.java.getSimpleName()
|
||||||
|
return argSimpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1 INVOKESTATIC kotlin/jvm/internal/Reflection\.getOrCreateKotlinClass
|
||||||
|
// 1 INVOKESTATIC kotlin/jvm/JvmClassMappingKt.getJavaClass
|
||||||
@@ -1,14 +1,18 @@
|
|||||||
class Klass
|
class Klass
|
||||||
|
|
||||||
fun foo() {
|
fun foo() {
|
||||||
val c0 = (Klass::class).java // prevent intrinsic .java for class literal
|
// Even though no intrinsic is used,
|
||||||
val c1 = Klass::class.java
|
// redundant boxing/unboxing optimizes out wrapping/unrapping java.lang.Class instances
|
||||||
val c2 = Int::class.java
|
val c0 = (Klass::class).java // LDC LKlass;.class
|
||||||
val c3 = Integer::class.java
|
|
||||||
|
|
||||||
|
val c1 = Klass::class.java // LDC LKlass;.class
|
||||||
|
|
||||||
|
val c2 = Int::class.java // GETSTATIC java/lang/Integer.TYPE
|
||||||
|
|
||||||
|
val c3 = Integer::class.java // LDC Ljava/lang/Integer;.class
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2 LDC LKlass;.class
|
// 2 LDC LKlass;.class
|
||||||
// 1 GETSTATIC java/lang/Integer.TYPE : Ljava/lang/Class;
|
// 1 GETSTATIC java/lang/Integer.TYPE : Ljava/lang/Class;
|
||||||
// 1 INVOKESTATIC kotlin/jvm.*\.getJava
|
// 0 INVOKESTATIC kotlin/jvm.*\.getJava
|
||||||
// 1 LDC Ljava/lang/Integer;.class
|
// 1 LDC Ljava/lang/Integer;.class
|
||||||
|
|||||||
@@ -412,6 +412,18 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("kClassInAnnotation.kt")
|
||||||
|
public void testKClassInAnnotation() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/boxingOptimization/kClassInAnnotation.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("kClassInAnnotationEscaping.kt")
|
||||||
|
public void testKClassInAnnotationEscaping() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/boxingOptimization/kClassInAnnotationEscaping.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("kt6842.kt")
|
@TestMetadata("kt6842.kt")
|
||||||
public void testKt6842() throws Exception {
|
public void testKt6842() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/boxingOptimization/kt6842.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/boxingOptimization/kt6842.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user