From 3bbd8979e4655922834c30298d9080c6bbdf7685 Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Thu, 3 Mar 2016 16:12:05 +0300 Subject: [PATCH] KT-9670: optimize Class <-> KClass wrapping/unwrapping as a special case of boxing/unboxing. NB doesn't work for arrays of classes. --- .../org/jetbrains/kotlin/codegen/AsmUtil.java | 7 +- .../optimization/boxing/BoxedBasicValue.kt | 14 +++- .../optimization/boxing/BoxingInterpreter.kt | 82 +++++++++++++------ .../boxingOptimization/kClassInAnnotation.kt | 16 ++++ .../kClassInAnnotationEscaping.kt | 20 +++++ .../javaExtensionPropertyIntrinsic.kt | 14 ++-- .../codegen/BytecodeTextTestGenerated.java | 12 +++ 7 files changed, 135 insertions(+), 30 deletions(-) create mode 100644 compiler/testData/codegen/bytecodeText/boxingOptimization/kClassInAnnotation.kt create mode 100644 compiler/testData/codegen/bytecodeText/boxingOptimization/kClassInAnnotationEscaping.kt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/AsmUtil.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/AsmUtil.java index 69fe13857d3..73ced433f33 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/AsmUtil.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/AsmUtil.java @@ -120,13 +120,18 @@ public class AsmUtil { @NotNull public static Type unboxType(@NotNull Type boxedType) { - Type primitiveType = primitiveTypeByBoxedType.get(boxedType); + Type primitiveType = unboxPrimitiveTypeOrNull(boxedType); if (primitiveType == null) { throw new UnsupportedOperationException("Unboxing: " + boxedType); } return primitiveType; } + @Nullable + public static Type unboxPrimitiveTypeOrNull(@NotNull Type boxedType) { + return primitiveTypeByBoxedType.get(boxedType); + } + public static boolean isIntPrimitive(Type type) { return type == Type.INT_TYPE || type == Type.SHORT_TYPE || type == Type.BYTE_TYPE || type == Type.CHAR_TYPE; } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/BoxedBasicValue.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/BoxedBasicValue.kt index aaad810b444..e47aa06c5c8 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/BoxedBasicValue.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/BoxedBasicValue.kt @@ -18,6 +18,7 @@ package org.jetbrains.kotlin.codegen.optimization.boxing import com.intellij.openapi.util.Pair 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.tree.AbstractInsnNode import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue @@ -35,7 +36,7 @@ class BoxedBasicValue( private val associatedVariables = HashSet() private val mergedWith = HashSet() - val primitiveType: Type = AsmUtil.unboxType(boxedType) + val primitiveType: Type = unboxType(boxedType) var isSafeToRemove = true; private set override fun equals(other: Any?) = @@ -84,4 +85,15 @@ class BoxedBasicValue( fun getUnboxingWithCastInsns(): Set> = 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") + } + } } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/BoxingInterpreter.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/BoxingInterpreter.kt index 879112a4745..81debf608bc 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/BoxingInterpreter.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/BoxingInterpreter.kt @@ -21,6 +21,7 @@ import org.jetbrains.kotlin.codegen.AsmUtil import org.jetbrains.kotlin.codegen.RangeCodegenUtil import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.resolve.jvm.AsmTypes import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType import org.jetbrains.org.objectweb.asm.Opcodes 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 return when { - isBoxing(insn) -> { + insn.isBoxing() -> { createNewBoxing(insn, value.type, null) } - isUnboxing(insn) && firstArg is BoxedBasicValue -> { + insn.isUnboxing() && firstArg is BoxedBasicValue -> { onUnboxing(insn, firstArg, value.type) value } - isIteratorMethodCallOfProgression(insn, values) -> { + insn.isIteratorMethodCallOfProgression(values) -> { ProgressionIteratorBasicValue(getValuesTypeOfProgressionClass(firstArg.type.internalName)) } - isNextMethodCallOfProgressionIterator(insn, values) -> { + insn.isNextMethodCallOfProgressionIterator(values) -> { val progressionIterator = firstArg as? ProgressionIteratorBasicValue ?: throw AssertionError("firstArg should be progression iterator") createNewBoxing(insn, AsmUtil.boxType(progressionIterator.valuesPrimitiveType), progressionIterator) @@ -120,6 +121,9 @@ open class BoxingInterpreter(private val insnList: InsnList) : OptimizationBasic private val UNBOXING_METHOD_NAMES = 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) = 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) = FqName(Type.getObjectType(internalClassName).className) - private fun isUnboxing(insn: AbstractInsnNode) = - insn.opcode == Opcodes.INVOKEVIRTUAL && run { - val methodInsn = insn as MethodInsnNode - isWrapperClassNameOrNumber(methodInsn.owner) && isUnboxingMethodName(methodInsn.name) + private fun AbstractInsnNode.isUnboxing() = + isPrimitiveUnboxing() || isJavaLangClassUnboxing() + + 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) = UNBOXING_METHOD_NAMES.contains(name) - private fun isBoxing(insn: AbstractInsnNode) = - insn.opcode == Opcodes.INVOKESTATIC && run { - val methodInsn = insn as MethodInsnNode - isWrapperClassName(methodInsn.owner) && methodInsn.name == "valueOf" && run { - val ownerType = Type.getObjectType(methodInsn.owner) - methodInsn.desc == Type.getMethodDescriptor(ownerType, AsmUtil.unboxType(ownerType)) - } + private fun AbstractInsnNode.isBoxing() = + this.isPrimitiveBoxing() || this.isJavaLangClassBoxing() + + private fun AbstractInsnNode.isPrimitiveBoxing() = + isMethodInsnWith(Opcodes.INVOKESTATIC) { + isWrapperClassName(owner) && + name == "valueOf" && + isBoxingMethodDescriptor() } - private fun isNextMethodCallOfProgressionIterator(insn: AbstractInsnNode, values: kotlin.collections.List) = - insn.opcode == Opcodes.INVOKEINTERFACE && - values[0] is ProgressionIteratorBasicValue && - (insn as MethodInsnNode).name == "next" + private fun MethodInsnNode.isBoxingMethodDescriptor(): Boolean { + val ownerType = Type.getObjectType(owner) + return desc == Type.getMethodDescriptor(ownerType, AsmUtil.unboxType(ownerType)) + } - private fun isIteratorMethodCallOfProgression(insn: AbstractInsnNode, values: kotlin.collections.List) = - insn.opcode == Opcodes.INVOKEINTERFACE && run { + private fun AbstractInsnNode.isJavaLangClassBoxing() = + isMethodInsnWith(Opcodes.INVOKESTATIC) { + owner == AsmTypes.REFLECTION && + name == "getOrCreateKotlinClass" && + desc == JLCLASS_TO_KCLASS + } + + private fun AbstractInsnNode.isNextMethodCallOfProgressionIterator(values: List) = + values[0] is ProgressionIteratorBasicValue && + isMethodInsnWith(INVOKEINTERFACE) { + name == "next" + } + + private fun AbstractInsnNode.isIteratorMethodCallOfProgression(values: List) = + isMethodInsnWith(INVOKEINTERFACE) { 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) = RangeCodegenUtil.isRangeOrProgression(buildFqNameByInternal(internalClassName)) private fun getValuesTypeOfProgressionClass(progressionClassInternalName: String) = - RangeCodegenUtil.getPrimitiveRangeOrProgressionElementType(buildFqNameByInternal(progressionClassInternalName))?.let { type -> + RangeCodegenUtil.getPrimitiveRangeOrProgressionElementType(buildFqNameByInternal(progressionClassInternalName))?.let { + type -> type.typeName.asString() } ?: error("type should be not null") } diff --git a/compiler/testData/codegen/bytecodeText/boxingOptimization/kClassInAnnotation.kt b/compiler/testData/codegen/bytecodeText/boxingOptimization/kClassInAnnotation.kt new file mode 100644 index 00000000000..64675ba2613 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/boxingOptimization/kClassInAnnotation.kt @@ -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 diff --git a/compiler/testData/codegen/bytecodeText/boxingOptimization/kClassInAnnotationEscaping.kt b/compiler/testData/codegen/bytecodeText/boxingOptimization/kClassInAnnotationEscaping.kt new file mode 100644 index 00000000000..d1568b07934 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/boxingOptimization/kClassInAnnotationEscaping.kt @@ -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 diff --git a/compiler/testData/codegen/bytecodeText/javaExtensionPropertyIntrinsic.kt b/compiler/testData/codegen/bytecodeText/javaExtensionPropertyIntrinsic.kt index 9662621e2df..5298a2753c2 100644 --- a/compiler/testData/codegen/bytecodeText/javaExtensionPropertyIntrinsic.kt +++ b/compiler/testData/codegen/bytecodeText/javaExtensionPropertyIntrinsic.kt @@ -1,14 +1,18 @@ class Klass fun foo() { - val c0 = (Klass::class).java // prevent intrinsic .java for class literal - val c1 = Klass::class.java - val c2 = Int::class.java - val c3 = Integer::class.java + // Even though no intrinsic is used, + // redundant boxing/unboxing optimizes out wrapping/unrapping java.lang.Class instances + val c0 = (Klass::class).java // LDC LKlass;.class + 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 // 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 diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java index 9bc3af1a879..3d24b9e41c8 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java @@ -412,6 +412,18 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest { 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") public void testKt6842() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/boxingOptimization/kt6842.kt");