JVM IR: Avoid boxing in generic floating point equality (KT-48635)

This commit is contained in:
Steven Schäfer
2022-05-30 12:17:46 +02:00
committed by Alexander Udalov
parent 36a154507e
commit dbb6144ab0
12 changed files with 229 additions and 20 deletions
@@ -13209,6 +13209,12 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
runTest("compiler/testData/codegen/box/dataClasses/doubleParam.kt");
}
@Test
@TestMetadata("equalityChecksPrimitiveUnboxed.kt")
public void testEqualityChecksPrimitiveUnboxed() throws Exception {
runTest("compiler/testData/codegen/box/dataClasses/equalityChecksPrimitiveUnboxed.kt");
}
@Test
@TestMetadata("floatParam.kt")
public void testFloatParam() throws Exception {
@@ -20693,6 +20699,12 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
runTest("compiler/testData/codegen/box/inlineClasses/equalityChecksPrimitiveGeneric.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("equalityChecksPrimitiveUnboxed.kt")
public void testEqualityChecksPrimitiveUnboxed() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/equalityChecksPrimitiveUnboxed.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("equalityForBoxesOfNullableValuesOfInlineClass.kt")
public void testEqualityForBoxesOfNullableValuesOfInlineClass() throws Exception {
@@ -115,7 +115,7 @@ class BooleanComparison(val op: IElementType, val a: MaterialValue, val b: Mater
}
class NonIEEE754FloatComparison(val op: IElementType, val a: MaterialValue, val b: MaterialValue) : BooleanValue(a.codegen) {
class NonIEEE754FloatComparison(op: IElementType, private val a: MaterialValue, private val b: MaterialValue) : BooleanValue(a.codegen) {
private val numberCompareOpcode = NumberCompare.getNumberCompareOpcode(op)
private fun invokeStaticComparison(type: Type) {
@@ -18,6 +18,7 @@ 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.types.classOrNull
@@ -80,14 +81,16 @@ class Equals(val operator: IElementType) : IntrinsicMethod() {
}
val leftType = with(codegen) { a.asmType }
if (expression.origin == IrStatementOrigin.EQEQEQ || expression.origin == IrStatementOrigin.EXCLEQEQ) {
return referenceEquals(a, b, leftType, codegen, data)
}
val rightType = with(codegen) { b.asmType }
val opToken = expression.origin
// 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) && !isPrimitive(rightType)) ||
if (((AsmUtil.isIntOrLongPrimitive(leftType) && !isPrimitive(rightType)) ||
(AsmUtil.isIntOrLongPrimitive(rightType) && AsmUtil.isBoxedPrimitiveType(leftType)))
) {
val aValue = a.accept(codegen, data).materializedAt(leftType, a.type)
@@ -95,29 +98,45 @@ class Equals(val operator: IElementType) : IntrinsicMethod() {
return PrimitiveToObjectComparison(operator, AsmUtil.isIntOrLongPrimitive(leftType), aValue, bValue)
}
if (isPrimitive(leftType) && leftType == rightType) {
// Generic floating point equality is specified to be reflexive, with NaN == NaN and -0 != +0.
return if (leftType == Type.FLOAT_TYPE || leftType == Type.DOUBLE_TYPE) {
val aValue = a.accept(codegen, data).materializedAt(leftType, a.type)
val bValue = b.accept(codegen, data).materializedAt(rightType, b.type)
return NonIEEE754FloatComparison(operator, aValue, bValue)
} else {
referenceEquals(a, b, leftType, codegen, data)
}
}
// 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
val useEquals = opToken !== IrStatementOrigin.EQEQEQ && opToken !== IrStatementOrigin.EXCLEQEQ &&
// `==` is `equals` for objects and floating-point numbers. In the latter case, the difference
// is that `equals` is a total order (-0 < +0 and NaN == NaN) and `===` is IEEE754-compliant.
(!isPrimitive(leftType) || leftType != rightType || leftType == Type.FLOAT_TYPE || leftType == Type.DOUBLE_TYPE)
// Reference equality can be used for enums.
&& !aIsEnum && !bIsEnum
return if (useEquals) {
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)
}
}
private fun referenceEquals(
left: IrExpression,
right: IrExpression,
leftType: Type,
codegen: ExpressionCodegen,
data: BlockInfo
): PromisedValue {
val operandType = if (!isPrimitive(leftType)) AsmTypes.OBJECT_TYPE else leftType
return if (operandType == Type.INT_TYPE && (left.isIntegerConst(0) || right.isIntegerConst(0))) {
val nonZero = if (left.isIntegerConst(0)) right else left
IntegerZeroComparison(operator, nonZero.accept(codegen, data).materializedAt(operandType, nonZero.type))
} else {
val operandType = if (!isPrimitive(leftType)) AsmTypes.OBJECT_TYPE else leftType
if (operandType == Type.INT_TYPE && (a.isIntegerConst(0) || b.isIntegerConst(0))) {
val nonZero = if (a.isIntegerConst(0)) b else a
IntegerZeroComparison(operator, nonZero.accept(codegen, data).materializedAt(operandType, nonZero.type))
} else {
val aValue = a.accept(codegen, data).materializedAt(operandType, a.type)
val bValue = b.accept(codegen, data).materializedAt(operandType, b.type)
BooleanComparison(operator, aValue, bValue)
}
val leftValue = left.accept(codegen, data).materializedAt(operandType, left.type)
val rightValue = right.accept(codegen, data).materializedAt(operandType, right.type)
BooleanComparison(operator, leftValue, rightValue)
}
}
}
@@ -0,0 +1,42 @@
// The purpose of this test is to ensure that we don't generate any primitive boxing in the implementation
// of a data class. See KT-48635.
data class VBoolean(val value: Boolean)
data class VByte(val value: Byte)
data class VChar(val value: Char)
data class VShort(val value: Short)
data class VInt(val value: Int)
data class VLong(val value: Long)
data class VFloat(val value: Float)
data class VDouble(val value: Double)
fun box(): String {
if (VBoolean(true) == VBoolean(false)) return "Fail 0"
if (VByte(0) == VByte(1)) return "Fail 1"
if (VChar('a') == VChar('b')) return "Fail 2"
if (VShort(0) == VShort(1)) return "Fail 3"
if (VInt(0) == VInt(1)) return "Fail 4"
if (VLong(0L) == VLong(1L)) return "Fail 5"
if (VFloat(0f) == VFloat(1f)) return "Fail 6"
if (VDouble(0.0) == VDouble(1.0)) return "Fail 7"
return "OK"
}
// CHECK_BYTECODE_TEXT
// 0 java/lang/Boolean.valueOf
// 0 java/lang/Byte.valueOf
// 0 java/lang/Character.valueOf
// 0 java/lang/Short.valueOf
// 0 java/lang/Integer.valueOf
// 0 java/lang/Long.valueOf
// 0 java/lang/Float.valueOf
// 0 java/lang/Double.valueOf
// 1 java/lang/Float.compare
// 1 java/lang/Double.compare
@@ -0,0 +1,54 @@
// WITH_STDLIB
// WORKS_WHEN_VALUE_CLASS
// LANGUAGE: +ValueClasses
// The purpose of this test is to ensure that we don't generate any primitive boxing in the implementation
// of a @JvmInline value class. See KT-48635.
OPTIONAL_JVM_INLINE_ANNOTATION
value class VBoolean(val value: Boolean)
OPTIONAL_JVM_INLINE_ANNOTATION
value class VByte(val value: Byte)
OPTIONAL_JVM_INLINE_ANNOTATION
value class VChar(val value: Char)
OPTIONAL_JVM_INLINE_ANNOTATION
value class VShort(val value: Short)
OPTIONAL_JVM_INLINE_ANNOTATION
value class VInt(val value: Int)
OPTIONAL_JVM_INLINE_ANNOTATION
value class VLong(val value: Long)
OPTIONAL_JVM_INLINE_ANNOTATION
value class VFloat(val value: Float)
OPTIONAL_JVM_INLINE_ANNOTATION
value class VDouble(val value: Double)
fun box(): String {
if (VBoolean(true) == VBoolean(false)) return "Fail 0"
if (VByte(0) == VByte(1)) return "Fail 1"
if (VChar('a') == VChar('b')) return "Fail 2"
if (VShort(0) == VShort(1)) return "Fail 3"
if (VInt(0) == VInt(1)) return "Fail 4"
if (VLong(0L) == VLong(1L)) return "Fail 5"
if (VFloat(0f) == VFloat(1f)) return "Fail 6"
if (VDouble(0.0) == VDouble(1.0)) return "Fail 7"
return "OK"
}
// CHECK_BYTECODE_TEXT
// 0 java/lang/Boolean.valueOf
// 0 java/lang/Byte.valueOf
// 0 java/lang/Character.valueOf
// 0 java/lang/Short.valueOf
// 0 java/lang/Integer.valueOf
// 0 java/lang/Long.valueOf
// 0 java/lang/Float.valueOf
// 0 java/lang/Double.valueOf
// 2 java/lang/Float.compare
// 2 java/lang/Double.compare
@@ -13089,6 +13089,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/dataClasses/doubleParam.kt");
}
@Test
@TestMetadata("equalityChecksPrimitiveUnboxed.kt")
public void testEqualityChecksPrimitiveUnboxed() throws Exception {
runTest("compiler/testData/codegen/box/dataClasses/equalityChecksPrimitiveUnboxed.kt");
}
@Test
@TestMetadata("floatParam.kt")
public void testFloatParam() throws Exception {
@@ -20231,6 +20237,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/inlineClasses/equalityChecksPrimitiveGeneric.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("equalityChecksPrimitiveUnboxed.kt")
public void testEqualityChecksPrimitiveUnboxed() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/equalityChecksPrimitiveUnboxed.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("equalityForBoxesOfNullableValuesOfInlineClass.kt")
public void testEqualityForBoxesOfNullableValuesOfInlineClass() throws Exception {
@@ -13209,6 +13209,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/dataClasses/doubleParam.kt");
}
@Test
@TestMetadata("equalityChecksPrimitiveUnboxed.kt")
public void testEqualityChecksPrimitiveUnboxed() throws Exception {
runTest("compiler/testData/codegen/box/dataClasses/equalityChecksPrimitiveUnboxed.kt");
}
@Test
@TestMetadata("floatParam.kt")
public void testFloatParam() throws Exception {
@@ -20693,6 +20699,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/inlineClasses/equalityChecksPrimitiveGeneric.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("equalityChecksPrimitiveUnboxed.kt")
public void testEqualityChecksPrimitiveUnboxed() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/equalityChecksPrimitiveUnboxed.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@Test
@TestMetadata("equalityForBoxesOfNullableValuesOfInlineClass.kt")
public void testEqualityForBoxesOfNullableValuesOfInlineClass() throws Exception {
@@ -10587,6 +10587,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/dataClasses/doubleParam.kt");
}
@TestMetadata("equalityChecksPrimitiveUnboxed.kt")
public void testEqualityChecksPrimitiveUnboxed() throws Exception {
runTest("compiler/testData/codegen/box/dataClasses/equalityChecksPrimitiveUnboxed.kt");
}
@TestMetadata("floatParam.kt")
public void testFloatParam() throws Exception {
runTest("compiler/testData/codegen/box/dataClasses/floatParam.kt");
@@ -17016,6 +17021,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/inlineClasses/equalityChecksPrimitiveGeneric.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@TestMetadata("equalityChecksPrimitiveUnboxed.kt")
public void testEqualityChecksPrimitiveUnboxed() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/equalityChecksPrimitiveUnboxed.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
}
@TestMetadata("equalityForBoxesOfNullableValuesOfInlineClass.kt")
public void testEqualityForBoxesOfNullableValuesOfInlineClass() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/equalityForBoxesOfNullableValuesOfInlineClass.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal());
@@ -9767,6 +9767,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/dataClasses/doubleParam.kt");
}
@Test
@TestMetadata("equalityChecksPrimitiveUnboxed.kt")
public void testEqualityChecksPrimitiveUnboxed() throws Exception {
runTest("compiler/testData/codegen/box/dataClasses/equalityChecksPrimitiveUnboxed.kt");
}
@Test
@TestMetadata("floatParam.kt")
public void testFloatParam() throws Exception {
@@ -15907,6 +15913,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/inlineClasses/equalityChecksPrimitiveGeneric.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
}
@Test
@TestMetadata("equalityChecksPrimitiveUnboxed.kt")
public void testEqualityChecksPrimitiveUnboxed() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/equalityChecksPrimitiveUnboxed.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
}
@Test
@TestMetadata("equalityForBoxesOfNullableValuesOfInlineClass.kt")
public void testEqualityForBoxesOfNullableValuesOfInlineClass() throws Exception {
@@ -9809,6 +9809,12 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/dataClasses/doubleParam.kt");
}
@Test
@TestMetadata("equalityChecksPrimitiveUnboxed.kt")
public void testEqualityChecksPrimitiveUnboxed() throws Exception {
runTest("compiler/testData/codegen/box/dataClasses/equalityChecksPrimitiveUnboxed.kt");
}
@Test
@TestMetadata("floatParam.kt")
public void testFloatParam() throws Exception {
@@ -15871,6 +15877,12 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/inlineClasses/equalityChecksPrimitiveGeneric.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
}
@Test
@TestMetadata("equalityChecksPrimitiveUnboxed.kt")
public void testEqualityChecksPrimitiveUnboxed() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/equalityChecksPrimitiveUnboxed.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
}
@Test
@TestMetadata("equalityForBoxesOfNullableValuesOfInlineClass.kt")
public void testEqualityForBoxesOfNullableValuesOfInlineClass() throws Exception {
@@ -8677,6 +8677,11 @@ public class IrCodegenBoxWasmTestGenerated extends AbstractIrCodegenBoxWasmTest
runTest("compiler/testData/codegen/box/dataClasses/doubleParam.kt");
}
@TestMetadata("equalityChecksPrimitiveUnboxed.kt")
public void testEqualityChecksPrimitiveUnboxed() throws Exception {
runTest("compiler/testData/codegen/box/dataClasses/equalityChecksPrimitiveUnboxed.kt");
}
@TestMetadata("floatParam.kt")
public void testFloatParam() throws Exception {
runTest("compiler/testData/codegen/box/dataClasses/floatParam.kt");
@@ -14096,6 +14101,11 @@ public class IrCodegenBoxWasmTestGenerated extends AbstractIrCodegenBoxWasmTest
runTest("compiler/testData/codegen/box/inlineClasses/equalityChecksPrimitiveGeneric.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
}
@TestMetadata("equalityChecksPrimitiveUnboxed.kt")
public void testEqualityChecksPrimitiveUnboxed() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/equalityChecksPrimitiveUnboxed.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
}
@TestMetadata("equalityForBoxesOfNullableValuesOfInlineClass.kt")
public void testEqualityForBoxesOfNullableValuesOfInlineClass() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/equalityForBoxesOfNullableValuesOfInlineClass.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
@@ -10741,6 +10741,12 @@ public class NativeCodegenBoxTestGenerated extends AbstractNativeCodegenBoxTest
runTest("compiler/testData/codegen/box/dataClasses/doubleParam.kt");
}
@Test
@TestMetadata("equalityChecksPrimitiveUnboxed.kt")
public void testEqualityChecksPrimitiveUnboxed() throws Exception {
runTest("compiler/testData/codegen/box/dataClasses/equalityChecksPrimitiveUnboxed.kt");
}
@Test
@TestMetadata("floatParam.kt")
public void testFloatParam() throws Exception {
@@ -16452,6 +16458,7 @@ public class NativeCodegenBoxTestGenerated extends AbstractNativeCodegenBoxTest
register("compiler/testData/codegen/box/inlineClasses/equalityChecksNullableGeneric2.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
register("compiler/testData/codegen/box/inlineClasses/equalityChecksPrimitive.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
register("compiler/testData/codegen/box/inlineClasses/equalityChecksPrimitiveGeneric.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
register("compiler/testData/codegen/box/inlineClasses/equalityChecksPrimitiveUnboxed.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
register("compiler/testData/codegen/box/inlineClasses/equalityForBoxesOfNullableValuesOfInlineClass.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
register("compiler/testData/codegen/box/inlineClasses/equalityForBoxesOfNullableValuesOfInlineClassGeneric.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
register("compiler/testData/codegen/box/inlineClasses/equalsCallsLeftArgument.kt", TransformersFunctions.getRemoveOptionalJvmInlineAnnotation());
@@ -17451,6 +17458,13 @@ public class NativeCodegenBoxTestGenerated extends AbstractNativeCodegenBoxTest
runTest("compiler/testData/codegen/box/inlineClasses/equalityChecksPrimitiveGeneric.kt");
}
@Test
@TestMetadata("equalityChecksPrimitiveUnboxed.kt")
public void testEqualityChecksPrimitiveUnboxed() throws Exception {
// There is a registered source transformer for the testcase: TransformersFunctions.getRemoveOptionalJvmInlineAnnotation()
runTest("compiler/testData/codegen/box/inlineClasses/equalityChecksPrimitiveUnboxed.kt");
}
@Test
@TestMetadata("equalityForBoxesOfNullableValuesOfInlineClass.kt")
public void testEqualityForBoxesOfNullableValuesOfInlineClass() throws Exception {