JVM IR: Optimize equality on class literals
This commit is contained in:
committed by
Alexander Udalov
parent
3a1d83e7c5
commit
a90c4d5dd5
+12
@@ -5139,6 +5139,18 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
|
||||
runTest("compiler/testData/codegen/box/classLiteral/bareArray.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("classEquality.kt")
|
||||
public void testClassEquality() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/classLiteral/classEquality.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("primitiveClassEquality.kt")
|
||||
public void testPrimitiveClassEquality() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/classLiteral/primitiveClassEquality.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("primitiveKClassEquality.kt")
|
||||
public void testPrimitiveKClassEquality() throws Exception {
|
||||
|
||||
+10
-9
@@ -1390,21 +1390,22 @@ class ExpressionCodegen(
|
||||
return MaterialValue(this@ExpressionCodegen, JAVA_STRING_TYPE, context.irBuiltIns.stringType)
|
||||
}
|
||||
|
||||
override fun visitGetClass(expression: IrGetClass, data: BlockInfo) =
|
||||
generateClassLiteralReference(expression, true, data)
|
||||
override fun visitGetClass(expression: IrGetClass, data: BlockInfo): PromisedValue =
|
||||
generateClassLiteralReference(expression, wrapIntoKClass = true, wrapPrimitives = false, data = data)
|
||||
|
||||
override fun visitClassReference(expression: IrClassReference, data: BlockInfo) =
|
||||
generateClassLiteralReference(expression, true, data)
|
||||
override fun visitClassReference(expression: IrClassReference, data: BlockInfo): PromisedValue =
|
||||
generateClassLiteralReference(expression, wrapIntoKClass = true, wrapPrimitives = false, data = data)
|
||||
|
||||
fun generateClassLiteralReference(
|
||||
classReference: IrExpression,
|
||||
wrapIntoKClass: Boolean,
|
||||
wrapPrimitives: Boolean,
|
||||
data: BlockInfo
|
||||
): PromisedValue {
|
||||
): MaterialValue {
|
||||
when (classReference) {
|
||||
is IrGetClass -> {
|
||||
// TODO transform one sort of access into the other?
|
||||
JavaClassProperty.invokeWith(classReference.argument.accept(this, data))
|
||||
JavaClassProperty.invokeWith(classReference.argument.accept(this, data), wrapPrimitives)
|
||||
}
|
||||
is IrClassReference -> {
|
||||
val classType = classReference.classType
|
||||
@@ -1416,7 +1417,7 @@ class ExpressionCodegen(
|
||||
}
|
||||
}
|
||||
|
||||
generateClassInstance(mv, classType, typeMapper)
|
||||
generateClassInstance(mv, classType, typeMapper, wrapPrimitives)
|
||||
}
|
||||
else -> {
|
||||
throw AssertionError("not an IrGetClass or IrClassReference: ${classReference.dump()}")
|
||||
@@ -1521,9 +1522,9 @@ class ExpressionCodegen(
|
||||
get() = this.classifierOrNull?.safeAs<IrTypeParameterSymbol>()?.owner?.isReified == true
|
||||
|
||||
companion object {
|
||||
internal fun generateClassInstance(v: InstructionAdapter, classType: IrType, typeMapper: IrTypeMapper) {
|
||||
internal fun generateClassInstance(v: InstructionAdapter, classType: IrType, typeMapper: IrTypeMapper, wrapPrimitives: Boolean) {
|
||||
val asmType = typeMapper.mapType(classType)
|
||||
if (classType.getClass()?.isSingleFieldValueClass == true || !isPrimitive(asmType)) {
|
||||
if (wrapPrimitives || classType.getClass()?.isSingleFieldValueClass == true || !isPrimitive(asmType)) {
|
||||
v.aconst(typeMapper.boxType(classType))
|
||||
} else {
|
||||
v.getstatic(boxType(asmType).internalName, "TYPE", "Ljava/lang/Class;")
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ class IrInlineIntrinsicsSupport(
|
||||
get() = context.state
|
||||
|
||||
override fun putClassInstance(v: InstructionAdapter, type: IrType) {
|
||||
ExpressionCodegen.generateClassInstance(v, type, typeMapper)
|
||||
ExpressionCodegen.generateClassInstance(v, type, typeMapper, wrapPrimitives = false)
|
||||
}
|
||||
|
||||
override fun generateTypeParameterContainer(v: InstructionAdapter, typeParameter: TypeParameterMarker) {
|
||||
|
||||
+23
-16
@@ -18,15 +18,10 @@ import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods
|
||||
import org.jetbrains.kotlin.config.LanguageFeature
|
||||
import org.jetbrains.kotlin.ir.declarations.isSingleFieldValueClass
|
||||
import org.jetbrains.kotlin.ir.descriptors.toIrBasedKotlinType
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.types.classOrNull
|
||||
import org.jetbrains.kotlin.ir.types.isNullable
|
||||
import org.jetbrains.kotlin.ir.util.isEnumClass
|
||||
import org.jetbrains.kotlin.ir.util.isEnumEntry
|
||||
import org.jetbrains.kotlin.ir.util.isIntegerConst
|
||||
import org.jetbrains.kotlin.ir.util.isNullConst
|
||||
import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
|
||||
@@ -110,15 +105,21 @@ class Equals(val operator: IElementType) : IntrinsicMethod() {
|
||||
}
|
||||
|
||||
// We can use reference equality for enums, otherwise we fall back to boxed equality.
|
||||
val aIsEnum = a.type.classOrNull?.owner?.run { isEnumClass || isEnumEntry } == true
|
||||
val bIsEnum = b.type.classOrNull?.owner?.run { isEnumClass || isEnumEntry } == true
|
||||
return if (aIsEnum || bIsEnum) {
|
||||
referenceEquals(a, b, leftType, codegen, data)
|
||||
} else {
|
||||
a.accept(codegen, data).materializeAt(AsmTypes.OBJECT_TYPE, codegen.context.irBuiltIns.anyNType)
|
||||
b.accept(codegen, data).materializeAt(AsmTypes.OBJECT_TYPE, codegen.context.irBuiltIns.anyNType)
|
||||
genAreEqualCall(codegen.mv)
|
||||
MaterialValue(codegen, Type.BOOLEAN_TYPE, codegen.context.irBuiltIns.booleanType)
|
||||
return when {
|
||||
a.isEnumValue || b.isEnumValue ->
|
||||
referenceEquals(a, b, leftType, codegen, data)
|
||||
|
||||
a.isClassValue && b.isClassValue -> {
|
||||
val leftValue = codegen.generateClassLiteralReference(a, wrapIntoKClass = false, wrapPrimitives = true, data = data)
|
||||
val rightValue = codegen.generateClassLiteralReference(b, wrapIntoKClass = false, wrapPrimitives = true, data = data)
|
||||
BooleanComparison(operator, leftValue, rightValue)
|
||||
}
|
||||
else -> {
|
||||
a.accept(codegen, data).materializeAt(AsmTypes.OBJECT_TYPE, codegen.context.irBuiltIns.anyNType)
|
||||
b.accept(codegen, data).materializeAt(AsmTypes.OBJECT_TYPE, codegen.context.irBuiltIns.anyNType)
|
||||
genAreEqualCall(codegen.mv)
|
||||
MaterialValue(codegen, Type.BOOLEAN_TYPE, codegen.context.irBuiltIns.booleanType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,6 +140,12 @@ class Equals(val operator: IElementType) : IntrinsicMethod() {
|
||||
BooleanComparison(operator, leftValue, rightValue)
|
||||
}
|
||||
}
|
||||
|
||||
private val IrExpression.isEnumValue: Boolean
|
||||
get() = type.classOrNull?.owner?.run { isEnumClass || isEnumEntry } == true
|
||||
|
||||
private val IrExpression.isClassValue: Boolean
|
||||
get() = this is IrGetClass || this is IrClassReference
|
||||
}
|
||||
|
||||
|
||||
|
||||
+8
-4
@@ -30,7 +30,7 @@ object JavaClassProperty : IntrinsicMethod() {
|
||||
value.mv.invokevirtual("java/lang/Object", "getClass", "()Ljava/lang/Class;", false)
|
||||
}
|
||||
|
||||
fun invokeWith(value: PromisedValue) =
|
||||
fun invokeWith(value: PromisedValue, wrapPrimitives: Boolean) =
|
||||
when {
|
||||
value.type == Type.VOID_TYPE ->
|
||||
invokeGetClass(value.materializedAt(AsmTypes.UNIT_TYPE, value.codegen.context.irBuiltIns.unitType))
|
||||
@@ -38,14 +38,18 @@ object JavaClassProperty : IntrinsicMethod() {
|
||||
invokeGetClass(value.materializedAtBoxed(value.irType))
|
||||
isPrimitive(value.type) -> {
|
||||
value.discard()
|
||||
value.mv.getstatic(boxType(value.type).internalName, "TYPE", "Ljava/lang/Class;")
|
||||
if (wrapPrimitives) {
|
||||
value.mv.aconst(boxType(value.type))
|
||||
} else {
|
||||
value.mv.getstatic(boxType(value.type).internalName, "TYPE", "Ljava/lang/Class;")
|
||||
}
|
||||
}
|
||||
else ->
|
||||
invokeGetClass(value.materialized())
|
||||
}
|
||||
|
||||
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? {
|
||||
invokeWith(expression.extensionReceiver!!.accept(codegen, data))
|
||||
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue {
|
||||
invokeWith(expression.extensionReceiver!!.accept(codegen, data), wrapPrimitives = false)
|
||||
return with(codegen) { expression.onStack }
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -28,6 +28,6 @@ object KClassJavaProperty : IntrinsicMethod() {
|
||||
val extensionReceiver = expression.extensionReceiver
|
||||
if (extensionReceiver !is IrClassReference && extensionReceiver !is IrGetClass)
|
||||
return null
|
||||
return codegen.generateClassLiteralReference(extensionReceiver, false, data)
|
||||
return codegen.generateClassLiteralReference(extensionReceiver, wrapIntoKClass = false, wrapPrimitives = false, data = data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
|
||||
open class A
|
||||
class B : A()
|
||||
|
||||
fun compareClasses(a: Any, b: Any) = a::class == b::class
|
||||
|
||||
fun isA(a: Any) = a::class == A::class
|
||||
|
||||
inline fun <reified T> isT(a: Any) = a::class == T::class
|
||||
|
||||
fun box(): String {
|
||||
if (!compareClasses("a", "b")) return "Fail 1"
|
||||
if (compareClasses(Any(), "")) return "Fail 2"
|
||||
if (!isA(A())) return "Fail 3"
|
||||
if (isA(B())) return "Fail 4"
|
||||
if (!isT<A>(A())) return "Fail 5"
|
||||
if (isT<A>(B())) return "Fail 6"
|
||||
if (isT<B>(A())) return "Fail 7"
|
||||
if (isT<Any>(B())) return "Fail 8"
|
||||
if (!isT<Int>(10)) return "Fail 9"
|
||||
if (!isT<Int>(10 as Any)) return "Fail 10"
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// IGNORE_BACKEND: JS, JS_IR, WASM
|
||||
|
||||
// boxed primitive comparisons
|
||||
fun isBoolean(a: Any) = a::class == true::class
|
||||
fun isChar(a: Any) = a::class == 'c'::class
|
||||
fun isByte(a: Any) = Byte::class == a::class
|
||||
fun isShort(a: Any) = 1.toShort()::class == a::class
|
||||
fun isInt(a: Any) = a::class == (40 + 2)::class
|
||||
fun isLong(a: Any) = a::class == 0L::class
|
||||
fun isFloat(a: Any) = a::class == 1.4f::class
|
||||
fun isDouble(a: Any) = a::class == 0.0::class
|
||||
|
||||
// reified primitive comparisons
|
||||
inline fun <reified T> isReifiedInt() = 1::class == T::class
|
||||
|
||||
fun box(): String {
|
||||
if (!isBoolean(true)) return "Fail 1"
|
||||
if (isBoolean(0)) return "Fail 2"
|
||||
if (!isChar('c')) return "Fail 3"
|
||||
if (isChar(0)) return "Fail 4"
|
||||
if (!isByte(0.toByte())) return "Fail 5"
|
||||
if (isByte(0)) return "Fail 6"
|
||||
if (!isShort(0.toShort())) return "Fail 7"
|
||||
if (isShort(0)) return "Fail 8"
|
||||
if (!isInt(0)) return "Fail 9"
|
||||
if (isInt("")) return "Fail 10"
|
||||
if (!isLong(0L)) return "Fail 11"
|
||||
if (isLong(0.0)) return "Fail 12"
|
||||
if (!isFloat(10.0f)) return "Fail 13"
|
||||
if (isFloat("")) return "Fail 14"
|
||||
if (!isDouble(1.0)) return "Fail 15"
|
||||
if (isDouble(0)) return "Fail 16"
|
||||
|
||||
if (!isReifiedInt<Int>()) return "Fail 17"
|
||||
if (isReifiedInt<Any>()) return "Fail 18"
|
||||
|
||||
if (1::class != Int::class) return "Fail 19"
|
||||
if ('c'::class == ""::class) return "Fail 20"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
+12
@@ -5025,6 +5025,18 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
||||
runTest("compiler/testData/codegen/box/classLiteral/bareArray.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("classEquality.kt")
|
||||
public void testClassEquality() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/classLiteral/classEquality.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("primitiveClassEquality.kt")
|
||||
public void testPrimitiveClassEquality() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/classLiteral/primitiveClassEquality.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("primitiveKClassEquality.kt")
|
||||
public void testPrimitiveKClassEquality() throws Exception {
|
||||
|
||||
+12
@@ -5139,6 +5139,18 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
runTest("compiler/testData/codegen/box/classLiteral/bareArray.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("classEquality.kt")
|
||||
public void testClassEquality() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/classLiteral/classEquality.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("primitiveClassEquality.kt")
|
||||
public void testPrimitiveClassEquality() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/classLiteral/primitiveClassEquality.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("primitiveKClassEquality.kt")
|
||||
public void testPrimitiveKClassEquality() throws Exception {
|
||||
|
||||
+10
@@ -4402,6 +4402,16 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
||||
runTest("compiler/testData/codegen/box/classLiteral/bareArray.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("classEquality.kt")
|
||||
public void testClassEquality() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/classLiteral/classEquality.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("primitiveClassEquality.kt")
|
||||
public void testPrimitiveClassEquality() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/classLiteral/primitiveClassEquality.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("primitiveKClassEquality.kt")
|
||||
public void testPrimitiveKClassEquality() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/classLiteral/primitiveKClassEquality.kt");
|
||||
|
||||
+12
@@ -3687,6 +3687,18 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
runTest("compiler/testData/codegen/box/classLiteral/bareArray.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("classEquality.kt")
|
||||
public void testClassEquality() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/classLiteral/classEquality.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("primitiveClassEquality.kt")
|
||||
public void testPrimitiveClassEquality() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/classLiteral/primitiveClassEquality.kt");
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestMetadata("compiler/testData/codegen/box/classLiteral/bound")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
|
||||
+12
@@ -3735,6 +3735,18 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
|
||||
runTest("compiler/testData/codegen/box/classLiteral/bareArray.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("classEquality.kt")
|
||||
public void testClassEquality() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/classLiteral/classEquality.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("primitiveClassEquality.kt")
|
||||
public void testPrimitiveClassEquality() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/classLiteral/primitiveClassEquality.kt");
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestMetadata("compiler/testData/codegen/box/classLiteral/bound")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
|
||||
+10
@@ -3317,6 +3317,16 @@ public class IrCodegenBoxWasmTestGenerated extends AbstractIrCodegenBoxWasmTest
|
||||
runTest("compiler/testData/codegen/box/classLiteral/bareArray.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("classEquality.kt")
|
||||
public void testClassEquality() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/classLiteral/classEquality.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("primitiveClassEquality.kt")
|
||||
public void testPrimitiveClassEquality() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/classLiteral/primitiveClassEquality.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/codegen/box/classLiteral/bound")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
+12
@@ -3847,6 +3847,18 @@ public class NativeCodegenBoxTestGenerated extends AbstractNativeCodegenBoxTest
|
||||
runTest("compiler/testData/codegen/box/classLiteral/bareArray.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("classEquality.kt")
|
||||
public void testClassEquality() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/classLiteral/classEquality.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("primitiveClassEquality.kt")
|
||||
public void testPrimitiveClassEquality() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/classLiteral/primitiveClassEquality.kt");
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestMetadata("compiler/testData/codegen/box/classLiteral/bound")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
|
||||
Reference in New Issue
Block a user