diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt index 3bfaddd42d9..020aa39c253 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt @@ -18,8 +18,6 @@ package org.jetbrains.kotlin.backend.konan.descriptors import org.jetbrains.kotlin.backend.common.atMostOne import org.jetbrains.kotlin.backend.konan.KonanBuiltIns -import org.jetbrains.kotlin.backend.konan.ValueType -import org.jetbrains.kotlin.backend.konan.isRepresentedAs import org.jetbrains.kotlin.backend.konan.isValueType import org.jetbrains.kotlin.backend.konan.llvm.functionName import org.jetbrains.kotlin.backend.konan.llvm.localHash @@ -30,10 +28,7 @@ import org.jetbrains.kotlin.builtins.isFunctionType import org.jetbrains.kotlin.builtins.isSuspendFunctionType import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor -import org.jetbrains.kotlin.incremental.components.NoLookupLocation -import org.jetbrains.kotlin.ir.descriptors.IrBuiltinOperatorDescriptorBase import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.OverridingUtil import org.jetbrains.kotlin.resolve.descriptorUtil.* import org.jetbrains.kotlin.resolve.scopes.MemberScope @@ -74,10 +69,10 @@ internal fun T.resolveFakeOverride(): T { private val intrinsicAnnotation = FqName("konan.internal.Intrinsic") -internal val FunctionDescriptor.isIntrinsic: Boolean +internal val CallableDescriptor.isIntrinsic: Boolean get() = when { this.annotations.hasAnnotation(intrinsicAnnotation) -> { - check(isExternal, { "Intrinsic function $name should be external" }) + // check(isExternal, { "Intrinsic function $name should be external" }) true } else -> false diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt index 9c48fe7105b..a009f78626d 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt @@ -121,6 +121,10 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable): Sym symbolTable.referenceSimpleFunction(it) } + val ieee754Equals = context.getInternalFunctions("ieee754Equals").map { + symbolTable.referenceSimpleFunction(it) + } + override val areEqual = symbolTable.referenceSimpleFunction(context.getInternalFunctions("areEqual").single()) override val ThrowNullPointerException = symbolTable.referenceSimpleFunction( diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt index a849acf0f4b..6ec55119464 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt @@ -458,6 +458,10 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, /* floating-point comparisons */ fun fcmpEq(arg0: LLVMValueRef, arg1: LLVMValueRef, name: String = ""): LLVMValueRef = LLVMBuildFCmp(builder, LLVMRealPredicate.LLVMRealOEQ, arg0, arg1, name)!! + fun fcmpGt(arg0: LLVMValueRef, arg1: LLVMValueRef, name: String = ""): LLVMValueRef = LLVMBuildFCmp(builder, LLVMRealPredicate.LLVMRealOGT, arg0, arg1, name)!! + fun fcmpGe(arg0: LLVMValueRef, arg1: LLVMValueRef, name: String = ""): LLVMValueRef = LLVMBuildFCmp(builder, LLVMRealPredicate.LLVMRealOGE, arg0, arg1, name)!! + fun fcmpLt(arg0: LLVMValueRef, arg1: LLVMValueRef, name: String = ""): LLVMValueRef = LLVMBuildFCmp(builder, LLVMRealPredicate.LLVMRealOLT, arg0, arg1, name)!! + fun fcmpLe(arg0: LLVMValueRef, arg1: LLVMValueRef, name: String = ""): LLVMValueRef = LLVMBuildFCmp(builder, LLVMRealPredicate.LLVMRealOLE, arg0, arg1, name)!! fun bitcast(type: LLVMTypeRef?, value: LLVMValueRef, name: String = "") = LLVMBuildBitCast(builder, value, type, name)!! diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index 06c1c67cf7e..eaa5a67b4da 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -44,9 +44,9 @@ import org.jetbrains.kotlin.ir.visitors.acceptVoid import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.DescriptorUtils -import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.SimpleType import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.types.typeUtil.isNothing import org.jetbrains.kotlin.types.typeUtil.isPrimitiveNumberType @@ -2016,15 +2016,21 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map { val arg0 = args[0] val arg1 = args[1] - assert (arg0.type == arg1.type, { "Types are different: '${llvmtype2string(arg0.type)}' and '${llvmtype2string(arg1.type)}'" }) + assert (arg0.type == arg1.type, + { "Types are different: '${llvmtype2string(arg0.type)}' and '${llvmtype2string(arg1.type)}'" }) - return when (LLVMGetTypeKind(arg0.type)) { - LLVMTypeKind.LLVMFloatTypeKind, LLVMTypeKind.LLVMDoubleTypeKind -> - functionGenerationContext.fcmpEq(arg0, arg1) + return functionGenerationContext.icmpEq(arg0, arg1) + } + "konan.internal.ieee754Equals" -> { + val arg0 = args[0] + val arg1 = args[1] + assert (arg0.type == arg1.type, + { "Types are different: '${llvmtype2string(arg0.type)}' and '${llvmtype2string(arg1.type)}'" }) + val type = LLVMGetTypeKind(arg0.type) + assert (type == LLVMTypeKind.LLVMFloatTypeKind || type == LLVMTypeKind.LLVMDoubleTypeKind, + { "Should be of floating point kind, not: '${llvmtype2string(arg0.type)}'"}) + return functionGenerationContext.fcmpEq(arg0, arg1) - else -> - functionGenerationContext.icmpEq(arg0, arg1) - } } "konan.internal.getContinuation" -> return getContinuation() } @@ -2332,22 +2338,44 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map): LLVMValueRef { - context.log{"evaluateCall : origin:${ir2string(callee)}"} + context.log{"evaluateOperatorCall : origin:${ir2string(callee)}"} val descriptor = callee.descriptor val ib = context.irModule!!.irBuiltins - when (descriptor) { - ib.eqeqeq -> return functionGenerationContext.icmpEq(args[0], args[1]) - ib.gt0 -> return functionGenerationContext.icmpGt(args[0], kImmZero) - ib.gteq0 -> return functionGenerationContext.icmpGe(args[0], kImmZero) - ib.lt0 -> return functionGenerationContext.icmpLt(args[0], kImmZero) - ib.lteq0 -> return functionGenerationContext.icmpLe(args[0], kImmZero) - ib.booleanNot -> return functionGenerationContext.icmpNe(args[0], kTrue) - else -> { - TODO(descriptor.name.toString()) + val funGen = functionGenerationContext + + return when { + descriptor == ib.eqeqeq -> funGen.icmpEq(args[0], args[1]) + descriptor == ib.booleanNot -> funGen.icmpNe(args[0], kTrue) + + descriptor.isComparisonDescriptor(ib.greaterFunByOperandType) -> { + if (args[0].type.isFloatingPoint()) funGen.fcmpGt(args[0], args[1]) + else funGen.icmpGt(args[0], args[1]) } + descriptor.isComparisonDescriptor(ib.greaterOrEqualFunByOperandType) -> { + if (args[0].type.isFloatingPoint()) funGen.fcmpGe(args[0], args[1]) + else funGen.icmpGe(args[0], args[1]) + } + descriptor.isComparisonDescriptor(ib.lessFunByOperandType) -> { + if (args[0].type.isFloatingPoint()) funGen.fcmpLt(args[0], args[1]) + else funGen.icmpLt(args[0], args[1]) + } + descriptor.isComparisonDescriptor(ib.lessOrEqualFunByOperandType) -> { + if (args[0].type.isFloatingPoint()) funGen.fcmpLe(args[0], args[1]) + else funGen.icmpLe(args[0], args[1]) + } + else -> TODO(descriptor.name.toString()) } } + private fun LLVMTypeRef.isFloatingPoint(): Boolean { + val typeKind = LLVMGetTypeKind(this) + return typeKind == LLVMTypeKind.LLVMFloatTypeKind || typeKind == LLVMTypeKind.LLVMDoubleTypeKind + } + + private fun FunctionDescriptor.isComparisonDescriptor(map: Map): Boolean { + return map.values.any { it.descriptor == this } + } + //-------------------------------------------------------------------------// private fun generateWhenCase(resultPhi: LLVMValueRef?, branch: IrBranch, diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/BuiltinOperatorLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/BuiltinOperatorLowering.kt index f489b86adf8..294cddc2ef8 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/BuiltinOperatorLowering.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/BuiltinOperatorLowering.kt @@ -20,6 +20,7 @@ import org.jetbrains.kotlin.backend.common.BodyLoweringPass import org.jetbrains.kotlin.backend.common.atMostOne import org.jetbrains.kotlin.backend.konan.Context import org.jetbrains.kotlin.backend.konan.isValueType +import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.ir.descriptors.IrBuiltinOperatorDescriptor import org.jetbrains.kotlin.ir.expressions.IrBody import org.jetbrains.kotlin.ir.expressions.IrCall @@ -28,9 +29,9 @@ import org.jetbrains.kotlin.ir.expressions.IrTypeOperatorCall import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol import org.jetbrains.kotlin.ir.util.isNullConst -import org.jetbrains.kotlin.ir.util.type import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid +import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.typeUtil.isNothing import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf @@ -71,14 +72,17 @@ private class BuiltinOperatorTransformer(val context: Context) : IrElementTransf return expression } + private fun isIeee754Equals(descriptor: FunctionDescriptor): Boolean = + irBuiltins.ieee754equalsFunByOperandType.values.any { it.descriptor == descriptor } + private fun transformBuiltinOperator(expression: IrCall): IrExpression { val descriptor = expression.descriptor + // IEEE754 comparison for floating point values are done by intrinsic + if (isIeee754Equals(descriptor)) return lowerEqeq(expression) + return when (descriptor) { - irBuiltins.eqeq -> { - lowerEqeq(expression.getValueArgument(0)!!, expression.getValueArgument(1)!!, - expression.startOffset, expression.endOffset) - } + irBuiltins.eqeq -> lowerEqeq(expression) irBuiltins.eqeqeq -> lowerEqeqeq(expression) @@ -98,39 +102,49 @@ private class BuiltinOperatorTransformer(val context: Context) : IrElementTransf return if (lhs.type.isValueType() && rhs.type.isValueType()) { // Achieve the same behavior as with JVM BE: if both sides of `===` are values, then compare by value: - lowerEqeq(lhs, rhs, expression.startOffset, expression.endOffset) + lowerEqeq(expression) // Note: such comparisons are deprecated. } else { expression } } - private fun lowerEqeq(lhs: IrExpression, rhs: IrExpression, startOffset: Int, endOffset: Int): IrExpression { + private fun lowerEqeq(expression: IrCall): IrExpression { // TODO: optimize boxing? + val startOffset = expression.startOffset + val endOffset = expression.endOffset - val equals = selectEqualsFunction(lhs, rhs) + val equals = selectEqualsFunction(expression) return IrCallImpl(startOffset, endOffset, equals).apply { - putValueArgument(0, lhs) - putValueArgument(1, rhs) + putValueArgument(0, expression.getValueArgument(0)!!) + putValueArgument(1, expression.getValueArgument(1)!!) } } - private fun selectEqualsFunction(lhs: IrExpression, rhs: IrExpression): IrSimpleFunctionSymbol { + private fun selectEqualsFunction(expression: IrCall): IrSimpleFunctionSymbol { + val lhs = expression.getValueArgument(0)!! + val rhs = expression.getValueArgument(1)!! + val nullableNothingType = builtIns.nullableNothingType if (lhs.type.isSubtypeOf(nullableNothingType) && rhs.type.isSubtypeOf(nullableNothingType)) { // Compare by reference if each part is either `Nothing` or `Nothing?`: return irBuiltins.eqeqeqSymbol } - // TODO: areEqualByValue intrinsics are specially treated by code generator + // TODO: areEqualByValue and ieee754Equals intrinsics are specially treated by code generator // and thus can be declared synthetically in the compiler instead of explicitly in the runtime. + // Find a type-compatible `konan.internal.ieee754Equals` intrinsic: + if (isIeee754Equals(expression.descriptor)) { + // FIXME: intrinsic should be also compatible with nullable types + selectIntrinsic(context.ir.symbols.ieee754Equals, lhs.type, rhs.type)?.let { + return it + } + } + // Find a type-compatible `konan.internal.areEqualByValue` intrinsic: - context.ir.symbols.areEqualByValue.atMostOne { - lhs.type.isSubtypeOf(it.owner.valueParameters[0].type) && - rhs.type.isSubtypeOf(it.owner.valueParameters[1].type) - }?.let { + selectIntrinsic(context.ir.symbols.areEqualByValue, lhs.type, rhs.type)?.let { return it } @@ -142,4 +156,11 @@ private class BuiltinOperatorTransformer(val context: Context) : IrElementTransf context.ir.symbols.areEqual } } + + private fun selectIntrinsic(from: List, lhsType: KotlinType, rhsType: KotlinType): + IrSimpleFunctionSymbol? { + return from.atMostOne { + lhsType.isSubtypeOf(it.owner.valueParameters[0].type) && rhsType.isSubtypeOf(it.owner.valueParameters[1].type) + } + } } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/ForLoopsLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/ForLoopsLowering.kt index 4a3c3525111..00dfd9fd0ec 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/ForLoopsLowering.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/ForLoopsLowering.kt @@ -424,10 +424,10 @@ private class ForLoopsTransformer(val context: Context) : IrElementTransformerVo // Condition for a corner case: for (i in a until Int.MIN_VALUE) {}. // Check if forLoopInfo.bound > MIN_VALUE. val progressionType = forLoopInfo.progressionInfo.progressionType - return irCall(context.irBuiltIns.gt0Symbol).apply { + return irCall(context.irBuiltIns.greaterFunByOperandType[context.irBuiltIns.int]?.symbol!!).apply { val minConst = when { progressionType.isIntProgression() -> IrConstImpl - .int(startOffset,endOffset, context.builtIns.intType, Int.MIN_VALUE) + .int(startOffset, endOffset, context.builtIns.intType, Int.MIN_VALUE) progressionType.isCharProgression() -> IrConstImpl .char(startOffset, endOffset, context.builtIns.charType, 0.toChar()) progressionType.isLongProgression() -> IrConstImpl @@ -441,21 +441,25 @@ private class ForLoopsTransformer(val context: Context) : IrElementTransformerVo putValueArgument(0, minConst) } putValueArgument(0, compareToCall) + putValueArgument(1, IrConstImpl.int(startOffset, endOffset, context.builtIns.intType, 0)) } } // TODO: Eliminate the loop if we can prove that it will not be executed. private fun DeclarationIrBuilder.buildEmptyCheck(loop: IrLoop, forLoopInfo: ForLoopInfo): IrExpression { + val builtIns = context.irBuiltIns val increasing = forLoopInfo.progressionInfo.increasing - val comparingBuiltIn = if (increasing) context.irBuiltIns.lteq0Symbol else context.irBuiltIns.gteq0Symbol + val comparingBuiltIn = if (increasing) builtIns.lessOrEqualFunByOperandType[builtIns.int]?.symbol + else builtIns.greaterOrEqualFunByOperandType[builtIns.int]?.symbol // Check if inductionVariable <= last. val compareTo = symbols.getBinaryOperator(OperatorNameConventions.COMPARE_TO, forLoopInfo.inductionVariable.descriptor.type, forLoopInfo.last.descriptor.type) - val check: IrExpression = irCall(comparingBuiltIn).apply { + val check: IrExpression = irCall(comparingBuiltIn!!).apply { putValueArgument(0, irCallOp(compareTo, irGet(forLoopInfo.inductionVariable), irGet(forLoopInfo.last))) + putValueArgument(1, IrConstImpl.int(startOffset, endOffset, context.builtIns.intType, 0)) } // Process closed and open ranges in different manners. diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/DescriptorTable.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/DescriptorTable.kt index a989c247065..6dff766a674 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/DescriptorTable.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/DescriptorTable.kt @@ -28,6 +28,7 @@ import org.jetbrains.kotlin.backend.konan.llvm.isExported import org.jetbrains.kotlin.backend.konan.llvm.typeInfoSymbolName import org.jetbrains.kotlin.backend.konan.llvm.symbolName import org.jetbrains.kotlin.backend.konan.llvm.localHash +import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction internal fun DeclarationDescriptor.symbolName(): String = when (this) { is FunctionDescriptor @@ -81,16 +82,12 @@ class IrDeserializationDescriptorIndex(irBuiltIns: IrBuiltIns) { } val IrBuiltIns.irBuiltInDescriptors - get() = listOf( - this.eqeqeq, - this.eqeq, - this.lt0, - this.lteq0, - this.gt0, - this.gteq0, - this.throwNpe, - this.booleanNot, - this.noWhenBranchMatchedException, - this.enumValueOf) + get() = (lessFunByOperandType.values + + lessOrEqualFunByOperandType.values + + greaterOrEqualFunByOperandType.values + + greaterFunByOperandType.values + + ieee754equalsFunByOperandType.values + + eqeqeqFun + eqeqFun + throwNpeFun + booleanNotFun + noWhenBranchMatchedExceptionFun + enumValueOfFun) + .map(IrSimpleFunction::descriptor) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUnboundSymbolReplacer.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUnboundSymbolReplacer.kt index 033c348c363..c0480b54c0a 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUnboundSymbolReplacer.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUnboundSymbolReplacer.kt @@ -40,9 +40,12 @@ internal fun IrModuleFragment.replaceUnboundSymbols(context: Context) { val collector = DeclarationSymbolCollector() with(collector) { with(irBuiltins) { - for (op in arrayOf(eqeqeqFun, eqeqFun, lt0Fun, lteq0Fun, gt0Fun, gteq0Fun, throwNpeFun, booleanNotFun, - noWhenBranchMatchedExceptionFun)) { - + for (op in arrayOf(eqeqeqFun, eqeqFun, throwNpeFun, booleanNotFun, noWhenBranchMatchedExceptionFun) + + lessFunByOperandType.values + + lessOrEqualFunByOperandType.values + + greaterOrEqualFunByOperandType.values + + greaterFunByOperandType.values + + ieee754equalsFunByOperandType.values) { register(op.symbol) } } diff --git a/runtime/src/main/cpp/Operator.cpp b/runtime/src/main/cpp/Operator.cpp index 289e1deca1e..4183fd8c6b0 100644 --- a/runtime/src/main/cpp/Operator.cpp +++ b/runtime/src/main/cpp/Operator.cpp @@ -324,13 +324,6 @@ KDouble Kotlin_Long_toDouble (KLong a ) { return a; } //--- Float -------------------------------------------------------------------// -KInt Kotlin_Float_compareTo_Byte (KFloat a, KByte b) { if (a == b) return 0; return (a < b) ? -1 : 1; } -KInt Kotlin_Float_compareTo_Short (KFloat a, KShort b) { if (a == b) return 0; return (a < b) ? -1 : 1; } -KInt Kotlin_Float_compareTo_Int (KFloat a, KInt b) { if (a == b) return 0; return (a < b) ? -1 : 1; } -KInt Kotlin_Float_compareTo_Long (KFloat a, KLong b) { if (a == b) return 0; return (a < b) ? -1 : 1; } -KInt Kotlin_Float_compareTo_Float (KFloat a, KFloat b) { if (a == b) return 0; return (a < b) ? -1 : 1; } -KInt Kotlin_Float_compareTo_Double (KFloat a, KDouble b) { if (a == b) return 0; return (a < b) ? -1 : 1; } - KFloat Kotlin_Float_plus_Byte (KFloat a, KByte b) { return a + b; } KFloat Kotlin_Float_plus_Short (KFloat a, KShort b) { return a + b; } KFloat Kotlin_Float_plus_Int (KFloat a, KInt b) { return a + b; } @@ -412,13 +405,6 @@ KBoolean Kotlin_Float_isFinite (KFloat a) { return isfinite(a); //--- Double ------------------------------------------------------------------// -KInt Kotlin_Double_compareTo_Byte (KDouble a, KByte b) { if (a == b) return 0; return (a < b) ? -1 : 1; } -KInt Kotlin_Double_compareTo_Short (KDouble a, KShort b) { if (a == b) return 0; return (a < b) ? -1 : 1; } -KInt Kotlin_Double_compareTo_Int (KDouble a, KInt b) { if (a == b) return 0; return (a < b) ? -1 : 1; } -KInt Kotlin_Double_compareTo_Long (KDouble a, KLong b) { if (a == b) return 0; return (a < b) ? -1 : 1; } -KInt Kotlin_Double_compareTo_Float (KDouble a, KFloat b) { if (a == b) return 0; return (a < b) ? -1 : 1; } -KInt Kotlin_Double_compareTo_Double (KDouble a, KDouble b) { if (a == b) return 0; return (a < b) ? -1 : 1; } - KDouble Kotlin_Double_plus_Byte (KDouble a, KByte b) { return a + b; } KDouble Kotlin_Double_plus_Short (KDouble a, KShort b) { return a + b; } KDouble Kotlin_Double_plus_Int (KDouble a, KInt b) { return a + b; } @@ -471,8 +457,7 @@ KLong Kotlin_Double_toLong (KDouble a ) { if (a <= (KDouble) INT64_MIN) return INT64_MIN; return a; } -KByte Kotlin_Double_toByte (KDouble a ) { return (KByte) Kotlin_Double_toInt(a); } -KShort Kotlin_Double_toShort (KDouble a ) { return (KShort) Kotlin_Double_toInt(a); } + KFloat Kotlin_Double_toFloat (KDouble a ) { return a; } KDouble Kotlin_Double_toDouble (KDouble a ) { return a; } diff --git a/runtime/src/main/kotlin/konan/internal/Boxing.kt b/runtime/src/main/kotlin/konan/internal/Boxing.kt index 79b70f1a86f..634fbe119bb 100644 --- a/runtime/src/main/kotlin/konan/internal/Boxing.kt +++ b/runtime/src/main/kotlin/konan/internal/Boxing.kt @@ -170,7 +170,7 @@ class FloatBox(val value: Float) : Number(), Comparable { return false } - return this.value == other.value + return this.value.equals(other.value) } override fun hashCode() = value.hashCode() @@ -197,7 +197,7 @@ class DoubleBox(val value: Double) : Number(), Comparable { return false } - return this.value == other.value + return this.value.equals(other.value) } override fun hashCode() = value.hashCode() diff --git a/runtime/src/main/kotlin/konan/internal/Intrinsics.kt b/runtime/src/main/kotlin/konan/internal/Intrinsics.kt index 84f2f403140..2d3a24af2a7 100644 --- a/runtime/src/main/kotlin/konan/internal/Intrinsics.kt +++ b/runtime/src/main/kotlin/konan/internal/Intrinsics.kt @@ -26,8 +26,9 @@ import kotlinx.cinterop.NativePtr @Intrinsic external fun areEqualByValue(first: Short, second: Short): Boolean @Intrinsic external fun areEqualByValue(first: Int, second: Int): Boolean @Intrinsic external fun areEqualByValue(first: Long, second: Long): Boolean -@Intrinsic external fun areEqualByValue(first: Float, second: Float): Boolean -@Intrinsic external fun areEqualByValue(first: Double, second: Double): Boolean + +@Intrinsic external fun ieee754Equals(first: Float, second: Float): Boolean +@Intrinsic external fun ieee754Equals(first: Double, second: Double): Boolean @Intrinsic external fun areEqualByValue(first: NativePtr, second: NativePtr): Boolean @Intrinsic external fun areEqualByValue(first: NativePointed?, second: NativePointed?): Boolean diff --git a/runtime/src/main/kotlin/kotlin/Primitives.kt b/runtime/src/main/kotlin/kotlin/Primitives.kt index fcf622fa056..eb5ba7dc94c 100644 --- a/runtime/src/main/kotlin/kotlin/Primitives.kt +++ b/runtime/src/main/kotlin/kotlin/Primitives.kt @@ -983,43 +983,52 @@ public final class Float : Number(), Comparable { * Returns zero if this value is equal to the specified other value, a negative number if its less than other, * or a positive number if its greater than other. */ - @SymbolName("Kotlin_Float_compareTo_Byte") - external public operator fun compareTo(other: Byte): Int + public operator fun compareTo(other: Byte): Int = compareTo(other.toFloat()) /** * Compares this value with the specified value for order. * Returns zero if this value is equal to the specified other value, a negative number if its less than other, * or a positive number if its greater than other. */ - @SymbolName("Kotlin_Float_compareTo_Short") - external public operator fun compareTo(other: Short): Int + public operator fun compareTo(other: Short): Int = compareTo(other.toFloat()) /** * Compares this value with the specified value for order. * Returns zero if this value is equal to the specified other value, a negative number if its less than other, * or a positive number if its greater than other. */ - @SymbolName("Kotlin_Float_compareTo_Int") - external public operator fun compareTo(other: Int): Int + public operator fun compareTo(other: Int): Int = compareTo(other.toFloat()) /** * Compares this value with the specified value for order. * Returns zero if this value is equal to the specified other value, a negative number if its less than other, * or a positive number if its greater than other. */ - @SymbolName("Kotlin_Float_compareTo_Long") - external public operator fun compareTo(other: Long): Int + public operator fun compareTo(other: Long): Int = compareTo(other.toFloat()) /** * Compares this value with the specified value for order. * Returns zero if this value is equal to the specified other value, a negative number if its less than other, * or a positive number if its greater than other. */ - @SymbolName("Kotlin_Float_compareTo_Float") - external public override operator fun compareTo(other: Float): Int + public override operator fun compareTo(other: Float): Int { + // if any of values in NaN both comparisons return false + if (this > other) return 1 + if (this < other) return -1 + + val thisBits = this.toBits() + val otherBits = other.toBits() + + // Canonical NaN bits representation higher than any other bit representvalue + return when { + (thisBits > otherBits) -> 1 + (thisBits < otherBits) -> -1 + else -> 0 + } + } + /** * Compares this value with the specified value for order. * Returns zero if this value is equal to the specified other value, a negative number if its less than other, * or a positive number if its greater than other. */ - @SymbolName("Kotlin_Float_compareTo_Double") - external public operator fun compareTo(other: Double): Int + public operator fun compareTo(other: Double): Int = - other.toDouble().compareTo(this) /** Adds the other value to this value. */ @SymbolName("Kotlin_Float_plus_Byte") @@ -1129,11 +1138,12 @@ public final class Float : Number(), Comparable { @SymbolName("Kotlin_Float_unaryMinus") external public operator fun unaryMinus(): Float - @SymbolName("Kotlin_Float_toByte") - external public override fun toByte(): Byte + public override fun toByte(): Byte = this.toInt().toByte() + public override fun toChar(): Char = this.toInt().toChar() - @SymbolName("Kotlin_Float_toShort") - external public override fun toShort(): Short + + public override fun toShort(): Short = this.toInt().toShort() + @SymbolName("Kotlin_Float_toInt") external public override fun toInt(): Int @SymbolName("Kotlin_Float_toLong") @@ -1143,14 +1153,9 @@ public final class Float : Number(), Comparable { @SymbolName("Kotlin_Float_toDouble") external public override fun toDouble(): Double - // Konan-specific. - // We intentionally provide this overload to equals() to avoid artifical boxing. - // Note that here we intentionally deviate from JVM Kotlin, where this method would be - // this.bits() == other.bits(). - public fun equals(other: Float): Boolean = konan.internal.areEqualByValue(this, other) + public fun equals(other: Float): Boolean = toBits() == other.toBits() - public override fun equals(other: Any?): Boolean = - other is Float && konan.internal.areEqualByValue(this, other) + public override fun equals(other: Any?): Boolean = other is Float && this.equals(other) public override fun toString() = NumberConverter.convert(this) @@ -1201,43 +1206,56 @@ public final class Double : Number(), Comparable { * Returns zero if this value is equal to the specified other value, a negative number if its less than other, * or a positive number if its greater than other. */ - @SymbolName("Kotlin_Double_compareTo_Byte") - external public operator fun compareTo(other: Byte): Int + public operator fun compareTo(other: Byte): Int = compareTo(other.toDouble()) + /** * Compares this value with the specified value for order. * Returns zero if this value is equal to the specified other value, a negative number if its less than other, * or a positive number if its greater than other. */ - @SymbolName("Kotlin_Double_compareTo_Short") - external public operator fun compareTo(other: Short): Int + public operator fun compareTo(other: Short): Int = compareTo(other.toDouble()) + /** * Compares this value with the specified value for order. * Returns zero if this value is equal to the specified other value, a negative number if its less than other, * or a positive number if its greater than other. */ - @SymbolName("Kotlin_Double_compareTo_Int") - external public operator fun compareTo(other: Int): Int + public operator fun compareTo(other: Int): Int = compareTo(other.toDouble()) + /** * Compares this value with the specified value for order. * Returns zero if this value is equal to the specified other value, a negative number if its less than other, * or a positive number if its greater than other. */ - @SymbolName("Kotlin_Double_compareTo_Long") - external public operator fun compareTo(other: Long): Int + public operator fun compareTo(other: Long): Int = compareTo(other.toDouble()) + /** * Compares this value with the specified value for order. * Returns zero if this value is equal to the specified other value, a negative number if its less than other, * or a positive number if its greater than other. */ - @SymbolName("Kotlin_Double_compareTo_Float") - external public operator fun compareTo(other: Float): Int + public operator fun compareTo(other: Float): Int = compareTo(other.toDouble()) + /** * Compares this value with the specified value for order. * Returns zero if this value is equal to the specified other value, a negative number if its less than other, * or a positive number if its greater than other. */ - @SymbolName("Kotlin_Double_compareTo_Double") - external public override operator fun compareTo(other: Double): Int + public override operator fun compareTo(other: Double): Int { + // if any of values in NaN both comparisons return false + if (this > other) return 1 + if (this < other) return -1 + + val thisBits = this.toBits() + val otherBits = other.toBits() + + // Canonical NaN bits representation higher than any other bit representvalue + return when { + (thisBits > otherBits) -> 1 + (thisBits < otherBits) -> -1 + else -> 0 + } + } /** Adds the other value to this value. */ @SymbolName("Kotlin_Double_plus_Byte") @@ -1347,14 +1365,14 @@ public final class Double : Number(), Comparable { @SymbolName("Kotlin_Double_unaryMinus") external public operator fun unaryMinus(): Double + public override fun toByte(): Byte = this.toInt().toByte() - @SymbolName("Kotlin_Double_toByte") - external public override fun toByte(): Byte public override fun toChar(): Char = this.toInt().toChar() - @SymbolName("Kotlin_Double_toShort") - external public override fun toShort(): Short - @SymbolName("Kotlin_Double_toInt") - external public override fun toInt(): Int + + public override fun toShort(): Short = this.toInt().toShort() + + public override fun toInt(): Int = this.toLong().toInt() + @SymbolName("Kotlin_Double_toLong") external public override fun toLong(): Long @SymbolName("Kotlin_Double_toFloat") @@ -1362,19 +1380,13 @@ public final class Double : Number(), Comparable { @SymbolName("Kotlin_Double_toDouble") external public override fun toDouble(): Double - // Konan-specific. - // Note that here we intentionally deviate from JVM Kotlin, where this method would be - // this.bits() == other.bits(). - public fun equals(other: Double): Boolean = konan.internal.areEqualByValue(this, other) + public fun equals(other: Double): Boolean = toBits() == other.toBits() - public override fun equals(other: Any?): Boolean = - other is Double && konan.internal.areEqualByValue(this, other) + public override fun equals(other: Any?): Boolean = other is Double && this.equals(other) public override fun toString() = NumberConverter.convert(this) - public override fun hashCode(): Int { - return bits().hashCode() - } + public override fun hashCode(): Int = bits().hashCode() @SymbolName("Kotlin_Double_bits") external public fun bits(): Long diff --git a/runtime/src/main/kotlin/kotlin/comparisons/Comparisons.kt b/runtime/src/main/kotlin/kotlin/comparisons/Comparisons.kt index 81502baa469..22a0e21d9e2 100644 --- a/runtime/src/main/kotlin/kotlin/comparisons/Comparisons.kt +++ b/runtime/src/main/kotlin/kotlin/comparisons/Comparisons.kt @@ -266,7 +266,7 @@ public inline fun > nullsFirst(): Comparator = nullsFirst(n * considering `null` value greater than any other value. */ public fun nullsLast(comparator: Comparator): Comparator { - return object: Comparator { + return object: Comparator { override fun compare(a: T?, b: T?): Int { if (a === b) return 0 if (a == null) return 1