diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt index 0c58fe2f6ef..aca681585f8 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt @@ -136,7 +136,7 @@ internal class KonanLower(val context: Context) { DefaultParameterInjector(context).runOnFilePostfix(irFile) } phaser.phase(KonanPhase.LOWER_BUILTIN_OPERATORS) { - BuiltinOperatorLowering(context).runOnFilePostfix(irFile) + BuiltinOperatorLowering(context).lower(irFile) } phaser.phase(KonanPhase.LOWER_INNER_CLASSES) { InnerClassLowering(context).runOnFilePostfix(irFile) 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 4462a9fbdb3..0ddecd6a5c4 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 @@ -185,11 +185,11 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable): Sym symbolTable.referenceSimpleFunction(it) } - val ieee754NullableEquals = context.getInternalFunctions("ieee754NullableEquals").map { - symbolTable.referenceSimpleFunction(it) - } + val equals = context.builtIns.any.unsubstitutedMemberScope + .getContributedFunctions(Name.identifier("equals"), NoLookupLocation.FROM_BACKEND) + .single().let { symbolTable.referenceSimpleFunction(it) } - override val areEqual = symbolTable.referenceSimpleFunction(context.getInternalFunctions("areEqual").single()) + override val areEqual get() = error("Must not be used") override val ThrowNullPointerException = symbolTable.referenceSimpleFunction( context.getInternalFunctions("ThrowNullPointerException").single()) 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 4704885a46d..467400d2803 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 @@ -16,44 +16,43 @@ package org.jetbrains.kotlin.backend.konan.lower -import org.jetbrains.kotlin.backend.common.BodyLoweringPass +import org.jetbrains.kotlin.backend.common.FileLoweringPass import org.jetbrains.kotlin.backend.common.atMostOne +import org.jetbrains.kotlin.backend.common.lower.IrBuildingTransformer +import org.jetbrains.kotlin.backend.common.lower.at +import org.jetbrains.kotlin.backend.common.lower.irNot 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.builders.* +import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.ir.descriptors.IrBuiltinOperatorDescriptor -import org.jetbrains.kotlin.ir.expressions.IrBody import org.jetbrains.kotlin.ir.expressions.IrCall import org.jetbrains.kotlin.ir.expressions.IrExpression 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.visitors.IrElementTransformerVoid import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.isNullable import org.jetbrains.kotlin.types.typeUtil.isNothing import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf +import org.jetbrains.kotlin.types.typeUtil.makeNullable /** * This lowering pass lowers some calls to [IrBuiltinOperatorDescriptor]s. */ -internal class BuiltinOperatorLowering(val context: Context) : BodyLoweringPass { - - private val transformer = BuiltinOperatorTransformer(context) - - override fun lower(irBody: IrBody) { - irBody.transformChildrenVoid(transformer) - } - -} - -private class BuiltinOperatorTransformer(val context: Context) : IrElementTransformerVoid() { +internal class BuiltinOperatorLowering(val context: Context) : FileLoweringPass, IrBuildingTransformer(context) { private val builtIns = context.builtIns private val irBuiltins = context.irModule!!.irBuiltins + private val symbols = context.ir.symbols + + override fun lower(irFile: IrFile) { + irFile.transformChildrenVoid(this) + } override fun visitCall(expression: IrCall): IrExpression { expression.transformChildrenVoid(this) @@ -104,56 +103,101 @@ private class BuiltinOperatorTransformer(val context: Context) : IrElementTransf } } + private fun IrBuilderWithScope.irLogicalAnd(lhs: IrExpression, rhs: IrExpression) = context.andand(lhs, rhs) + private fun IrBuilderWithScope.irIsNull(exp: IrExpression) = irEqeqeq(exp, irNull()) + private fun IrBuilderWithScope.irIsNotNull(exp: IrExpression) = irNot(irEqeqeq(exp, irNull())) + private fun lowerEqeq(expression: IrCall): IrExpression { // TODO: optimize boxing? - val startOffset = expression.startOffset - val endOffset = expression.endOffset - val equals = selectEqualsFunction(expression) + builder.at(expression).run { + val lhs = expression.getValueArgument(0)!! + val rhs = expression.getValueArgument(1)!! - return IrCallImpl(startOffset, endOffset, equals).apply { - putValueArgument(0, expression.getValueArgument(0)!!) - putValueArgument(1, 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 irEqeqeq(lhs, rhs) + } + + // Find a type-compatible `konan.internal.areEqualByValue` intrinsic: + selectIntrinsic(symbols.areEqualByValue, lhs.type, rhs.type, false)?.let { + return irCall(it).apply { + putValueArgument(0, lhs) + putValueArgument(1, rhs) + } + } + + if (lhs.isNullConst() || rhs.isNullConst()) { + // or compare by reference if left or right part is `null`: + return irEqeqeq(lhs, rhs) + } + + // 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. + fun callEquals(lhs: IrExpression, rhs: IrExpression) = + if (expression.descriptor in ieee754EqualsDescriptors()) + // Find a type-compatible `konan.internal.ieee754Equals` intrinsic: + irCall(selectIntrinsic(symbols.ieee754Equals, lhs.type, rhs.type, true)!!).apply { + putValueArgument(0, lhs) + putValueArgument(1, rhs) + } + else + irCall(symbols.equals).apply { + dispatchReceiver = lhs + putValueArgument(0, rhs) + } + + val lhsIsNotNullable = !lhs.type.isNullable() + val rhsIsNotNullable = !rhs.type.isNullable() + + return if (expression.descriptor in ieee754EqualsDescriptors()) { + if (lhsIsNotNullable && rhsIsNotNullable) + callEquals(lhs, rhs) + else irBlock { + val lhsTemp = irTemporary(lhs) + val rhsTemp = irTemporary(rhs) + if (lhsIsNotNullable xor rhsIsNotNullable) { // Exactly one nullable. + +irLogicalAnd( + irIsNotNull(irGet((if (lhsIsNotNullable) rhsTemp else lhsTemp).symbol)), + callEquals(irGet(lhsTemp.symbol), irGet(rhsTemp.symbol)) + ) + } else { // Both are nullable. + +irIfThenElse(builtIns.booleanType, irIsNull(irGet(lhsTemp.symbol)), + irIsNull(irGet(rhsTemp.symbol)), + irLogicalAnd( + irIsNotNull(irGet(rhsTemp.symbol)), + callEquals(irGet(lhsTemp.symbol), irGet(rhsTemp.symbol)) + ) + ) + } + } + } else { + if (lhsIsNotNullable) + callEquals(lhs, rhs) + else { + irBlock { + val lhsTemp = irTemporary(lhs) + if (rhsIsNotNullable) + +irLogicalAnd(irIsNotNull(irGet(lhsTemp.symbol)), callEquals(irGet(lhsTemp.symbol), rhs)) + else { + val rhsTemp = irTemporary(rhs) + +irIfThenElse(builtIns.booleanType, irIsNull(irGet(lhsTemp.symbol)), + irIsNull(irGet(rhsTemp.symbol)), + callEquals(irGet(lhsTemp.symbol), irGet(rhsTemp.symbol)) + ) + } + } + } + } } } - 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 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 (expression.descriptor in ieee754EqualsDescriptors()) { - return if (lhs.type.isNullable() || rhs.type.isNullable()) - selectIntrinsic(context.ir.symbols.ieee754NullableEquals, lhs.type, rhs.type)!! - else - selectIntrinsic(context.ir.symbols.ieee754Equals, lhs.type, rhs.type)!! - } - - // Find a type-compatible `konan.internal.areEqualByValue` intrinsic: - selectIntrinsic(context.ir.symbols.areEqualByValue, lhs.type, rhs.type)?.let { - return it - } - - return if (lhs.isNullConst() || rhs.isNullConst()) { - // or compare by reference if left or right part is `null`: - irBuiltins.eqeqeqSymbol - } else { - // or use the general implementation: - context.ir.symbols.areEqual - } - } - - private fun selectIntrinsic(from: List, lhsType: KotlinType, rhsType: KotlinType): - IrSimpleFunctionSymbol? = from.atMostOne { - lhsType.isSubtypeOf(it.owner.valueParameters[0].type) && rhsType.isSubtypeOf(it.owner.valueParameters[1].type) - } + private fun selectIntrinsic(from: List, lhsType: KotlinType, rhsType: KotlinType, allowNullable: Boolean) = + from.atMostOne { + val leftParamType = it.owner.valueParameters[0].type + val rightParamType = it.owner.valueParameters[1].type + (lhsType.isSubtypeOf(leftParamType) || (allowNullable && lhsType.isSubtypeOf(leftParamType.makeNullable()))) + && (rhsType.isSubtypeOf(rightParamType) || (allowNullable && rhsType.isSubtypeOf(rightParamType.makeNullable()))) + } } diff --git a/backend.native/tests/serialization/deserialized_inline0.kt b/backend.native/tests/serialization/deserialized_inline0.kt index 1687a3f529c..dfec217a10c 100644 --- a/backend.native/tests/serialization/deserialized_inline0.kt +++ b/backend.native/tests/serialization/deserialized_inline0.kt @@ -21,21 +21,9 @@ fun inline_assert() { //assert(true) } -fun inline_areEqual() { - val a = 17 - val b = "some string" - println(konan.internal.areEqual(a, 17)) - println(konan.internal.areEqual(a, a)) - println(konan.internal.areEqual(17, 17)) - println(konan.internal.areEqual(b, "some string")) - println(konan.internal.areEqual("some string", b)) - println(konan.internal.areEqual(b, b)) -} - @Test fun runTest() { inline_todo() inline_assert() inline_maxof() - inline_areEqual() } diff --git a/runtime/src/main/kotlin/konan/internal/Intrinsics.kt b/runtime/src/main/kotlin/konan/internal/Intrinsics.kt index f79d0cc7ec7..a11e093b3d9 100644 --- a/runtime/src/main/kotlin/konan/internal/Intrinsics.kt +++ b/runtime/src/main/kotlin/konan/internal/Intrinsics.kt @@ -30,25 +30,6 @@ import kotlinx.cinterop.NativePtr @Intrinsic external fun ieee754Equals(first: Float, second: Float): Boolean @Intrinsic external fun ieee754Equals(first: Double, second: Double): Boolean -@Suppress("NOTHING_TO_INLINE") -inline fun ieee754NullableEquals(first: Float?, second: Float?): Boolean = when { - first == null -> second == null - second == null -> false - else -> ieee754Equals(first, second) -} - -@Suppress("NOTHING_TO_INLINE") -inline fun ieee754NullableEquals(first: Double?, second: Double?): Boolean = when { - first == null -> second == null - second == null -> false - else -> ieee754Equals(first, second) -} - @Intrinsic external fun areEqualByValue(first: NativePtr, second: NativePtr): Boolean @Intrinsic external fun areEqualByValue(first: NativePointed?, second: NativePointed?): Boolean -@Intrinsic external fun areEqualByValue(first: CPointer<*>?, second: CPointer<*>?): Boolean - -@Suppress("NOTHING_TO_INLINE") -inline fun areEqual(first: Any?, second: Any?): Boolean { - return if (first == null) second == null else first.equals(second) -} \ No newline at end of file +@Intrinsic external fun areEqualByValue(first: CPointer<*>?, second: CPointer<*>?): Boolean \ No newline at end of file