Support @CName annotation for reverse C interop, similar to @JvmName. (#1237)

This commit is contained in:
Nikolay Igotti
2018-01-17 19:34:00 +03:00
committed by GitHub
parent 76bfb9322d
commit 77f220f335
7 changed files with 111 additions and 33 deletions
@@ -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<ExportedElement>()
val scopes = mutableListOf<ExportedElementScope>()
private val scopeNames = mutableSetOf<String>()
private val scopeNamesMap = mutableMapOf<DeclarationDescriptor, String>()
private val scopeNamesMap = mutableMapOf<Pair<DeclarationDescriptor, Boolean>, 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<Pair<String, ClassDescriptor>> {
fun makeCFunctionSignature(shortName: Boolean): List<Pair<String, ClassDescriptor>> {
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<String, String> {
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<Pair<String, ClassDescriptor>>): 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<String>()
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()
}
}
@@ -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
}
@@ -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")
+2 -1
View File
@@ -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) {
@@ -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.
@@ -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);
@@ -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.
*/