Add prelimitary support for using Objective-C types in framework API

This commit is contained in:
Svyatoslav Scherbina
2018-04-02 11:10:50 +03:00
committed by SvyatoslavScherbina
parent 80d026afa5
commit 7d7f0f46e6
12 changed files with 97 additions and 37 deletions
@@ -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
@@ -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)" +
@@ -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()
@@ -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()
@@ -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)
@@ -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,
@@ -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()
@@ -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
@@ -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
+13
View File
@@ -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;
+2 -1
View File
@@ -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) {