diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt index 9445a8d200f..5ccf531c3b8 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt @@ -27,6 +27,8 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) { val getPointerSize = packageScope.getContributedFunctions("getPointerSize").single() + val nullableInteropValueTypes = listOf(ValueType.C_POINTER, ValueType.NATIVE_POINTED) + private val nativePtr = packageScope.getContributedClassifier(nativePtrName) as ClassDescriptor private val nativePointed = packageScope.getContributedClassifier(nativePointedName) as ClassDescriptor diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ValueTypes.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ValueTypes.kt index 8e0d5235965..077b39fa032 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ValueTypes.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ValueTypes.kt @@ -24,7 +24,11 @@ enum class ValueType(val classFqName: FqNameUnsafe, val isNullable: Boolean = fa FLOAT(KotlinBuiltIns.FQ_NAMES._float), DOUBLE(KotlinBuiltIns.FQ_NAMES._double), - UNBOUND_CALLABLE_REFERENCE(FqNameUnsafe("konan.internal.UnboundCallableReference")) + UNBOUND_CALLABLE_REFERENCE(FqNameUnsafe("konan.internal.UnboundCallableReference")), + NATIVE_PTR(InteropBuiltIns.FqNames.nativePtr), + + NATIVE_POINTED(InteropBuiltIns.FqNames.nativePointed, isNullable = true), + C_POINTER(InteropBuiltIns.FqNames.cPointer, isNullable = true) } private fun KotlinType.isConstructedFromGivenClass(fqName: FqNameUnsafe) = diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/IrUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/IrUtils.kt index ecc7203d33e..41efb6bd32d 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/IrUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/IrUtils.kt @@ -65,6 +65,8 @@ internal fun IrMemberAccessExpression.addArguments(args: Map>) = this.addArguments(args.toMap()) +internal fun IrExpression.isNullConst() = this is IrConst<*> && this.kind == IrConstKind.Null + fun ir2string(ir: IrElement?): String = ir2stringWhole(ir).takeWhile { it != '\n' } fun ir2stringWhole(ir: IrElement?): String { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt index 7e7be223413..d6707d6af9e 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt @@ -15,7 +15,9 @@ private val valueTypes = ValueType.values().associate { ValueType.LONG -> LLVMInt64Type() ValueType.FLOAT -> LLVMFloatType() ValueType.DOUBLE -> LLVMDoubleType() - ValueType.UNBOUND_CALLABLE_REFERENCE -> int8TypePtr + + ValueType.UNBOUND_CALLABLE_REFERENCE, + ValueType.NATIVE_PTR, ValueType.NATIVE_POINTED, ValueType.C_POINTER -> int8TypePtr }!! } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt index cf619e512f5..4844d205380 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt @@ -6,8 +6,10 @@ import org.jetbrains.kotlin.backend.konan.Context import org.jetbrains.kotlin.backend.konan.ValueType import org.jetbrains.kotlin.backend.konan.descriptors.getKonanInternalClass import org.jetbrains.kotlin.backend.konan.descriptors.getKonanInternalFunctions +import org.jetbrains.kotlin.backend.konan.ir.isNullConst import org.jetbrains.kotlin.backend.konan.notNullableIsRepresentedAs import org.jetbrains.kotlin.backend.konan.isRepresentedAs +import org.jetbrains.kotlin.backend.konan.util.atMostOne import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor @@ -103,6 +105,10 @@ private class AutoboxingTransformer(val context: Context) : AbstractValueUsageTr } override fun IrExpression.useAs(type: KotlinType): IrExpression { + val interop = context.interopBuiltIns + if (this.isNullConst() && interop.nullableInteropValueTypes.any { type.isRepresentedAs(it) }) { + return IrCallImpl(startOffset, endOffset, interop.getNativeNullPtr).uncheckedCast(type) + } val actualType = when (this) { is IrCall -> this.descriptor.original.returnType ?: this.type @@ -176,6 +182,14 @@ private class AutoboxingTransformer(val context: Context) : AbstractValueUsageTr } private fun IrExpression.unbox(valueType: ValueType): IrExpression { + val unboxFunctionName = "unbox${valueType.shortName}" + + context.builtIns.getKonanInternalFunctions(unboxFunctionName).atMostOne()?.let { + return IrCallImpl(startOffset, endOffset, it).apply { + putValueArgument(0, this@unbox.uncheckedCast(it.valueParameters[0].type)) + }.uncheckedCast(this.type) + } + val boxGetter = getBoxType(valueType) .memberScope.getContributedDescriptors() .filterIsInstance() 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 3e0166a2f5b..5d1db61e7f5 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 @@ -3,6 +3,8 @@ package org.jetbrains.kotlin.backend.konan.lower import org.jetbrains.kotlin.backend.common.BodyLoweringPass import org.jetbrains.kotlin.backend.konan.Context import org.jetbrains.kotlin.backend.konan.descriptors.getKonanInternalFunctions +import org.jetbrains.kotlin.backend.konan.ir.isNullConst +import org.jetbrains.kotlin.backend.konan.util.atMostOne import org.jetbrains.kotlin.ir.descriptors.IrBuiltinOperatorDescriptor import org.jetbrains.kotlin.ir.expressions.IrBody import org.jetbrains.kotlin.ir.expressions.IrCall @@ -69,9 +71,15 @@ private class BuiltinOperatorTransformer(val context: Context) : IrElementTransf // and thus can be declared synthetically in the compiler instead of explicitly in the runtime. // Find a type-compatible `konan.internal.areEqualByValue` intrinsic: - val equals = builtIns.getKonanInternalFunctions("areEqualByValue").firstOrNull { + val equals = builtIns.getKonanInternalFunctions("areEqualByValue").atMostOne { lhs.type.isSubtypeOf(it.valueParameters[0].type) && rhs.type.isSubtypeOf(it.valueParameters[1].type) - } ?: builtIns.getKonanInternalFunctions("areEqual").single() // or use the general implementation. + } ?: if (lhs.isNullConst() || rhs.isNullConst()) { + // or compare by reference if left or right part is `null`: + irBuiltins.eqeqeq + } else { + // or use the general implementation: + builtIns.getKonanInternalFunctions("areEqual").single() + } return IrCallImpl(startOffset, endOffset, equals).apply { putValueArgument(0, lhs) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/util/util.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/util/util.kt index 9c1eb21a457..bf32b140230 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/util/util.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/util/util.kt @@ -20,3 +20,12 @@ fun nTabs(amount: Int): String { return String.format("%1$-${(amount+1)*4}s", "") } +fun Collection.atMostOne(): T? { + return when (this.size) { + 0 -> null + 1 -> this.iterator().next() + else -> throw IllegalArgumentException("Collection has more than one element.") + } +} + +inline fun Iterable.atMostOne(predicate: (T) -> Boolean): T? = this.filter(predicate).atMostOne() \ No newline at end of file diff --git a/runtime/src/main/kotlin/konan/internal/InteropBoxing.kt b/runtime/src/main/kotlin/konan/internal/InteropBoxing.kt new file mode 100644 index 00000000000..8677bd7e280 --- /dev/null +++ b/runtime/src/main/kotlin/konan/internal/InteropBoxing.kt @@ -0,0 +1,53 @@ +package konan.internal + +import kotlinx.cinterop.* + +class NativePtrBox(val value: NativePtr) { + override fun equals(other: Any?): Boolean { + if (other !is NativePtrBox) { + return false + } + + return this.value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() +} + +fun boxNativePtr(value: NativePtr) = NativePtrBox(value) + +class NativePointedBox(val value: NativePointed) { + override fun equals(other: Any?): Boolean { + if (other !is NativePointedBox) { + return false + } + + return this.value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() +} + +fun boxNativePointed(value: NativePointed?) = if (value != null) NativePointedBox(value) else null +fun unboxNativePointed(box: NativePointedBox?) = box?.value + +class CPointerBox(val value: CPointer<*>) { + override fun equals(other: Any?): Boolean { + if (other !is CPointerBox) { + return false + } + + return this.value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() +} + +fun boxCPointer(value: CPointer<*>?) = if (value != null) CPointerBox(value) else null +fun unboxCPointer(box: CPointerBox?) = box?.value \ No newline at end of file diff --git a/runtime/src/main/kotlin/konan/internal/Intrinsics.kt b/runtime/src/main/kotlin/konan/internal/Intrinsics.kt index a3509e77fc8..cf48c5599de 100644 --- a/runtime/src/main/kotlin/konan/internal/Intrinsics.kt +++ b/runtime/src/main/kotlin/konan/internal/Intrinsics.kt @@ -1,5 +1,9 @@ package konan.internal +import kotlinx.cinterop.CPointer +import kotlinx.cinterop.NativePointed +import kotlinx.cinterop.NativePtr + @Intrinsic external fun areEqualByValue(first: Boolean, second: Boolean): Boolean @Intrinsic external fun areEqualByValue(first: Char, second: Char): Boolean @Intrinsic external fun areEqualByValue(first: Byte, second: Byte): Boolean @@ -9,9 +13,9 @@ package konan.internal @Intrinsic external fun areEqualByValue(first: Float, second: Float): Boolean @Intrinsic external fun areEqualByValue(first: Double, second: Double): Boolean -// For comparing with `null`: -@Intrinsic external fun areEqualByValue(first: Nothing?, second: Any?): Boolean -@Intrinsic external fun areEqualByValue(first: Any?, second: Nothing?): Boolean +@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 inline fun areEqual(first: Any?, second: Any?): Boolean { return if (first == null) second == null else first.equals(second)