Use compiler-generated C stubs when overriding some Objective-C methods (#2697)
* Add tests to interop_objc_smoke * Use compiler-generated C stubs when overriding some Objective-C methods
This commit is contained in:
committed by
Nikolay Igotti
parent
0c90464ac4
commit
4d6614e674
@@ -106,7 +106,7 @@ 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 ObjCBridge(val selector: String, val encoding: String, val imp: String = "")
|
||||
|
||||
@Target(AnnotationTarget.CONSTRUCTOR)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
|
||||
@@ -14,4 +14,16 @@ public annotation class CCall(val id: String) {
|
||||
@Target(AnnotationTarget.VALUE_PARAMETER)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class WCString
|
||||
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class ReturnsRetained
|
||||
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class ConsumesReceiver
|
||||
|
||||
@Target(AnnotationTarget.VALUE_PARAMETER)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class Consumed
|
||||
}
|
||||
|
||||
+27
-5
@@ -19,6 +19,7 @@ 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,6 +75,8 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
|
||||
if (context.nativeBridges.isSupported(this)) {
|
||||
val result = mutableListOf<String>()
|
||||
result.add("@ObjCMethod".applyToStrings(method.selector, bridgeName))
|
||||
if (method.nsConsumesSelf) result.add("@CCall.ConsumesReceiver")
|
||||
if (method.nsReturnsRetained) result.add("@CCall.ReturnsRetained")
|
||||
result.add(header)
|
||||
|
||||
if (method.isInit) {
|
||||
@@ -136,9 +139,16 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
|
||||
}
|
||||
}
|
||||
|
||||
val objCBridge = "@ObjCBridge".applyToStrings(
|
||||
*mutableListOf<String>().apply {
|
||||
add(method.selector)
|
||||
add(method.encoding)
|
||||
addIfNotNull(implementationTemplate)
|
||||
}.toTypedArray()
|
||||
)
|
||||
|
||||
context.addTopLevelDeclaration(
|
||||
listOf("@kotlin.native.internal.ExportForCompiler",
|
||||
"@ObjCBridge".applyToStrings(method.selector, method.encoding, implementationTemplate))
|
||||
listOf("@kotlin.native.internal.ExportForCompiler", objCBridge)
|
||||
+ block(bridgeHeader, bodyLines)
|
||||
)
|
||||
|
||||
@@ -154,7 +164,7 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
|
||||
private val kotlinParameters: List<KotlinParameter>
|
||||
private val kotlinReturnType: String
|
||||
private val header: String
|
||||
private val implementationTemplate: String
|
||||
private val implementationTemplate: String?
|
||||
internal val bridgeName: String
|
||||
private val bridgeHeader: String
|
||||
|
||||
@@ -194,7 +204,8 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
|
||||
val name = kotlinParameterNames[index]
|
||||
|
||||
val kotlinType = stubGenerator.mirror(it.type).argType
|
||||
kotlinParameters.add(KotlinParameter(name, kotlinType))
|
||||
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()))
|
||||
@@ -233,7 +244,12 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
|
||||
}
|
||||
bodyGenerator.returnResult(result)
|
||||
|
||||
this.implementationTemplate = genImplementationTemplate(stubGenerator)
|
||||
this.implementationTemplate = if (needsImplementationTemplate()) {
|
||||
genImplementationTemplate(stubGenerator)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
this.bodyLines = bodyGenerator.build()
|
||||
|
||||
bridgeName = "objcKniBridge${stubGenerator.nextUniqueId()}"
|
||||
@@ -277,6 +293,10 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
|
||||
}
|
||||
}
|
||||
|
||||
private fun needsImplementationTemplate(): Boolean =
|
||||
method.getReturnType(container.classOrProtocol).isBlockPointer() ||
|
||||
method.parameters.any { it.type.isBlockPointer() }
|
||||
|
||||
private fun genImplementationTemplate(stubGenerator: StubGenerator): String = when (container) {
|
||||
is ObjCClassOrProtocol -> {
|
||||
val codeBuilder = NativeCodeBuilder(stubGenerator.simpleBridgeGenerator.topLevelNativeScope)
|
||||
@@ -289,6 +309,8 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
+2
-2
@@ -95,7 +95,7 @@ fun IrClass.isKotlinObjCClass(): Boolean = this.isObjCClass() && !this.isExterna
|
||||
data class ObjCMethodInfo(val bridge: FunctionDescriptor,
|
||||
val selector: String,
|
||||
val encoding: String,
|
||||
val imp: String)
|
||||
val imp: String?)
|
||||
|
||||
private fun CallableDescriptor.getBridgeAnnotation() =
|
||||
this.annotations.findAnnotation(objCBridgeFqName)
|
||||
@@ -121,7 +121,7 @@ private fun objCMethodInfoByBridge(packageView: PackageViewDescriptor, bridgeNam
|
||||
bridge = bridge,
|
||||
selector = bridgeAnnotation.getStringValue("selector"),
|
||||
encoding = bridgeAnnotation.getStringValue("encoding"),
|
||||
imp = bridgeAnnotation.getStringValue("imp")
|
||||
imp = bridgeAnnotation.getStringValueOrNull("imp")
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+133
-35
@@ -93,7 +93,7 @@ internal fun KotlinStubs.generateCCall(expression: IrCall, builder: IrBuilderWit
|
||||
|
||||
val cFunctionBuilder = callBuilder.cFunctionBuilder
|
||||
|
||||
val callee = expression.symbol.owner
|
||||
val callee = expression.symbol.owner as IrSimpleFunction
|
||||
|
||||
// TODO: consider computing all arguments before converting.
|
||||
|
||||
@@ -148,9 +148,9 @@ internal fun KotlinStubs.generateCCall(expression: IrCall, builder: IrBuilderWit
|
||||
|
||||
val returnValuePassing = if (isInvoke) {
|
||||
val returnType = expression.getTypeArgument(expression.typeArgumentsCount - 1)!!
|
||||
mapReturnType(returnType, TypeLocation.FunctionCallResult(expression))
|
||||
mapReturnType(returnType, TypeLocation.FunctionCallResult(expression), signature = null)
|
||||
} else {
|
||||
mapReturnType(callee.returnType, TypeLocation.FunctionCallResult(expression))
|
||||
mapReturnType(callee.returnType, TypeLocation.FunctionCallResult(expression), signature = callee)
|
||||
}
|
||||
|
||||
val result = with(returnValuePassing) {
|
||||
@@ -176,7 +176,8 @@ internal fun KotlinStubs.generateCCall(expression: IrCall, builder: IrBuilderWit
|
||||
|
||||
private class CCallbackBuilder(
|
||||
val stubs: KotlinStubs,
|
||||
val location: IrElement
|
||||
val location: IrElement,
|
||||
val isObjCMethod: Boolean
|
||||
) {
|
||||
|
||||
val irBuiltIns: IrBuiltIns get() = stubs.irBuiltIns
|
||||
@@ -205,18 +206,29 @@ private fun CCallbackBuilder.passThroughBridge(
|
||||
return bridgeBuilder.addParameter(kotlinBridgeParameterType, cBridgeParameterType).first
|
||||
}
|
||||
|
||||
private fun CCallbackBuilder.addParameter(it: IrValueParameter) {
|
||||
val typeLocation = TypeLocation.FunctionPointerParameter(cFunctionBuilder.numberOfParameters, location)
|
||||
val valuePassing = stubs.mapType(it.type, typeLocation)
|
||||
private fun CCallbackBuilder.addParameter(it: IrValueParameter, functionParameter: IrValueParameter) {
|
||||
val typeLocation = if (isObjCMethod) {
|
||||
TypeLocation.ObjCMethodParameter(it.index, functionParameter)
|
||||
} else {
|
||||
TypeLocation.FunctionPointerParameter(cFunctionBuilder.numberOfParameters, location)
|
||||
}
|
||||
|
||||
val valuePassing = mapParameter(it, typeLocation)
|
||||
|
||||
val kotlinArgument = with(valuePassing) { receiveValue() }
|
||||
kotlinCallBuilder.arguments += kotlinArgument
|
||||
}
|
||||
|
||||
private fun CCallbackBuilder.build(function: IrSimpleFunction, signature: IrFunction): String {
|
||||
private fun CCallbackBuilder.build(function: IrSimpleFunction, signature: IrSimpleFunction): String {
|
||||
val kotlinCall = kotlinCallBuilder.build(function)
|
||||
|
||||
with(stubs.mapReturnType(signature.returnType, TypeLocation.FunctionPointerReturnValue(location))) {
|
||||
val typeLocation = if (isObjCMethod) {
|
||||
TypeLocation.ObjCMethodReturnValue(function)
|
||||
} else {
|
||||
TypeLocation.FunctionPointerReturnValue(location)
|
||||
}
|
||||
|
||||
with(stubs.mapReturnType(signature.returnType, typeLocation, signature)) {
|
||||
returnValue(kotlinCall)
|
||||
}
|
||||
|
||||
@@ -246,18 +258,25 @@ private fun KotlinStubs.generateCFunction(
|
||||
isObjCMethod: Boolean,
|
||||
location: IrElement
|
||||
): String {
|
||||
val callbackBuilder = CCallbackBuilder(this, location)
|
||||
val callbackBuilder = CCallbackBuilder(this, location, isObjCMethod)
|
||||
|
||||
signature.dispatchReceiverParameter?.let { callbackBuilder.addParameter(it) }
|
||||
if (isObjCMethod) {
|
||||
// assert(mapType(signature.dispatchReceiverParameter!!.type) is ObjCReferenceValuePassing)
|
||||
val receiver = signature.dispatchReceiverParameter!!
|
||||
assert(isObjCReferenceType(receiver.type))
|
||||
val valuePassing = ObjCReferenceValuePassing(symbols, receiver.type, retained = signature.consumesReceiver())
|
||||
val kotlinArgument = with(valuePassing) { callbackBuilder.receiveValue() }
|
||||
callbackBuilder.kotlinCallBuilder.arguments += kotlinArgument
|
||||
|
||||
// Selector is ignored:
|
||||
with(TrivialValuePassing(symbols.nativePtrType, CTypes.voidPtr)) { callbackBuilder.receiveValue() }
|
||||
} else {
|
||||
require(signature.dispatchReceiverParameter == null)
|
||||
}
|
||||
signature.extensionReceiverParameter?.let { callbackBuilder.addParameter(it) }
|
||||
|
||||
signature.extensionReceiverParameter?.let { callbackBuilder.addParameter(it, function.extensionReceiverParameter!!) }
|
||||
|
||||
signature.valueParameters.forEach {
|
||||
callbackBuilder.addParameter(it)
|
||||
callbackBuilder.addParameter(it, function.valueParameters[it.index])
|
||||
}
|
||||
|
||||
return callbackBuilder.build(function, signature)
|
||||
@@ -266,11 +285,14 @@ private fun KotlinStubs.generateCFunction(
|
||||
internal fun KotlinStubs.generateCFunctionPointer(
|
||||
function: IrSimpleFunction,
|
||||
signature: IrSimpleFunction,
|
||||
isObjCMethod: Boolean,
|
||||
expression: IrExpression
|
||||
): IrExpression {
|
||||
val cFunction = generateCFunction(function, signature, isObjCMethod, expression)
|
||||
val fakeFunction = createFakeKotlinExternalFunction(signature, cFunction, isObjCMethod)
|
||||
val fakeFunction = generateCFunctionAndFakeKotlinExternalFunction(
|
||||
function,
|
||||
signature,
|
||||
isObjCMethod = false,
|
||||
location = expression
|
||||
)
|
||||
addKotlin(fakeFunction)
|
||||
|
||||
return IrFunctionReferenceImpl(
|
||||
@@ -283,6 +305,16 @@ internal fun KotlinStubs.generateCFunctionPointer(
|
||||
)
|
||||
}
|
||||
|
||||
internal fun KotlinStubs.generateCFunctionAndFakeKotlinExternalFunction(
|
||||
function: IrSimpleFunction,
|
||||
signature: IrSimpleFunction,
|
||||
isObjCMethod: Boolean,
|
||||
location: IrElement
|
||||
): IrSimpleFunction {
|
||||
val cFunction = generateCFunction(function, signature, isObjCMethod, location)
|
||||
return createFakeKotlinExternalFunction(signature, cFunction, isObjCMethod)
|
||||
}
|
||||
|
||||
private fun KotlinStubs.createFakeKotlinExternalFunction(
|
||||
signature: IrSimpleFunction,
|
||||
cFunctionName: String,
|
||||
@@ -345,13 +377,20 @@ private fun IrType.isCEnumType(): Boolean {
|
||||
|
||||
// TODO: get rid of consulting descriptors for annotations.
|
||||
// Make sure external stubs always get proper annotaions.
|
||||
private fun IrValueParameter.isWCStringParameter() =
|
||||
this.annotations.hasAnnotation(cCall.child(Name.identifier("WCString"))) ||
|
||||
this.descriptor.annotations.hasAnnotation(cCall.child(Name.identifier("WCString")))
|
||||
private fun IrDeclaration.hasCCallAnnotation(name: String): Boolean =
|
||||
this.annotations.hasAnnotation(cCall.child(Name.identifier(name))) ||
|
||||
this.descriptor.annotations.hasAnnotation(cCall.child(Name.identifier(name)))
|
||||
|
||||
private fun IrValueParameter.isCStringParameter() =
|
||||
this.annotations.hasAnnotation(cCall.child(Name.identifier("CString"))) ||
|
||||
this.descriptor.annotations.hasAnnotation(cCall.child(Name.identifier("CString")))
|
||||
|
||||
private fun IrValueParameter.isWCStringParameter() = hasCCallAnnotation("WCString")
|
||||
|
||||
private fun IrValueParameter.isCStringParameter() = hasCCallAnnotation("CString")
|
||||
|
||||
private fun IrValueParameter.isConsumed() = hasCCallAnnotation("Consumed")
|
||||
|
||||
private fun IrSimpleFunction.consumesReceiver() = hasCCallAnnotation("ConsumesReceiver")
|
||||
|
||||
private fun IrSimpleFunction.returnsRetained() = hasCCallAnnotation("ReturnsRetained")
|
||||
|
||||
private fun getStructSpelling(kotlinClass: IrClass): String? =
|
||||
kotlinClass.getAnnotationArgumentValue(FqName("kotlinx.cinterop.internal.CStruct"), "spelling")
|
||||
@@ -393,17 +432,31 @@ private fun KotlinToCCallBuilder.mapParameter(
|
||||
}
|
||||
}
|
||||
|
||||
private fun CCallbackBuilder.mapParameter(
|
||||
parameter: IrValueParameter,
|
||||
typeLocation: TypeLocation
|
||||
): ValuePassing = when {
|
||||
stubs.isObjCReferenceType(parameter.type) ->
|
||||
ObjCReferenceValuePassing(symbols, parameter.type, retained = parameter.isConsumed())
|
||||
|
||||
else -> stubs.mapType(parameter.type, typeLocation)
|
||||
}
|
||||
|
||||
private sealed class TypeLocation(val element: IrElement) {
|
||||
class FunctionArgument(val argument: IrExpression) : TypeLocation(argument)
|
||||
class FunctionCallResult(val call: IrCall) : TypeLocation(call)
|
||||
|
||||
class FunctionPointerParameter(val index: Int, element: IrElement) : TypeLocation(element)
|
||||
class FunctionPointerReturnValue(element: IrElement) : TypeLocation(element)
|
||||
|
||||
class ObjCMethodParameter(val index: Int, element: IrElement) : TypeLocation(element)
|
||||
class ObjCMethodReturnValue(element: IrElement) : TypeLocation(element)
|
||||
}
|
||||
|
||||
private fun KotlinStubs.mapReturnType(type: IrType, location: TypeLocation): ValueReturning =
|
||||
private fun KotlinStubs.mapReturnType(type: IrType, location: TypeLocation, signature: IrSimpleFunction?): ValueReturning =
|
||||
when {
|
||||
type.isUnit() -> VoidReturning
|
||||
isObjCReferenceType(type) -> ObjCReferenceValuePassing(symbols, type, signature?.returnsRetained() ?: false)
|
||||
else -> mapType(type, location)
|
||||
}
|
||||
|
||||
@@ -448,7 +501,7 @@ private fun KotlinStubs.mapType(type: IrType, reportUnsupportedType: (String) ->
|
||||
}
|
||||
|
||||
type.isFunction() -> reportUnsupportedType("")
|
||||
isObjCReferenceType(type) -> ObjCReferenceValuePassing(symbols, type)
|
||||
isObjCReferenceType(type) -> ObjCReferenceValuePassing(symbols, type, retained = false)
|
||||
|
||||
else -> reportUnsupportedType("doesn't correspond to any C type")
|
||||
}
|
||||
@@ -456,16 +509,19 @@ private fun KotlinStubs.mapType(type: IrType, reportUnsupportedType: (String) ->
|
||||
private fun KotlinStubs.isObjCReferenceType(type: IrType): Boolean {
|
||||
if (target.family != Family.OSX && target.family != Family.IOS) return false
|
||||
|
||||
// Handle the same types as produced by [objCPointerMirror] in Interop/StubGenerator/.../Mappings.kt.
|
||||
|
||||
if (type.isObjCObjectType()) return true
|
||||
|
||||
val descriptor = type.classifierOrNull?.descriptor ?: return false
|
||||
val builtIns = irBuiltIns.builtIns
|
||||
|
||||
return when (descriptor) {
|
||||
builtIns.any,
|
||||
builtIns.string,
|
||||
builtIns.list, builtIns.mutableList,
|
||||
builtIns.set, builtIns.mutableSet,
|
||||
builtIns.map, builtIns.mutableMap -> true
|
||||
builtIns.set,
|
||||
builtIns.map -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
@@ -489,7 +545,11 @@ private abstract class SimpleValuePassing : ValuePassing {
|
||||
abstract val kotlinBridgeType: IrType
|
||||
abstract val cBridgeType: CType
|
||||
abstract val cType: CType
|
||||
|
||||
abstract fun IrBuilderWithScope.kotlinToBridged(expression: IrExpression): IrExpression
|
||||
open fun IrBuilderWithScope.kotlinCallbackResultToBridged(expression: IrExpression): IrExpression =
|
||||
kotlinToBridged(expression)
|
||||
|
||||
abstract fun IrBuilderWithScope.bridgedToKotlin(expression: IrExpression, symbols: KonanSymbols): IrExpression
|
||||
abstract fun bridgedToC(expression: String): String
|
||||
abstract fun cToBridged(expression: String): String
|
||||
@@ -522,7 +582,7 @@ private abstract class SimpleValuePassing : ValuePassing {
|
||||
bridgeBuilder.setReturnType(kotlinBridgeType, cBridgeType)
|
||||
|
||||
kotlinBridgeStatements += with(bridgeBuilder.kotlinIrBuilder) {
|
||||
irReturn(kotlinToBridged(expression))
|
||||
irReturn(kotlinCallbackResultToBridged(expression))
|
||||
}
|
||||
val cBridgeCall = buildCBridgeCall()
|
||||
cBodyLines += "return ${bridgedToC(cBridgeCall)};"
|
||||
@@ -689,7 +749,11 @@ private class CEnumValuePassing(
|
||||
override fun cToBridged(expression: String): String = with(baseValuePassing) { cToBridged(expression) }
|
||||
}
|
||||
|
||||
private class ObjCReferenceValuePassing(private val symbols: KonanSymbols, private val type: IrType) : SimpleValuePassing() {
|
||||
private class ObjCReferenceValuePassing(
|
||||
private val symbols: KonanSymbols,
|
||||
private val type: IrType,
|
||||
private val retained: Boolean
|
||||
) : SimpleValuePassing() {
|
||||
override val kotlinBridgeType: IrType
|
||||
get() = symbols.nativePtrType
|
||||
override val cBridgeType: CType
|
||||
@@ -697,15 +761,47 @@ private class ObjCReferenceValuePassing(private val symbols: KonanSymbols, priva
|
||||
override val cType: CType
|
||||
get() = CTypes.voidPtr
|
||||
|
||||
override fun IrBuilderWithScope.kotlinToBridged(expression: IrExpression): IrExpression =
|
||||
irCall(symbols.interopObjCObjectRawValueGetter.owner).apply {
|
||||
extensionReceiver = expression
|
||||
override fun IrBuilderWithScope.kotlinToBridged(expression: IrExpression): IrExpression {
|
||||
val ptr = irCall(symbols.interopObjCObjectRawValueGetter.owner).apply {
|
||||
extensionReceiver = expression
|
||||
}
|
||||
return if (retained) {
|
||||
irCall(symbols.interopObjCRetain).apply {
|
||||
putValueArgument(0, ptr)
|
||||
}
|
||||
} else {
|
||||
ptr
|
||||
}
|
||||
}
|
||||
|
||||
override fun IrBuilderWithScope.bridgedToKotlin(expression: IrExpression, symbols: KonanSymbols): IrExpression =
|
||||
irCall(symbols.interopInterpretObjCPointerOrNull, listOf(type)).apply {
|
||||
putValueArgument(0, expression)
|
||||
override fun IrBuilderWithScope.kotlinCallbackResultToBridged(expression: IrExpression): IrExpression {
|
||||
if (retained) return kotlinToBridged(expression) // Optimization.
|
||||
// Kotlin code may loose the ownership on this pointer after returning from the bridge,
|
||||
// so retain the pointer and autorelease it:
|
||||
return irCall(symbols.interopObjcRetainAutoreleaseReturnValue.owner).apply {
|
||||
putValueArgument(0, kotlinToBridged(expression))
|
||||
}
|
||||
// TODO: optimize by using specialized Kotlin-to-ObjC converter.
|
||||
}
|
||||
|
||||
override fun IrBuilderWithScope.bridgedToKotlin(expression: IrExpression, symbols: KonanSymbols): IrExpression {
|
||||
fun wrapObjCPtr(ptr: IrExpression) = irCall(symbols.interopInterpretObjCPointerOrNull, listOf(type)).apply {
|
||||
putValueArgument(0, ptr)
|
||||
}
|
||||
|
||||
return if (retained) {
|
||||
irBlock(startOffset, endOffset) {
|
||||
val ptrVar = irTemporary(expression)
|
||||
val resultVar = irTemporary(wrapObjCPtr(irGet(ptrVar)))
|
||||
+irCall(symbols.interopObjCRelease.owner).apply {
|
||||
putValueArgument(0, irGet(ptrVar))
|
||||
}
|
||||
+irGet(resultVar)
|
||||
}
|
||||
} else {
|
||||
wrapObjCPtr(expression)
|
||||
}
|
||||
}
|
||||
|
||||
override fun bridgedToC(expression: String): String = expression
|
||||
override fun cToBridged(expression: String): String = expression
|
||||
@@ -797,6 +893,8 @@ private fun KotlinStubs.reportUnsupportedType(reason: String, type: IrType, loca
|
||||
is TypeLocation.FunctionCallResult -> " of return value"
|
||||
is TypeLocation.FunctionPointerParameter -> " of callback parameter ${location.index + 1}"
|
||||
is TypeLocation.FunctionPointerReturnValue -> " of callback return value"
|
||||
is TypeLocation.ObjCMethodParameter -> " of overridden Objective-C method parameter"
|
||||
is TypeLocation.ObjCMethodReturnValue -> " of overridden Objective-C method return value"
|
||||
}
|
||||
|
||||
reportError(location.element, "type ${type.toKotlinType()}$typeLocation is not supported here" +
|
||||
|
||||
+10
-10
@@ -217,17 +217,11 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val
|
||||
|
||||
val interopAllocObjCObject = symbolTable.referenceSimpleFunction(context.interopBuiltIns.allocObjCObject)
|
||||
|
||||
val interopObjCRelease = symbolTable.referenceSimpleFunction(
|
||||
context.interopBuiltIns.packageScope
|
||||
.getContributedFunctions(Name.identifier("objc_release"), NoLookupLocation.FROM_BACKEND)
|
||||
.single()
|
||||
)
|
||||
val interopObjCRelease = interopFunction("objc_release")
|
||||
|
||||
val interopObjCRetain = symbolTable.referenceSimpleFunction(
|
||||
context.interopBuiltIns.packageScope
|
||||
.getContributedFunctions(Name.identifier("objc_retain"), NoLookupLocation.FROM_BACKEND)
|
||||
.single()
|
||||
)
|
||||
val interopObjCRetain = interopFunction("objc_retain")
|
||||
|
||||
val interopObjcRetainAutoreleaseReturnValue = interopFunction("objc_retainAutoreleaseReturnValue")
|
||||
|
||||
val interopGetObjCClass = symbolTable.referenceSimpleFunction(context.interopBuiltIns.getObjCClass)
|
||||
|
||||
@@ -502,6 +496,12 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val
|
||||
Name.identifier(className), NoLookupLocation.FROM_BACKEND
|
||||
) as ClassDescriptor)
|
||||
|
||||
private fun interopFunction(name: String) = symbolTable.referenceSimpleFunction(
|
||||
context.interopBuiltIns.packageScope
|
||||
.getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BACKEND)
|
||||
.single()
|
||||
)
|
||||
|
||||
val functions = (0 .. KONAN_FUNCTION_INTERFACES_MAX_PARAMETERS)
|
||||
.map { symbolTable.referenceClass(builtIns.getFunction(it)) }
|
||||
|
||||
|
||||
+17
-14
@@ -10,7 +10,6 @@ import llvm.LLVMValueRef
|
||||
import org.jetbrains.kotlin.backend.common.atMostOne
|
||||
import org.jetbrains.kotlin.backend.konan.*
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.getStringValue
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.getStringValueOrNull
|
||||
import org.jetbrains.kotlin.backend.konan.irasdescriptors.*
|
||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
||||
@@ -27,7 +26,7 @@ internal class KotlinObjCClassInfoGenerator(override val context: Context) : Con
|
||||
val instanceMethods = generateInstanceMethodDescs(irClass)
|
||||
|
||||
val companionObject = irClass.declarations.filterIsInstance<IrClass>().atMostOne { it.isCompanion }
|
||||
val classMethods = companionObject?.generateOverridingMethodDescs() ?: emptyList()
|
||||
val classMethods = companionObject?.generateMethodDescs().orEmpty()
|
||||
|
||||
val superclassName = irClass.getSuperClassNotAny()!!.let {
|
||||
context.llvm.imports.add(it.llvmSymbolOrigin)
|
||||
@@ -71,11 +70,13 @@ internal class KotlinObjCClassInfoGenerator(override val context: Context) : Con
|
||||
objCLLvmDeclarations.bodyOffsetGlobal.setInitializer(Int32(0))
|
||||
}
|
||||
|
||||
private fun IrClass.generateMethodDescs(): List<ObjCMethodDesc> =
|
||||
this.generateOverridingMethodDescs() + this.generateImpMethodDescs()
|
||||
|
||||
private fun generateInstanceMethodDescs(
|
||||
irClass: IrClass
|
||||
): List<ObjCMethodDesc> = mutableListOf<ObjCMethodDesc>().apply {
|
||||
addAll(irClass.generateOverridingMethodDescs())
|
||||
addAll(irClass.generateImpMethodDescs())
|
||||
addAll(irClass.generateMethodDescs())
|
||||
val allImplementedSelectors = this.map { it.selector }.toSet()
|
||||
|
||||
assert(irClass.getSuperClassNotAny()!!.isExternalObjCClass())
|
||||
@@ -112,19 +113,21 @@ internal class KotlinObjCClassInfoGenerator(override val context: Context) : Con
|
||||
staticData.cStringLiteral(encoding)
|
||||
)
|
||||
|
||||
private fun generateMethodDesc(info: ObjCMethodInfo) = ObjCMethodDesc(
|
||||
info.selector,
|
||||
info.encoding,
|
||||
context.llvm.externalFunction(
|
||||
info.imp,
|
||||
functionType(voidType),
|
||||
origin = info.bridge.llvmSymbolOrigin
|
||||
)
|
||||
)
|
||||
private fun generateMethodDesc(info: ObjCMethodInfo) = info.imp?.let { imp ->
|
||||
ObjCMethodDesc(
|
||||
info.selector,
|
||||
info.encoding,
|
||||
context.llvm.externalFunction(
|
||||
imp,
|
||||
functionType(voidType),
|
||||
origin = info.bridge.llvmSymbolOrigin
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun IrClass.generateOverridingMethodDescs(): List<ObjCMethodDesc> =
|
||||
this.simpleFunctions().filter { it.isReal }
|
||||
.mapNotNull { it.getObjCMethodInfo() }.map { generateMethodDesc(it) }
|
||||
.mapNotNull { it.getObjCMethodInfo() }.mapNotNull { generateMethodDesc(it) }
|
||||
|
||||
private fun IrClass.generateImpMethodDescs(): List<ObjCMethodDesc> = this.declarations
|
||||
.filterIsInstance<IrSimpleFunction>()
|
||||
|
||||
+27
-1
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.backend.common.push
|
||||
import org.jetbrains.kotlin.backend.konan.*
|
||||
import org.jetbrains.kotlin.backend.konan.cgen.KotlinStubs
|
||||
import org.jetbrains.kotlin.backend.konan.cgen.generateCCall
|
||||
import org.jetbrains.kotlin.backend.konan.cgen.generateCFunctionAndFakeKotlinExternalFunction
|
||||
import org.jetbrains.kotlin.backend.konan.cgen.generateCFunctionPointer
|
||||
import org.jetbrains.kotlin.backend.konan.getInlinedClass
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.allOverriddenFunctions
|
||||
@@ -794,8 +795,33 @@ private class InteropTransformer(val context: Context, val irFile: IrFile) : IrB
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitClass(declaration: IrClass): IrStatement {
|
||||
super.visitClass(declaration)
|
||||
if (declaration.isKotlinObjCClass()) {
|
||||
val imps = declaration.simpleFunctions().filter { it.isReal }.flatMap { function ->
|
||||
function.overriddenSymbols.mapNotNull {
|
||||
val info = it.owner.getExternalObjCMethodInfo()
|
||||
if (info == null || info.imp != null) {
|
||||
null
|
||||
} else {
|
||||
generateWithStubs(it.owner) {
|
||||
generateCFunctionAndFakeKotlinExternalFunction(
|
||||
function,
|
||||
it.owner,
|
||||
isObjCMethod = true,
|
||||
location = function
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
declaration.addChildren(imps)
|
||||
}
|
||||
return declaration
|
||||
}
|
||||
|
||||
private fun generateCFunctionPointer(function: IrSimpleFunction, expression: IrExpression): IrExpression =
|
||||
generateWithStubs { generateCFunctionPointer(function, function, false, expression) }
|
||||
generateWithStubs { generateCFunctionPointer(function, function, expression) }
|
||||
|
||||
override fun visitCall(expression: IrCall): IrExpression {
|
||||
|
||||
|
||||
@@ -3046,7 +3046,7 @@ task interop_objc_smoke(type: RunInteropKonanTest) {
|
||||
"5\n" +
|
||||
"String macro\n" +
|
||||
"CFString macro\n" +
|
||||
"Deallocated\nDeallocated\n"
|
||||
"Deallocated\nDeallocated\nDeallocated\n"
|
||||
|
||||
if (isAppleTarget(project)) {
|
||||
source = "interop/objc/smoke.kt"
|
||||
|
||||
@@ -65,4 +65,52 @@ int formatStringLength(NSString* format, ...);
|
||||
typedef NS_ENUM(int32_t, ForwardDeclaredEnum);
|
||||
typedef NS_ENUM(int32_t, ForwardDeclaredEnum) {
|
||||
ZERO, ONE, TWO,
|
||||
};
|
||||
};
|
||||
|
||||
@protocol ObjectFactory
|
||||
@required
|
||||
-(id)create;
|
||||
@end;
|
||||
|
||||
id createObjectWithFactory(id<ObjectFactory> factory) {
|
||||
return [factory create];
|
||||
}
|
||||
|
||||
@protocol BlockProvider
|
||||
@required
|
||||
-(int (^)(int)) block;
|
||||
@end;
|
||||
|
||||
int callProvidedBlock(id<BlockProvider> blockProvider, int argument) {
|
||||
return [blockProvider block](argument);
|
||||
}
|
||||
|
||||
@protocol BlockConsumer
|
||||
@required
|
||||
-(int)callBlock:(int (^)(int))block argument:(int)argument;
|
||||
@end;
|
||||
|
||||
int callPlusOneBlock(id<BlockConsumer> blockConsumer, int argument) {
|
||||
return [blockConsumer callBlock:^int(int p) { return p + 1; } argument:argument];
|
||||
}
|
||||
|
||||
@protocol CustomRetainMethods
|
||||
@required
|
||||
-(id)returnRetained:(id)obj __attribute__((ns_returns_retained));
|
||||
-(void)consume:(id) __attribute__((ns_consumed)) obj;
|
||||
-(void)consumeSelf __attribute__((ns_consumes_self));
|
||||
@end;
|
||||
|
||||
extern BOOL unexpectedDeallocation;
|
||||
|
||||
@interface MustNotBeDeallocated : NSObject
|
||||
@end;
|
||||
|
||||
void useCustomRetainMethods(id<CustomRetainMethods> p) {
|
||||
MustNotBeDeallocated* obj = [MustNotBeDeallocated new];
|
||||
CFBridgingRetain(obj); // Over-retain to detect possible over-release.
|
||||
[p returnRetained:obj];
|
||||
|
||||
[p consume:p];
|
||||
[p consumeSelf];
|
||||
}
|
||||
@@ -82,6 +82,30 @@ fun run() {
|
||||
|
||||
println(STRING_MACRO)
|
||||
println(CFSTRING_MACRO)
|
||||
|
||||
// Ensure that overriding method bridge has retain-autorelease sequence:
|
||||
createObjectWithFactory(object : NSObject(), ObjectFactoryProtocol {
|
||||
override fun create() = autoreleasepool { NSObject() }
|
||||
})
|
||||
|
||||
assertEquals(222, callProvidedBlock(object : NSObject(), BlockProviderProtocol {
|
||||
override fun block(): (Int) -> Int = { it * 2 }
|
||||
}, 111))
|
||||
|
||||
assertEquals(322, callPlusOneBlock(object : NSObject(), BlockConsumerProtocol {
|
||||
override fun callBlock(block: ((Int) -> Int)?, argument: Int) = block!!(argument)
|
||||
}, 321))
|
||||
|
||||
autoreleasepool {
|
||||
useCustomRetainMethods(object : Foo(), CustomRetainMethodsProtocol {
|
||||
override fun returnRetained(obj: Any?) = obj
|
||||
override fun consume(obj: Any?) {}
|
||||
override fun consumeSelf() {}
|
||||
})
|
||||
}
|
||||
|
||||
assertFalse(unexpectedDeallocation)
|
||||
|
||||
}
|
||||
|
||||
fun MutablePairProtocol.swap() {
|
||||
|
||||
@@ -68,3 +68,11 @@ int formatStringLength(NSString* format, ...) {
|
||||
va_end(args);
|
||||
return result.length;
|
||||
}
|
||||
|
||||
BOOL unexpectedDeallocation = NO;
|
||||
|
||||
@implementation MustNotBeDeallocated
|
||||
-(void)dealloc {
|
||||
unexpectedDeallocation = YES;
|
||||
}
|
||||
@end;
|
||||
|
||||
Reference in New Issue
Block a user