Fix C API generation. (#1182)
This commit is contained in:
+88
-25
@@ -32,8 +32,8 @@ import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPublicApi
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
import org.jetbrains.kotlin.konan.file.*
|
||||
import org.jetbrains.kotlin.konan.target.*
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
|
||||
private enum class ScopeKind {
|
||||
TOP,
|
||||
@@ -64,12 +64,56 @@ private operator fun String.times(count: Int): String {
|
||||
return builder.toString()
|
||||
}
|
||||
|
||||
private val cKeywords = setOf(
|
||||
// Actual C keywords.
|
||||
"auto", "break", "case",
|
||||
"char", "const", "continue",
|
||||
"default", "do", "double",
|
||||
"else", "enum", "extern",
|
||||
"float", "for", "goto",
|
||||
"if", "int", "long",
|
||||
"register", "return",
|
||||
"short", "signed", "sizeof", "static", "struct", "switch",
|
||||
"typedef", "union", "unsigned",
|
||||
"void", "volatile", "while",
|
||||
// C99-specific.
|
||||
"_Bool", "_Complex", "_Imaginary", "inline", "restrict",
|
||||
// C11-specific.
|
||||
"_Alignas", "_Alignof", "_Atomic", "_Generic", "_Noreturn", "_Static_assert", "_Thread_local",
|
||||
// Not exactly keywords, but reserved or standard-defined.
|
||||
"and", "not", "or", "xor",
|
||||
"bool", "complex", "imaginary"
|
||||
)
|
||||
|
||||
private fun org.jetbrains.kotlin.types.KotlinType.isGeneric() =
|
||||
constructor.declarationDescriptor is TypeParameterDescriptor
|
||||
|
||||
private fun isExportedFunction(descriptor: FunctionDescriptor): Boolean {
|
||||
if (!descriptor.isEffectivelyPublicApi || !descriptor.kind.isReal)
|
||||
return false
|
||||
descriptor.allParameters.forEach {
|
||||
if (it.type.isGeneric()) return false
|
||||
}
|
||||
val returnType = descriptor.returnType
|
||||
if (returnType == null) return true
|
||||
return !returnType.isGeneric()
|
||||
}
|
||||
|
||||
private fun isExportedClass(descriptor: ClassDescriptor): Boolean {
|
||||
if (!descriptor.isEffectivelyPublicApi)
|
||||
return false
|
||||
// Do not export types with type parameters.
|
||||
// TODO: is it correct?
|
||||
return descriptor.declaredTypeParameters.isEmpty()
|
||||
}
|
||||
|
||||
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>()
|
||||
|
||||
|
||||
override fun toString(): String {
|
||||
return "$kind: $name ${elements.joinToString(", ")} ${scopes.joinToString("\n")}"
|
||||
}
|
||||
@@ -85,8 +129,12 @@ private class ExportedElementScope(val kind: ScopeKind, val name: String) {
|
||||
|
||||
fun scopeUniqueName(descriptor: DeclarationDescriptor): String {
|
||||
scopeNamesMap[descriptor]?.apply { return this }
|
||||
var computedName = descriptor.fqNameSafe.shortName().asString()
|
||||
while (scopeNames.contains(computedName)) {
|
||||
var computedName = when (descriptor) {
|
||||
is PropertyGetterDescriptor -> "get_${descriptor.correspondingProperty.name.asString()}"
|
||||
is PropertySetterDescriptor -> "set_${descriptor.correspondingProperty.name.asString()}"
|
||||
else -> descriptor.fqNameSafe.shortName().asString()
|
||||
}
|
||||
while (scopeNames.contains(computedName) || cKeywords.contains(computedName)) {
|
||||
computedName += "_"
|
||||
}
|
||||
scopeNames += computedName
|
||||
@@ -120,7 +168,21 @@ private class ExportedElement(val kind: ElementKind,
|
||||
val function = declaration as FunctionDescriptor
|
||||
cname = "_konan_function_${owner.nextFunctionIndex()}"
|
||||
val llvmFunction = owner.codegen.llvmFunction(function)
|
||||
val bridge = LLVMAddAlias(context.llvmModule, llvmFunction.type, llvmFunction, cname)!!
|
||||
// If function is virtual, we need to resolve receiver properly.
|
||||
val bridge = if (!DescriptorUtils.isTopLevelDeclaration(function) && function.isOverridable) {
|
||||
// We need LLVMGetElementType() as otherwise type is function pointer.
|
||||
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 callee = lookupVirtualImpl(receiver, function)
|
||||
val result = callAtFunctionScope(callee, args, Lifetime.RETURN_VALUE)
|
||||
ret(result)
|
||||
}
|
||||
|
||||
} else {
|
||||
LLVMAddAlias(context.llvmModule, llvmFunction.type, llvmFunction, cname)!!
|
||||
}
|
||||
LLVMSetLinkage(bridge, LLVMLinkage.LLVMExternalLinkage)
|
||||
}
|
||||
isClass -> {
|
||||
@@ -136,13 +198,7 @@ private class ExportedElement(val kind: ElementKind,
|
||||
}
|
||||
}
|
||||
|
||||
fun functionName(descriptor: FunctionDescriptor): String {
|
||||
return when (descriptor) {
|
||||
is PropertyGetterDescriptor -> "get_${descriptor.correspondingProperty.name.asString()}"
|
||||
is PropertySetterDescriptor -> "set_${descriptor.correspondingProperty.name.asString()}"
|
||||
else -> scope.scopeUniqueName(descriptor)
|
||||
}
|
||||
}
|
||||
fun uniqueName(descriptor: FunctionDescriptor) = scope.scopeUniqueName(descriptor)
|
||||
|
||||
val isFunction = declaration is FunctionDescriptor
|
||||
val isClass = declaration is ClassDescriptor
|
||||
@@ -157,9 +213,9 @@ private class ExportedElement(val kind: ElementKind,
|
||||
original is ConstructorDescriptor ->
|
||||
scope.scopeUniqueName(original.constructedClass) to original.constructedClass
|
||||
// Suspend functions actually return Any?.
|
||||
original.isSuspend -> functionName(original) to
|
||||
original.isSuspend -> uniqueName(original) to
|
||||
TypeUtils.getClassDescriptor(owner.context.builtIns.nullableAnyType)!!
|
||||
else -> functionName(original) to TypeUtils.getClassDescriptor(original.returnType!!)!!
|
||||
else -> uniqueName(original) to TypeUtils.getClassDescriptor(original.returnType!!)!!
|
||||
}
|
||||
val params = ArrayList(original.explicitParameters.map {
|
||||
owner.translateName(it.name.asString()) to TypeUtils.getClassDescriptor(it.type)!!
|
||||
@@ -274,30 +330,40 @@ private class ExportedElement(val kind: ElementKind,
|
||||
return builder.toString()
|
||||
}
|
||||
|
||||
private fun addUsedType(type: org.jetbrains.kotlin.types.KotlinType, set: MutableSet<ClassDescriptor>) {
|
||||
if (type.constructor.declarationDescriptor is TypeParameterDescriptor) return
|
||||
val clazz = TypeUtils.getClassDescriptor(type)
|
||||
if (clazz == null) {
|
||||
println("cannot get class for $type")
|
||||
} else {
|
||||
set += clazz
|
||||
}
|
||||
}
|
||||
|
||||
fun addUsedTypes(set: MutableSet<ClassDescriptor>) {
|
||||
val descriptor = declaration
|
||||
when (descriptor) {
|
||||
is FunctionDescriptor -> {
|
||||
val original = descriptor.original
|
||||
original.allParameters.forEach { set += TypeUtils.getClassDescriptor(it.type)!! }
|
||||
original.returnType?.let { set += TypeUtils.getClassDescriptor(it)!! }
|
||||
original.allParameters.forEach { addUsedType(it.type, set) }
|
||||
original.returnType?.let { addUsedType(it, set) }
|
||||
}
|
||||
is PropertyAccessorDescriptor -> {
|
||||
val original = descriptor.original
|
||||
set += TypeUtils.getClassDescriptor(original.correspondingProperty.type)!!
|
||||
addUsedType(original.correspondingProperty.type, set)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class CAdapterGenerator(val context: Context,
|
||||
internal val codegen: CodeGenerator) : IrElementVisitorVoid {
|
||||
internal class CAdapterGenerator(
|
||||
val context: Context, internal val codegen: CodeGenerator) : IrElementVisitorVoid {
|
||||
|
||||
private val scopes = mutableListOf<ExportedElementScope>()
|
||||
internal val prefix = context.config.moduleId
|
||||
private lateinit var outputStreamWriter: PrintWriter
|
||||
|
||||
override fun visitElement(element: IrElement) {
|
||||
//println(ir2string(element))
|
||||
element.acceptChildrenVoid(this)
|
||||
}
|
||||
|
||||
@@ -319,14 +385,13 @@ internal class CAdapterGenerator(val context: Context,
|
||||
|
||||
override fun visitFunction(declaration: IrFunction) {
|
||||
val descriptor = declaration.descriptor
|
||||
if (!descriptor.isEffectivelyPublicApi || !descriptor.kind.isReal) return
|
||||
if (!isExportedFunction(descriptor)) return
|
||||
ExportedElement(ElementKind.FUNCTION, scopes.last(), declaration.descriptor, this)
|
||||
}
|
||||
|
||||
override fun visitClass(declaration: IrClass) {
|
||||
val descriptor = declaration.descriptor
|
||||
if (!descriptor.isEffectivelyPublicApi)
|
||||
return
|
||||
if (!isExportedClass(descriptor)) return
|
||||
// TODO: fix me!
|
||||
val shortName = descriptor.fqNameSafe.shortName()
|
||||
if (shortName.isSpecial || shortName.asString().contains("<anonymous>"))
|
||||
@@ -358,9 +423,7 @@ internal class CAdapterGenerator(val context: Context,
|
||||
outputStreamWriter.println(string)
|
||||
}
|
||||
|
||||
private fun makeElementDefinition(element: ExportedElement,
|
||||
kind: DefinitionKind,
|
||||
indent: Int) {
|
||||
private fun makeElementDefinition(element: ExportedElement, kind: DefinitionKind, indent: Int) {
|
||||
when (kind) {
|
||||
DefinitionKind.C_HEADER_STRUCT -> {
|
||||
when {
|
||||
|
||||
+1
-1
@@ -146,7 +146,7 @@ internal class BlockAdapterToFunctionGenerator(val objCExportCodeGenerator: ObjC
|
||||
(0 .. numberOfParameters).map { int8TypePtr }
|
||||
)
|
||||
|
||||
val result = generateFunction(codegen, functionType, "invokeBlock$numberOfParameters") {
|
||||
val result = generateFunction(codegen, functionType, "invokeBlock$numberOfParameters") {
|
||||
val blockPtr = bitcast(pointerType(blockLiteralType), param(0))
|
||||
val kotlinFunction = loadSlot(structGep(blockPtr, 1), isVar = false)
|
||||
|
||||
|
||||
@@ -2345,7 +2345,14 @@ task produce_dynamic(type: DynamicKonanTest) {
|
||||
disabled = (project.testTarget != null && project.testTarget != project.hostName)
|
||||
source = "produce_dynamic/simple/hello.kt"
|
||||
cSource = "$projectDir/produce_dynamic/simple/main.c"
|
||||
goldValue = "Hello, dynamic!\n"
|
||||
goldValue = "Hello, dynamic!\n" +
|
||||
"Base.foo\n" +
|
||||
"Base.fooParam: a 1\n" +
|
||||
"Child.fooParam: b 2\n" +
|
||||
"Child.fooParam: c 3\n" +
|
||||
"Impl1.I: d 4 Impl1\n" +
|
||||
"Impl2.I: e 5 Impl2\n" +
|
||||
"String is Kotlin/Native\n"
|
||||
}
|
||||
|
||||
task buildKonanTests(type: BuildKonanTest) {
|
||||
|
||||
@@ -1,4 +1,37 @@
|
||||
|
||||
// Top level functions.
|
||||
fun hello() {
|
||||
println("Hello, dynamic!")
|
||||
}
|
||||
|
||||
fun getString() = "Kotlin/Native"
|
||||
|
||||
// Class with inheritance.
|
||||
open class Base {
|
||||
open fun foo() = println("Base.foo")
|
||||
|
||||
open fun fooParam(arg0: String, arg1: Int) = println("Base.fooParam: $arg0 $arg1")
|
||||
|
||||
}
|
||||
|
||||
class Child : Base() {
|
||||
override fun fooParam(arg0: String, arg1: Int) = println("Child.fooParam: $arg0 $arg1")
|
||||
}
|
||||
|
||||
// Interface.
|
||||
interface I {
|
||||
fun foo(arg0: String, arg1: Int, arg2: I)
|
||||
fun fooImpl() = foo("Hi", 239, this)
|
||||
}
|
||||
|
||||
open class Impl1: I {
|
||||
override fun foo(arg0: String, arg1: Int, arg2: I) {
|
||||
println("Impl1.I: $arg0 $arg1 ${arg2::class.qualifiedName}")
|
||||
}
|
||||
}
|
||||
|
||||
class Impl2 : Impl1() {
|
||||
override fun foo(arg0: String, arg1: Int, arg2: I) {
|
||||
println("Impl2.I: $arg0 $arg1 ${arg2::class.qualifiedName}")
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,36 @@
|
||||
#include <stdio.h>
|
||||
#include "testlib_api.h"
|
||||
|
||||
#define __ testlib_symbols()->
|
||||
#define T_(x) testlib_kref_ ## x
|
||||
#define CAST(T, v) testlib_kref_ ## T { .pinned = v }
|
||||
|
||||
int main(void) {
|
||||
testlib_symbols()->kotlin.root.hello();
|
||||
T_(Base) base = __ kotlin.root.Base.Base();
|
||||
T_(Child) child = __ kotlin.root.Child.Child();
|
||||
T_(Impl1) impl1 = __ kotlin.root.Impl1.Impl1();
|
||||
T_(Impl2) impl2 = __ kotlin.root.Impl2.Impl2();
|
||||
T_(Base) casted_child = { .pinned = child.pinned };
|
||||
T_(I) casted_impl1 = { .pinned = impl1.pinned };
|
||||
T_(I) casted_impl2 = { .pinned = impl2.pinned };
|
||||
const char* string = __ kotlin.root.getString();
|
||||
|
||||
__ kotlin.root.hello();
|
||||
__ kotlin.root.Base.foo(base);
|
||||
__ kotlin.root.Base.fooParam(base, "a", 1);
|
||||
__ kotlin.root.Child.fooParam(child, "b", 2);
|
||||
__ kotlin.root.Base.fooParam(casted_child, "c", 3);
|
||||
__ kotlin.root.I.foo(casted_impl1, "d", 4, casted_impl1);
|
||||
__ kotlin.root.I.foo(casted_impl2, "e", 5, casted_impl2);
|
||||
|
||||
printf("String is %s\n", string);
|
||||
|
||||
__ DisposeString(string);
|
||||
__ DisposeStablePointer(base.pinned);
|
||||
__ DisposeStablePointer(child.pinned);
|
||||
__ DisposeStablePointer(impl1.pinned);
|
||||
__ DisposeStablePointer(impl2.pinned);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user