From ad8160ae83202695308a6986afd730257557ea4f Mon Sep 17 00:00:00 2001 From: Pavel Punegov <32519625+PavelPunegov@users.noreply.github.com> Date: Thu, 22 Feb 2018 15:04:20 +0300 Subject: [PATCH] Support IEEE754 floating point comparisons Add an intrinsic that performs check according to the standard Add floating point compare methods from LLVM Support new IR builtins, symbols and descriptors Rewrite compareTo floating point checks to Kotlin Rewrite equals to be the same as in JVM --- .../konan/descriptors/DescriptorUtils.kt | 9 +- .../jetbrains/kotlin/backend/konan/ir/Ir.kt | 4 + .../backend/konan/llvm/CodeGenerator.kt | 4 + .../kotlin/backend/konan/llvm/IrToBitcode.kt | 64 +++++++--- .../konan/lower/BuiltinOperatorLowering.kt | 53 ++++++--- .../backend/konan/lower/ForLoopsLowering.kt | 12 +- .../konan/serialization/DescriptorTable.kt | 19 ++- .../kotlin/ir/util/IrUnboundSymbolReplacer.kt | 9 +- runtime/src/main/cpp/Operator.cpp | 17 +-- .../src/main/kotlin/konan/internal/Boxing.kt | 4 +- .../main/kotlin/konan/internal/Intrinsics.kt | 5 +- runtime/src/main/kotlin/kotlin/Primitives.kt | 112 ++++++++++-------- .../kotlin/kotlin/comparisons/Comparisons.kt | 2 +- 13 files changed, 184 insertions(+), 130 deletions(-) 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