From ce4eecebf4b41c4aebcaca25997f2772e48bb8c4 Mon Sep 17 00:00:00 2001 From: Sergey Bogolepov Date: Sat, 25 Nov 2023 11:58:08 +0200 Subject: [PATCH] KT-63264: Introduce a new ExportedBridge annotation Swift Export Frontend generates a Kotlin file/library that contains a set of simple bridging functions that connect Swift wrappers to their Kotlin counterparts. There are certain requirements for these wrappers: 1. They should not be DCEd when compiling a binary library. In other words, these functions are roots. 2. They should provide a stable simple binary name. 3. Their signatures should be much simpler and restricted comparing to other Kotlin functions. Altogether, these requirements should be covered by introducing the new ExportedBridge annotation. Note: Frontend checks of ExportedBridge functions are not implemented yet. --- .../jetbrains/kotlin/name/NativeRuntimeNames.kt | 1 + .../kotlin/backend/konan/RuntimeNames.kt | 1 + .../kotlin/backend/konan/llvm/BinaryInterface.kt | 16 +++++++++++----- .../kotlin/backend/konan/llvm/CodeGenerator.kt | 2 ++ .../backend/konan/llvm/RetainAnnotation.kt | 2 ++ .../backend/konan/optimizations/DataFlowIR.kt | 1 + .../kotlin/kotlin/native/internal/Annotations.kt | 12 ++++++++++++ 7 files changed, 30 insertions(+), 5 deletions(-) diff --git a/core/compiler.common.native/src/org/jetbrains/kotlin/name/NativeRuntimeNames.kt b/core/compiler.common.native/src/org/jetbrains/kotlin/name/NativeRuntimeNames.kt index 18b9c24eafd..dd9e0e6e7ac 100644 --- a/core/compiler.common.native/src/org/jetbrains/kotlin/name/NativeRuntimeNames.kt +++ b/core/compiler.common.native/src/org/jetbrains/kotlin/name/NativeRuntimeNames.kt @@ -11,6 +11,7 @@ object NativeRuntimeNames { object Annotations { val symbolNameClassId = ClassId(kotlinNativePackage, Name.identifier("SymbolName")) val cNameClassId = ClassId(kotlinNativePackage, Name.identifier("CName")) + val exportedBridgeClassId = ClassId(kotlinNativeInternalPackage, Name.identifier("ExportedBridge")) val exportForCppRuntimeClassId = ClassId(kotlinNativeInternalPackage, Name.identifier("ExportForCppRuntime")) val exportForCompilerClassId = ClassId(kotlinNativeInternalPackage, Name.identifier("ExportForCompiler")) val gcUnsafeCallClassId = ClassId(kotlinNativeInternalPackage, Name.identifier("GCUnsafeCall")) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/RuntimeNames.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/RuntimeNames.kt index 4f1906f146a..5dccc484449 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/RuntimeNames.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/RuntimeNames.kt @@ -8,6 +8,7 @@ object RuntimeNames { val cnameAnnotation = NativeRuntimeNames.Annotations.cNameClassId.asSingleFqName() val frozenAnnotation = FqName("kotlin.native.internal.Frozen") val exportForCppRuntime = NativeRuntimeNames.Annotations.exportForCppRuntimeClassId.asSingleFqName() + val exportedBridge = NativeRuntimeNames.Annotations.exportedBridgeClassId.asSingleFqName() val exportForCompilerAnnotation = NativeRuntimeNames.Annotations.exportForCompilerClassId.asSingleFqName() val exportTypeInfoAnnotation = FqName("kotlin.native.internal.ExportTypeInfo") val cCall = FqName("kotlinx.cinterop.internal.CCall") diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt index 95ec5ff4c9b..37c790824ad 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt @@ -6,19 +6,21 @@ package org.jetbrains.kotlin.backend.konan.llvm import llvm.LLVMTypeRef -import org.jetbrains.kotlin.backend.common.serialization.mangle.MangleConstant import org.jetbrains.kotlin.backend.common.serialization.mangle.SpecialDeclarationType import org.jetbrains.kotlin.backend.konan.RuntimeNames import org.jetbrains.kotlin.backend.konan.descriptors.externalSymbolOrThrow -import org.jetbrains.kotlin.ir.util.getAnnotationStringValue import org.jetbrains.kotlin.backend.konan.descriptors.isAbstract import org.jetbrains.kotlin.backend.konan.ir.isUnit +import org.jetbrains.kotlin.backend.konan.serialization.AbstractKonanIrMangler +import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.declarations.IrDeclaration +import org.jetbrains.kotlin.ir.declarations.IrField +import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.objcinterop.isExternalObjCClass import org.jetbrains.kotlin.ir.objcinterop.isKotlinObjCClass -import org.jetbrains.kotlin.backend.konan.serialization.AbstractKonanIrMangler -import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.util.findAnnotation import org.jetbrains.kotlin.ir.util.fqNameForIrSerialization +import org.jetbrains.kotlin.ir.util.getAnnotationStringValue import org.jetbrains.kotlin.ir.util.render import org.jetbrains.kotlin.konan.library.KonanLibrary import org.jetbrains.kotlin.library.uniqueName @@ -62,6 +64,10 @@ object KonanBinaryInterface { private fun withPrefix(prefix: String, mangle: String) = "$prefix:$mangle" + private fun IrFunction.findManglingAnnotation() = + this.annotations.findAnnotation(RuntimeNames.exportForCppRuntime) + ?: this.annotations.findAnnotation(RuntimeNames.exportedBridge) + private fun IrFunction.funSymbolNameImpl(containerName: String?): String { if (isExternal) { this.externalSymbolOrThrow()?.let { @@ -69,7 +75,7 @@ object KonanBinaryInterface { } } - this.annotations.findAnnotation(RuntimeNames.exportForCppRuntime)?.let { + this.findManglingAnnotation()?.let { val name = it.getAnnotationStringValue() ?: this.name.asString() return name // no wrapping currently required } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt index acf0b4abc18..5a18cde2dec 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt @@ -114,6 +114,8 @@ internal inline fun generateFunction( val llvmFunction = codegen.llvmFunction(function) val isCToKotlinBridge = function.origin == CBridgeOrigin.C_TO_KOTLIN_BRIDGE + // TODO: Alternative approach: lowering that changes origin of such functions to C_TO_KOTLIN_BRIDGE? + || function.hasAnnotation(RuntimeNames.exportedBridge) val functionGenerationContext = DefaultFunctionGenerationContext( llvmFunction, diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RetainAnnotation.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RetainAnnotation.kt index 8b1c45b96fa..0547d6778d3 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RetainAnnotation.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RetainAnnotation.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.backend.konan.llvm +import org.jetbrains.kotlin.backend.konan.RuntimeNames import org.jetbrains.kotlin.ir.util.getAnnotationStringValue import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.util.findAnnotation @@ -18,5 +19,6 @@ internal fun IrFunction.retainAnnotation(target: KonanTarget): Boolean { if (this.annotations.findAnnotation(retainAnnotationName) != null) return true val forTarget = this.annotations.findAnnotation(retainForTargetAnnotationName) if (forTarget != null && forTarget.getAnnotationStringValue() == target.name) return true + if (this.annotations.findAnnotation(RuntimeNames.exportedBridge) != null) return true return false } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt index ae15c1496bd..4b183dbbee8 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt @@ -603,6 +603,7 @@ internal object DataFlowIR { if (returnsNothing) attributes = attributes or FunctionAttributes.RETURNS_NOTHING if (it.hasAnnotation(RuntimeNames.exportForCppRuntime) + || it.hasAnnotation(RuntimeNames.exportedBridge) || it.getExternalObjCMethodInfo() != null // TODO-DCE-OBJC-INIT || it.hasAnnotation(RuntimeNames.objCMethodImp)) { attributes = attributes or FunctionAttributes.EXPLICITLY_EXPORTED diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/Annotations.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/Annotations.kt index 3d2c0be0a30..fe28f2404c5 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/Annotations.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/Annotations.kt @@ -5,6 +5,8 @@ package kotlin.native.internal +import kotlin.experimental.ExperimentalNativeApi + /** * Makes this function to be possible to call by given name from C++ part of runtime using C ABI. * The parameters are mapped in an implementation-dependent manner. @@ -180,3 +182,13 @@ internal annotation class InternalForKotlinNativeTests @InternalForKotlinNativeTests @Target(AnnotationTarget.FILE) public annotation class ReflectionPackageName(val name: String) + +/** + * Indicates that the marked function is an exported bridge between Kotlin and the platform. + * This annotation prevents the function from being removed by DCE + * and specifies a stable [bridgeName] for the function symbol. + */ +@Target(AnnotationTarget.FUNCTION) +@Retention(value = AnnotationRetention.BINARY) +@ExperimentalNativeApi +public annotation class ExportedBridge(val bridgeName: String) \ No newline at end of file