diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt index 4c5eb250a67..15c080fe1a8 100644 --- a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt @@ -23,9 +23,12 @@ interface ObjCClass : ObjCObject typealias ObjCObjectMeta = ObjCClass abstract class ObjCObjectBase protected constructor() : ObjCObject { - final override fun equals(other: Any?): Boolean = this.uncheckedCast().equals(other) - final override fun hashCode(): Int = ObjCHashCode(this.rawPtr) - final override fun toString(): String = ObjCToString(this.rawPtr) + final override fun equals(other: Any?): Boolean { + val thisAny: Any = this + return thisAny.equals(other) // Call it virtually because ObjCObjectBase is a fake type. + } + final override fun hashCode(): Int = ObjCHashCode(this.rawPtr()) + final override fun toString(): String = ObjCToString(this.rawPtr()) } abstract class ObjCObjectBaseMeta protected constructor() : ObjCObjectBase(), ObjCObjectMeta {} @@ -43,7 +46,11 @@ class ObjCPointerHolder(inline val rawPtr: NativePtr) { } final override fun equals(other: Any?): Boolean = - (other is ObjCPointerHolder) && ObjCEquals(this.rawPtr, other.rawPtr) + if (other is ObjCPointerHolder) { + ObjCEquals(this.rawPtr, other.rawPtr) + } else { + other == this + } final override fun hashCode(): Int = ObjCHashCode(this.rawPtr) final override fun toString(): String = ObjCToString(this.rawPtr) @@ -61,7 +68,7 @@ private fun ObjCObjectBase.superInitCheck(superInitCallResult: ObjCObject?) { if (superInitCallResult == null) throw RuntimeException("Super initialization failed") - if (superInitCallResult.rawPtr != this.rawPtr) + if (superInitCallResult.rawPtr() != this.rawPtr()) throw UnsupportedOperationException("Super initializer has replaced object") } @@ -79,18 +86,14 @@ inline fun interpretObjCPointer(rawPtr: NativePtr): T = if (raw throw NullPointerException() } -inline val ObjCObject.rawPtr: NativePtr get() = (this.uncheckedCast()).rawPtr -inline val ObjCObject?.rawPtr: NativePtr get() = if (this != null) { - (this.uncheckedCast()).rawPtr -} else { - nativeNullPtr -} +@SymbolName("Kotlin_Interop_refToObjC") +external fun ObjCObject?.rawPtr(): NativePtr @SymbolName("Kotlin_Interop_createKotlinObjectHolder") external fun createKotlinObjectHolder(any: Any?): NativePtr inline fun unwrapKotlinObjectHolder(holder: ObjCObject?): T { - return unwrapKotlinObjectHolderImpl(holder!!.rawPtr) as T + return unwrapKotlinObjectHolderImpl(holder!!.rawPtr()) as T } @PublishedApi diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCUtils.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCUtils.kt index aac60ef47ad..cae9babcfbf 100644 --- a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCUtils.kt +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCUtils.kt @@ -31,7 +31,7 @@ fun ObjCObject.reinterpret() = this.uncheckedCast() // TODO: null checks var ObjCObjectVar.value: T get() = interpretObjCPointerOrNull(nativeMemUtils.getNativePtr(this)).uncheckedCast() - set(value) = nativeMemUtils.putNativePtr(this, value.rawPtr) + set(value) = nativeMemUtils.putNativePtr(this, value.rawPtr()) /** * Makes Kotlin method in Objective-C class accessible through Objective-C dispatch diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/Mappings.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/Mappings.kt index 3ab4f3678f9..a91f1bc0335 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/Mappings.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/Mappings.kt @@ -192,7 +192,7 @@ sealed class TypeInfo { } class ObjCPointerInfo(val kotlinType: KotlinType, val type: ObjCPointer) : TypeInfo() { - override fun argToBridged(expr: String) = "$expr.rawPtr" + override fun argToBridged(expr: String) = "$expr.rawPtr()" override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope, nativeBacked: NativeBacked) = "interpretObjCPointerOrNull<${kotlinType.render(scope)}>($expr)" + diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt index ebc81d9c9b0..fe76b0a5a3e 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt @@ -112,13 +112,13 @@ class ObjCMethodStub(stubGenerator: StubGenerator, if (method.nsConsumesSelf) { // TODO: do this later due to possible exceptions - bodyGenerator.out("objc_retain($kniReceiverParameter.rawPtr)") + bodyGenerator.out("objc_retain($kniReceiverParameter.rawPtr())") } kotlinObjCBridgeParameters.add(kniReceiverParameter to KotlinTypes.objCObject.type) nativeBridgeArguments.add( TypedKotlinValue(voidPtr, - "getReceiverOrSuper($kniReceiverParameter.rawPtr, $kniSuperClassParameter)")) + "getReceiverOrSuper($kniReceiverParameter.rawPtr(), $kniSuperClassParameter)")) val kotlinParameterNames = method.getKotlinParameterNames() 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 d8983d51199..33c6d1b4de8 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 @@ -172,10 +172,9 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) { val getObjCClass = packageScope.getContributedFunctions("getObjCClass").single() - val objCObjectRawPtr = packageScope.getContributedVariables("rawPtr").single { + val objCObjectRawPtr = packageScope.getContributedFunctions("rawPtr").single { val extensionReceiverType = it.extensionReceiverParameter?.type - extensionReceiverType != null && !extensionReceiverType.isMarkedNullable && - TypeUtils.getClassDescriptor(extensionReceiverType) == objCObject + extensionReceiverType != null && TypeUtils.getClassDescriptor(extensionReceiverType) == objCObject } val getObjCReceiverOrSuper = packageScope.getContributedFunctions("getReceiverOrSuper").single() 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 e3b871e29ff..4ad8f2b879d 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 @@ -117,7 +117,7 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable): Sym symbolTable.referenceSimpleFunction(context.interopBuiltIns.objCObjectSuperInitCheck) val interopObjCObjectRawValueGetter = - symbolTable.referenceSimpleFunction(context.interopBuiltIns.objCObjectRawPtr.getter!!) + symbolTable.referenceSimpleFunction(context.interopBuiltIns.objCObjectRawPtr) val interopInvokeImpls = context.interopBuiltIns.invokeImpls.mapValues { (_, function) -> symbolTable.referenceSimpleFunction(function) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt index cd80e20f75f..f8b66ac8bca 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt @@ -440,6 +440,15 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) { null } + val objCExportEnabled = if (context.config.produce.isNativeBinary) { + // Note: this defines the global declared in runtime (if any). + staticData.placeGlobal("objCExportEnabled", Int8(0), isExported = true).also { + it.setConstant(true) + } + } else { + null + } + val tlsMode by lazy { when (target) { KonanTarget.WASM32, diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt index dc7adff6a2e..22bf044d22d 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt @@ -218,6 +218,7 @@ internal class ObjCExportCodeGenerator( emitSortedAdapters(placedInterfaceAdapters, "Kotlin_ObjCExport_sortedProtocolAdapters") context.llvm.kObjectReservedTailSize!!.setInitializer(Int32(runtime.pointerSize)) + context.llvm.objCExportEnabled!!.setInitializer(Int8(1)) dataGenerator.finishModule() // TODO: move to appropriate place. } @@ -445,6 +446,11 @@ private fun ObjCExportCodeGenerator.emitSpecialClassesConvertions() { emitBoxConverter(it) } + setObjCExportTypeInfo( + context.interopBuiltIns.objCPointerHolder, + constPointer(codegen.llvmFunction(context.ir.symbols.objCPointerHolderValueGetter.owner)) + ) + emitFunctionConverters() emitKotlinFunctionAdaptersToBlock() diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt index 1da3b7ea222..09545c9a653 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt @@ -92,8 +92,11 @@ internal class ObjCExportHeaderGenerator(val context: Context) { private val kotlinAnyName = namer.kotlinAnyName private val stubs = mutableListOf() - private val classToName = mutableMapOf() - private val interfaceToName = mutableMapOf() + private val classOrInterfaceToName = mutableMapOf() + + internal val classForwardDeclarations = mutableSetOf() + internal val protocolForwardDeclarations = mutableSetOf() + private val extensions = mutableMapOf>() val extraClassesToTranslate = mutableSetOf() @@ -154,17 +157,11 @@ internal class ObjCExportHeaderGenerator(val context: Context) { } } - fun translateClassName(descriptor: ClassDescriptor): String { - val descriptorToName = if (descriptor.isInterface) interfaceToName else classToName + fun translateClassName(descriptor: ClassDescriptor): String = classOrInterfaceToName.getOrPut(descriptor) { + assert(mapper.shouldBeExposed(descriptor)) + val forwardDeclarations = if (descriptor.isInterface) protocolForwardDeclarations else classForwardDeclarations - return descriptorToName.getOrPut(descriptor) { - if (!mapper.shouldBeExposed(descriptor)) { - context.reportCompilationError("Can't produce ${descriptor.fqNameSafe.asString()} to framework API") - throw KonanCompilationException() - } - - namer.getClassOrProtocolName(descriptor) - } + namer.getClassOrProtocolName(descriptor).also { forwardDeclarations += it } } private fun translateInterface(descriptor: ClassDescriptor) { @@ -555,13 +552,13 @@ internal class ObjCExportHeaderGenerator(val context: Context) { add("#import ") add("") - if (classToName.isNotEmpty()) { - add("@class ${classToName.values.joinToString()};") + if (classForwardDeclarations.isNotEmpty()) { + add("@class ${classForwardDeclarations.joinToString()};") add("") } - if (interfaceToName.isNotEmpty()) { - add("@protocol ${interfaceToName.values.joinToString()};") + if (protocolForwardDeclarations.isNotEmpty()) { + add("@protocol ${protocolForwardDeclarations.joinToString()};") add("") } @@ -790,6 +787,10 @@ private fun ObjCExportHeaderGenerator.mapReferenceTypeIgnoringNullability( return ObjCIdType } + if (classDescriptor.defaultType.isObjCObjectType()) { + return mapObjCObjectReferenceTypeIgnoringNullability(classDescriptor) + } + scheduleClassToBeGenerated(classDescriptor) return if (classDescriptor.isInterface) { @@ -799,6 +800,32 @@ private fun ObjCExportHeaderGenerator.mapReferenceTypeIgnoringNullability( } } +private tailrec fun ObjCExportHeaderGenerator.mapObjCObjectReferenceTypeIgnoringNullability( + descriptor: ClassDescriptor +): ObjCNonNullReferenceType { + // TODO: more precise types can be used. + + if (descriptor.isObjCMetaClass()) return ObjCIdType + + if (descriptor.isExternalObjCClass()) { + return if (descriptor.isInterface) { + val name = descriptor.name.asString().removeSuffix("Protocol") + protocolForwardDeclarations += name + ObjCProtocolType(name) + } else { + val name = descriptor.name.asString() + classForwardDeclarations += name + ObjCClassType(name) + } + } + + if (descriptor.isKotlinObjCClass()) { + return mapObjCObjectReferenceTypeIgnoringNullability(descriptor.getSuperClassOrAny()) + } + + return ObjCIdType +} + private fun ObjCExportHeaderGenerator.scheduleClassToBeGenerated(classDescriptor: ClassDescriptor) { if (classDescriptor !in generatedClasses) { extraClassesToTranslate += classDescriptor diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt index eb0e048bcf9..41561dc9799 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt @@ -49,6 +49,8 @@ internal fun ObjCExportMapper.getClassIfCategory(descriptor: CallableMemberDescr val extensionReceiverType = descriptor.extensionReceiverParameter?.type ?: return null + if (extensionReceiverType.isObjCObjectType()) return null + val erasedClass = extensionReceiverType.getErasedTypeClass() return if (this.isRepresentedAsObjCInterface(erasedClass)) { erasedClass diff --git a/runtime/src/main/cpp/ObjCExport.mm b/runtime/src/main/cpp/ObjCExport.mm index 1d49959a661..6a59a1ab3eb 100644 --- a/runtime/src/main/cpp/ObjCExport.mm +++ b/runtime/src/main/cpp/ObjCExport.mm @@ -36,6 +36,9 @@ #import "Utils.h" #import "Exceptions.h" +// Note: defined by a compiler-generated bitcode. +extern "C" const uint8_t objCExportEnabled; + struct ObjCToKotlinMethodAdapter { const char* selector; const char* encoding; @@ -513,6 +516,16 @@ extern "C" id Kotlin_ObjCExport_refToObjC(ObjHeader* obj) { return Kotlin_ObjCExport_refToObjC_slowpath(obj); } +extern "C" ALWAYS_INLINE id Kotlin_Interop_refToObjC(ObjHeader* obj) { + if (obj == nullptr) { + return nullptr; + } else if (!objCExportEnabled || obj->type_info() == theObjCPointerHolderTypeInfo) { + return *reinterpret_cast(obj + 1); // First field. + } else { + return Kotlin_ObjCExport_refToObjC(obj); + } +} + extern "C" OBJ_GETTER(Kotlin_ObjCExport_refFromObjC, id obj) { if (obj == nullptr) RETURN_OBJ(nullptr); id convertible = (id)obj; diff --git a/runtime/src/main/cpp/ObjCInteropUtils.mm b/runtime/src/main/cpp/ObjCInteropUtils.mm index cf6cd3a238b..c1d0f7decb3 100644 --- a/runtime/src/main/cpp/ObjCInteropUtils.mm +++ b/runtime/src/main/cpp/ObjCInteropUtils.mm @@ -78,7 +78,8 @@ OBJ_GETTER(Kotlin_Interop_ObjCToString, id ptr) { } KInt Kotlin_Interop_ObjCHashCode(id ptr) { - return (KInt) ptr.hash; + KLong hash = ptr.hash; + return (KInt)(hash ^ (hash >> 32)); } KBoolean Kotlin_Interop_ObjCEquals(id ptr, id otherPtr) {