diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCDataGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCDataGenerator.kt index 9c9d7bc5fe3..b32bb5ba38c 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCDataGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCDataGenerator.kt @@ -89,10 +89,17 @@ internal class ObjCDataGenerator(val codegen: CodeGenerator) { ) fun emitEmptyClass(name: String, superName: String) { + emitClass(name, superName, instanceMethods = emptyList()) + } + + class Method(val selector: String, val encoding: String, val imp: ConstPointer) + + fun emitClass(name: String, superName: String, instanceMethods: List) { val runtime = context.llvm.runtime fun struct(name: String) = runtime.getStructType(name) val classRoType = struct("_class_ro_t") + val methodType = struct("_objc_method") val methodListType = struct("__method_list_t") val protocolListType = struct("_objc_protocol_list") val ivarListType = struct("_ivar_list_t") @@ -100,6 +107,31 @@ internal class ObjCDataGenerator(val codegen: CodeGenerator) { val classNameLiteral = classNames.get(name) + fun emitInstanceMethodList(): ConstPointer { + if (instanceMethods.isEmpty()) return NullPointer(methodListType) + + val methodStructs = instanceMethods.map { + Struct(methodType, selectors.get(it.selector), encodings.get(it.encoding), it.imp.bitcast(int8TypePtr)) + } + + val methodList = Struct( + Int32(LLVMABISizeOfType(codegen.llvmTargetData, methodType).toInt()), + Int32(instanceMethods.size), + ConstArray(methodType, methodStructs) + ) + + val globalName = "\u0001l_OBJC_\$_INSTANCE_METHODS_$name" + val global = context.llvm.staticData.placeGlobal(globalName, methodList).also { + it.setLinkage(LLVMLinkage.LLVMPrivateLinkage) + it.setAlignment(runtime.pointerAlignment) + it.setSection("__DATA, __objc_const") + } + + context.llvm.compilerUsedGlobals += global.llvmGlobal + + return global.pointer.bitcast(pointerType(methodListType)) + } + fun buildClassRo(isMetaclass: Boolean): ConstPointer { // TODO: add NonFragileABI_Class_CompiledByARC flag? @@ -124,7 +156,7 @@ internal class ObjCDataGenerator(val codegen: CodeGenerator) { fields += Int32(size) fields += NullPointer(int8Type) // ivar layout name fields += classNameLiteral - fields += NullPointer(methodListType) + fields += if (isMetaclass) NullPointer(methodListType) else emitInstanceMethodList() fields += NullPointer(protocolListType) fields += NullPointer(ivarListType) fields += NullPointer(int8Type) // ivar layout @@ -220,6 +252,9 @@ internal class ObjCDataGenerator(val codegen: CodeGenerator) { private val selectors = CStringLiteralsTable("OBJC_METH_VAR_NAME_", "__TEXT,__objc_methname,cstring_literals") + private val encodings = + CStringLiteralsTable("OBJC_METH_VAR_TYPE_", "__TEXT,__objc_methtype,cstring_literals") + private inner class CStringLiteralsTable(val label: String, val section: String) { private val literals = mutableMapOf() 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 fbcccf5fee2..ec57a0e2030 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 @@ -13,6 +13,7 @@ import org.jetbrains.kotlin.backend.konan.descriptors.* import org.jetbrains.kotlin.backend.konan.ir.constructedClass import org.jetbrains.kotlin.backend.konan.llvm.* import org.jetbrains.kotlin.backend.konan.llvm.objc.ObjCCodeGenerator +import org.jetbrains.kotlin.backend.konan.llvm.objc.ObjCDataGenerator import org.jetbrains.kotlin.backend.konan.objcexport.* import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.konan.CurrentKonanModuleOrigin @@ -52,6 +53,8 @@ internal class ObjCExportCodeGenerator( } } + val referencedSelectors = mutableMapOf() + // TODO: currently bridges don't have any custom `landingpad`s, // so it is correct to use [callAtFunctionScope] here. // However, exception handling probably should be refactored @@ -231,6 +234,20 @@ internal class ObjCExportCodeGenerator( emitSortedAdapters(placedClassAdapters, "Kotlin_ObjCExport_sortedClassAdapters") emitSortedAdapters(placedInterfaceAdapters, "Kotlin_ObjCExport_sortedProtocolAdapters") + emitSelectorsHolder() + } + + private fun emitSelectorsHolder() { + val impType = functionType(voidType, false, int8TypePtr, int8TypePtr) + val imp = generateFunction(codegen, impType, "") { + unreachable() + } + + val methods = referencedSelectors.map { (selector, bridge) -> + ObjCDataGenerator.Method(selector, getEncoding(bridge), constPointer(imp)) + } + + dataGenerator.emitClass("KotlinSelectorsHolder", "NSObject", instanceMethods = methods) } private val impType = pointerType(functionType(int8TypePtr, true, int8TypePtr, int8TypePtr)) @@ -674,7 +691,11 @@ private fun ObjCExportCodeGenerator.generateKotlinToObjCBridge( } MethodBridgeReceiver.Instance -> kotlinReferenceToObjC(parameters[parameter]!!) - MethodBridgeSelector -> genSelector(namer.getSelector(baseMethod)) + MethodBridgeSelector -> { + val selector = namer.getSelector(baseMethod) + referencedSelectors.getOrPut(selector) { methodBridge } + genSelector(selector) + } MethodBridgeReceiver.Static, MethodBridgeReceiver.Factory ->