JVM IR: Optimize equality on class literals

This commit is contained in:
Steven Schäfer
2022-07-27 20:55:45 +02:00
committed by Alexander Udalov
parent 3a1d83e7c5
commit a90c4d5dd5
15 changed files with 199 additions and 31 deletions
@@ -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 {
@@ -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;")
@@ -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) {
@@ -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
}
@@ -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 }
}
}
@@ -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"
}
@@ -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 {
@@ -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 {
@@ -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");
@@ -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")
@@ -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")
@@ -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)
@@ -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")