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:
SvyatoslavScherbina
2019-02-22 13:51:08 +03:00
committed by Nikolay Igotti
parent 0c90464ac4
commit 4d6614e674
12 changed files with 311 additions and 70 deletions
@@ -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
}
@@ -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"
@@ -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")
)
}
@@ -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" +
@@ -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)) }
@@ -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>()
@@ -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 {
+1 -1
View File
@@ -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"
+49 -1
View File
@@ -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;