Support @CName annotation for reverse C interop, similar to @JvmName. (#1237)
This commit is contained in:
+77
-27
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -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
|
||||
}
|
||||
|
||||
+7
-1
@@ -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")
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user