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 d7e9f43d9ea..73ce1144431 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 @@ -19,11 +19,13 @@ package org.jetbrains.kotlin.backend.jvm.intrinsics import com.intellij.psi.tree.IElementType 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.comparisonOperandType import org.jetbrains.kotlin.codegen.BranchedValue import org.jetbrains.kotlin.codegen.NumberCompare import org.jetbrains.kotlin.codegen.ObjectCompare +import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression import org.jetbrains.kotlin.lexer.KtSingleValueToken import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType @@ -84,6 +86,29 @@ 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) { + private val numberCompareOpcode = NumberCompare.getNumberCompareOpcode(op) + + private fun invokeStaticComparison(type: Type) { + when (type) { + Type.FLOAT_TYPE -> mv.invokestatic("java/lang/Float", "compare", "(FF)I", false) + Type.DOUBLE_TYPE -> mv.invokestatic("java/lang/Double", "compare", "(DD)I", false) + else -> throw UnsupportedOperationException() + } + } + + override fun jumpIfFalse(target: Label) { + invokeStaticComparison(a.type) + mv.visitJumpInsn(numberCompareOpcode, target) + } + + override fun jumpIfTrue(target: Label) { + invokeStaticComparison(a.type) + mv.visitJumpInsn(BranchedValue.negatedOperations[numberCompareOpcode]!!, target) + } +} + class PrimitiveComparison( private val primitiveNumberType: KotlinType, private val operatorToken: KtSingleValueToken @@ -93,6 +118,17 @@ class PrimitiveComparison( val (left, right) = expression.receiverAndArgs() val a = left.accept(codegen, data).coerce(parameterType, left.type).materialized val b = right.accept(codegen, data).coerce(parameterType, right.type).materialized - return BooleanComparison(operatorToken, a, b) + + val useNonIEEE754Comparison = + !codegen.context.state.languageVersionSettings.supportsFeature(LanguageFeature.ProperIeee754Comparisons) + && (parameterType == Type.FLOAT_TYPE || parameterType == Type.DOUBLE_TYPE) + && (left.isSmartcastFromHigherThanNullable(codegen.context) || right.isSmartcastFromHigherThanNullable(codegen.context)) + + return if (useNonIEEE754Comparison) { + NonIEEE754FloatComparison(operatorToken, a, b) + } else { + BooleanComparison(operatorToken, a, b) + } } } + 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 53ac8e90700..b8055c231fb 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 @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.backend.jvm.intrinsics import com.intellij.psi.tree.IElementType 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.codegen.AsmUtil import org.jetbrains.kotlin.codegen.AsmUtil.genAreEqualCall import org.jetbrains.kotlin.codegen.AsmUtil.isPrimitive @@ -15,8 +16,8 @@ import org.jetbrains.kotlin.codegen.StackValue import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods import org.jetbrains.kotlin.codegen.pseudoInsns.fakeAlwaysFalseIfeq import org.jetbrains.kotlin.codegen.pseudoInsns.fakeAlwaysTrueIfeq -import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression -import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin +import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.types.isNullable import org.jetbrains.kotlin.ir.types.toKotlinType import org.jetbrains.kotlin.ir.util.isNullConst @@ -95,18 +96,28 @@ class Ieee754Equals(val operandType: Type) : IntrinsicMethod() { } } - val arg0Type = expression.getValueArgument(0)!!.type.toKotlinType() + val arg0 = expression.getValueArgument(0)!! + val arg1 = expression.getValueArgument(1)!! + + val arg0Type = arg0.type.toKotlinType() if (!arg0Type.isPrimitiveNumberOrNullableType() && !arg0Type.upperBoundedByPrimitiveNumberOrNullableType()) throw AssertionError("Should be primitive or nullable primitive type: $arg0Type") - val arg1Type = expression.getValueArgument(1)!!.type.toKotlinType() + val arg1Type = arg1.type.toKotlinType() if (!arg1Type.isPrimitiveNumberOrNullableType() && !arg1Type.upperBoundedByPrimitiveNumberOrNullableType()) throw AssertionError("Should be primitive or nullable primitive type: $arg1Type") val arg0isNullable = arg0Type.isNullable() val arg1isNullable = arg1Type.isNullable() + val useNonIEEE754Comparison = + !context.state.languageVersionSettings.supportsFeature(LanguageFeature.ProperIeee754Comparisons) + && (arg0.isSmartcastFromHigherThanNullable(context) || arg1.isSmartcastFromHigherThanNullable(context)) + return when { + useNonIEEE754Comparison -> + Ieee754AreEqual(AsmTypes.OBJECT_TYPE, AsmTypes.OBJECT_TYPE) + !arg0isNullable && !arg1isNullable -> object : IrIntrinsicFunction(expression, signature, context, listOf(operandType, operandType)) { override fun genInvokeInstruction(v: InstructionAdapter) { @@ -126,3 +137,4 @@ class Ieee754Equals(val operandType: Type) : IntrinsicMethod() { } } } + diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/IrUtils.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/IrUtils.kt index 3103b791af7..d9cb26bb2d2 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/IrUtils.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/IrUtils.kt @@ -17,8 +17,7 @@ import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope import org.jetbrains.kotlin.ir.builders.Scope import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns -import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin -import org.jetbrains.kotlin.ir.expressions.IrConst +import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.symbols.IrClassSymbol import org.jetbrains.kotlin.ir.symbols.IrSymbol import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol @@ -138,3 +137,12 @@ fun JvmBackendContext.createJvmIrBuilder( fun IrDeclaration.isInCurrentModule(): Boolean = getPackageFragment() is IrFile + +// Determine if the IrExpression is smartcast, and if so, if it is cast from higher than nullable target types. +// This is needed to pinpoint exceptional treatment of IEEE754 floating point comparisons, where proper IEEE +// comparisons are used "if values are statically known to be of primitive numeric types", taken to mean as +// "not learned through smartcasting". +fun IrExpression.isSmartcastFromHigherThanNullable(context: JvmBackendContext) = + this is IrTypeOperatorCall && + operator == IrTypeOperator.IMPLICIT_CAST && + !this.argument.type.isSubtypeOf(type.makeNullable(), context.irBuiltIns) diff --git a/compiler/testData/codegen/box/ieee754/equalsDouble.kt b/compiler/testData/codegen/box/ieee754/equalsDouble.kt index 944984e66c8..eca47b808ec 100644 --- a/compiler/testData/codegen/box/ieee754/equalsDouble.kt +++ b/compiler/testData/codegen/box/ieee754/equalsDouble.kt @@ -1,5 +1,4 @@ // !LANGUAGE: -ProperIeee754Comparisons -// IGNORE_BACKEND: JVM_IR // DONT_TARGET_EXACT_BACKEND: JS_IR fun equals1(a: Double, b: Double) = a == b diff --git a/compiler/testData/codegen/box/ieee754/equalsFloat.kt b/compiler/testData/codegen/box/ieee754/equalsFloat.kt index 17bc8319e91..f733deec06e 100644 --- a/compiler/testData/codegen/box/ieee754/equalsFloat.kt +++ b/compiler/testData/codegen/box/ieee754/equalsFloat.kt @@ -1,5 +1,4 @@ // !LANGUAGE: -ProperIeee754Comparisons -// IGNORE_BACKEND: JVM_IR // DONT_TARGET_EXACT_BACKEND: JS_IR fun equals1(a: Float, b: Float) = a == b diff --git a/compiler/testData/codegen/box/ieee754/equalsNaN.kt b/compiler/testData/codegen/box/ieee754/equalsNaN.kt index 1f44c5f0c45..3fe196d3c14 100644 --- a/compiler/testData/codegen/box/ieee754/equalsNaN.kt +++ b/compiler/testData/codegen/box/ieee754/equalsNaN.kt @@ -1,5 +1,4 @@ // !LANGUAGE: -ProperIeee754Comparisons -// IGNORE_BACKEND: JVM_IR // DONT_TARGET_EXACT_BACKEND: JS_IR // WITH_RUNTIME diff --git a/compiler/testData/codegen/box/ieee754/equalsNullableDouble.kt b/compiler/testData/codegen/box/ieee754/equalsNullableDouble.kt index b94003d715a..83ea21d9e68 100644 --- a/compiler/testData/codegen/box/ieee754/equalsNullableDouble.kt +++ b/compiler/testData/codegen/box/ieee754/equalsNullableDouble.kt @@ -1,5 +1,4 @@ // !LANGUAGE: -ProperIeee754Comparisons -// IGNORE_BACKEND: JVM_IR // DONT_TARGET_EXACT_BACKEND: JS_IR fun equals1(a: Double, b: Double?) = a == b diff --git a/compiler/testData/codegen/box/ieee754/equalsNullableFloat.kt b/compiler/testData/codegen/box/ieee754/equalsNullableFloat.kt index 80cbca5d6d6..50f62117ce1 100644 --- a/compiler/testData/codegen/box/ieee754/equalsNullableFloat.kt +++ b/compiler/testData/codegen/box/ieee754/equalsNullableFloat.kt @@ -1,5 +1,4 @@ // !LANGUAGE: -ProperIeee754Comparisons -// IGNORE_BACKEND: JVM_IR // DONT_TARGET_EXACT_BACKEND: JS_IR fun equals1(a: Float, b: Float?) = a == b diff --git a/compiler/testData/codegen/box/ieee754/greaterDouble.kt b/compiler/testData/codegen/box/ieee754/greaterDouble.kt index 0abf1358552..97550192672 100644 --- a/compiler/testData/codegen/box/ieee754/greaterDouble.kt +++ b/compiler/testData/codegen/box/ieee754/greaterDouble.kt @@ -1,5 +1,4 @@ // !LANGUAGE: -ProperIeee754Comparisons -// IGNORE_BACKEND: JVM_IR // DONT_TARGET_EXACT_BACKEND: JS_IR fun greater1(a: Double, b: Double) = a > b diff --git a/compiler/testData/codegen/box/ieee754/greaterFloat.kt b/compiler/testData/codegen/box/ieee754/greaterFloat.kt index ee952c4987b..b5b0844ad5e 100644 --- a/compiler/testData/codegen/box/ieee754/greaterFloat.kt +++ b/compiler/testData/codegen/box/ieee754/greaterFloat.kt @@ -1,5 +1,4 @@ // !LANGUAGE: -ProperIeee754Comparisons -// IGNORE_BACKEND: JVM_IR // DONT_TARGET_EXACT_BACKEND: JS_IR fun greater1(a: Float, b: Float) = a > b diff --git a/compiler/testData/codegen/box/ieee754/lessDouble.kt b/compiler/testData/codegen/box/ieee754/lessDouble.kt index d6d27b86693..3a5d0734dcf 100644 --- a/compiler/testData/codegen/box/ieee754/lessDouble.kt +++ b/compiler/testData/codegen/box/ieee754/lessDouble.kt @@ -1,5 +1,4 @@ // !LANGUAGE: -ProperIeee754Comparisons -// IGNORE_BACKEND: JVM_IR // DONT_TARGET_EXACT_BACKEND: JS_IR fun less1(a: Double, b: Double) = a < b diff --git a/compiler/testData/codegen/box/ieee754/lessFloat.kt b/compiler/testData/codegen/box/ieee754/lessFloat.kt index 2800adcc2a7..b62dabdd52b 100644 --- a/compiler/testData/codegen/box/ieee754/lessFloat.kt +++ b/compiler/testData/codegen/box/ieee754/lessFloat.kt @@ -1,5 +1,4 @@ // !LANGUAGE: -ProperIeee754Comparisons -// IGNORE_BACKEND: JVM_IR // DONT_TARGET_EXACT_BACKEND: JS_IR fun less1(a: Float, b: Float) = a < b diff --git a/compiler/testData/codegen/box/ieee754/smartCastToDifferentTypes.kt b/compiler/testData/codegen/box/ieee754/smartCastToDifferentTypes.kt index 42e07668d22..6bca2efbccb 100644 --- a/compiler/testData/codegen/box/ieee754/smartCastToDifferentTypes.kt +++ b/compiler/testData/codegen/box/ieee754/smartCastToDifferentTypes.kt @@ -1,5 +1,4 @@ // !LANGUAGE: -ProperIeee754Comparisons -// IGNORE_BACKEND: JVM_IR // IGNORE_BACKEND: NATIVE // DONT_TARGET_EXACT_BACKEND: JS_IR fun box(): String { diff --git a/compiler/testData/codegen/box/ieee754/smartCastToDifferentTypesWithNumericPromotion.kt b/compiler/testData/codegen/box/ieee754/smartCastToDifferentTypesWithNumericPromotion.kt index e5a7d3b68db..850130b5ed1 100644 --- a/compiler/testData/codegen/box/ieee754/smartCastToDifferentTypesWithNumericPromotion.kt +++ b/compiler/testData/codegen/box/ieee754/smartCastToDifferentTypesWithNumericPromotion.kt @@ -1,6 +1,5 @@ // !LANGUAGE: -ProperIeee754Comparisons // IGNORE_BACKEND: NATIVE -// IGNORE_BACKEND: JVM_IR // DONT_TARGET_EXACT_BACKEND: JS_IR fun eqDI(x: Any?, y: Any?) = x is Double? && y is Int? && x == y fun eqDL(x: Any?, y: Any?) = x is Double? && y is Long? && x == y diff --git a/compiler/testData/codegen/box/ieee754/when.kt b/compiler/testData/codegen/box/ieee754/when.kt index d544f25f123..7b851b7f132 100644 --- a/compiler/testData/codegen/box/ieee754/when.kt +++ b/compiler/testData/codegen/box/ieee754/when.kt @@ -1,5 +1,4 @@ // !LANGUAGE: -ProperIeee754Comparisons -// IGNORE_BACKEND: JVM_IR // DONT_TARGET_EXACT_BACKEND: JS_IR fun box(): String { diff --git a/compiler/testData/codegen/box/ieee754/whenNoSubject.kt b/compiler/testData/codegen/box/ieee754/whenNoSubject.kt index 29f4fb9af47..e0b371506f9 100644 --- a/compiler/testData/codegen/box/ieee754/whenNoSubject.kt +++ b/compiler/testData/codegen/box/ieee754/whenNoSubject.kt @@ -1,5 +1,4 @@ // !LANGUAGE: -ProperIeee754Comparisons -// IGNORE_BACKEND: JVM_IR // DONT_TARGET_EXACT_BACKEND: JS_IR fun box(): String { diff --git a/compiler/testData/codegen/bytecodeText/intrinsicsCompare/differentTypes.kt b/compiler/testData/codegen/bytecodeText/intrinsicsCompare/differentTypes.kt index b4bd546f53b..e7437c18cd6 100644 --- a/compiler/testData/codegen/bytecodeText/intrinsicsCompare/differentTypes.kt +++ b/compiler/testData/codegen/bytecodeText/intrinsicsCompare/differentTypes.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR fun box(): String { val zero: Any = 0.0 val floatZero: Any = -0.0F