Fix C API generation. (#1182)

This commit is contained in:
Nikolay Igotti
2017-12-25 16:24:16 +03:00
committed by GitHub
parent 38069d47ea
commit a05688351a
5 changed files with 160 additions and 28 deletions
@@ -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 {
@@ -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)
+8 -1
View File
@@ -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;
}