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:
Dmitry Petrov
2016-03-03 16:12:05 +03:00
parent 2ecb8896cc
commit 3bbd8979e4
7 changed files with 135 additions and 30 deletions
@@ -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;
} }
@@ -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")
}
}
} }
@@ -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")
} }
@@ -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
@@ -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");