diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt index fc5b964e53d..41584bd3b08 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt @@ -134,6 +134,8 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC val jsToString = getInternalFunction("toString") val jsAnyToString = getInternalFunction("anyToString") val jsCompareTo = getInternalFunction("compareTo") + // TODO: Use annotations + val compareToDoNotIntrinsicify = getInternalFunction("compareToDoNotIntrinsicify") val jsEquals = getInternalFunction("equals") // Coroutines diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/IntrinsicifyCallsLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/IntrinsicifyCallsLowering.kt index 9030ce86a26..a420f1d060b 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/IntrinsicifyCallsLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/IntrinsicifyCallsLowering.kt @@ -6,9 +6,9 @@ package org.jetbrains.kotlin.ir.backend.js.lower import org.jetbrains.kotlin.backend.common.FileLoweringPass -import org.jetbrains.kotlin.backend.common.utils.isSubtypeOf import org.jetbrains.kotlin.backend.common.utils.isSubtypeOfClass import org.jetbrains.kotlin.descriptors.PropertyAccessorDescriptor +import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder @@ -16,10 +16,6 @@ import org.jetbrains.kotlin.ir.backend.js.ir.irCall import org.jetbrains.kotlin.ir.backend.js.utils.ConversionNames import org.jetbrains.kotlin.ir.backend.js.utils.Namer import org.jetbrains.kotlin.ir.backend.js.utils.OperatorNames -import org.jetbrains.kotlin.ir.declarations.IrClass -import org.jetbrains.kotlin.ir.declarations.IrFile -import org.jetbrains.kotlin.ir.declarations.IrFunction -import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl @@ -131,9 +127,8 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL symbolToTransformer.run { add(irBuiltIns.eqeqeqSymbol, intrinsics.jsEqeqeq) add(irBuiltIns.eqeqSymbol, ::transformEqeqOperator) - - // ieee754equals can only be applied in between statically known Floats or Doubles - add(irBuiltIns.ieee754equalsFunByOperandType, intrinsics.jsEqeqeq) + // ieee754equals can only be applied in between statically known Floats, Doubles, null or undefined + add(irBuiltIns.ieee754equalsFunByOperandType, ::chooseEqualityOperatorForPrimitiveTypes) add(irBuiltIns.booleanNotSymbol, intrinsics.jsNot) @@ -237,11 +232,6 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL ) - addWithPredicate( - Name.identifier("compareTo"), ::shouldReplaceCompareToWithRuntimeCall, - { call -> irCall(call, intrinsics.jsCompareTo, dispatchReceiverAsFirstArgument = true) } - ) - put(Name.identifier("toString")) { call -> if (shouldReplaceToStringWithRuntimeCall(call)) { if (call.isSuperToAny()) { @@ -266,7 +256,7 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL } } - + put(Name.identifier("compareTo"), ::transformCompareToMethodCall) put(Name.identifier("equals"), ::transformEqualsMethodCall) } @@ -352,6 +342,12 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL } } + override fun visitFunction(declaration: IrFunction): IrStatement { + if (declaration.symbol == intrinsics.compareToDoNotIntrinsicify) + return declaration + return super.visitFunction(declaration) + } + override fun visitCall(expression: IrCall): IrExpression { val call = super.visitCall(expression) @@ -430,50 +426,6 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL } } - private fun transformEqeqOperator(call: IrCall): IrExpression { - val lhs = call.getValueArgument(0)!! - val rhs = call.getValueArgument(1)!! - - // Special optimization for " == null" - if (lhs.isNullConst() || rhs.isNullConst()) - return irCall(call, intrinsics.jsEqeq.symbol) - - return when (translateEquals(lhs.type, rhs.type)) { - is IdentityOperator -> irCall(call, intrinsics.jsEqeqeq.symbol) - is EqualityOperator -> irCall(call, intrinsics.jsEqeq.symbol) - is RuntimeFunctionCall -> irCall(call, intrinsics.jsEquals) - is RuntimeOrMethodCall -> { - assert(!lhs.type.isNullable()) - val equalsMethod = lhs.type.findEqualsMethod(rhs.type) - if (equalsMethod != null) { - irCall(call, equalsMethod.symbol, firstArgumentAsDispatchReceiver = true) - } else { - irCall(call, intrinsics.jsEquals) - } - } - } - } - - private fun IrType.findEqualsMethod(rhs: IrType): IrSimpleFunction? { - val classifier = classifierOrNull ?: return null - return ((classifier.owner as? IrClass) ?: return null).declarations - .filterIsInstance() - .filter { - it.name == Name.identifier("equals") - && it.valueParameters.size == 1 - && rhs.isSubtypeOf(it.valueParameters[0].type) - && !it.isFakeOverriddenFromAny() - } - .maxWith( // Find the most specific function - Comparator { f1, f2 -> - val t1 = f1.valueParameters[0].type - val t2 = f2.valueParameters[0].type - if (t1.isSubtypeOf(t2)) { - if (t2.isSubtypeOf(t1)) 0 else 1 - } else -1 - } - ) - } private fun withLongCoercion(intrinsic: IrSimpleFunction): (IrCall) -> IrExpression = { call -> assert(call.valueArgumentsCount == 1) @@ -528,177 +480,149 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL } } - private fun transformEqualsMethodCall(call: IrCall): IrExpression { - val symbol = call.symbol - val function = (symbol.owner as? IrFunction) ?: return call - val lhs = function.dispatchReceiverParameter ?: function.extensionReceiverParameter ?: return call - val rhs = call.getValueArgument(0) ?: return call - return when (translateEquals(lhs.type, rhs.type)) { - is IdentityOperator -> irCall(call, intrinsics.jsEqeqeq.symbol) - is EqualityOperator -> irCall(call, intrinsics.jsEqeq.symbol) - is RuntimeFunctionCall -> irCall(call, intrinsics.jsEquals, true) - is RuntimeOrMethodCall -> if (symbol.owner.isFakeOverriddenFromAny()) { - if (call.isSuperToAny()) { - irCall(call, intrinsics.jsEqeqeq.symbol, dispatchReceiverAsFirstArgument = true) - } else { - irCall(call, intrinsics.jsEquals, dispatchReceiverAsFirstArgument = true) - } - } else { + private fun transformEqeqOperator(call: IrCall): IrExpression { + val lhs = call.getValueArgument(0)!! + val rhs = call.getValueArgument(1)!! + + + val lhsJsType = lhs.type.getPrimitiveType() + val rhsJsType = rhs.type.getPrimitiveType() + + val equalsMethod = lhs.type.findEqualsMethod() + val isLhsPrimitive = lhsJsType != PrimitiveType.OTHER + + return when { + lhs.type is IrDynamicType -> + irCall(call, intrinsics.jsEqeq.symbol) + + // Special optimization for " == null" + lhs.isNullConst() || rhs.isNullConst() -> + irCall(call, intrinsics.jsEqeq.symbol) + + // For non-float primitives of the same type use JS `==` + isLhsPrimitive && lhsJsType == rhsJsType && lhsJsType != PrimitiveType.FLOATING_POINT_NUMBER -> + chooseEqualityOperatorForPrimitiveTypes(call) + + !isLhsPrimitive && !lhs.type.toKotlinType().isNullable() && equalsMethod != null -> + irCall(call, equalsMethod.symbol, firstArgumentAsDispatchReceiver = true) + + else -> + irCall(call, intrinsics.jsEquals) + } + } + + private fun chooseEqualityOperatorForPrimitiveTypes(call: IrCall): IrExpression = when { + call.allValueArgumentsAreNullable() -> + irCall(call, intrinsics.jsEqeq.symbol) + else -> + irCall(call, intrinsics.jsEqeqeq.symbol) + } + + private fun IrCall.allValueArgumentsAreNullable() = + (0 until valueArgumentsCount).all { getValueArgument(it)!!.type.isNullable() } + + private fun transformCompareToMethodCall(call: IrCall): IrExpression { + val function = call.symbol.owner as IrSimpleFunction + if (function.parent !is IrClass) return call + + fun IrSimpleFunction.isFakeOverriddenFromComparable(): Boolean = when { + origin != IrDeclarationOrigin.FAKE_OVERRIDE -> + parentAsClass.thisReceiver!!.type.isComparable() + + else -> overriddenSymbols.all { it.owner.isFakeOverriddenFromComparable() } + } + + return when { + // Use runtime function call in case when receiverType is a primitive JS type that doesn't have `compareTo` method, + // or has a potential to be primitive type (being fake overridden from `Comparable`) + function.isMethodOfPrimitiveJSType() || function.isFakeOverriddenFromComparable() -> + irCall(call, intrinsics.jsCompareTo, dispatchReceiverAsFirstArgument = true) + + // Valid `compareTo` method must be present at this point + else -> call - } + } + } + + + private fun transformEqualsMethodCall(call: IrCall): IrExpression { + val function = call.symbol.owner + return when { + // Nothing special + !function.isEqualsInheritedFromAny() -> call + + // `Any.equals` works as identity operator + call.isSuperToAny() -> + irCall(call, intrinsics.jsEqeqeq.symbol, dispatchReceiverAsFirstArgument = true) + + // Use runtime function call in case when receiverType is a primitive JS type that doesn't have `equals` method, + // or has a potential to be primitive type (being fake overridden from `Any`) + function.isMethodOfPotentiallyPrimitiveJSType() -> + irCall(call, intrinsics.jsEquals, dispatchReceiverAsFirstArgument = true) + + // Valid `equals` method must be present at this point + else -> call + } + } + + private fun IrType.findEqualsMethod(): IrSimpleFunction? { + val klass = getClass() ?: return null + return klass.declarations + .filterIsInstance() + .filter { it.isEqualsInheritedFromAny() && !it.isFakeOverriddenFromAny() } + .also { assert(it.size <= 1) } + .singleOrNull() + } + + private fun IrFunction.isMethodOfPrimitiveJSType() = + dispatchReceiverParameter?.type?.getPrimitiveType() != PrimitiveType.OTHER + + private fun IrFunction.isMethodOfPotentiallyPrimitiveJSType() = + isMethodOfPrimitiveJSType() || isFakeOverriddenFromAny() + + private fun IrFunction.isEqualsInheritedFromAny() = + name == Name.identifier("equals") && + dispatchReceiverParameter != null && + valueParameters.size == 1 && + valueParameters[0].type.isNullableAny() + + private fun shouldReplaceToStringWithRuntimeCall(call: IrCall): Boolean { + // TODO: (KOTLIN-CR-2079) + // - User defined extension functions Any?.toString() call can be lost during lowering. + // - Use direct method call for dynamic types??? + // - Define Any?.toString() in runtime library and stop intrincifying extensions + + if (call.valueArgumentsCount > 0) + return false + + val receiverParameterType = with(call.symbol.owner) { + dispatchReceiverParameter ?: extensionReceiverParameter + }?.type ?: return false + + return receiverParameterType.run { + isArray() || isAny() || isNullable() || this is IrDynamicType || isString() } } } -fun shouldReplaceToStringWithRuntimeCall(call: IrCall): Boolean { - // TODO: (KOTLIN-CR-2079) - // - User defined extension functions Any?.toString() call can be lost during lowering. - // - Use direct method call for dynamic types??? - // - Define Any?.toString() in runtime library and stop intrincifying extensions +enum class PrimitiveType { + FLOATING_POINT_NUMBER, + INTEGER_NUMBER, + STRING, + BOOLEAN, + OTHER +} - if (call.valueArgumentsCount > 0) - return false - - val receiverParameterType = with(call.symbol.owner) { - dispatchReceiverParameter ?: extensionReceiverParameter - }?.type ?: return false - - return receiverParameterType.run { - isArray() || isAny() || isNullable() || this is IrDynamicType || isString() +fun IrType.getPrimitiveType() = makeNotNull().run { + when { + isBoolean() -> PrimitiveType.BOOLEAN + isByte() || isShort() || isInt() -> PrimitiveType.INTEGER_NUMBER + isFloat() || isDouble() -> PrimitiveType.FLOATING_POINT_NUMBER + isString() -> PrimitiveType.STRING + else -> PrimitiveType.OTHER } } -fun shouldReplaceCompareToWithRuntimeCall(call: IrCall): Boolean { - // TODO: Replace all compareTo to with runtime call when Comparable<*>.compareTo() bridge is implemented - return call.symbol.owner.dispatchReceiverParameter?.run { - type is IrDynamicType - || type.isJsNumber() - || type.isNullableJsNumber() - || type.isBoolean() || type.isNullableBoolean() - || type.isString() || type.isNullableString() - } ?: false -} - -/* - Equality translation table: - -| | JsN | JsN? | Long | Long? | Bool | Bool? | String | String? | Other | Other? | -|----------------|------|------|------|-------|------|-------|--------|---------|-------|--------| -| JsN | === | === | == | == | === | === | === | === | K.eq | K.eq | -| JsN? | === | == | == | == | === | K.eq | === | K.eq | K.eq | K.eq | -| Long | == | == | K.eq | K.eq | === | === | === | === | K.eq | K.eq | -| Long? | == | == | K.eq | K.eq | === | K.eq | === | K.eq | K.eq | K.eq | -| Bool | === | === | === | === | === | === | === | === | K.eq | K.eq | -| Bool? | === | K.eq | === | K.eq | === | == | === | K.eq | K.eq | K.eq | -| String | === | === | === | === | === | === | === | === | K.eq | K.eq | -| String? | === | K.eq | === | K.eq | === | K.eq | === | == | K.eq | K.eq | -| Other with .eq | .eq | .eq | .eq | .eq | .eq | .eq | .eq | .eq | .eq | .eq | -| Other w/o .eq | K.eq | K.eq | K.eq | K.eq | K.eq | K.eq | K.eq | K.eq | K.eq | K.eq | -| Other? | K.eq | K.eq | K.eq | K.eq | K.eq | K.eq | K.eq | K.eq | K.eq | K.eq | - - -JsNumber -- type lowered to JS Number - K.eq -- runtime library call - .eq -- .equals(x) method call - - */ - -sealed class EqualityLoweringType -object IdentityOperator : EqualityLoweringType() -object EqualityOperator : EqualityLoweringType() -object RuntimeFunctionCall : EqualityLoweringType() -object RuntimeOrMethodCall : EqualityLoweringType() - -fun translateEquals(lhs: IrType, rhs: IrType): EqualityLoweringType = when { - lhs is IrDynamicType -> EqualityOperator - lhs.isJsNumber() -> translateEqualsForJsNumber(rhs) - lhs.isNullableJsNumber() -> translateEqualsForNullableJsNumber(rhs) - lhs.isLong() -> translateEqualsForLong(rhs) - lhs.isNullableLong() -> translateEqualsForNullableLong(rhs) - lhs.isBoolean() -> translateEqualsForBoolean(rhs) - lhs.isNullableBoolean() -> translateEqualsForNullableBoolean(rhs) - lhs.isString() -> translateEqualsForString(rhs) - lhs.isNullableString() -> translateEqualsForNullableString(rhs) - // TODO: Fix unbound symbols (in inline) - lhs.toKotlinType().isNullable() -> RuntimeFunctionCall - else -> RuntimeOrMethodCall -} - -fun translateEqualsForJsNumber(rhs: IrType): EqualityLoweringType = when { - rhs.isJsNumber() || rhs.isNullableJsNumber() -> IdentityOperator - rhs.isLong() || rhs.isNullableLong() -> EqualityOperator - rhs.isBoolean() || rhs.isNullableBoolean() -> IdentityOperator - rhs.isString() || rhs.isNullableString() -> IdentityOperator - else -> RuntimeFunctionCall -} - -fun translateEqualsForNullableJsNumber(rhs: IrType): EqualityLoweringType = when { - rhs.isJsNumber() -> IdentityOperator - rhs.isNullableJsNumber() -> EqualityOperator - rhs.isLong() || rhs.isNullableLong() -> EqualityOperator - rhs.isBoolean() || rhs.isString() -> IdentityOperator - else -> RuntimeFunctionCall -} - -fun translateEqualsForLong(rhs: IrType): EqualityLoweringType = when { - rhs.isJsNumber() || rhs.isNullableJsNumber() -> EqualityOperator - rhs.isLong() || rhs.isNullableLong() -> RuntimeFunctionCall - rhs.isBoolean() || rhs.isNullableBoolean() -> IdentityOperator - rhs.isString() || rhs.isNullableString() -> IdentityOperator - else -> RuntimeFunctionCall -} - -fun translateEqualsForNullableLong(rhs: IrType): EqualityLoweringType = when { - rhs.isJsNumber() || rhs.isNullableJsNumber() -> EqualityOperator - rhs.isLong() || rhs.isNullableLong() -> RuntimeFunctionCall - rhs.isBoolean() -> IdentityOperator - rhs.isString() -> IdentityOperator - else -> RuntimeFunctionCall -} - -fun translateEqualsForBoolean(rhs: IrType): EqualityLoweringType = when { - rhs.isJsNumber() || rhs.isNullableJsNumber() -> IdentityOperator - rhs.isLong() || rhs.isNullableLong() -> IdentityOperator - rhs.isBoolean() || rhs.isNullableBoolean() -> IdentityOperator - rhs.isString() || rhs.isNullableString() -> IdentityOperator - else -> RuntimeFunctionCall -} - -fun translateEqualsForNullableBoolean(rhs: IrType): EqualityLoweringType = when { - rhs.isJsNumber() -> IdentityOperator - rhs.isNullableJsNumber() -> RuntimeFunctionCall - rhs.isLong() -> IdentityOperator - rhs.isNullableLong() -> RuntimeFunctionCall - rhs.isBoolean() -> IdentityOperator - rhs.isNullableBoolean() -> EqualityOperator - rhs.isString() -> IdentityOperator - else -> RuntimeFunctionCall -} - -fun translateEqualsForString(rhs: IrType): EqualityLoweringType = when { - rhs.isJsNumber() || rhs.isNullableJsNumber() -> IdentityOperator - rhs.isLong() || rhs.isNullableLong() -> IdentityOperator - rhs.isBoolean() || rhs.isNullableBoolean() -> IdentityOperator - rhs.isString() || rhs.isNullableString() -> IdentityOperator - else -> RuntimeFunctionCall -} - -fun translateEqualsForNullableString(rhs: IrType): EqualityLoweringType = when { - rhs.isJsNumber() -> IdentityOperator - rhs.isNullableJsNumber() -> RuntimeFunctionCall - rhs.isLong() -> IdentityOperator - rhs.isNullableLong() -> RuntimeFunctionCall - rhs.isBoolean() -> IdentityOperator - rhs.isNullableBoolean() -> RuntimeFunctionCall - rhs.isString() -> IdentityOperator - rhs.isNullableString() -> EqualityOperator - else -> RuntimeFunctionCall -} - -private fun IrType.isNullableJsNumber(): Boolean = isNullablePrimitiveType() && !isNullableLong() && !isNullableChar() - -private fun IrType.isJsNumber(): Boolean = isPrimitiveType() && !isLong() && !isChar() - private fun MemberToTransformer.op(type: IrType, name: Name, v: IrSimpleFunctionSymbol) { op(type, name, v = { irCall(it, v, dispatchReceiverAsFirstArgument = true) }) } @@ -723,6 +647,12 @@ private fun SymbolToTransformer.add(from: Map, to: } } +private fun SymbolToTransformer.add(from: Map, to: (IrCall) -> IrExpression) { + from.forEach { _, func -> + add(func.symbol, to) + } +} + private fun SymbolToTransformer.add(from: IrFunctionSymbol, to: (IrCall) -> IrExpression) { put(from, to) } @@ -780,4 +710,4 @@ private val IrClassSymbol.sizeConstructor get() = owner.declarations.filterIsInstance().first { it.valueParameters.size == 1 }.symbol private val IrClassSymbol.lengthProperty - get() = owner.declarations.filterIsInstance().first { it.name.asString() == "length" }.getter!!.symbol \ No newline at end of file + get() = owner.declarations.filterIsInstance().first { it.name.asString() == "length" }.getter!!.symbol diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/types/irTypePredicates.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/types/irTypePredicates.kt index 92708be5101..2bb17b46829 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/types/irTypePredicates.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/types/irTypePredicates.kt @@ -60,6 +60,8 @@ fun IrType.isFloat(): Boolean = isNotNullClassType(KotlinBuiltIns.FQ_NAMES._floa fun IrType.isDouble(): Boolean = isNotNullClassType(KotlinBuiltIns.FQ_NAMES._double) fun IrType.isNumber(): Boolean = isNotNullClassType(KotlinBuiltIns.FQ_NAMES.number) +fun IrType.isComparable(): Boolean = isNotNullClassType(KotlinBuiltIns.FQ_NAMES.comparable.toUnsafe()) + fun IrType.isNullableBoolean(): Boolean = isNullableClassType(KotlinBuiltIns.FQ_NAMES._boolean) fun IrType.isNullableLong(): Boolean = isNullableClassType(KotlinBuiltIns.FQ_NAMES._long) fun IrType.isNullableChar(): Boolean = isNullableClassType(KotlinBuiltIns.FQ_NAMES._char) diff --git a/compiler/testData/codegen/box/boxingOptimization/maxMinBy.kt b/compiler/testData/codegen/box/boxingOptimization/maxMinBy.kt index 47041dbdf2f..00adbc52f4c 100644 --- a/compiler/testData/codegen/box/boxingOptimization/maxMinBy.kt +++ b/compiler/testData/codegen/box/boxingOptimization/maxMinBy.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // WITH_RUNTIME fun box(): String { diff --git a/compiler/testData/codegen/box/ieee754/dataClass.kt b/compiler/testData/codegen/box/ieee754/dataClass.kt index 40ab9af31d0..fb661368e8e 100644 --- a/compiler/testData/codegen/box/ieee754/dataClass.kt +++ b/compiler/testData/codegen/box/ieee754/dataClass.kt @@ -1,5 +1,4 @@ // IGNORE_BACKEND: JVM_IR -// IGNORE_BACKEND: JS_IR data class Test(val z1: Double, val z2: Double?) fun box(): String { diff --git a/compiler/testData/codegen/box/ieee754/equalsNaN_properIeeeComparisons.kt b/compiler/testData/codegen/box/ieee754/equalsNaN_properIeeeComparisons.kt index 11042003d0f..8b9fb0428e1 100644 --- a/compiler/testData/codegen/box/ieee754/equalsNaN_properIeeeComparisons.kt +++ b/compiler/testData/codegen/box/ieee754/equalsNaN_properIeeeComparisons.kt @@ -1,5 +1,6 @@ // !LANGUAGE: +ProperIeee754Comparisons // IGNORE_BACKEND: JVM_IR +// IGNORE_BACKEND: JS_IR // WITH_RUNTIME import kotlin.test.* @@ -94,6 +95,7 @@ fun box(): String { assertTrue(adnq == dn, "Double: (Any?)NaN != NaN") assertTrue(adnq == adn, "Double: (Any?)NaN != (Any)NaN") + // JS IR fails due to: https://bugs.openjdk.java.net/browse/JDK-8141407 assertFalse(dnq == dnq, "Double: NaN? == NaN?") assertTrue(dnq == adnq, "Double: NaN? != (Any?)NaN") assertTrue(adnq == dnq, "Double: (Any?)NaN != NaN?") @@ -114,6 +116,7 @@ fun box(): String { assertFalse(adnq != dn, "Double: (Any?)NaN != NaN") assertFalse(adnq != adn, "Double: (Any?)NaN != (Any)NaN") + // JS IR fails due to: https://bugs.openjdk.java.net/browse/JDK-8141407 assertTrue(dnq != dnq, "Double: NaN? == NaN?") assertFalse(dnq != adnq, "Double: NaN? != (Any?)NaN") assertFalse(adnq != dnq, "Double: (Any?)NaN != NaN?") diff --git a/compiler/testData/codegen/box/ieee754/inline.kt b/compiler/testData/codegen/box/ieee754/inline.kt index e9d91271b94..c1d3e721321 100644 --- a/compiler/testData/codegen/box/ieee754/inline.kt +++ b/compiler/testData/codegen/box/ieee754/inline.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR inline fun less(a: Comparable, b: Double): Boolean { return a < b } diff --git a/compiler/testData/codegen/box/inlineClasses/inlineClassEqualityShouldUseTotalOrderForFloatingPointData.kt b/compiler/testData/codegen/box/inlineClasses/inlineClassEqualityShouldUseTotalOrderForFloatingPointData.kt index 925bc8ae730..1f0e893f9a5 100644 --- a/compiler/testData/codegen/box/inlineClasses/inlineClassEqualityShouldUseTotalOrderForFloatingPointData.kt +++ b/compiler/testData/codegen/box/inlineClasses/inlineClassEqualityShouldUseTotalOrderForFloatingPointData.kt @@ -1,5 +1,5 @@ // !LANGUAGE: +InlineClasses -// IGNORE_BACKEND: JVM_IR, JS_IR +// IGNORE_BACKEND: JVM_IR inline class InlineFloat(val data: Float) diff --git a/compiler/testData/codegen/box/inlineClasses/kt26103_contravariantUnderlyingType.kt b/compiler/testData/codegen/box/inlineClasses/kt26103_contravariantUnderlyingType.kt index ad92ebf2f63..4327e8ad1ce 100644 --- a/compiler/testData/codegen/box/inlineClasses/kt26103_contravariantUnderlyingType.kt +++ b/compiler/testData/codegen/box/inlineClasses/kt26103_contravariantUnderlyingType.kt @@ -1,6 +1,6 @@ // !LANGUAGE: +InlineClasses // WITH_RUNTIME -// IGNORE_BACKEND: JVM_IR, JS_IR +// IGNORE_BACKEND: JVM_IR inline class GCmp(val xc: Comparable) inline class GSCmp(val sc: Comparable) diff --git a/compiler/testData/codegen/box/ir/primitiveNumberComparisons/comparableToDouble.kt b/compiler/testData/codegen/box/ir/primitiveNumberComparisons/comparableToDouble.kt index d072ea665ca..1127fde60a6 100644 --- a/compiler/testData/codegen/box/ir/primitiveNumberComparisons/comparableToDouble.kt +++ b/compiler/testData/codegen/box/ir/primitiveNumberComparisons/comparableToDouble.kt @@ -1,5 +1,4 @@ // !LANGUAGE: +ProperIeee754Comparisons -//IGNORE_BACKEND: JS_IR fun ltDD(x: Comparable, y: Double) = x is Double && x < y diff --git a/compiler/testData/codegen/box/primitiveTypes/crossTypeEquals.kt b/compiler/testData/codegen/box/primitiveTypes/crossTypeEquals.kt index a5058f2eb79..4f8745d94b9 100644 --- a/compiler/testData/codegen/box/primitiveTypes/crossTypeEquals.kt +++ b/compiler/testData/codegen/box/primitiveTypes/crossTypeEquals.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // TARGET_BACKEND: JS // Contains JS-specific checks, e.g. null == undefined diff --git a/compiler/testData/codegen/box/ranges/contains/inRangeLiteralComposition.kt b/compiler/testData/codegen/box/ranges/contains/inRangeLiteralComposition.kt index 727b5fe61bf..2e086ceafcd 100644 --- a/compiler/testData/codegen/box/ranges/contains/inRangeLiteralComposition.kt +++ b/compiler/testData/codegen/box/ranges/contains/inRangeLiteralComposition.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // WITH_RUNTIME val i1 = 1 diff --git a/compiler/testData/codegen/box/regressions/kt1800.kt b/compiler/testData/codegen/box/regressions/kt1800.kt index e74b2c962a7..85b88bbac74 100644 --- a/compiler/testData/codegen/box/regressions/kt1800.kt +++ b/compiler/testData/codegen/box/regressions/kt1800.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // WITH_RUNTIME //KT-1800 error/NonExistentClass generated on runtime package i diff --git a/compiler/testData/codegen/box/regressions/kt2017.kt b/compiler/testData/codegen/box/regressions/kt2017.kt index 91f8fc4a70e..3b1b64b5dc1 100644 --- a/compiler/testData/codegen/box/regressions/kt2017.kt +++ b/compiler/testData/codegen/box/regressions/kt2017.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // WITH_RUNTIME fun box(): String { diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java index 76cfcf0546f..cdfc1897b45 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java @@ -6404,6 +6404,11 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { runTest("js/js.translator/testData/box/number/numberConversions.kt"); } + @TestMetadata("numberEquals.kt") + public void testNumberEquals() throws Exception { + runTest("js/js.translator/testData/box/number/numberEquals.kt"); + } + @TestMetadata("numberIncDec.kt") public void testNumberIncDec() throws Exception { runTest("js/js.translator/testData/box/number/numberIncDec.kt"); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrBoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrBoxJsTestGenerated.java index bf6e5512401..070355a0ea8 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrBoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrBoxJsTestGenerated.java @@ -6404,6 +6404,11 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { runTest("js/js.translator/testData/box/number/numberConversions.kt"); } + @TestMetadata("numberEquals.kt") + public void testNumberEquals() throws Exception { + runTest("js/js.translator/testData/box/number/numberEquals.kt"); + } + @TestMetadata("numberIncDec.kt") public void testNumberIncDec() throws Exception { runTest("js/js.translator/testData/box/number/numberIncDec.kt"); diff --git a/js/js.translator/testData/box/expression/compareTo/booleanCompareTo.kt b/js/js.translator/testData/box/expression/compareTo/booleanCompareTo.kt index 05f5338b450..fef7541c1fe 100644 --- a/js/js.translator/testData/box/expression/compareTo/booleanCompareTo.kt +++ b/js/js.translator/testData/box/expression/compareTo/booleanCompareTo.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // EXPECTED_REACHABLE_NODES: 1282 fun box(): String { val r1 = trueFun() > falseFun() @@ -24,4 +23,4 @@ fun box(): String { fun trueFun() = true -fun falseFun() = false \ No newline at end of file +fun falseFun() = false diff --git a/js/js.translator/testData/box/number/numberEquals.kt b/js/js.translator/testData/box/number/numberEquals.kt new file mode 100644 index 00000000000..7a213123325 --- /dev/null +++ b/js/js.translator/testData/box/number/numberEquals.kt @@ -0,0 +1,57 @@ +// !LANGUAGE: +ProperIeee754Comparisons +// IGNORE_BACKEND: JS +// EXPECTED_REACHABLE_NODES: 1112 +package foo + + +fun testNullable(): String { + val undefined: Double? = js("undefined") + val doubleNull: Double? = null + + val plusZero: Double? = +0.0 + val minusZero: Double? = -0.0 + + if ((+0.0).equals(minusZero)) return "Total order fail" + if (plusZero != minusZero) return "IEEE 754 equals fail" + + if (plusZero == doubleNull) return "+0.0 != null fail" + if (plusZero == undefined) return "+0.0 != undefined fail" + + if (undefined != doubleNull) return "undefined == null fail" + if (undefined != undefined) return "undefined = undefined fail" + if (doubleNull != doubleNull) return "doubleNull = doubleNull fail" + + // Double == Float + val plusZeroAny: Any? = +0.0 + val minusZeroAny: Any? = -0.0f + + if (plusZeroAny is Double && minusZeroAny is Float) { + if (plusZeroAny != minusZeroAny) return "IEEE 754 quals fail 2" + } + + return "OK" +} + +fun box(): String { + val plusZero: Double = +0.0 + val minusZero: Double = -0.0 + + if (plusZero.equals(minusZero)) return "Total order fail" + if (plusZero != minusZero) return "IEEE 754 equals fail" + + val plusZeroFloat: Float = +0.0f + val minusZeroFloat: Float = -0.0f + + if (plusZeroFloat.equals(minusZeroFloat)) return "Total order fail 2" + if (plusZeroFloat != minusZeroFloat) return "IEEE 754 equals fail 2" + + if ((plusZero as Any) == (minusZero as Any)) return "Total order fail 4" + if ((plusZeroFloat as Any) == (minusZeroFloat as Any)) return "Total order fail 5" + if (plusZero == (minusZero as Any)) return "Total order fail 6" + + val nullableRes = testNullable() + if (nullableRes != "OK") + return "Nullable" + nullableRes + + return "OK" +} diff --git a/libraries/stdlib/js/irRuntime/compareTo.kt b/libraries/stdlib/js/irRuntime/compareTo.kt index b1a0a74b0cd..a3b5c3eceb0 100644 --- a/libraries/stdlib/js/irRuntime/compareTo.kt +++ b/libraries/stdlib/js/irRuntime/compareTo.kt @@ -20,10 +20,13 @@ fun compareTo(a: dynamic, b: dynamic): Int { return primitiveCompareTo(a, b) } - // TODO: Replace to a.unsafeCast>().compareTo(b) when bridge is implemented - return js("a.compareTo(b)").unsafeCast() + return compareToDoNotIntrinsicify(a, b) } +// TODO: Use @DoNotIntrincify annotation +private fun > compareToDoNotIntrinsicify(a: Comparable, b: T) = + a.compareTo(b) + fun primitiveCompareTo(a: dynamic, b: dynamic): Int = js("a < b ? -1 : a > b ? 1 : 0").unsafeCast()