diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CAdapterGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CAdapterGenerator.kt index adfbe029563..f6fad545821 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CAdapterGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CAdapterGenerator.kt @@ -27,6 +27,7 @@ import org.jetbrains.kotlin.backend.konan.descriptors.isUnit import org.jetbrains.kotlin.backend.konan.llvm.* import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.resolve.annotations.* import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPublicApi import org.jetbrains.kotlin.types.TypeUtils @@ -50,6 +51,7 @@ private enum class ElementKind { } private enum class DefinitionKind { + C_HEADER_DECLARATION, C_HEADER_STRUCT, C_SOURCE_DECLARATION, C_SOURCE_STRUCT @@ -85,7 +87,9 @@ private val cKeywords = setOf( // Not exactly keywords, but reserved or standard-defined. "and", "not", "or", "xor", "bool", "complex", "imaginary" - ) +) + +private val cnameAnnotation = FqName("konan.internal.CName") private fun org.jetbrains.kotlin.types.KotlinType.isGeneric() = constructor.declarationDescriptor is TypeParameterDescriptor @@ -112,11 +116,19 @@ private fun isExportedClass(descriptor: ClassDescriptor): Boolean { return true } +private fun functionImplName(descriptor: DeclarationDescriptor, default: String, shortName: Boolean): String { + assert(descriptor is FunctionDescriptor) + val annotation = descriptor.annotations.findAnnotation(cnameAnnotation) ?: return default + val key = if (shortName) "shortName" else "fullName" + val value = annotation.argumentValue(key) as String? + return value.takeIf { value != null && value.isNotEmpty() } ?: default +} + private class ExportedElementScope(val kind: ScopeKind, val name: String) { val elements = mutableListOf() val scopes = mutableListOf() private val scopeNames = mutableSetOf() - private val scopeNamesMap = mutableMapOf() + private val scopeNamesMap = mutableMapOf, String>() override fun toString(): String { return "$kind: $name ${elements.joinToString(", ")} ${scopes.joinToString("\n")}" @@ -131,19 +143,20 @@ private class ExportedElementScope(val kind: ScopeKind, val name: String) { } } - fun scopeUniqueName(descriptor: DeclarationDescriptor): String { - scopeNamesMap[descriptor]?.apply { return this } + fun scopeUniqueName(descriptor: DeclarationDescriptor, shortName: Boolean): String { + scopeNamesMap[descriptor to shortName]?.apply { return this } var computedName = when (descriptor) { is ConstructorDescriptor -> "${descriptor.constructedClass.fqNameSafe.shortName().asString()}" is PropertyGetterDescriptor -> "get_${descriptor.correspondingProperty.name.asString()}" is PropertySetterDescriptor -> "set_${descriptor.correspondingProperty.name.asString()}" + is FunctionDescriptor -> functionImplName(descriptor, descriptor.fqNameSafe.shortName().asString(), shortName) else -> descriptor.fqNameSafe.shortName().asString() } while (scopeNames.contains(computedName) || cKeywords.contains(computedName)) { computedName += "_" } scopeNames += computedName - scopeNamesMap[descriptor] = computedName + scopeNamesMap[descriptor to shortName] = computedName return computedName } } @@ -180,7 +193,7 @@ private class ExportedElement(val kind: ElementKind, generateFunction(owner.codegen, LLVMGetElementType(llvmFunction.type)!!, cname) { val receiver = param(0) val numParams = LLVMCountParams(llvmFunction) - val args = (0 .. numParams - 1).map { index -> param(index) } + val args = (0..numParams - 1).map { index -> param(index) } val callee = lookupVirtualImpl(receiver, function) val result = call(callee, args, exceptionHandler = ExceptionHandler.Caller, verbatim = true) ret(result) @@ -211,25 +224,33 @@ private class ExportedElement(val kind: ElementKind, } } - fun uniqueName(descriptor: DeclarationDescriptor) = scope.scopeUniqueName(descriptor) + fun uniqueName(descriptor: DeclarationDescriptor, shortName: Boolean) = + scope.scopeUniqueName(descriptor, shortName) val isFunction = declaration is FunctionDescriptor + val isTopLevelFunction: Boolean + get() { + if (declaration !is FunctionDescriptor || !declaration.annotations.hasAnnotation(cnameAnnotation)) + return false + val annotation = declaration.annotations.findAnnotation(cnameAnnotation)!! + val fullName = annotation.argumentValue("fullName") as String + return fullName.isNotEmpty() + } val isClass = declaration is ClassDescriptor && declaration.kind != ClassKind.ENUM_ENTRY - val isEnumClass = declaration is ClassDescriptor && declaration.kind == ClassKind.ENUM_CLASS val isEnumEntry = declaration is ClassDescriptor && declaration.kind == ClassKind.ENUM_ENTRY - fun makeCFunctionSignature(): List> { + fun makeCFunctionSignature(shortName: Boolean): List> { if (!isFunction) { throw Error("only for functions") } val descriptor = declaration val original = descriptor.original as FunctionDescriptor val returned = when { - original is ConstructorDescriptor -> uniqueName(original) to original.constructedClass - // Suspend functions actually return 'Any?'. - original.isSuspend -> uniqueName(original) to + original is ConstructorDescriptor -> uniqueName(original, shortName) to original.constructedClass + // Suspend functions actually return 'Any?'. + original.isSuspend -> uniqueName(original, shortName) to TypeUtils.getClassDescriptor(owner.context.builtIns.nullableAnyType)!! - else -> uniqueName(original) to TypeUtils.getClassDescriptor(original.returnType!!)!! + else -> uniqueName(original, shortName) to TypeUtils.getClassDescriptor(original.returnType!!)!! } val params = ArrayList(original.explicitParameters.mapIndexed() { index, it -> "p$index /* ${it.name.asString()} */" to TypeUtils.getClassDescriptor(it.type)!! @@ -260,10 +281,18 @@ private class ExportedElement(val kind: ElementKind, fun makeFunctionPointerString(): String { - val signature = makeCFunctionSignature() + val signature = makeCFunctionSignature(true) return "${owner.translateType(signature[0].second)} (*${signature[0].first})(${signature.drop(1).map { it -> "${owner.translateType(it.second)} ${it.first}" }.joinToString(", ")});" } + fun makeTopLevelFunctionString(): Pair { + val signature = makeCFunctionSignature(false) + val name = signature[0].first + return (name to + "extern ${owner.translateType(signature[0].second)} $name(${signature.drop(1).map { it -> "${owner.translateType(it.second)} ${it.first}" }.joinToString(", ")});") + } + + fun makeFunctionDeclaration(): String { assert(isFunction) val bridge = makeBridgeSignature() @@ -273,7 +302,7 @@ private class ExportedElement(val kind: ElementKind, builder.append("(${bridge.drop(1).joinToString(", ")});\n") // Now the C function body. - builder.append(translateBody(makeCFunctionSignature())) + builder.append(translateBody(makeCFunctionSignature(false))) return builder.toString() } @@ -319,9 +348,16 @@ private class ExportedElement(val kind: ElementKind, } } + val cnameImpl: String + get() = if (isTopLevelFunction) + functionImplName(declaration, "******" /* Default value must never be used. */, false) + else + "${cname}_impl" + private fun translateBody(cfunction: List>): String { + val visibility = if (isTopLevelFunction) "RUNTIME_USED extern \"C\"" else "static" val builder = StringBuilder() - builder.append("static ${owner.translateType(cfunction[0].second)} ${cname}_impl(${cfunction.drop(1).mapIndexed { index, it -> "${owner.translateType(it.second)} arg${index}" }.joinToString(", ")}) {\n") + builder.append("$visibility ${owner.translateType(cfunction[0].second)} ${cnameImpl}(${cfunction.drop(1).mapIndexed { index, it -> "${owner.translateType(it.second)} arg${index}" }.joinToString(", ")}) {\n") val args = ArrayList(cfunction.drop(1).mapIndexed { index, pair -> translateArgument("arg$index", pair.second, Direction.C_TO_KOTLIN, builder) }) @@ -501,8 +537,8 @@ internal class CAdapterGenerator( override fun visitModuleDeclaration(descriptor: ModuleDescriptor, ignored: Void?): Boolean { currentPackageFragments = descriptor.getPackageFragments().sortedWith( - Comparator { - o1, o2 -> o1.fqName.toString().compareTo(o2.fqName.toString()) + Comparator { o1, o2 -> + o1.fqName.toString().compareTo(o2.fqName.toString()) }) seenPackageFragments.clear() descriptor.getPackage(FqName.ROOT).accept(this, null) @@ -560,6 +596,16 @@ internal class CAdapterGenerator( private fun makeElementDefinition(element: ExportedElement, kind: DefinitionKind, indent: Int) { when (kind) { + DefinitionKind.C_HEADER_DECLARATION -> { + when { + element.isTopLevelFunction -> { + val (name, declaration) = element.makeTopLevelFunctionString() + exportedSymbols += name + output(declaration, 0) + } + } + } + DefinitionKind.C_HEADER_STRUCT -> { when { element.isFunction -> @@ -589,7 +635,7 @@ internal class CAdapterGenerator( DefinitionKind.C_SOURCE_STRUCT -> { when { element.isFunction -> - output("/* ${element.name} = */ ${element.cname}_impl, ", indent) + output("/* ${element.name} = */ ${element.cnameImpl}, ", indent) element.isClass -> output("/* Type for ${element.name} = */ ${element.cname}, ", indent) element.isEnumEntry -> @@ -600,9 +646,7 @@ internal class CAdapterGenerator( } } - private fun makeScopeDefinitions(scope: ExportedElementScope, - kind: DefinitionKind, - indent: Int) { + private fun makeScopeDefinitions(scope: ExportedElementScope, kind: DefinitionKind, indent: Int) { if (kind == DefinitionKind.C_HEADER_STRUCT) output("struct {", indent) if (kind == DefinitionKind.C_SOURCE_STRUCT) output(".${scope.name} = {", indent) scope.elements.forEach { makeElementDefinition(it, kind, indent + 1) } @@ -632,11 +676,14 @@ internal class CAdapterGenerator( } } + val exportedSymbols = mutableListOf() + private fun makeGlobalStruct(top: ExportedElementScope) { val headerFile = context.config.tempFiles.cAdapterHeader outputStreamWriter = headerFile.printWriter() val exportedSymbol = "${prefix}_symbols" + exportedSymbols += exportedSymbol output("#ifndef KONAN_${prefix.toUpperCase()}_H") output("#define KONAN_${prefix.toUpperCase()}_H") @@ -660,6 +707,9 @@ internal class CAdapterGenerator( output("") defineUsedTypes(top, 0) + output("") + makeScopeDefinitions(top, DefinitionKind.C_HEADER_DECLARATION, 0) + output("") output("typedef struct {") output("/* Service functions. */", 1) @@ -684,8 +734,8 @@ internal class CAdapterGenerator( println("Produced dynamic library API in ${prefix}_api.h") outputStreamWriter = context.config.tempFiles - .cAdapterCpp - .printWriter() + .cAdapterCpp + .printWriter() // Include header into C++ source. headerFile.forEachLine { it -> output(it) } @@ -750,10 +800,10 @@ internal class CAdapterGenerator( if (context.config.targetManager.target.family == Family.WINDOWS) { outputStreamWriter = context.config.tempFiles - .cAdapterDef - .printWriter() + .cAdapterDef + .printWriter() output("EXPORTS") - output(exportedSymbol) + exportedSymbols.forEach { output(it) } outputStreamWriter.close() } } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/TempFiles.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/TempFiles.kt index a6bcff0fe28..c4803848459 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/TempFiles.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/TempFiles.kt @@ -26,7 +26,6 @@ class TempFiles(val outputName: String) { val cAdapterBitcode by lazy { createTempFile("api", ".bc").deleteOnExit() } val nativeBinaryFileName get() = nativeBinaryFile.absolutePath - val cAdapterHeaderName get() = cAdapterHeader.absolutePath val cAdapterCppName get() = cAdapterCpp.absolutePath val cAdapterBitcodeName get() = cAdapterBitcode.absolutePath } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt index f9a8fa05255..e4e5a5867b8 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt @@ -55,7 +55,11 @@ internal tailrec fun DeclarationDescriptor.isExported(): Boolean { // Treat any `@ExportForCppRuntime` declaration as exported. return true } - if (this.annotations.hasAnnotation(exportForCompilerAnnotation)){ + if (this.annotations.findAnnotation(cnameAnnotation) != null) { + // Treat `@CName` declaration as exported. + return true + } + if (this.annotations.hasAnnotation(exportForCompilerAnnotation)) { return true } if (this.annotations.hasAnnotation(publishedApiAnnotation)){ @@ -96,6 +100,8 @@ private val symbolNameAnnotation = FqName("konan.SymbolName") private val exportForCppRuntimeAnnotation = FqName("konan.internal.ExportForCppRuntime") +private val cnameAnnotation = FqName("konan.internal.CName") + private val exportForCompilerAnnotation = FqName("konan.internal.ExportForCompiler") private val publishedApiAnnotation = FqName("kotlin.PublishedApi") diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 54c71aa9a69..2325de7c175 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -2375,7 +2375,8 @@ task produce_dynamic(type: DynamicKonanTest) { "RO property is 42\n" + "RW property is 239\n" + "enum100 = 100\n" + - "object = 42\n" + "object = 42\n" + + "topLevel = 777 3\n" } task buildKonanStdlibTests(type: BuildKonanTest) { diff --git a/backend.native/tests/produce_dynamic/simple/hello.kt b/backend.native/tests/produce_dynamic/simple/hello.kt index b3b088b216c..b0ab29f6b21 100644 --- a/backend.native/tests/produce_dynamic/simple/hello.kt +++ b/backend.native/tests/produce_dynamic/simple/hello.kt @@ -12,6 +12,16 @@ open class Base { open fun fooParam(arg0: String, arg1: Int) = println("Base.fooParam: $arg0 $arg1") + @konan.internal.CName(fullName = "", shortName = "strangeName") fun странноеИмя() = 111 + +} + +@konan.internal.CName(fullName = "topLevelFunctionFromC", shortName = "topLevelFunctionFromCShort") +fun topLevelFunction(x1: Int, x2: Int) = x1 - x2 + +@konan.internal.CName("topLevelFunctionVoidFromC") +fun topLevelFunctionVoid(x1: Int) { + assert(x1 == 42) } // Enum. diff --git a/backend.native/tests/produce_dynamic/simple/main.c b/backend.native/tests/produce_dynamic/simple/main.c index ee440211778..1ce33c1ef3c 100644 --- a/backend.native/tests/produce_dynamic/simple/main.c +++ b/backend.native/tests/produce_dynamic/simple/main.c @@ -18,7 +18,6 @@ int main(void) { const char* string = __ kotlin.root.getString(); - __ kotlin.root.hello(); __ kotlin.root.Base.foo(base); __ kotlin.root.Base.fooParam(base, "a", 1); @@ -37,6 +36,10 @@ int main(void) { printf("object = %d\n", __ kotlin.root.Codeable.asCode(object1)); + topLevelFunctionVoidFromC(42); + __ kotlin.root.topLevelFunctionVoid(42); + printf("topLevel = %d %d\n", topLevelFunctionFromC(780, 3), __ kotlin.root.topLevelFunctionFromCShort(5, 2)); + __ DisposeString(string); __ DisposeStablePointer(base.pinned); __ DisposeStablePointer(child.pinned); diff --git a/runtime/src/main/kotlin/konan/internal/Annotations.kt b/runtime/src/main/kotlin/konan/internal/Annotations.kt index 157d3768423..89fc804f285 100644 --- a/runtime/src/main/kotlin/konan/internal/Annotations.kt +++ b/runtime/src/main/kotlin/konan/internal/Annotations.kt @@ -24,10 +24,19 @@ package konan.internal * * If the name is not specified, the function to call will be available by its Kotlin unqualified name. */ -//@Target(AnnotationTarget.FUNCTION) -//@Retention(AnnotationRetention.SOURCE) +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR) +@Retention(AnnotationRetention.BINARY) annotation class ExportForCppRuntime(val name: String = "") +/** + * Makes top level function available from C/C++ code with the given name. + * `fullName` controls the name of top level function, `shortName` controls the short name. + * If `fullName` is empty, no top level declaration is being created. + */ +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.BINARY) +annotation class CName(val fullName: String = "", val shortName: String = "") + /** * This annotation denotes that the element is intrinsic and its usages require special handling in compiler. */