diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCCodeGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCCodeGenerator.kt index 87d6e5eba38..71298cf9680 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCCodeGenerator.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCCodeGenerator.kt @@ -48,14 +48,6 @@ internal open class ObjCCodeGenerator(val codegen: CodeGenerator) { origin = context.stdlibModule.llvmSymbolOrigin )) - val objcAutorelease = context.llvm.externalFunction(LlvmFunctionProto( - "llvm.objc.autorelease", - LlvmRetType(int8TypePtr), - listOf(LlvmParamType(int8TypePtr)), - listOf(LlvmFunctionAttribute.NoUnwind), - origin = context.stdlibModule.llvmSymbolOrigin - )) - val objcAutoreleaseReturnValue = context.llvm.externalFunction(LlvmFunctionProto( "llvm.objc.autoreleaseReturnValue", LlvmRetType(int8TypePtr), diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/BlockPointerSupport.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/BlockPointerSupport.kt index c0a8f01b850..0ee2a25ed10 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/BlockPointerSupport.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/BlockPointerSupport.kt @@ -34,10 +34,6 @@ internal fun ObjCExportCodeGeneratorBase.generateBlockToKotlinFunctionConverter( LlvmFunctionSignature(invokeMethod, codegen), "invokeFunction${bridge.nameSuffix}" ).generate { - val args = (0 until bridge.numberOfParameters).map { index -> - kotlinReferenceToLocalObjC(param(index + 1)) - } - val thisRef = param(0) val associatedObjectHolder = if (useSeparateHolder) { val bodyPtr = bitcast(pointerType(bodyType), thisRef) @@ -51,10 +47,20 @@ internal fun ObjCExportCodeGeneratorBase.generateBlockToKotlinFunctionConverter( ) val invoke = loadBlockInvoke(blockPtr, bridge) + + val args = (0 until bridge.numberOfParameters).map { index -> + kotlinReferenceToRetainedObjC(param(index + 1)) + } + switchThreadStateIfExperimentalMM(ThreadState.Native) // Using terminatingExceptionHandler, so any exception thrown by `invoke` will lead to the termination, // and switching the thread state back to `Runnable` on exceptional path is not required. val result = call(invoke, listOf(blockPtr) + args, exceptionHandler = terminatingExceptionHandler) + + args.forEach { + objcReleaseFromNativeThreadState(it) + } + switchThreadStateIfExperimentalMM(ThreadState.Runnable) val kotlinResult = if (bridge.returnsVoid) { diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt index d8b9c301006..e79c517de85 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt @@ -87,9 +87,13 @@ internal class ObjCExportFunctionGenerationContext( fun objcReleaseFromRunnableThreadState(objCReference: LLVMValueRef) { switchThreadStateIfExperimentalMM(ThreadState.Native) + objcReleaseFromNativeThreadState(objCReference) + switchThreadStateIfExperimentalMM(ThreadState.Runnable) + } + + fun objcReleaseFromNativeThreadState(objCReference: LLVMValueRef) { // It is nounwind, so no exception handler is required. call(objCExportCodegen.objcRelease, listOf(objCReference), exceptionHandler = ExceptionHandler.None) - switchThreadStateIfExperimentalMM(ThreadState.Runnable) } override fun processReturns() { @@ -300,29 +304,11 @@ internal class ObjCExportCodeGenerator( resultLifetime ) - private fun ObjCExportFunctionGenerationContext.kotlinFunctionToObjCBlockPointer( - typeBridge: BlockPointerBridge, - value: LLVMValueRef - ) = callFromBridge(objcAutorelease, listOf(kotlinFunctionToRetainedObjCBlockPointer(typeBridge, value))) - internal fun ObjCExportFunctionGenerationContext.kotlinFunctionToRetainedObjCBlockPointer( typeBridge: BlockPointerBridge, value: LLVMValueRef ) = callFromBridge(kotlinFunctionToRetainedBlockConverter(typeBridge), listOf(value)) - fun ObjCExportFunctionGenerationContext.kotlinToLocalObjC( - value: LLVMValueRef, - typeBridge: TypeBridge - ): LLVMValueRef = if (LLVMTypeOf(value) == voidType) { - typeBridge.makeNothing() - } else { - when (typeBridge) { - is ReferenceBridge -> kotlinReferenceToLocalObjC(value) - is BlockPointerBridge -> kotlinFunctionToObjCBlockPointer(typeBridge, value) // TODO: use stack-allocated block here. - is ValueTypeBridge -> kotlinToObjC(value, typeBridge.objCValueType) - } - } - fun ObjCExportFunctionGenerationContext.objCToKotlin( value: LLVMValueRef, typeBridge: TypeBridge, @@ -1184,6 +1170,8 @@ private fun ObjCExportCodeGenerator.generateKotlinToObjCBridge( parameterDescriptor to param(index) }.toMap() + val objCReferenceArgsToRelease = mutableListOf() + val objCArgs = methodBridge.parametersAssociated(irFunction).map { (bridge, parameter) -> when (bridge) { is MethodBridgeValueParameter.Mapped -> { @@ -1194,10 +1182,26 @@ private fun ObjCExportCodeGenerator.generateKotlinToObjCBridge( expectedType = parameterToBase[parameter]!!.type, resultLifetime = Lifetime.ARGUMENT ) - kotlinToLocalObjC(kotlinValue, bridge.bridge) + if (LLVMTypeOf(kotlinValue) == voidType) { + bridge.bridge.makeNothing() + } else { + when (bridge.bridge) { + is ReferenceBridge -> kotlinReferenceToRetainedObjC(kotlinValue).also { objCReferenceArgsToRelease += it } + is BlockPointerBridge -> kotlinFunctionToRetainedObjCBlockPointer(bridge.bridge, kotlinValue) // TODO: use stack-allocated block here. + .also { objCReferenceArgsToRelease += it } + is ValueTypeBridge -> kotlinToObjC(kotlinValue, bridge.bridge.objCValueType) + } + } } - MethodBridgeReceiver.Instance -> kotlinReferenceToLocalObjC(parameters[parameter]!!) + MethodBridgeReceiver.Instance -> { + // `kotlinReferenceToLocalObjC` can add the result to autoreleasepool in some cases. But not here. + // Because this `parameter` is the receiver of a bridge in an Obj-C/Swift class extending + // Kotlin class or interface. + // So `kotlinReferenceToLocalObjC` here just unwraps the Obj-C reference + // without using autoreleasepool or any other reference counting operations. + kotlinReferenceToLocalObjC(parameters[parameter]!!) + } MethodBridgeSelector -> { val selector = baseMethod.selector // Selector is referenced thus should be defined to avoid false positive non-public API rejection: @@ -1224,13 +1228,14 @@ private fun ObjCExportCodeGenerator.generateKotlinToObjCBridge( Lifetime.ARGUMENT ) + // TODO: use stack-allocated block here instead. val converter = if (bridge.useUnitCompletion) { unitContinuationToRetainedCompletionConverter } else { continuationToRetainedCompletionConverter } - val retainedCompletion = callFromBridge(converter, listOf(intercepted)) - callFromBridge(objcAutorelease, listOf(retainedCompletion)) // TODO: use stack-allocated block here instead. + callFromBridge(converter, listOf(intercepted)) + .also { objCReferenceArgsToRelease += it } } } } @@ -1239,6 +1244,11 @@ private fun ObjCExportCodeGenerator.generateKotlinToObjCBridge( // Using terminatingExceptionHandler, so any exception thrown by the method will lead to the termination, // and switching the thread state back to `Runnable` on exceptional path is not required. val targetResult = call(objcMsgSend, objCArgs, exceptionHandler = terminatingExceptionHandler) + + objCReferenceArgsToRelease.forEach { + objcReleaseFromNativeThreadState(it) + } + switchThreadStateIfExperimentalMM(ThreadState.Runnable) assert(baseMethod.symbol !is IrConstructorSymbol) diff --git a/kotlin-native/backend.native/tests/objcexport/expectedLazy.h b/kotlin-native/backend.native/tests/objcexport/expectedLazy.h index c761abde108..e1e3f56bb68 100644 --- a/kotlin-native/backend.native/tests/objcexport/expectedLazy.h +++ b/kotlin-native/backend.native/tests/objcexport/expectedLazy.h @@ -1020,11 +1020,18 @@ __attribute__((swift_name("NoAutoreleaseSendHelper"))) @protocol KtNoAutoreleaseSendHelper @required - (void)sendKotlinObjectKotlinObject:(KtKotlinObject *)kotlinObject __attribute__((swift_name("sendKotlinObject(kotlinObject:)"))); +- (void (^)(KtKotlinObject *))blockReceivingKotlinObject __attribute__((swift_name("blockReceivingKotlinObject()"))); - (void)sendSwiftObjectSwiftObject:(id)swiftObject __attribute__((swift_name("sendSwiftObject(swiftObject:)"))); - (void)sendListList:(NSArray *)list __attribute__((swift_name("sendList(list:)"))); - (void)sendStringString:(NSString *)string __attribute__((swift_name("sendString(string:)"))); - (void)sendNumberNumber:(id)number __attribute__((swift_name("sendNumber(number:)"))); - (void)sendBlockBlock:(KtKotlinObject *(^)(void))block __attribute__((swift_name("sendBlock(block:)"))); + +/** + @note This method converts instances of CancellationException to errors. + Other uncaught Kotlin exceptions are fatal. +*/ +- (void)sendCompletionWithCompletionHandler:(void (^)(id _Nullable_result, NSError * _Nullable))completionHandler __attribute__((swift_name("sendCompletion(completionHandler:)"))); @end; __attribute__((swift_name("NoAutoreleaseReceiveHelper"))) @@ -1043,11 +1050,18 @@ __attribute__((swift_name("NoAutoreleaseKotlinSendHelper"))) @interface KtNoAutoreleaseKotlinSendHelper : KtBase - (instancetype)initWithKotlinLivenessTracker:(KtKotlinLivenessTracker *)kotlinLivenessTracker __attribute__((swift_name("init(kotlinLivenessTracker:)"))) __attribute__((objc_designated_initializer)); - (void)sendKotlinObjectKotlinObject:(KtKotlinObject *)kotlinObject __attribute__((swift_name("sendKotlinObject(kotlinObject:)"))); +- (void (^)(KtKotlinObject *))blockReceivingKotlinObject __attribute__((swift_name("blockReceivingKotlinObject()"))); - (void)sendSwiftObjectSwiftObject:(id)swiftObject __attribute__((swift_name("sendSwiftObject(swiftObject:)"))); - (void)sendListList:(NSArray *)list __attribute__((swift_name("sendList(list:)"))); - (void)sendStringString:(NSString *)string __attribute__((swift_name("sendString(string:)"))); - (void)sendNumberNumber:(id)number __attribute__((swift_name("sendNumber(number:)"))); - (void)sendBlockBlock:(KtKotlinObject *(^)(void))block __attribute__((swift_name("sendBlock(block:)"))); + +/** + @note This method converts instances of CancellationException to errors. + Other uncaught Kotlin exceptions are fatal. +*/ +- (void)sendCompletionWithCompletionHandler:(void (^)(id _Nullable_result, NSError * _Nullable))completionHandler __attribute__((swift_name("sendCompletion(completionHandler:)"))); @property (readonly) KtKotlinLivenessTracker *kotlinLivenessTracker __attribute__((swift_name("kotlinLivenessTracker"))); @end; @@ -1091,11 +1105,13 @@ __attribute__((swift_name("NoAutoreleaseKt"))) @interface KtNoAutoreleaseKt : KtBase + (void)gc __attribute__((swift_name("gc()"))); + (void)callSendKotlinObjectHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendKotlinObject(helper:tracker:)"))); ++ (void)sendKotlinObjectToBlockHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("sendKotlinObjectToBlock(helper:tracker:)"))); + (void)callSendSwiftObjectHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker swiftObject:(id)swiftObject __attribute__((swift_name("callSendSwiftObject(helper:tracker:swiftObject:)"))); + (void)callSendListHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendList(helper:tracker:)"))); + (void)callSendStringHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendString(helper:tracker:)"))); + (void)callSendNumberHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendNumber(helper:tracker:)"))); + (void)callSendBlockHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendBlock(helper:tracker:)"))); ++ (void)callSendCompletionHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendCompletion(helper:tracker:)"))); + (void)callReceiveKotlinObjectHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveKotlinObject(helper:tracker:)"))); + (void)callReceiveSwiftObjectHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveSwiftObject(helper:tracker:)"))); + (void)callReceiveListHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveList(helper:tracker:)"))); diff --git a/kotlin-native/backend.native/tests/objcexport/expectedLazyLegacySuspendUnit.h b/kotlin-native/backend.native/tests/objcexport/expectedLazyLegacySuspendUnit.h index 4cb084445e1..90a99a286e4 100644 --- a/kotlin-native/backend.native/tests/objcexport/expectedLazyLegacySuspendUnit.h +++ b/kotlin-native/backend.native/tests/objcexport/expectedLazyLegacySuspendUnit.h @@ -962,11 +962,18 @@ __attribute__((swift_name("NoAutoreleaseSendHelper"))) @protocol KtNoAutoreleaseSendHelper @required - (void)sendKotlinObjectKotlinObject:(KtKotlinObject *)kotlinObject __attribute__((swift_name("sendKotlinObject(kotlinObject:)"))); +- (void (^)(KtKotlinObject *))blockReceivingKotlinObject __attribute__((swift_name("blockReceivingKotlinObject()"))); - (void)sendSwiftObjectSwiftObject:(id)swiftObject __attribute__((swift_name("sendSwiftObject(swiftObject:)"))); - (void)sendListList:(NSArray *)list __attribute__((swift_name("sendList(list:)"))); - (void)sendStringString:(NSString *)string __attribute__((swift_name("sendString(string:)"))); - (void)sendNumberNumber:(id)number __attribute__((swift_name("sendNumber(number:)"))); - (void)sendBlockBlock:(KtKotlinObject *(^)(void))block __attribute__((swift_name("sendBlock(block:)"))); + +/** + @note This method converts instances of CancellationException to errors. + Other uncaught Kotlin exceptions are fatal. +*/ +- (void)sendCompletionWithCompletionHandler:(void (^)(id _Nullable_result, NSError * _Nullable))completionHandler __attribute__((swift_name("sendCompletion(completionHandler:)"))); @end; __attribute__((swift_name("NoAutoreleaseReceiveHelper"))) @@ -985,11 +992,18 @@ __attribute__((swift_name("NoAutoreleaseKotlinSendHelper"))) @interface KtNoAutoreleaseKotlinSendHelper : KtBase - (instancetype)initWithKotlinLivenessTracker:(KtKotlinLivenessTracker *)kotlinLivenessTracker __attribute__((swift_name("init(kotlinLivenessTracker:)"))) __attribute__((objc_designated_initializer)); - (void)sendKotlinObjectKotlinObject:(KtKotlinObject *)kotlinObject __attribute__((swift_name("sendKotlinObject(kotlinObject:)"))); +- (void (^)(KtKotlinObject *))blockReceivingKotlinObject __attribute__((swift_name("blockReceivingKotlinObject()"))); - (void)sendSwiftObjectSwiftObject:(id)swiftObject __attribute__((swift_name("sendSwiftObject(swiftObject:)"))); - (void)sendListList:(NSArray *)list __attribute__((swift_name("sendList(list:)"))); - (void)sendStringString:(NSString *)string __attribute__((swift_name("sendString(string:)"))); - (void)sendNumberNumber:(id)number __attribute__((swift_name("sendNumber(number:)"))); - (void)sendBlockBlock:(KtKotlinObject *(^)(void))block __attribute__((swift_name("sendBlock(block:)"))); + +/** + @note This method converts instances of CancellationException to errors. + Other uncaught Kotlin exceptions are fatal. +*/ +- (void)sendCompletionWithCompletionHandler:(void (^)(id _Nullable_result, NSError * _Nullable))completionHandler __attribute__((swift_name("sendCompletion(completionHandler:)"))); @property (readonly) KtKotlinLivenessTracker *kotlinLivenessTracker __attribute__((swift_name("kotlinLivenessTracker"))); @end; @@ -1033,11 +1047,13 @@ __attribute__((swift_name("NoAutoreleaseKt"))) @interface KtNoAutoreleaseKt : KtBase + (void)gc __attribute__((swift_name("gc()"))); + (void)callSendKotlinObjectHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendKotlinObject(helper:tracker:)"))); ++ (void)sendKotlinObjectToBlockHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("sendKotlinObjectToBlock(helper:tracker:)"))); + (void)callSendSwiftObjectHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker swiftObject:(id)swiftObject __attribute__((swift_name("callSendSwiftObject(helper:tracker:swiftObject:)"))); + (void)callSendListHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendList(helper:tracker:)"))); + (void)callSendStringHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendString(helper:tracker:)"))); + (void)callSendNumberHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendNumber(helper:tracker:)"))); + (void)callSendBlockHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendBlock(helper:tracker:)"))); ++ (void)callSendCompletionHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendCompletion(helper:tracker:)"))); + (void)callReceiveKotlinObjectHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveKotlinObject(helper:tracker:)"))); + (void)callReceiveSwiftObjectHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveSwiftObject(helper:tracker:)"))); + (void)callReceiveListHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveList(helper:tracker:)"))); diff --git a/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h b/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h index f03b5b2398b..90d2f89c7e1 100644 --- a/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h +++ b/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h @@ -962,11 +962,18 @@ __attribute__((swift_name("NoAutoreleaseSendHelper"))) @protocol KtNoAutoreleaseSendHelper @required - (void)sendKotlinObjectKotlinObject:(KtKotlinObject *)kotlinObject __attribute__((swift_name("sendKotlinObject(kotlinObject:)"))); +- (void (^)(KtKotlinObject *))blockReceivingKotlinObject __attribute__((swift_name("blockReceivingKotlinObject()"))); - (void)sendSwiftObjectSwiftObject:(id)swiftObject __attribute__((swift_name("sendSwiftObject(swiftObject:)"))); - (void)sendListList:(NSArray *)list __attribute__((swift_name("sendList(list:)"))); - (void)sendStringString:(NSString *)string __attribute__((swift_name("sendString(string:)"))); - (void)sendNumberNumber:(id)number __attribute__((swift_name("sendNumber(number:)"))); - (void)sendBlockBlock:(KtKotlinObject *(^)(void))block __attribute__((swift_name("sendBlock(block:)"))); + +/** + @note This method converts instances of CancellationException to errors. + Other uncaught Kotlin exceptions are fatal. +*/ +- (void)sendCompletionWithCompletionHandler:(void (^)(id _Nullable_result, NSError * _Nullable))completionHandler __attribute__((swift_name("sendCompletion(completionHandler:)"))); @end; __attribute__((swift_name("NoAutoreleaseReceiveHelper"))) @@ -985,11 +992,18 @@ __attribute__((swift_name("NoAutoreleaseKotlinSendHelper"))) @interface KtNoAutoreleaseKotlinSendHelper : KtBase - (instancetype)initWithKotlinLivenessTracker:(KtKotlinLivenessTracker *)kotlinLivenessTracker __attribute__((swift_name("init(kotlinLivenessTracker:)"))) __attribute__((objc_designated_initializer)); - (void)sendKotlinObjectKotlinObject:(KtKotlinObject *)kotlinObject __attribute__((swift_name("sendKotlinObject(kotlinObject:)"))); +- (void (^)(KtKotlinObject *))blockReceivingKotlinObject __attribute__((swift_name("blockReceivingKotlinObject()"))); - (void)sendSwiftObjectSwiftObject:(id)swiftObject __attribute__((swift_name("sendSwiftObject(swiftObject:)"))); - (void)sendListList:(NSArray *)list __attribute__((swift_name("sendList(list:)"))); - (void)sendStringString:(NSString *)string __attribute__((swift_name("sendString(string:)"))); - (void)sendNumberNumber:(id)number __attribute__((swift_name("sendNumber(number:)"))); - (void)sendBlockBlock:(KtKotlinObject *(^)(void))block __attribute__((swift_name("sendBlock(block:)"))); + +/** + @note This method converts instances of CancellationException to errors. + Other uncaught Kotlin exceptions are fatal. +*/ +- (void)sendCompletionWithCompletionHandler:(void (^)(id _Nullable_result, NSError * _Nullable))completionHandler __attribute__((swift_name("sendCompletion(completionHandler:)"))); @property (readonly) KtKotlinLivenessTracker *kotlinLivenessTracker __attribute__((swift_name("kotlinLivenessTracker"))); @end; @@ -1033,11 +1047,13 @@ __attribute__((swift_name("NoAutoreleaseKt"))) @interface KtNoAutoreleaseKt : KtBase + (void)gc __attribute__((swift_name("gc()"))); + (void)callSendKotlinObjectHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendKotlinObject(helper:tracker:)"))); ++ (void)sendKotlinObjectToBlockHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("sendKotlinObjectToBlock(helper:tracker:)"))); + (void)callSendSwiftObjectHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker swiftObject:(id)swiftObject __attribute__((swift_name("callSendSwiftObject(helper:tracker:swiftObject:)"))); + (void)callSendListHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendList(helper:tracker:)"))); + (void)callSendStringHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendString(helper:tracker:)"))); + (void)callSendNumberHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendNumber(helper:tracker:)"))); + (void)callSendBlockHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendBlock(helper:tracker:)"))); ++ (void)callSendCompletionHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendCompletion(helper:tracker:)"))); + (void)callReceiveKotlinObjectHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveKotlinObject(helper:tracker:)"))); + (void)callReceiveSwiftObjectHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveSwiftObject(helper:tracker:)"))); + (void)callReceiveListHelper:(id)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveList(helper:tracker:)"))); diff --git a/kotlin-native/backend.native/tests/objcexport/noAutorelease.kt b/kotlin-native/backend.native/tests/objcexport/noAutorelease.kt index 4c091e34ee7..cb2c8d1085d 100644 --- a/kotlin-native/backend.native/tests/objcexport/noAutorelease.kt +++ b/kotlin-native/backend.native/tests/objcexport/noAutorelease.kt @@ -1,5 +1,7 @@ package noAutorelease +import kotlin.coroutines.* +import kotlin.coroutines.intrinsics.* import kotlin.native.internal.NativePtr import kotlin.native.ref.WeakReference import kotlin.test.* @@ -23,11 +25,13 @@ class KotlinObject interface NoAutoreleaseSendHelper { fun sendKotlinObject(kotlinObject: KotlinObject) + fun blockReceivingKotlinObject(): (KotlinObject) -> Unit fun sendSwiftObject(swiftObject: Any) fun sendList(list: List<*>) fun sendString(string: String) fun sendNumber(number: Any) fun sendBlock(block: () -> KotlinObject) + suspend fun sendCompletion(): Any? } interface NoAutoreleaseReceiveHelper { @@ -41,11 +45,16 @@ interface NoAutoreleaseReceiveHelper { class NoAutoreleaseKotlinSendHelper(val kotlinLivenessTracker: KotlinLivenessTracker) : NoAutoreleaseSendHelper { override fun sendKotlinObject(kotlinObject: KotlinObject) = kotlinLivenessTracker.add(kotlinObject) + override fun blockReceivingKotlinObject(): (KotlinObject) -> Unit = { kotlinLivenessTracker.add(it) } override fun sendSwiftObject(swiftObject: Any) = kotlinLivenessTracker.add(swiftObject) override fun sendList(list: List<*>) = kotlinLivenessTracker.add(list) override fun sendString(string: String) = kotlinLivenessTracker.add(string) override fun sendNumber(number: Any) = kotlinLivenessTracker.add(number) override fun sendBlock(block: () -> KotlinObject) = kotlinLivenessTracker.add(block) + override suspend fun sendCompletion() = suspendCoroutineUninterceptedOrReturn { continuation -> + kotlinLivenessTracker.add(continuation) + null + } } class NoAutoreleaseKotlinReceiveHelper(val kotlinLivenessTracker: KotlinLivenessTracker) : NoAutoreleaseReceiveHelper { @@ -82,6 +91,14 @@ fun callSendKotlinObject(helper: NoAutoreleaseSendHelper, tracker: KotlinLivenes tracker.add(kotlinObject) } +fun sendKotlinObjectToBlock(helper: NoAutoreleaseSendHelper, tracker: KotlinLivenessTracker) { + val kotlinObject = KotlinObject() + + helper.blockReceivingKotlinObject()(kotlinObject) + helper.blockReceivingKotlinObject()(kotlinObject) + tracker.add(kotlinObject) +} + fun callSendSwiftObject(helper: NoAutoreleaseSendHelper, tracker: KotlinLivenessTracker, swiftObject: Any) { helper.sendSwiftObject(swiftObject) helper.sendSwiftObject(swiftObject) @@ -120,6 +137,22 @@ fun callSendBlock(helper: NoAutoreleaseSendHelper, tracker: KotlinLivenessTracke tracker.add(block) } +private class EmptyContinuation : Continuation { + override val context: CoroutineContext = EmptyCoroutineContext + override fun resumeWith(result: Result) { result.getOrThrow() } +} + +fun callSendCompletion(helper: NoAutoreleaseSendHelper, tracker: KotlinLivenessTracker) { + val completion = EmptyContinuation() + + suspend { + helper.sendCompletion() + helper.sendCompletion() + }.startCoroutine(completion) + + tracker.add(completion) +} + fun callReceiveKotlinObject(helper: NoAutoreleaseReceiveHelper, tracker: KotlinLivenessTracker) = repeat(2) { tracker.add(helper.receiveKotlinObject()) } diff --git a/kotlin-native/backend.native/tests/objcexport/noAutorelease.swift b/kotlin-native/backend.native/tests/objcexport/noAutorelease.swift index adf5b0c02d1..184d4c37e1f 100644 --- a/kotlin-native/backend.native/tests/objcexport/noAutorelease.swift +++ b/kotlin-native/backend.native/tests/objcexport/noAutorelease.swift @@ -40,6 +40,12 @@ private class NoAutoreleaseSwiftHelper : NoAutoreleaseSendHelper, NoAutoreleaseR swiftLivenessTracker.add(kotlinObject) } + func blockReceivingKotlinObject() -> (KotlinObject) -> Void { + return { + self.swiftLivenessTracker.add($0) + } + } + func sendSwiftObject(swiftObject: Any) { swiftLivenessTracker.add(swiftObject as AnyObject) } @@ -57,6 +63,10 @@ private class NoAutoreleaseSwiftHelper : NoAutoreleaseSendHelper, NoAutoreleaseR func sendBlock(block: @escaping () -> KotlinObject) { } + func sendCompletion(completionHandler: @escaping (Any?, Error?) -> Void) { + completionHandler(nil, nil) + } + let kotlinObject = KotlinObject() let swiftObject = SwiftObject2() let list = createList() @@ -220,6 +230,12 @@ private func testSendKotlinObjectToKotlin() throws { } } +private func testSendKotlinObjectToKotlinBlock() throws { + try testSendToKotlin({ KotlinObject() }) { + $0.blockReceivingKotlinObject()($1) + } +} + private func testSendSwiftObjectToKotlin() throws { try testSendToKotlin({ SwiftObject1() }) { $0.sendSwiftObject(swiftObject: $1) @@ -250,6 +266,12 @@ private func testSendBlockToKotlin() throws { } } +private func testSendCompletionToKotlin() throws { + try testSendToKotlin({ createCompletion() }, flags: TestFlags(trackSwiftLifetime: false)) { + $0.sendCompletion(completionHandler: $1) + } +} + private func testReceiveKotlinObjectFromKotlin() throws { try testReceiveFromKotlin { $0.receiveKotlinObject() @@ -328,6 +350,15 @@ private func testSendKotlinObjectToSwift() throws { } } +private func testSendKotlinObjectToSwiftBlock() throws { + // Getting Swift block in Kotlin still adds it to the autoreleasepool. + // The block is not our target, so just use `checkAutorelease: false` flag + // to disable the relevant check for now. + try testCallToSwift(flags: TestFlags(checkAutorelease: false)) { + NoAutoreleaseKt.sendKotlinObjectToBlock(helper: $0, tracker: $1) + } +} + private func testSendSwiftObjectToSwift() throws { try testCallToSwift { NoAutoreleaseKt.callSendSwiftObject(helper: $0, tracker: $1, swiftObject: SwiftObject4()) @@ -361,6 +392,12 @@ private func testSendBlockToSwift() throws { } } +private func testSendCompletionToSwift() throws { + try testCallToSwift(flags: TestFlags(trackSwiftLifetime: false)) { + NoAutoreleaseKt.callSendCompletion(helper: $0, tracker: $1) + } +} + private func testReceiveKotlinObjectFromSwift() throws { try testCallToSwift(flags: TestFlags(runGCTwice: true)) { NoAutoreleaseKt.callReceiveKotlinObject(helper: $0, tracker: $1) @@ -421,6 +458,10 @@ private func createBlock(swiftLivenessTracker: SwiftLivenessTracker) -> () -> Ko } } +private func createCompletion() -> (Any?, Error?) -> Void { + return { _, _ in } +} + private func createString() -> String { return NSObject().description // Make it dynamic. } @@ -434,11 +475,13 @@ class NoAutoreleaseTests : SimpleTestProvider { super.init() test("testSendKotlinObjectToKotlin", testSendKotlinObjectToKotlin) + test("testSendKotlinObjectToKotlinBlock", testSendKotlinObjectToKotlinBlock) test("testSendSwiftObjectToKotlin", testSendSwiftObjectToKotlin) test("testSendListToKotlin", testSendListToKotlin) test("testSendStringToKotlin", testSendStringToKotlin) test("testSendNumberToKotlin", testSendNumberToKotlin) test("testSendBlockToKotlin", testSendBlockToKotlin) + test("testSendCompletionToKotlin", testSendCompletionToKotlin) test("testReceiveKotlinObjectFromKotlin", testReceiveKotlinObjectFromKotlin) test("testReceiveSwiftObjectFromKotlin", testReceiveSwiftObjectFromKotlin) @@ -454,18 +497,16 @@ class NoAutoreleaseTests : SimpleTestProvider { test("testGetKotlinSingleton", testGetKotlinSingleton) test("testGetKotlinEnumEntry", testGetKotlinEnumEntry) -#if false test("testSendKotlinObjectToSwift", testSendKotlinObjectToSwift) -#endif - + test("testSendKotlinObjectToSwiftBlock", testSendKotlinObjectToSwiftBlock) test("testSendSwiftObjectToSwift", testSendSwiftObjectToSwift) - -#if false test("testSendListToSwift", testSendListToSwift) test("testSendStringToSwift", testSendStringToSwift) test("testSendNumberToSwift", testSendNumberToSwift) test("testSendBlockToSwift", testSendBlockToSwift) + test("testSendCompletionToSwift", testSendCompletionToSwift) +#if false test("testReceiveKotlinObjectFromSwift", testReceiveKotlinObjectFromSwift) test("testReceiveSwiftObjectFromSwift", testReceiveSwiftObjectFromSwift) test("testReceiveListFromSwift", testReceiveListFromSwift)