From 7bebb8d960d7e3837f171936639517d3776a33ca Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Tue, 9 Jan 2018 17:06:38 +0300 Subject: [PATCH] Expose object instances and enum entries to produced framework #KT-21892 Fixed. Fix #1165 Also handle some clashes with NSObject method names. --- .../konan/EnumSpecialDescriptorsFactory.kt | 4 +- .../konan/descriptors/DescriptorUtils.kt | 8 ++ .../backend/konan/llvm/CodeGenerator.kt | 26 ++++++ .../objcexport/ObjCExportCodeGenerator.kt | 81 ++++++++++++++++--- .../objcexport/ObjCExportHeaderGenerator.kt | 29 +++++-- .../konan/objcexport/ObjCExportNamer.kt | 74 ++++++++++++++++- 6 files changed, 199 insertions(+), 23 deletions(-) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/EnumSpecialDescriptorsFactory.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/EnumSpecialDescriptorsFactory.kt index 2faac36f69f..24e638c225f 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/EnumSpecialDescriptorsFactory.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/EnumSpecialDescriptorsFactory.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.backend.konan import org.jetbrains.kotlin.backend.jvm.descriptors.initialize import org.jetbrains.kotlin.backend.konan.descriptors.synthesizedName import org.jetbrains.kotlin.backend.common.ir.createSimpleDelegatingConstructorDescriptor +import org.jetbrains.kotlin.backend.konan.descriptors.enumEntries import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.descriptors.impl.* @@ -127,8 +128,7 @@ internal class EnumSpecialDeclarationsFactory(val context: Context) { private fun createEnumEntriesMap(enumClassDescriptor: ClassDescriptor): Map { val map = mutableMapOf() - enumClassDescriptor.unsubstitutedMemberScope.getContributedDescriptors() - .filter { it is ClassDescriptor && it.kind == ClassKind.ENUM_ENTRY } + enumClassDescriptor.enumEntries .sortedBy { it.name } .forEachIndexed { index, entry -> map.put(entry.name, index) } return map diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt index 9ec2c4d3d0f..5a46d6ba4cc 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt @@ -336,3 +336,11 @@ fun ModuleDescriptor.getPackageFragments(): List = getPackagesFqNames(this).flatMap { getPackage(it).fragments.filter { it.module == this } } + +val ClassDescriptor.enumEntries: List + get() { + assert(this.kind == ClassKind.ENUM_CLASS) + return this.unsubstitutedMemberScope.getContributedDescriptors() + .filterIsInstance() + .filter { it.kind == ClassKind.ENUM_ENTRY } + } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt index ee858d32460..36a2b8c1321 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt @@ -27,6 +27,7 @@ import org.jetbrains.kotlin.backend.konan.isObjCClass import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.FunctionDescriptor internal class CodeGenerator(override val context: Context) : ContextUtils { @@ -596,6 +597,31 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, return valuePhi } + /** + * Note: the same code is generated as IR in [org.jetbrains.kotlin.backend.konan.lower.EnumUsageLowering]. + */ + fun getEnumEntry(descriptor: ClassDescriptor, exceptionHandler: ExceptionHandler): LLVMValueRef { + assert(descriptor.kind == ClassKind.ENUM_ENTRY) + + val enumClassDescriptor = descriptor.containingDeclaration as ClassDescriptor + val loweredEnum = context.specialDeclarationsFactory.getLoweredEnum(enumClassDescriptor) + + val ordinal = loweredEnum.entriesMap[descriptor.name]!! + val values = call( + loweredEnum.valuesGetter.descriptor.original.llvmFunction, + emptyList(), + Lifetime.ARGUMENT, + exceptionHandler + ) + + return call( + loweredEnum.itemGetterDescriptor.original.llvmFunction, + listOf(values, Int32(ordinal).llvm), + Lifetime.GLOBAL, + exceptionHandler + ) + } + fun resetDebugLocation() { if (!context.shouldContainDebugInfo()) return if (!currentPositionHolder.isAfterTerminator) 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 030cc0abb72..fefeebfb918 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 @@ -137,6 +137,10 @@ internal class ObjCExportCodeGenerator( } } + fun FunctionGenerationContext.initRuntimeIfNeeded() { + callFromBridge(context.llvm.initRuntimeIfNeeded, emptyList()) + } + inline fun FunctionGenerationContext.convertKotlin( genValue: (Lifetime) -> LLVMValueRef, actualType: KotlinType, @@ -440,8 +444,7 @@ private fun ObjCExportCodeGenerator.generateObjCImp( // TODO: check for abstract class if it is a constructor. if (methodBridge.isKotlinTopLevel) { - callFromBridge(context.llvm.initRuntimeIfNeeded, emptyList()) - // For instance methods it gets called when allocating. + initRuntimeIfNeeded() // For instance methods it gets called when allocating. } if (target == null) { @@ -494,7 +497,7 @@ private fun ObjCExportCodeGenerator.generateObjCImpForArrayConstructor( val result = LLVMAddFunction(context.llvmModule, "", objCFunctionType(methodBridge))!! generateFunction(codegen, result) { - callFromBridge(context.llvm.initRuntimeIfNeeded, emptyList()) + initRuntimeIfNeeded() // For instance methods it gets called when allocating. val kotlinValueArgs = methodBridge.paramBridges .drop(1) // Drop class method receiver. @@ -786,8 +789,22 @@ private fun ObjCExportCodeGenerator.createTypeAdapter( emptyList() } - if (descriptor.isUnit()) { - classAdapters += createUnitInstanceAdapter() + when (descriptor.kind) { + ClassKind.OBJECT -> { + classAdapters += if (descriptor.isUnit()) { + createUnitInstanceAdapter() + } else { + createObjectInstanceAdapter(descriptor) + } + } + ClassKind.ENUM_CLASS -> { + descriptor.enumEntries.mapTo(classAdapters) { + createEnumEntryAdapter(it) + } + } + else -> { + // Nothing special. + } } return ObjCTypeAdapter( @@ -832,13 +849,14 @@ private fun ObjCExportCodeGenerator.createDirectAdapters( } } -private fun ObjCExportCodeGenerator.createUnitInstanceAdapter(): ObjCExportCodeGenerator.ObjCToKotlinMethodAdapter { - val selector = "unit" - val methodBridge = MethodBridge(ReferenceBridge, listOf(ReferenceBridge, ReferenceBridge)) +private inline fun ObjCExportCodeGenerator.generateObjCToKotlinMethodAdapter( + methodBridge: MethodBridge, + selector: String, + block: FunctionGenerationContext.() -> Unit +): ObjCExportCodeGenerator.ObjCToKotlinMethodAdapter { val encoding = getEncoding(methodBridge) - val imp = generateFunction(codegen, objCFunctionType(methodBridge), "") { - ret(callFromBridge(context.llvm.Kotlin_ObjCExport_convertUnit, listOf(codegen.theUnitInstanceRef.llvm))) + block() } LLVMSetLinkage(imp, LLVMLinkage.LLVMPrivateLinkage) @@ -846,6 +864,49 @@ private fun ObjCExportCodeGenerator.createUnitInstanceAdapter(): ObjCExportCodeG return ObjCToKotlinMethodAdapter(selector, encoding, constPointer(imp)) } +private fun ObjCExportCodeGenerator.createUnitInstanceAdapter() = + generateObjCToKotlinMethodAdapter( + MethodBridge(ReferenceBridge, listOf(ReferenceBridge, ReferenceBridge)), + namer.getObjectInstanceSelector(context.builtIns.unit) + ) { + initRuntimeIfNeeded() // For instance methods it gets called when allocating. + + ret(callFromBridge(context.llvm.Kotlin_ObjCExport_convertUnit, listOf(codegen.theUnitInstanceRef.llvm))) + } + +private fun ObjCExportCodeGenerator.createObjectInstanceAdapter( + descriptor: ClassDescriptor +): ObjCExportCodeGenerator.ObjCToKotlinMethodAdapter { + assert(descriptor.kind == ClassKind.OBJECT) + assert(!descriptor.isUnit()) + + val selector = namer.getObjectInstanceSelector(descriptor) + val methodBridge = MethodBridge(ReferenceBridge, listOf(ReferenceBridge, ReferenceBridge)) + + return generateObjCToKotlinMethodAdapter(methodBridge, selector) { + initRuntimeIfNeeded() // For instance methods it gets called when allocating. + + val value = getObjectValue(descriptor, ExceptionHandler.Caller, locationInfo = null) + ret(kotlinToObjC(value, ReferenceBridge)) + } +} + +private fun ObjCExportCodeGenerator.createEnumEntryAdapter( + descriptor: ClassDescriptor +): ObjCExportCodeGenerator.ObjCToKotlinMethodAdapter { + assert(descriptor.kind == ClassKind.ENUM_ENTRY) + + val selector = namer.getEnumEntrySelector(descriptor) + val methodBridge = MethodBridge(ReferenceBridge, listOf(ReferenceBridge, ReferenceBridge)) + + return generateObjCToKotlinMethodAdapter(methodBridge, selector) { + initRuntimeIfNeeded() // For instance methods it gets called when allocating. + + val value = getEnumEntry(descriptor, ExceptionHandler.Caller) + ret(kotlinToObjC(value, ReferenceBridge)) + } +} + private fun List.toMethods(): List = this.flatMap { when (it) { is PropertyDescriptor -> listOfNotNull(it.getter, it.setter) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt index 173353bd42e..c0735f19246 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt @@ -18,10 +18,10 @@ package org.jetbrains.kotlin.backend.konan.objcexport import org.jetbrains.kotlin.backend.konan.Context import org.jetbrains.kotlin.backend.konan.KonanCompilationException +import org.jetbrains.kotlin.backend.konan.descriptors.enumEntries import org.jetbrains.kotlin.backend.konan.descriptors.getPackageFragments import org.jetbrains.kotlin.backend.konan.descriptors.isArray import org.jetbrains.kotlin.backend.konan.descriptors.isInterface -import org.jetbrains.kotlin.backend.konan.descriptors.isUnit import org.jetbrains.kotlin.backend.konan.reportCompilationError import org.jetbrains.kotlin.builtins.getReceiverTypeFromFunctionType import org.jetbrains.kotlin.builtins.getReturnTypeFromFunctionType @@ -203,11 +203,6 @@ internal class ObjCExportHeaderGenerator(val context: Context) { +"@interface $name : $superName${descriptor.superProtocolsClause}" - if (descriptor.isUnit()) { - +"+ (instancetype)unit NS_SWIFT_NAME(init());" - +"" - } - val presentConstructors = mutableSetOf() descriptor.constructors.filter { mapper.shouldBeExposed(it) }.forEach { @@ -217,12 +212,32 @@ internal class ObjCExportHeaderGenerator(val context: Context) { +"" } - if (descriptor.isArray) { + if (descriptor.isArray || descriptor.kind == ClassKind.OBJECT || descriptor.kind == ClassKind.ENUM_CLASS) { +"+(instancetype)alloc __attribute__((unavailable));" +"+(instancetype)allocWithZone:(struct _NSZone *)zone __attribute__((unavailable));" +"" } + // TODO: consider adding exception-throwing impls for these. + when (descriptor.kind) { + ClassKind.OBJECT -> { + +"+(instancetype)${namer.getObjectInstanceSelector(descriptor)} NS_SWIFT_NAME(init());" + +"" + } + ClassKind.ENUM_CLASS -> { + val type = mapType(descriptor.defaultType, ReferenceBridge) + + descriptor.enumEntries.forEach { + val entryName = namer.getEnumEntrySelector(it) + +"@property (class, readonly) ${type.render(entryName)};" + } + +"" + } + else -> { + // Nothing special. + } + } + // Hide "unimplemented" super constructors: superClass?.constructors?.filter { mapper.shouldBeExposed(it) }?.forEach { if (getSelector(it) !in presentConstructors) { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt index 99b174dd5bc..da197cc9c11 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt @@ -35,6 +35,17 @@ internal class ObjCExportNamer(val context: Context, val mapper: ObjCExportMappe private val topLevelNamePrefix = context.moduleDescriptor.namePrefix private val methodSelectors = object : Mapping() { + + // Try to avoid clashing with critical NSObject instance methods: + + private val reserved = setOf( + "retain", "release", "autorelease", + "class", "superclass", + "hash" + ) + + override fun reserved(name: String) = name in reserved + override fun conflict(first: FunctionDescriptor, second: FunctionDescriptor): Boolean = !mapper.canHaveSameSelector(first, second) } @@ -58,6 +69,30 @@ internal class ObjCExportNamer(val context: Context, val mapper: ObjCExportMappe override fun conflict(first: Any, second: Any): Boolean = true } + private abstract inner class ClassPropertyNameMapping : Mapping() { + + // Try to avoid clashing with NSObject class methods: + + private val reserved = setOf( + "retain", "release", "autorelease", + "initialize", "load", "alloc", "new", "class", "superclass", + "classFallbacksForKeyedArchiver", "classForKeyedUnarchiver", + "description", "debugDescription", "version", "hash", + "useStoredAccessor" + ) + + override fun reserved(name: String) = name in reserved + } + + private val objectInstanceSelectors = object : ClassPropertyNameMapping() { + override fun conflict(first: ClassDescriptor, second: ClassDescriptor) = false + } + + private val enumEntrySelectors = object : ClassPropertyNameMapping() { + override fun conflict(first: ClassDescriptor, second: ClassDescriptor) = + first.containingDeclaration == second.containingDeclaration + } + fun getPackageName(fqName: FqName): String = classNames.getOrPut(fqName) { StringBuilder().apply { append(topLevelNamePrefix) @@ -168,6 +203,30 @@ internal class ObjCExportNamer(val context: Context, val mapper: ObjCExportMappe } } + fun getObjectInstanceSelector(descriptor: ClassDescriptor): String { + assert(descriptor.kind == ClassKind.OBJECT) + + return objectInstanceSelectors.getOrPut(descriptor) { + val name = descriptor.name.asString().decapitalize().mangleIfSpecialFamily("get") + + StringBuilder(name).mangledSequence { append("_") } + } + } + + fun getEnumEntrySelector(descriptor: ClassDescriptor): String { + assert(descriptor.kind == ClassKind.ENUM_ENTRY) + + return enumEntrySelectors.getOrPut(descriptor) { + // FOO_BAR_BAZ -> fooBarBaz: + val name = descriptor.name.asString().split('_').mapIndexed { index, s -> + val lower = s.toLowerCase() + if (index == 0) lower else lower.capitalize() + }.joinToString("").mangleIfSpecialFamily("the") + + StringBuilder(name).mangledSequence { append("_") } + } + } + init { val any = context.builtIns.any @@ -204,18 +263,22 @@ internal class ObjCExportNamer(val context: Context, val mapper: ObjCExportMappe else -> this.name.asString() } - val trimmedCandidate = candidate.dropWhile { it == '_' } + return candidate.mangleIfSpecialFamily("do") + } + + private fun String.mangleIfSpecialFamily(prefix: String): String { + val trimmed = this.dropWhile { it == '_' } for (family in listOf("alloc", "copy", "mutableCopy", "new", "init")) { - if (trimmedCandidate.startsWithWords(family)) { + if (trimmed.startsWithWords(family)) { // Then method can be detected as having special family by Objective-C compiler. // mangle the name: - return "do" + candidate.capitalize() + return prefix + this.capitalize() } } // TODO: handle clashes with NSObject methods etc. - return candidate + return this } private fun String.startsWithWords(words: String) = this.startsWith(words) && @@ -226,6 +289,7 @@ internal class ObjCExportNamer(val context: Context, val mapper: ObjCExportMappe private val nameToElements = mutableMapOf>() abstract fun conflict(first: T, second: T): Boolean + open fun reserved(name: N) = false fun getOrPut(element: T, nameCandidates: () -> Sequence): N { getIfAssigned(element)?.let { return it } @@ -244,6 +308,8 @@ internal class ObjCExportNamer(val context: Context, val mapper: ObjCExportMappe fun tryAssign(element: T, name: N): Boolean { if (element in elementToName) error(element) + if (reserved(name)) return false + val elements = nameToElements.getOrPut(name) { mutableListOf() } if (elements.any { conflict(element, it) }) { return false