From 324f54ba1e6fb88581f4ed0abe1116a4464153ba Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Tue, 29 Aug 2017 15:01:48 +0300 Subject: [PATCH] Implement @ExportObjCClass --- .../kotlinx/cinterop/ObjectiveCUtils.kt | 11 ++++ .../kotlin/backend/konan/InteropUtils.kt | 26 ++++----- .../konan/descriptors/DescriptorUtils.kt | 11 ++-- .../llvm/KotlinObjCClassInfoGenerator.kt | 20 +++++-- .../backend/konan/lower/InteropLowering.kt | 10 ++++ .../org/jetbrains/kotlin/ir/util/IrUtils2.kt | 53 +++++++++++++++++++ 6 files changed, 110 insertions(+), 21 deletions(-) diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCUtils.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCUtils.kt index 9ff90565ae9..fbc0f38d8d0 100644 --- a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCUtils.kt +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCUtils.kt @@ -45,3 +45,14 @@ annotation class ObjCAction @Target(AnnotationTarget.PROPERTY) @Retention(AnnotationRetention.SOURCE) annotation class ObjCOutlet + +/** + * Makes Kotlin subclass of Objective-C class visible for runtime lookup + * after Kotlin `main` function gets invoked. + * + * Note: runtime lookup can be forced even when the class is referenced statically from + * Objective-C source code by adding `__attribute__((objc_runtime_visible))` to its `@interface`. + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.SOURCE) +annotation class ExportObjCClass(val name: String = "") diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt index 754b4678702..eeb76d87b83 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt @@ -43,9 +43,9 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) { val nullableInteropValueTypes = listOf(ValueType.C_POINTER, ValueType.NATIVE_POINTED) - private val nativePointed = packageScope.getContributedClassifier(nativePointedName) as ClassDescriptor + private val nativePointed = packageScope.getContributedClass(nativePointedName) - val cPointer = this.packageScope.getContributedClassifier(cPointerName) as ClassDescriptor + val cPointer = this.packageScope.getContributedClass(cPointerName) val cPointerRawValue = cPointer.unsubstitutedMemberScope.getContributedVariables("rawValue").single() @@ -70,7 +70,7 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) { val typeOf = packageScope.getContributedFunctions("typeOf").single() - val nativeMemUtils = packageScope.getContributedClassifier("nativeMemUtils") as ClassDescriptor + val nativeMemUtils = packageScope.getContributedClass("nativeMemUtils") private val primitives = listOf( builtIns.byte, builtIns.short, builtIns.int, builtIns.long, @@ -94,8 +94,8 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) { val workerPackageScope = builtIns.builtInsModule.getPackage(FqName("konan.worker")).memberScope - val scheduleFunction = (workerPackageScope.getContributedClassifier("Worker") as ClassDescriptor). - unsubstitutedMemberScope.getContributedFunctions("schedule").single() + val scheduleFunction = workerPackageScope.getContributedClass("Worker") + .unsubstitutedMemberScope.getContributedFunctions("schedule").single() val scheduleImplFunction = workerPackageScope.getContributedFunctions("scheduleImpl").single() @@ -128,8 +128,8 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) { packageScope.getContributedFunctions(name).single() }.toMap() - val objCObject = packageScope.getContributedClassifier("ObjCObject") as ClassDescriptor - val objCPointerHolder = packageScope.getContributedClassifier("ObjCPointerHolder") as ClassDescriptor + val objCObject = packageScope.getContributedClass("ObjCObject") + val objCPointerHolder = packageScope.getContributedClass("ObjCPointerHolder") val objCPointerHolderValue = objCPointerHolder.unsubstitutedMemberScope .getContributedDescriptors().filterIsInstance().single() @@ -157,19 +157,21 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) { val objCObjectSuperInitCheck = packageScope.getContributedFunctions("superInitCheck").single() val objCObjectInitBy = packageScope.getContributedFunctions("initBy").single() - val objCAction = packageScope.getContributedClassifier("ObjCAction") as ClassDescriptor + val objCAction = packageScope.getContributedClass("ObjCAction") - val objCOutlet = packageScope.getContributedClassifier("ObjCOutlet") as ClassDescriptor + val objCOutlet = packageScope.getContributedClass("ObjCOutlet") - val objCMethodImp = packageScope.getContributedClassifier("ObjCMethodImp") as ClassDescriptor + val objCMethodImp = packageScope.getContributedClass("ObjCMethodImp") + + val exportObjCClass = packageScope.getContributedClass("ExportObjCClass") } private fun MemberScope.getContributedVariables(name: String) = this.getContributedVariables(Name.identifier(name), NoLookupLocation.FROM_BUILTINS) -private fun MemberScope.getContributedClassifier(name: String) = - this.getContributedClassifier(Name.identifier(name), NoLookupLocation.FROM_BUILTINS) +private fun MemberScope.getContributedClass(name: String): ClassDescriptor = + this.getContributedClassifier(Name.identifier(name), NoLookupLocation.FROM_BUILTINS) as ClassDescriptor private fun MemberScope.getContributedFunctions(name: String) = this.getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BUILTINS) \ No newline at end of file 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 a44a71f5a2b..652ef3cd2db 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 @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.backend.konan.descriptors +import org.jetbrains.kotlin.backend.common.atMostOne import org.jetbrains.kotlin.backend.konan.KonanBuiltIns import org.jetbrains.kotlin.backend.konan.ValueType import org.jetbrains.kotlin.backend.konan.isRepresentedAs @@ -310,9 +311,11 @@ internal val FunctionDescriptor.needsInlining: Boolean internal val FunctionDescriptor.needsSerializedIr: Boolean get() = (this.needsInlining && this.isExported()) -fun AnnotationDescriptor.getStringValue(name: String): String { - val constantValue = this.allValueArguments.entries.single { +fun AnnotationDescriptor.getStringValueOrNull(name: String): String? { + val constantValue = this.allValueArguments.entries.atMostOne { it.key.asString() == name - }.value - return constantValue.value as String + }?.value + return constantValue?.value as String? } + +fun AnnotationDescriptor.getStringValue(name: String): String = this.getStringValueOrNull(name)!! diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/KotlinObjCClassInfoGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/KotlinObjCClassInfoGenerator.kt index d942041bc6b..02b82aa97e8 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/KotlinObjCClassInfoGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/KotlinObjCClassInfoGenerator.kt @@ -21,6 +21,7 @@ import org.jetbrains.kotlin.backend.konan.Context import org.jetbrains.kotlin.backend.konan.ObjCMethodInfo import org.jetbrains.kotlin.backend.konan.descriptors.contributedMethods import org.jetbrains.kotlin.backend.konan.descriptors.getStringValue +import org.jetbrains.kotlin.backend.konan.descriptors.getStringValueOrNull import org.jetbrains.kotlin.backend.konan.getObjCMethodInfo import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ConstructorDescriptor @@ -49,11 +50,7 @@ internal class KotlinObjCClassInfoGenerator(override val context: Context) : Con val bodySize = LLVMStoreSizeOfType(llvmTargetData, context.llvmDeclarations.forClass(descriptor).bodyType).toInt() - val className = if (descriptor.isExported()) { - staticData.cStringLiteral(descriptor.fqNameSafe.asString()) - } else { - NullPointer(int8Type) // Generate as anonymous. - } + val className = selectClassName(descriptor)?.let { staticData.cStringLiteral(it) } ?: NullPointer(int8Type) val info = Struct(runtime.kotlinObjCClassInfo, className, @@ -83,6 +80,19 @@ internal class KotlinObjCClassInfoGenerator(override val context: Context) : Con objCLLvmDeclarations.bodyOffsetGlobal.setInitializer(Int32(0)) } + private fun selectClassName(descriptor: ClassDescriptor): String? { + val exportObjCClassAnnotation = + descriptor.annotations.findAnnotation(context.interopBuiltIns.exportObjCClass.fqNameSafe) + + return if (exportObjCClassAnnotation != null) { + exportObjCClassAnnotation.getStringValueOrNull("name") ?: descriptor.name.asString() + } else if (descriptor.isExported()) { + descriptor.fqNameSafe.asString() + } else { + null // Generate as anonymous. + } + } + private fun createMethodDesc(selector: String, encoding: String, imp: ConstPointer) = Struct( runtime.objCMethodDescription, staticData.cStringLiteral(selector), diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt index 5503e0d64d0..5b3a688aeb8 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt @@ -61,9 +61,14 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme lateinit var currentFile: IrFile + private val topLevelInitializers = mutableListOf() + override fun lower(irFile: IrFile) { currentFile = irFile irFile.transformChildrenVoid(this) + + topLevelInitializers.forEach { irFile.addTopLevelInitializer(it) } + topLevelInitializers.clear() } private fun IrBuilderWithScope.callAlloc(classSymbol: IrClassSymbol): IrExpression { @@ -119,6 +124,11 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme else -> null } }.let { irClass.declarations.addAll(it) } + + if (irClass.descriptor.annotations.hasAnnotation(interop.exportObjCClass.fqNameSafe)) { + val irBuilder = context.createIrBuilder(currentFile.symbol).at(irClass) + topLevelInitializers.add(irBuilder.getObjCClass(irClass.symbol)) + } } private fun generateActionImp(function: IrSimpleFunction): IrSimpleFunction { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt index a8d6c34f3a6..60391057228 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt @@ -16,10 +16,63 @@ package org.jetbrains.kotlin.ir.util +import org.jetbrains.kotlin.backend.konan.descriptors.synthesizedName +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.descriptors.SourceElement +import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.descriptors.annotations.Annotations +import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl +import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin +import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.declarations.impl.IrFieldImpl import org.jetbrains.kotlin.ir.expressions.IrConst import org.jetbrains.kotlin.ir.expressions.IrConstKind import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrTypeOperator +import org.jetbrains.kotlin.ir.expressions.impl.IrExpressionBodyImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrTypeOperatorCallImpl +import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns +import org.jetbrains.kotlin.types.KotlinType //TODO: delete file on next kotlin dependency update internal fun IrExpression.isNullConst() = this is IrConst<*> && this.kind == IrConstKind.Null +private var topLevelInitializersCounter = 0 + +internal fun IrFile.addTopLevelInitializer(expression: IrExpression) { + val fieldDescriptor = PropertyDescriptorImpl.create( + this.packageFragmentDescriptor, + Annotations.EMPTY, + Modality.FINAL, + Visibilities.PRIVATE, + false, + "topLevelInitializer${topLevelInitializersCounter++}".synthesizedName, + CallableMemberDescriptor.Kind.DECLARATION, + SourceElement.NO_SOURCE, + false, + false, + false, + false, + false, + false + ) + + val builtIns = fieldDescriptor.builtIns + fieldDescriptor.setType(builtIns.unitType, emptyList(), null, null as KotlinType?) + fieldDescriptor.initialize(null, null) + + val irField = IrFieldImpl( + expression.startOffset, expression.endOffset, + IrDeclarationOrigin.DEFINED, fieldDescriptor + ) + + val initializer = IrTypeOperatorCallImpl( + expression.startOffset, expression.endOffset, builtIns.unitType, + IrTypeOperator.IMPLICIT_COERCION_TO_UNIT, builtIns.unitType, expression + ) + + irField.initializer = IrExpressionBodyImpl(expression.startOffset, expression.endOffset, initializer) + + this.declarations.add(irField) +}