Use compiler-generated C stubs to call Objective-C methods

Also
*   Use compiler-generated C stubs when overriding all Objective-C methods
*   Support block types in C stubs generator
This commit is contained in:
Svyatoslav Scherbina
2019-03-13 11:13:28 +03:00
committed by SvyatoslavScherbina
parent 68efc823f1
commit 54881b421c
24 changed files with 963 additions and 484 deletions
@@ -42,7 +42,7 @@ fun optional(): Nothing = throw RuntimeException("Do not call me!!!")
@Deprecated(
"Add @OverrideInit to constructor to make it override Objective-C initializer",
level = DeprecationLevel.WARNING
level = DeprecationLevel.ERROR
)
@TypedIntrinsic(IntrinsicType.OBJC_INIT_BY)
external fun <T : ObjCObjectBase> T.initBy(constructorCall: T): T
@@ -56,8 +56,7 @@ private fun ObjCObjectBase.superInitCheck(superInitCallResult: ObjCObject?) {
throw UnsupportedOperationException("Super initializer has replaced object")
}
@Deprecated("Use plain Kotlin cast", ReplaceWith("this as T"), DeprecationLevel.WARNING)
fun <T : Any?> Any?.uncheckedCast(): T = @Suppress("UNCHECKED_CAST") (this as T) // TODO: make private
internal fun <T : Any?> Any?.uncheckedCast(): T = @Suppress("UNCHECKED_CAST") (this as T)
@SymbolName("Kotlin_Interop_refFromObjC")
external fun <T> interpretObjCPointerOrNull(objcPtr: NativePtr): T?
@@ -93,8 +92,9 @@ var <T : Any?> ObjCNotImplementedVar<T>.value: T
typealias ObjCStringVarOf<T> = ObjCNotImplementedVar<T>
typealias ObjCBlockVar<T> = ObjCNotImplementedVar<T>
@TypedIntrinsic(IntrinsicType.OBJC_GET_RECEIVER_OR_SUPER)
external fun getReceiverOrSuper(receiver: NativePtr, superClass: NativePtr): COpaquePointer?
@TypedIntrinsic(IntrinsicType.OBJC_CREATE_SUPER_STRUCT)
@PublishedApi
internal external fun createObjCSuperStruct(receiver: NativePtr, superClass: NativePtr): NativePtr
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
@@ -102,11 +102,7 @@ annotation class ExternalObjCClass(val protocolGetter: String = "", val binaryNa
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.BINARY)
annotation class ObjCMethod(val selector: String, val bridge: String)
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.BINARY)
annotation class ObjCBridge(val selector: String, val encoding: String, val imp: String = "")
annotation class ObjCMethod(val selector: String, val encoding: String, val isStret: Boolean = false)
@Target(AnnotationTarget.CONSTRUCTOR)
@Retention(AnnotationRetention.BINARY)
@@ -114,7 +110,7 @@ annotation class ObjCConstructor(val initSelector: String, val designated: Boole
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.BINARY)
annotation class ObjCFactory(val bridge: String)
annotation class ObjCFactory(val selector: String, val encoding: String, val isStret: Boolean = false)
@Target(AnnotationTarget.FILE)
@Retention(AnnotationRetention.BINARY)
@@ -158,10 +154,13 @@ private fun allocObjCObject(clazz: NativePtr): NativePtr {
@kotlin.native.internal.ExportForCompiler
private external fun <T : ObjCObject> getObjCClass(): NativePtr
@PublishedApi
@TypedIntrinsic(IntrinsicType.OBJC_GET_MESSENGER)
external fun getMessenger(superClass: NativePtr): COpaquePointer?
internal external fun getMessenger(superClass: NativePtr): COpaquePointer?
@PublishedApi
@TypedIntrinsic(IntrinsicType.OBJC_GET_MESSENGER_STRET)
external fun getMessengerStret(superClass: NativePtr): COpaquePointer?
internal external fun getMessengerStret(superClass: NativePtr): COpaquePointer?
internal class ObjCWeakReferenceImpl : kotlin.native.ref.WeakReferenceImpl() {
@@ -180,14 +179,18 @@ private external fun ObjCWeakReferenceImpl.init(objcPtr: NativePtr)
// Konan runtme:
@Deprecated("Use plain Kotlin cast of String to NSString", level = DeprecationLevel.WARNING)
@Deprecated("Use plain Kotlin cast of String to NSString", level = DeprecationLevel.ERROR)
@SymbolName("Kotlin_Interop_CreateNSStringFromKString")
external fun CreateNSStringFromKString(str: String?): NativePtr
@Deprecated("Use plain Kotlin cast of NSString to String", level = DeprecationLevel.WARNING)
@Deprecated("Use plain Kotlin cast of NSString to String", level = DeprecationLevel.ERROR)
@SymbolName("Kotlin_Interop_CreateKStringFromNSString")
external fun CreateKStringFromNSString(ptr: NativePtr): String?
@PublishedApi
@SymbolName("Kotlin_Interop_CreateObjCObjectHolder")
internal external fun createObjCObjectHolder(ptr: NativePtr): Any?
// Objective-C runtime:
@SymbolName("objc_retainAutoreleaseReturnValue")
@@ -25,7 +25,7 @@ inline fun <R> autoreleasepool(block: () -> R): R {
}
}
@Deprecated("Use plain Kotlin cast", ReplaceWith("this as T"), DeprecationLevel.WARNING)
@Deprecated("Use plain Kotlin cast", ReplaceWith("this as T"), DeprecationLevel.ERROR)
fun <T : ObjCObject> ObjCObject.reinterpret() = @Suppress("DEPRECATION") this.uncheckedCast<T>()
// TODO: null checks
@@ -19,7 +19,6 @@ package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.native.interop.gen.jvm.StubGenerator
import org.jetbrains.kotlin.native.interop.indexer.*
import org.jetbrains.kotlin.utils.addIfNotNull
private fun ObjCMethod.getKotlinParameterNames(forConstructorOrFactory: Boolean = false): List<String> {
val selectorParts = this.selector.split(":")
@@ -74,12 +73,11 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
override fun generate(context: StubGenerationContext): Sequence<String> =
if (context.nativeBridges.isSupported(this)) {
val result = mutableListOf<String>()
result.add(objCMethodAnnotation)
if (method.nsConsumesSelf) result.add("@CCall.ConsumesReceiver")
if (method.nsReturnsRetained) result.add("@CCall.ReturnsRetained")
result.addAll(objCMethodAnnotations)
result.add(header)
if (method.isInit) {
// TODO: generate only constructor/factory in this case.
val kotlinScope = stubGenerator.kotlinFile
val newParameterNames = method.getKotlinParameterNames(forConstructorOrFactory = true)
@@ -132,26 +130,13 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
}
result.add("")
result.add("@ObjCFactory".applyToStrings(bridgeName))
result.addAll(buildObjCMethodAnnotations("@ObjCFactory"))
result.add("external fun <T : $className> $receiver.create($parameters): $returnType")
}
is ObjCProtocol -> {} // Nothing to do.
}
}
val objCBridge = "@ObjCBridge".applyToStrings(
*mutableListOf<String>().apply {
add(method.selector)
add(method.encoding)
addIfNotNull(implementationTemplate)
}.toTypedArray()
)
context.addTopLevelDeclaration(
listOf("@kotlin.native.internal.ExportForCompiler", objCBridge)
+ block(bridgeHeader, bodyLines)
)
result.asSequence()
} else {
sequenceOf(
@@ -160,48 +145,18 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
)
}
private val bodyLines: List<String>
private val kotlinParameters: List<KotlinParameter>
private val kotlinReturnType: String
private val header: String
private val implementationTemplate: String?
internal val bridgeName: String
private val bridgeHeader: String
internal val objCMethodAnnotation: String
internal val objCMethodAnnotations: List<String>
private val isStret: Boolean
init {
val bodyGenerator = KotlinCodeBuilder(scope = stubGenerator.kotlinFile)
kotlinParameters = mutableListOf()
val kotlinObjCBridgeParameters = mutableListOf<KotlinParameter>()
val nativeBridgeArguments = mutableListOf<TypedKotlinValue>()
val kniReceiverParameter = "kniR"
val kniSuperClassParameter = "kniSC"
val kniSelectorParameter = "kniSEL"
val voidPtr = PointerType(VoidType)
val returnType = method.getReturnType(container.classOrProtocol)
val messengerGetter =
if (returnType.isStret(stubGenerator.configuration.target)) "getMessengerStret" else "getMessenger"
kotlinObjCBridgeParameters.add(KotlinParameter(kniSuperClassParameter, KotlinTypes.nativePtr))
nativeBridgeArguments.add(TypedKotlinValue(voidPtr, "$messengerGetter($kniSuperClassParameter)"))
if (method.nsConsumesSelf) {
// TODO: do this later due to possible exceptions
bodyGenerator.out("objc_retain($kniReceiverParameter)")
}
kotlinObjCBridgeParameters.add(KotlinParameter(kniReceiverParameter, KotlinTypes.nativePtr))
nativeBridgeArguments.add(
TypedKotlinValue(voidPtr,
"getReceiverOrSuper($kniReceiverParameter, $kniSuperClassParameter)"))
kotlinObjCBridgeParameters.add(KotlinParameter(kniSelectorParameter, KotlinTypes.cOpaquePointer))
nativeBridgeArguments.add(TypedKotlinValue(voidPtr, kniSelectorParameter))
isStret = returnType.isStret(stubGenerator.configuration.target)
val kotlinParameterNames = method.getKotlinParameterNames()
@@ -211,9 +166,6 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
val kotlinType = stubGenerator.mirror(it.type).argType
val annotatedName = if (it.nsConsumed) "@CCall.Consumed $name" else name
kotlinParameters.add(KotlinParameter(annotatedName, kotlinType))
kotlinObjCBridgeParameters.add(KotlinParameter(name, kotlinType))
nativeBridgeArguments.add(TypedKotlinValue(it.type, name.asSimpleName()))
}
this.kotlinReturnType = if (returnType.unwrapTypedefs() is VoidType) {
@@ -222,55 +174,7 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
stubGenerator.mirror(returnType).argType
}.render(stubGenerator.kotlinFile)
val result = stubGenerator.mappingBridgeGenerator.kotlinToNative(
bodyGenerator,
this@ObjCMethodStub,
returnType,
nativeBridgeArguments,
independent = when (container) {
is ObjCClassOrProtocol -> true // Every proper instance has this method in its method table.
is ObjCCategory -> false // Method is contributed by native dependency.
}
) { nativeValues ->
val messengerParameterTypes = mutableListOf<String>()
messengerParameterTypes.add("void*")
messengerParameterTypes.add("SEL")
method.parameters.forEach {
messengerParameterTypes.add(it.getTypeStringRepresentation())
}
val messengerReturnType = returnType.getStringRepresentation()
val messengerType =
"$messengerReturnType (* ${method.cAttributes}) (${messengerParameterTypes.joinToString()})"
val messenger = "(($messengerType) ${nativeValues.first()})"
val messengerArguments = nativeValues.drop(1)
"$messenger(${messengerArguments.joinToString()})"
}
bodyGenerator.returnResult(result)
this.implementationTemplate = if (needsImplementationTemplate()) {
genImplementationTemplate(stubGenerator)
} else {
null
}
this.bodyLines = bodyGenerator.build()
bridgeName = "objcKniBridge${stubGenerator.nextUniqueId()}"
objCMethodAnnotation = "@ObjCMethod".applyToStrings(method.selector, bridgeName)
this.bridgeHeader = buildString {
append("internal fun ")
append(bridgeName)
append('(')
kotlinObjCBridgeParameters.renderParametersTo(stubGenerator.kotlinFile, this)
append("): ")
append(kotlinReturnType)
}
objCMethodAnnotations = buildObjCMethodAnnotations("@ObjCMethod")
val joinedKotlinParameters = kotlinParameters.renderParameters(stubGenerator.kotlinFile)
@@ -302,24 +206,26 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
}
}
private fun needsImplementationTemplate(): Boolean =
method.getReturnType(container.classOrProtocol).isBlockPointer() ||
method.parameters.any { it.type.isBlockPointer() }
private fun buildObjCMethodAnnotations(main: String) = listOfNotNull(
buildObjCMethodAnnotation(main),
"@CCall.ConsumesReceiver".takeIf { method.nsConsumesSelf },
"@CCall.ReturnsRetained".takeIf { method.nsReturnsRetained }
)
private fun genImplementationTemplate(stubGenerator: StubGenerator): String = when (container) {
is ObjCClassOrProtocol -> {
val codeBuilder = NativeCodeBuilder(stubGenerator.simpleBridgeGenerator.topLevelNativeScope)
val result = codeBuilder.genMethodImp(stubGenerator, this, method, container)
stubGenerator.simpleBridgeGenerator.insertNativeBridge(this, emptyList(), codeBuilder.lines)
result
private fun buildObjCMethodAnnotation(annotation: String) = buildString {
append(annotation)
append('(')
append(method.selector.quoteAsKotlinLiteral())
append(", ")
append(method.encoding.quoteAsKotlinLiteral())
if (isStret) {
append(", ")
append("true")
}
is ObjCCategory -> ""
append(')')
}
}
private fun Type.isBlockPointer() = this.unwrapTypedefs() is ObjCBlockPointer
private fun deprecatedInit(className: String, initParameterNames: List<String>, factory: Boolean): String {
val replacement = if (factory) "$className.create" else className
val replacementKind = if (factory) "factory method" else "constructor"
@@ -720,11 +626,11 @@ class ObjCPropertyStub(
}
val result = mutableListOf(
"$modifiers$kind $receiver${property.name.asSimpleName()}: $kotlinType",
" ${getterStub.objCMethodAnnotation} external get"
" ${getterStub.objCMethodAnnotations.joinToString(" ")} external get"
)
property.setter?.let {
result.add(" ${setterStub!!.objCMethodAnnotation} external set")
result.add(" ${setterStub!!.objCMethodAnnotations.joinToString(" ")} external set")
}
return result.asSequence()
@@ -741,85 +647,6 @@ fun ObjCClassOrProtocol.kotlinClassName(isMeta: Boolean): String {
return if (isMeta) "${baseClassName}Meta" else baseClassName
}
private fun Parameter.getTypeStringRepresentation() =
(if (this.nsConsumed) "__attribute__((ns_consumed)) " else "") + type.getStringRepresentation()
private fun ObjCMethod.getSelfTypeStringRepresentation() = if (this.nsConsumesSelf) {
"__attribute__((ns_consumed)) id"
} else {
"id"
}
val ObjCMethod.cAttributes get() = if (this.nsReturnsRetained) {
"__attribute__((ns_returns_retained)) "
} else {
""
}
private fun NativeCodeBuilder.genMethodImp(
stubGenerator: StubGenerator,
nativeBacked: NativeBacked,
method: ObjCMethod,
container: ObjCClassOrProtocol
): String {
val returnType = method.getReturnType(container)
val cReturnType = returnType.getStringRepresentation()
val bridgeArguments = mutableListOf<TypedNativeValue>()
val parameters = mutableListOf<Pair<String, String>>()
parameters.add("self" to method.getSelfTypeStringRepresentation())
val receiverType = ObjCIdType(ObjCPointer.Nullability.NonNull, protocols = emptyList())
bridgeArguments.add(TypedNativeValue(receiverType, "self"))
parameters.add("_cmd" to "SEL")
method.parameters.forEachIndexed { index, parameter ->
val name = "p$index"
parameters.add(name to parameter.getTypeStringRepresentation())
bridgeArguments.add(TypedNativeValue(parameter.type, name))
}
val functionName = "knimi_" + stubGenerator.pkgName.replace('.', '_') + stubGenerator.nextUniqueId()
val functionAttr = method.cAttributes
out("$cReturnType $functionName(${parameters.joinToString { it.second + " " + it.first }}) $functionAttr{")
val callExpr = stubGenerator.mappingBridgeGenerator.nativeToKotlin(
this,
nativeBacked,
returnType,
bridgeArguments
) { kotlinValues ->
val kotlinReceiverType = stubGenerator.declarationMapper
.getKotlinClassFor(container, isMeta = method.isClass)
.type.render(stubGenerator.kotlinFile)
val kotlinRawReceiver = kotlinValues.first()
val kotlinReceiver = "$kotlinRawReceiver.uncheckedCast<$kotlinReceiverType>()"
val namedArguments = kotlinValues.drop(1).zip(method.getKotlinParameterNames()) { value, name ->
"${name.asSimpleName()} = $value"
}
"${kotlinReceiver}.${method.kotlinName.asSimpleName()}(${namedArguments.joinToString()})"
}
if (returnType.unwrapTypedefs() is VoidType) {
out(" $callExpr;")
} else {
out(" return $callExpr;")
}
out("}")
return functionName
}
private fun genProtocolGetter(
stubGenerator: StubGenerator,
nativeBacked: NativeBacked,