diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt index adaa156de01..cd274a85953 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt @@ -487,6 +487,9 @@ private fun ObjCExportCodeGenerator.setObjCExportTypeInfo( private val ObjCExportCodeGenerator.kotlinToObjCFunctionType: LLVMTypeRef get() = functionType(int8TypePtr, false, codegen.kObjHeaderPtr) +private val ObjCExportCodeGenerator.objCToKotlinFunctionType: LLVMTypeRef + get() = functionType(codegen.kObjHeaderPtr, false, int8TypePtr, codegen.kObjHeaderPtrPtr) + private fun ObjCExportCodeGenerator.emitBoxConverters() { val irBuiltIns = context.irBuiltIns @@ -537,22 +540,38 @@ private fun ObjCExportCodeGenerator.emitFunctionConverters() { } private fun ObjCExportCodeGenerator.emitBlockToKotlinFunctionConverters() { - val converters = context.ir.symbols.functionIrClassFactory.builtFunctionNClasses.map { - val bridge = BlockPointerBridge(numberOfParameters = it.arity, returnsVoid = false) - constPointer(blockToKotlinFunctionConverter(bridge)) + val functionClassesByArity = + context.ir.symbols.functionIrClassFactory.builtFunctionNClasses.associateBy { it.arity } + + val count = (functionClassesByArity.keys.max() ?: -1) + 1 + + val converters = (0 until count).map { arity -> + val functionClass = functionClassesByArity[arity] + if (functionClass != null) { + val bridge = BlockPointerBridge(numberOfParameters = functionClass.arity, returnsVoid = false) + constPointer(blockToKotlinFunctionConverter(bridge)) + } else { + NullPointer(objCToKotlinFunctionType) + } } + val ptr = staticData.placeGlobalArray( "", - converters.first().llvmType, + pointerType(objCToKotlinFunctionType), converters ).pointer.getElementPtr(0) - // Note: this global replaces the weak global defined in runtime. + // Note: replacing weak globals defined in runtime. replaceExternalWeakOrCommonGlobal( "Kotlin_ObjCExport_blockToFunctionConverters", ptr, context.standardLlvmSymbolsOrigin ) + replaceExternalWeakOrCommonGlobal( + "Kotlin_ObjCExport_blockToFunctionConverters_size", + Int32(count), + context.standardLlvmSymbolsOrigin + ) } private fun ObjCExportCodeGenerator.emitSpecialClassesConvertions() { diff --git a/backend.native/tests/framework/values/values.kt b/backend.native/tests/framework/values/values.kt index 224e185aa4b..d533a70e81e 100644 --- a/backend.native/tests/framework/values/values.kt +++ b/backend.native/tests/framework/values/values.kt @@ -16,6 +16,9 @@ import kotlin.reflect.KClass import kotlin.reflect.KProperty import kotlinx.cinterop.* +// Ensure loaded function IR classes aren't ordered by arity: +internal fun referenceFunction1(block: (Any?) -> Unit) {} + // Constants const val dbl: Double = 3.14 const val flt: Float = 2.73F diff --git a/backend.native/tests/framework/values/values.swift b/backend.native/tests/framework/values/values.swift index 1b43568752f..c9860ab0818 100644 --- a/backend.native/tests/framework/values/values.swift +++ b/backend.native/tests/framework/values/values.swift @@ -392,6 +392,13 @@ func testLambda() throws { try assertTrue(ValuesKt.isFunction0(obj: blockMustBeFunction0)) try assertFalse(ValuesKt.isFunction(obj: NSObject())) try assertFalse(ValuesKt.isFunction0(obj: NSObject())) + + // Test no function class for dynamic conversion: + let blockAsMissingFunction: @convention(block) (AnyObject?, AnyObject?, AnyObject?, AnyObject?, AnyObject?) -> AnyObject? + = { return $0 ?? $1 ?? $2 ?? $3 ?? $4 } + + try assertTrue(ValuesKt.isFunction(obj: blockAsMissingFunction)) + try assertFalse(ValuesKt.isFunction0(obj: blockAsMissingFunction)) } // -------- Tests for classes and interfaces ------- diff --git a/runtime/src/main/cpp/ObjCExport.mm b/runtime/src/main/cpp/ObjCExport.mm index 55ee2fbf158..e9c512a2bb9 100644 --- a/runtime/src/main/cpp/ObjCExport.mm +++ b/runtime/src/main/cpp/ObjCExport.mm @@ -440,6 +440,9 @@ static const char* getBlockEncoding(id block) { // Note: replaced by compiler in appropriate compilation modes. __attribute__((weak)) convertReferenceFromObjC* Kotlin_ObjCExport_blockToFunctionConverters = nullptr; +__attribute__((weak)) int Kotlin_ObjCExport_blockToFunctionConverters_size = 0; + +extern "C" id objc_retainBlock(id self); static OBJ_GETTER(blockToKotlinImp, id block, SEL cmd) { const char* encoding = getBlockEncoding(block); @@ -448,10 +451,6 @@ static OBJ_GETTER(blockToKotlinImp, id block, SEL cmd) { NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:encoding]; int parameterCount = signature.numberOfArguments - 1; // 1 for the block itself. - if (parameterCount > 22) { - [NSException raise:NSGenericException format:@"Blocks with %d (>22) parameters aren't supported", parameterCount]; - } - for (int i = 1; i <= parameterCount; ++i) { const char* argEncoding = [signature getArgumentTypeAtIndex:i]; if (argEncoding[0] != '@') { @@ -466,7 +465,17 @@ static OBJ_GETTER(blockToKotlinImp, id block, SEL cmd) { format:@"Blocks with non-reference-typed return value aren't supported (%s)", returnTypeEncoding]; } - RETURN_RESULT_OF(Kotlin_ObjCExport_blockToFunctionConverters[parameterCount], block); + auto converter = parameterCount < Kotlin_ObjCExport_blockToFunctionConverters_size + ? Kotlin_ObjCExport_blockToFunctionConverters[parameterCount] + : nullptr; + + if (converter != nullptr) { + RETURN_RESULT_OF(converter, block); + } else { + // There is no function class for this arity, so resulting object will not be cast to FunctionN class, + // and it is enough to convert block to arbitrary object conforming Function. + RETURN_RESULT_OF(AllocInstanceWithAssociatedObject, theOpaqueFunctionTypeInfo, objc_retainBlock(block)); + } } static id Kotlin_ObjCExport_refToObjC_slowpath(ObjHeader* obj); diff --git a/runtime/src/main/cpp/Types.h b/runtime/src/main/cpp/Types.h index 8aa17aed174..199d8a9ea9f 100644 --- a/runtime/src/main/cpp/Types.h +++ b/runtime/src/main/cpp/Types.h @@ -96,6 +96,7 @@ extern const TypeInfo* theFloatArrayTypeInfo; extern const TypeInfo* theForeignObjCObjectTypeInfo; extern const TypeInfo* theFreezableAtomicReferenceTypeInfo; extern const TypeInfo* theObjCObjectWrapperTypeInfo; +extern const TypeInfo* theOpaqueFunctionTypeInfo; extern const TypeInfo* theShortArrayTypeInfo; extern const TypeInfo* theStringTypeInfo; extern const TypeInfo* theThrowableTypeInfo; diff --git a/runtime/src/main/kotlin/kotlin/native/internal/ObjCExportUtils.kt b/runtime/src/main/kotlin/kotlin/native/internal/ObjCExportUtils.kt index 33a7eede1e1..f102045ef6f 100644 --- a/runtime/src/main/kotlin/kotlin/native/internal/ObjCExportUtils.kt +++ b/runtime/src/main/kotlin/kotlin/native/internal/ObjCExportUtils.kt @@ -295,3 +295,7 @@ private fun Kotlin_Throwable_getMessage(throwable: Throwable): String? = throwab @ExportForCppRuntime private fun Kotlin_ObjCExport_getWrappedError(throwable: Throwable): Any? = (throwable as? ObjCErrorException)?.error + +@ExportTypeInfo("theOpaqueFunctionTypeInfo") +@PublishedApi +internal class OpaqueFunction : Function