diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/CompareTo.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/CompareTo.kt index 20617cea078..b7100d17dce 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/CompareTo.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/CompareTo.kt @@ -21,8 +21,8 @@ import org.jetbrains.kotlin.backend.jvm.JvmBackendContext import org.jetbrains.kotlin.backend.jvm.codegen.* import org.jetbrains.kotlin.backend.jvm.ir.isSmartcastFromHigherThanNullable import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.codegen.AsmUtil import org.jetbrains.kotlin.codegen.AsmUtil.comparisonOperandType -import org.jetbrains.kotlin.codegen.AsmUtil.unboxType import org.jetbrains.kotlin.codegen.BranchedValue import org.jetbrains.kotlin.codegen.NumberCompare import org.jetbrains.kotlin.codegen.ObjectCompare @@ -135,20 +135,27 @@ class NonIEEE754FloatComparison(val op: IElementType, val a: MaterialValue, val } } -class PrimitiveToBoxedComparison( +class PrimitiveToObjectComparison( val op: IElementType, private val boxedValue: MaterialValue, - private val loadOther: () -> MaterialValue + private val useNullCheck: Boolean, + private val primitiveType: Type, + private val loadOther: () -> MaterialValue, ) : BooleanValue(boxedValue.codegen) { override fun jumpIfFalse(target: Label) { val compareLabel = Label() mv.dup() - mv.ifnonnull(compareLabel) + if (useNullCheck) { + mv.ifnonnull(compareLabel) + } else { + mv.instanceOf(AsmUtil.boxType(primitiveType)) + mv.ifne(compareLabel) + } mv.pop() mv.goTo(target) mv.mark(compareLabel) - val unboxedValue = boxedValue.materializedAt(unboxType(boxedValue.type), boxedValue.irType) + val unboxedValue = boxedValue.materializedAt(primitiveType, boxedValue.irType) BooleanComparison(op, unboxedValue, loadOther()).jumpIfFalse(target) } @@ -156,11 +163,16 @@ class PrimitiveToBoxedComparison( val compareLabel = Label() val endLabel = Label() mv.dup() - mv.ifnonnull(compareLabel) + if (useNullCheck) { + mv.ifnonnull(compareLabel) + } else { + mv.instanceOf(AsmUtil.boxType(primitiveType)) + mv.ifne(compareLabel) + } mv.pop() mv.goTo(endLabel) mv.mark(compareLabel) - val unboxedValue = boxedValue.materializedAt(unboxType(boxedValue.type), boxedValue.irType) + val unboxedValue = boxedValue.materializedAt(primitiveType, boxedValue.irType) BooleanComparison(op, unboxedValue, loadOther()).jumpIfTrue(target) mv.mark(endLabel) } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Equals.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Equals.kt index 5f86aed4753..77962db081d 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Equals.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Equals.kt @@ -28,6 +28,7 @@ import org.jetbrains.kotlin.ir.util.isNullConst import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi2ir.generators.hasNoSideEffects import org.jetbrains.kotlin.resolve.jvm.AsmTypes +import org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature import org.jetbrains.kotlin.types.isNullable import org.jetbrains.kotlin.types.typeUtil.isPrimitiveNumberOrNullableType @@ -98,19 +99,25 @@ class Equals(val operator: IElementType) : IntrinsicMethod() { } } + // Avoid boxing for `primitive == object` and `boxed primitive == primitive` where we know + // what comparison means. The optimization does not apply to `object == primitive` as equals + // could be overridden for the object. if ((opToken == IrStatementOrigin.EQEQ || opToken == IrStatementOrigin.EXCLEQ) && - (AsmUtil.isIntOrLongPrimitive(leftType) || AsmUtil.isIntOrLongPrimitive(rightType)) && - (AsmUtil.isBoxedTypeOf(leftType, rightType) || AsmUtil.isBoxedTypeOf(rightType, leftType)) + ((AsmUtil.isIntOrLongPrimitive(leftType) && !AsmUtil.isPrimitive(rightType)) || + (AsmUtil.isIntOrLongPrimitive(rightType) && AsmUtil.isBoxedTypeOf(leftType, rightType))) ) { val leftIsPrimitive = AsmUtil.isIntOrLongPrimitive(leftType) + val primitiveType = if (leftIsPrimitive) leftType else rightType + val nonPrimitiveType = if (leftIsPrimitive) rightType else leftType + val useNullCheck = AsmUtil.isBoxedTypeOf(nonPrimitiveType, primitiveType) return if (leftIsPrimitive) { val loadOther = loadOther(a, leftType) val boxedValue = b.accept(codegen, data).materializedAt(rightType, b.type) - PrimitiveToBoxedComparison(operator, boxedValue, loadOther) + PrimitiveToObjectComparison(operator, boxedValue, useNullCheck, primitiveType, loadOther) } else { val boxedValue = a.accept(codegen, data).materializedAt(leftType, a.type) val loadOther = loadOther(b, rightType) - PrimitiveToBoxedComparison(operator, boxedValue, loadOther) + PrimitiveToObjectComparison(operator, boxedValue, useNullCheck, primitiveType, loadOther) } } diff --git a/compiler/testData/codegen/bytecodeText/conditions/noBoxingForPrimitiveEqObject.kt b/compiler/testData/codegen/bytecodeText/conditions/noBoxingForPrimitiveEqObject.kt index 2321696701e..b6ea04c6c67 100644 --- a/compiler/testData/codegen/bytecodeText/conditions/noBoxingForPrimitiveEqObject.kt +++ b/compiler/testData/codegen/bytecodeText/conditions/noBoxingForPrimitiveEqObject.kt @@ -1,6 +1,3 @@ -// IGNORE_BACKEND: JVM_IR -// TODO KT-36646 Don't box primitive values in equality comparison with nullable primitive values in JVM_IR - fun testBoolean1(a: Boolean, b: Any?) = a == b fun testBoolean2(a: Boolean, b: Any?) = a != b diff --git a/compiler/testData/ir/irText/expressions/primitiveComparisons.txt b/compiler/testData/ir/irText/expressions/primitiveComparisons.txt index dda66849909..6ac3f6799de 100644 --- a/compiler/testData/ir/irText/expressions/primitiveComparisons.txt +++ b/compiler/testData/ir/irText/expressions/primitiveComparisons.txt @@ -5,80 +5,80 @@ FILE fqName: fileName:/primitiveComparisons.kt BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun btest1 (a: kotlin.Byte, b: kotlin.Byte): kotlin.Boolean declared in ' CALL 'public final fun greater (arg0: kotlin.Int, arg1: kotlin.Int): kotlin.Boolean declared in kotlin.internal.ir' type=kotlin.Boolean origin=GT - arg0: CALL 'public open fun toInt (): kotlin.Int declared in kotlin.Byte' type=kotlin.Int origin=null - $this: GET_VAR 'a: kotlin.Byte declared in .btest1' type=kotlin.Byte origin=null - arg1: CALL 'public open fun toInt (): kotlin.Int declared in kotlin.Byte' type=kotlin.Int origin=null - $this: GET_VAR 'b: kotlin.Byte declared in .btest1' type=kotlin.Byte origin=null + arg0: TYPE_OP type=kotlin.Int origin=IMPLICIT_CAST typeOperand=kotlin.Int + GET_VAR 'a: kotlin.Byte declared in .btest1' type=kotlin.Byte origin=null + arg1: TYPE_OP type=kotlin.Int origin=IMPLICIT_CAST typeOperand=kotlin.Int + GET_VAR 'b: kotlin.Byte declared in .btest1' type=kotlin.Byte origin=null FUN name:btest2 visibility:public modality:FINAL <> (a:kotlin.Byte, b:kotlin.Byte) returnType:kotlin.Boolean VALUE_PARAMETER name:a index:0 type:kotlin.Byte VALUE_PARAMETER name:b index:1 type:kotlin.Byte BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun btest2 (a: kotlin.Byte, b: kotlin.Byte): kotlin.Boolean declared in ' CALL 'public final fun less (arg0: kotlin.Int, arg1: kotlin.Int): kotlin.Boolean declared in kotlin.internal.ir' type=kotlin.Boolean origin=LT - arg0: CALL 'public open fun toInt (): kotlin.Int declared in kotlin.Byte' type=kotlin.Int origin=null - $this: GET_VAR 'a: kotlin.Byte declared in .btest2' type=kotlin.Byte origin=null - arg1: CALL 'public open fun toInt (): kotlin.Int declared in kotlin.Byte' type=kotlin.Int origin=null - $this: GET_VAR 'b: kotlin.Byte declared in .btest2' type=kotlin.Byte origin=null + arg0: TYPE_OP type=kotlin.Int origin=IMPLICIT_CAST typeOperand=kotlin.Int + GET_VAR 'a: kotlin.Byte declared in .btest2' type=kotlin.Byte origin=null + arg1: TYPE_OP type=kotlin.Int origin=IMPLICIT_CAST typeOperand=kotlin.Int + GET_VAR 'b: kotlin.Byte declared in .btest2' type=kotlin.Byte origin=null FUN name:btest3 visibility:public modality:FINAL <> (a:kotlin.Byte, b:kotlin.Byte) returnType:kotlin.Boolean VALUE_PARAMETER name:a index:0 type:kotlin.Byte VALUE_PARAMETER name:b index:1 type:kotlin.Byte BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun btest3 (a: kotlin.Byte, b: kotlin.Byte): kotlin.Boolean declared in ' CALL 'public final fun greaterOrEqual (arg0: kotlin.Int, arg1: kotlin.Int): kotlin.Boolean declared in kotlin.internal.ir' type=kotlin.Boolean origin=GTEQ - arg0: CALL 'public open fun toInt (): kotlin.Int declared in kotlin.Byte' type=kotlin.Int origin=null - $this: GET_VAR 'a: kotlin.Byte declared in .btest3' type=kotlin.Byte origin=null - arg1: CALL 'public open fun toInt (): kotlin.Int declared in kotlin.Byte' type=kotlin.Int origin=null - $this: GET_VAR 'b: kotlin.Byte declared in .btest3' type=kotlin.Byte origin=null + arg0: TYPE_OP type=kotlin.Int origin=IMPLICIT_CAST typeOperand=kotlin.Int + GET_VAR 'a: kotlin.Byte declared in .btest3' type=kotlin.Byte origin=null + arg1: TYPE_OP type=kotlin.Int origin=IMPLICIT_CAST typeOperand=kotlin.Int + GET_VAR 'b: kotlin.Byte declared in .btest3' type=kotlin.Byte origin=null FUN name:btest4 visibility:public modality:FINAL <> (a:kotlin.Byte, b:kotlin.Byte) returnType:kotlin.Boolean VALUE_PARAMETER name:a index:0 type:kotlin.Byte VALUE_PARAMETER name:b index:1 type:kotlin.Byte BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun btest4 (a: kotlin.Byte, b: kotlin.Byte): kotlin.Boolean declared in ' CALL 'public final fun lessOrEqual (arg0: kotlin.Int, arg1: kotlin.Int): kotlin.Boolean declared in kotlin.internal.ir' type=kotlin.Boolean origin=LTEQ - arg0: CALL 'public open fun toInt (): kotlin.Int declared in kotlin.Byte' type=kotlin.Int origin=null - $this: GET_VAR 'a: kotlin.Byte declared in .btest4' type=kotlin.Byte origin=null - arg1: CALL 'public open fun toInt (): kotlin.Int declared in kotlin.Byte' type=kotlin.Int origin=null - $this: GET_VAR 'b: kotlin.Byte declared in .btest4' type=kotlin.Byte origin=null + arg0: TYPE_OP type=kotlin.Int origin=IMPLICIT_CAST typeOperand=kotlin.Int + GET_VAR 'a: kotlin.Byte declared in .btest4' type=kotlin.Byte origin=null + arg1: TYPE_OP type=kotlin.Int origin=IMPLICIT_CAST typeOperand=kotlin.Int + GET_VAR 'b: kotlin.Byte declared in .btest4' type=kotlin.Byte origin=null FUN name:stest1 visibility:public modality:FINAL <> (a:kotlin.Short, b:kotlin.Short) returnType:kotlin.Boolean VALUE_PARAMETER name:a index:0 type:kotlin.Short VALUE_PARAMETER name:b index:1 type:kotlin.Short BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun stest1 (a: kotlin.Short, b: kotlin.Short): kotlin.Boolean declared in ' CALL 'public final fun greater (arg0: kotlin.Int, arg1: kotlin.Int): kotlin.Boolean declared in kotlin.internal.ir' type=kotlin.Boolean origin=GT - arg0: CALL 'public open fun toInt (): kotlin.Int declared in kotlin.Short' type=kotlin.Int origin=null - $this: GET_VAR 'a: kotlin.Short declared in .stest1' type=kotlin.Short origin=null - arg1: CALL 'public open fun toInt (): kotlin.Int declared in kotlin.Short' type=kotlin.Int origin=null - $this: GET_VAR 'b: kotlin.Short declared in .stest1' type=kotlin.Short origin=null + arg0: TYPE_OP type=kotlin.Int origin=IMPLICIT_CAST typeOperand=kotlin.Int + GET_VAR 'a: kotlin.Short declared in .stest1' type=kotlin.Short origin=null + arg1: TYPE_OP type=kotlin.Int origin=IMPLICIT_CAST typeOperand=kotlin.Int + GET_VAR 'b: kotlin.Short declared in .stest1' type=kotlin.Short origin=null FUN name:stest2 visibility:public modality:FINAL <> (a:kotlin.Short, b:kotlin.Short) returnType:kotlin.Boolean VALUE_PARAMETER name:a index:0 type:kotlin.Short VALUE_PARAMETER name:b index:1 type:kotlin.Short BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun stest2 (a: kotlin.Short, b: kotlin.Short): kotlin.Boolean declared in ' CALL 'public final fun less (arg0: kotlin.Int, arg1: kotlin.Int): kotlin.Boolean declared in kotlin.internal.ir' type=kotlin.Boolean origin=LT - arg0: CALL 'public open fun toInt (): kotlin.Int declared in kotlin.Short' type=kotlin.Int origin=null - $this: GET_VAR 'a: kotlin.Short declared in .stest2' type=kotlin.Short origin=null - arg1: CALL 'public open fun toInt (): kotlin.Int declared in kotlin.Short' type=kotlin.Int origin=null - $this: GET_VAR 'b: kotlin.Short declared in .stest2' type=kotlin.Short origin=null + arg0: TYPE_OP type=kotlin.Int origin=IMPLICIT_CAST typeOperand=kotlin.Int + GET_VAR 'a: kotlin.Short declared in .stest2' type=kotlin.Short origin=null + arg1: TYPE_OP type=kotlin.Int origin=IMPLICIT_CAST typeOperand=kotlin.Int + GET_VAR 'b: kotlin.Short declared in .stest2' type=kotlin.Short origin=null FUN name:stest3 visibility:public modality:FINAL <> (a:kotlin.Short, b:kotlin.Short) returnType:kotlin.Boolean VALUE_PARAMETER name:a index:0 type:kotlin.Short VALUE_PARAMETER name:b index:1 type:kotlin.Short BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun stest3 (a: kotlin.Short, b: kotlin.Short): kotlin.Boolean declared in ' CALL 'public final fun greaterOrEqual (arg0: kotlin.Int, arg1: kotlin.Int): kotlin.Boolean declared in kotlin.internal.ir' type=kotlin.Boolean origin=GTEQ - arg0: CALL 'public open fun toInt (): kotlin.Int declared in kotlin.Short' type=kotlin.Int origin=null - $this: GET_VAR 'a: kotlin.Short declared in .stest3' type=kotlin.Short origin=null - arg1: CALL 'public open fun toInt (): kotlin.Int declared in kotlin.Short' type=kotlin.Int origin=null - $this: GET_VAR 'b: kotlin.Short declared in .stest3' type=kotlin.Short origin=null + arg0: TYPE_OP type=kotlin.Int origin=IMPLICIT_CAST typeOperand=kotlin.Int + GET_VAR 'a: kotlin.Short declared in .stest3' type=kotlin.Short origin=null + arg1: TYPE_OP type=kotlin.Int origin=IMPLICIT_CAST typeOperand=kotlin.Int + GET_VAR 'b: kotlin.Short declared in .stest3' type=kotlin.Short origin=null FUN name:stest4 visibility:public modality:FINAL <> (a:kotlin.Short, b:kotlin.Short) returnType:kotlin.Boolean VALUE_PARAMETER name:a index:0 type:kotlin.Short VALUE_PARAMETER name:b index:1 type:kotlin.Short BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun stest4 (a: kotlin.Short, b: kotlin.Short): kotlin.Boolean declared in ' CALL 'public final fun lessOrEqual (arg0: kotlin.Int, arg1: kotlin.Int): kotlin.Boolean declared in kotlin.internal.ir' type=kotlin.Boolean origin=LTEQ - arg0: CALL 'public open fun toInt (): kotlin.Int declared in kotlin.Short' type=kotlin.Int origin=null - $this: GET_VAR 'a: kotlin.Short declared in .stest4' type=kotlin.Short origin=null - arg1: CALL 'public open fun toInt (): kotlin.Int declared in kotlin.Short' type=kotlin.Int origin=null - $this: GET_VAR 'b: kotlin.Short declared in .stest4' type=kotlin.Short origin=null + arg0: TYPE_OP type=kotlin.Int origin=IMPLICIT_CAST typeOperand=kotlin.Int + GET_VAR 'a: kotlin.Short declared in .stest4' type=kotlin.Short origin=null + arg1: TYPE_OP type=kotlin.Int origin=IMPLICIT_CAST typeOperand=kotlin.Int + GET_VAR 'b: kotlin.Short declared in .stest4' type=kotlin.Short origin=null FUN name:itest1 visibility:public modality:FINAL <> (a:kotlin.Int, b:kotlin.Int) returnType:kotlin.Boolean VALUE_PARAMETER name:a index:0 type:kotlin.Int VALUE_PARAMETER name:b index:1 type:kotlin.Int