From a05688351a40bfb2ca8e648f442d5960d9146aca Mon Sep 17 00:00:00 2001 From: Nikolay Igotti Date: Mon, 25 Dec 2017 16:24:16 +0300 Subject: [PATCH] Fix C API generation. (#1182) --- .../kotlin/backend/konan/CAdapterGenerator.kt | 113 ++++++++++++++---- .../llvm/objcexport/BlockPointerSupport.kt | 2 +- backend.native/tests/build.gradle | 9 +- .../tests/produce_dynamic/simple/hello.kt | 33 +++++ .../tests/produce_dynamic/simple/main.c | 31 ++++- 5 files changed, 160 insertions(+), 28 deletions(-) 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 8b9c060139d..b3480d44e23 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 @@ -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() val scopes = mutableListOf() private val scopeNames = mutableSetOf() private val scopeNamesMap = mutableMapOf() + 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) { + 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) { 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() 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("")) @@ -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 { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/BlockPointerSupport.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/BlockPointerSupport.kt index 6e223bc1c30..14a6ecbc8b3 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/BlockPointerSupport.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/BlockPointerSupport.kt @@ -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) diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 36f89e96655..668aabf220a 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -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) { diff --git a/backend.native/tests/produce_dynamic/simple/hello.kt b/backend.native/tests/produce_dynamic/simple/hello.kt index 1f6b3fead7b..301c158bc10 100644 --- a/backend.native/tests/produce_dynamic/simple/hello.kt +++ b/backend.native/tests/produce_dynamic/simple/hello.kt @@ -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}") + } +} \ No newline at end of file diff --git a/backend.native/tests/produce_dynamic/simple/main.c b/backend.native/tests/produce_dynamic/simple/main.c index 66eb6ce9481..238db64d757 100644 --- a/backend.native/tests/produce_dynamic/simple/main.c +++ b/backend.native/tests/produce_dynamic/simple/main.c @@ -1,7 +1,36 @@ +#include #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; }