Add prelimitary support for using Objective-C types in framework API
This commit is contained in:
committed by
SvyatoslavScherbina
parent
80d026afa5
commit
7d7f0f46e6
@@ -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<ObjCPointerHolder>().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 <T : ObjCObject> interpretObjCPointer(rawPtr: NativePtr): T = if (raw
|
||||
throw NullPointerException()
|
||||
}
|
||||
|
||||
inline val ObjCObject.rawPtr: NativePtr get() = (this.uncheckedCast<ObjCPointerHolder>()).rawPtr
|
||||
inline val ObjCObject?.rawPtr: NativePtr get() = if (this != null) {
|
||||
(this.uncheckedCast<ObjCPointerHolder>()).rawPtr
|
||||
} else {
|
||||
nativeNullPtr
|
||||
}
|
||||
@SymbolName("Kotlin_Interop_refToObjC")
|
||||
external fun ObjCObject?.rawPtr(): NativePtr
|
||||
|
||||
@SymbolName("Kotlin_Interop_createKotlinObjectHolder")
|
||||
external fun createKotlinObjectHolder(any: Any?): NativePtr
|
||||
|
||||
inline fun <reified T : Any> unwrapKotlinObjectHolder(holder: ObjCObject?): T {
|
||||
return unwrapKotlinObjectHolderImpl(holder!!.rawPtr) as T
|
||||
return unwrapKotlinObjectHolderImpl(holder!!.rawPtr()) as T
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
|
||||
@@ -31,7 +31,7 @@ fun <T : ObjCObject> ObjCObject.reinterpret() = this.uncheckedCast<T>()
|
||||
// TODO: null checks
|
||||
var <T : ObjCObject?> ObjCObjectVar<T>.value: T
|
||||
get() = interpretObjCPointerOrNull<T>(nativeMemUtils.getNativePtr(this)).uncheckedCast<T>()
|
||||
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
|
||||
|
||||
+1
-1
@@ -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)" +
|
||||
|
||||
+2
-2
@@ -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()
|
||||
|
||||
|
||||
+2
-3
@@ -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()
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
+9
@@ -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,
|
||||
|
||||
+6
@@ -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()
|
||||
|
||||
+43
-16
@@ -92,8 +92,11 @@ internal class ObjCExportHeaderGenerator(val context: Context) {
|
||||
private val kotlinAnyName = namer.kotlinAnyName
|
||||
|
||||
private val stubs = mutableListOf<Stub>()
|
||||
private val classToName = mutableMapOf<ClassDescriptor, String>()
|
||||
private val interfaceToName = mutableMapOf<ClassDescriptor, String>()
|
||||
private val classOrInterfaceToName = mutableMapOf<ClassDescriptor, String>()
|
||||
|
||||
internal val classForwardDeclarations = mutableSetOf<String>()
|
||||
internal val protocolForwardDeclarations = mutableSetOf<String>()
|
||||
|
||||
private val extensions = mutableMapOf<ClassDescriptor, MutableList<CallableMemberDescriptor>>()
|
||||
val extraClassesToTranslate = mutableSetOf<ClassDescriptor>()
|
||||
|
||||
@@ -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 <Foundation/Foundation.h>")
|
||||
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
|
||||
|
||||
+2
@@ -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
|
||||
|
||||
@@ -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<id*>(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<ConvertibleToKotlin>)obj;
|
||||
|
||||
@@ -78,7 +78,8 @@ OBJ_GETTER(Kotlin_Interop_ObjCToString, id <NSObject> ptr) {
|
||||
}
|
||||
|
||||
KInt Kotlin_Interop_ObjCHashCode(id <NSObject> ptr) {
|
||||
return (KInt) ptr.hash;
|
||||
KLong hash = ptr.hash;
|
||||
return (KInt)(hash ^ (hash >> 32));
|
||||
}
|
||||
|
||||
KBoolean Kotlin_Interop_ObjCEquals(id <NSObject> ptr, id otherPtr) {
|
||||
|
||||
Reference in New Issue
Block a user