From f9f418c51beced6025bdf8bf4e538bbeece2974d Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Fri, 23 Sep 2016 16:05:22 +0300 Subject: [PATCH] Interop: add minor code improvements --- .../Interop/Indexer/src/callback/c/callback.c | 24 ++-- .../native/interop/indexer/Callbacks.kt | 21 ++++ .../native/interop/indexer/NativeIndex.kt | 39 +++++- .../native/interop/gen/jvm/StubGenerator.kt | 116 +++++++++++++++--- .../kotlin/NativeInteropPlugin.groovy | 8 +- 5 files changed, 172 insertions(+), 36 deletions(-) diff --git a/experiments/Interop/Indexer/src/callback/c/callback.c b/experiments/Interop/Indexer/src/callback/c/callback.c index 05ab3330a41..c9a521f96b0 100644 --- a/experiments/Interop/Indexer/src/callback/c/callback.c +++ b/experiments/Interop/Indexer/src/callback/c/callback.c @@ -1,31 +1,29 @@ -#include #include #include -#include #include static JavaVM *vm = NULL; +// Returns the JNI env which can be used by the caller. +// If current thread is not attached to JVM, then it gets attached as daemon. static JNIEnv* getCurrentEnv() { -// printf("%p\n", pthread_self()); JNIEnv* env; assert (vm != NULL); jint res = (*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_1); if (res != JNI_OK) { - assert(res == JNI_EDETACHED); - res = (*vm)->AttachCurrentThreadAsDaemon(vm, (void**)&env, NULL); - assert (res == JNI_OK); + assert (res == JNI_EDETACHED); + res = (*vm)->AttachCurrentThreadAsDaemon(vm, (void**)&env, NULL); + assert (res == JNI_OK); } return env; } jint JNI_OnLoad(JavaVM *vm_, void *reserved) { vm = vm_; - getCurrentEnv(); return JNI_VERSION_1_1; } - +// The implementation of native callback. void indexDeclarationNativeCallback(void* arg1, void* arg2) { JNIEnv* env = getCurrentEnv(); @@ -34,15 +32,15 @@ void indexDeclarationNativeCallback(void* arg1, void* arg2) { if (cls == NULL) { cls = (*env)->FindClass(env, "org/jetbrains/kotlin/native/interop/indexer/indexDeclarationCallback"); - assert(cls != NULL); - entryFromNative = (*env)->GetStaticMethodID(env, cls, "entryFromNative", "(J)V"); - assert(entryFromNative != NULL); + assert(cls != NULL); + entryFromNative = (*env)->GetStaticMethodID(env, cls, "entryFromNative", "(J)V"); + assert(entryFromNative != NULL); } (*env)->CallStaticVoidMethod(env, cls, entryFromNative, (jlong) arg2); if ((*env)->ExceptionCheck(env)) { - (*env)->ExceptionDescribe(env); - assert(0); + (*env)->ExceptionDescribe(env); + assert(0); } } diff --git a/experiments/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Callbacks.kt b/experiments/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Callbacks.kt index 8d16885055b..229ae4ef294 100644 --- a/experiments/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Callbacks.kt +++ b/experiments/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Callbacks.kt @@ -3,21 +3,39 @@ package org.jetbrains.kotlin.native.interop.indexer import clang.CXIdxDeclInfo import kotlin_native.interop.NativePtr +/** + * This object helps to create native callbacks with signature `void (CXClientData, const CXIdxDeclInfo *)`. + * At any time only one instance of native callback can exist. +*/ internal object indexDeclarationCallback { + /** + * The Kotlin function to be called when native callback is called. + */ private var function: ((CXIdxDeclInfo)->Unit)? = null + /** + * Creates instance of native callback with given Kotlin implementation. + * + * To create another one, this instance must be destroyed using [reset]. + */ fun setUp(func: (CXIdxDeclInfo)->Unit): NativePtr { assert (function == null) function = func return nativeCallbackPtr } + /** + * Destroys the (only) instance of native callback. + */ fun reset() { assert (function != null) function = null } + /** + * This function is called from native callback implementation. + */ @JvmStatic private fun entryFromNative(info: Long) { function!!(CXIdxDeclInfo(NativePtr.Companion.byValue(info)!!)) @@ -30,5 +48,8 @@ internal object indexDeclarationCallback { nativeCallbackPtr = NativePtr.byValue(nativeCallbackPtr())!! } + /** + * Returns the pointer to native callback. + */ private external fun nativeCallbackPtr(): Long } \ No newline at end of file diff --git a/experiments/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt b/experiments/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt index 00fa8abae67..c41e5384ea1 100644 --- a/experiments/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt +++ b/experiments/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt @@ -2,41 +2,68 @@ package org.jetbrains.kotlin.native.interop.indexer import java.io.File +/** + * Retrieves the definitions from given C header file using given compiler arguments (e.g. defines). + */ fun buildNativeIndex(headerFile: File, args: List): NativeIndex = buildNativeIndexImpl(headerFile, args) +/** + * This class describes the IR of definitions from C header file(s). + */ abstract class NativeIndex { abstract val structs: List abstract val enums: List abstract val functions: List } +/** + * C struct field. + */ +class Field(val name: String, val type: Type, val offset: Long) -class Field(val name: String, val type: Type, val offset: Long) { - override fun toString(): String = "$type $name at $offset" -} - +/** + * C struct declaration. + */ abstract class StructDecl(val spelling: String) { abstract val def: StructDef? - } +/** + * C struct definition. + */ abstract class StructDef(val size: Long, val decl: StructDecl) { + abstract val fields: List } +/** + * C enum value. + */ class EnumValue(val name: String, val value: Long) +/** + * C enum definition. + */ abstract class EnumDef(val spelling: String, val baseType: PrimitiveType) { + abstract val values: List } +/** + * C function parameter. + */ class Parameter(val name: String?, val type: Type) +/** + * C function declaration. + */ class FunctionDecl(val name: String, val parameters: List, val returnType: Type) - +/** + * C type. + */ open class Type open class PrimitiveType : Type() diff --git a/experiments/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt b/experiments/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt index 853f7aaf8bf..630c6ff677c 100644 --- a/experiments/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt +++ b/experiments/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt @@ -9,19 +9,32 @@ class StubGenerator( val libName: String, val excludedFunctions: Set) { + /** + * The names that should not be used for struct classes to prevent name clashes + */ val forbiddenStructNames = run { val functionNames = nativeIndex.functions.map { it.name } val fieldNames = nativeIndex.structs.mapNotNull { it.def }.flatMap { it.fields }.map { it.name } (functionNames + fieldNames).toSet() } + /** + * The name to be used for this struct in Kotlin + */ val StructDecl.kotlinName: String - get() = if (spelling.startsWith("struct ") || spelling.startsWith("union ")) { - spelling.substringAfter(' ') - } else { - spelling + get() { + val strippedCName = if (spelling.startsWith("struct ") || spelling.startsWith("union ")) { + spelling.substringAfter(' ') + } else { + spelling + } + + return if (strippedCName !in forbiddenStructNames) strippedCName else (strippedCName + "Struct") } + /** + * The name to be used for this enum in Kotlin + */ val EnumDef.kotlinName: String get() = if (spelling.startsWith("enum ")) { spelling.substringAfter(' ') @@ -38,11 +51,10 @@ class StubGenerator( val functionsToBind = nativeIndex.functions.filter { it.name !in excludedFunctions } - private fun mangleStructName(name: String) = if (name !in forbiddenStructNames) name else (name + "Struct") - - val StructDecl.mangledName: String - get() = mangleStructName(kotlinName) - + /** + * The output currently used by the generator. + * Should append line separator after any usage. + */ private var out: (String) -> Unit = { throw IllegalStateException() } @@ -70,7 +82,11 @@ class StubGenerator( return withOutput({ oldOut(" $it") }, action) } - // TODO: use libclang to implement: + /** + * Returns the expression which could be used for this type in C code. + * + * TODO: use libclang to implement: + */ fun Type.getStringRepresentation(): String { return when (this) { @@ -118,8 +134,18 @@ class StubGenerator( else -> throw NotImplementedError() } + /** + * Describes the Kotlin native reference type + * + * @param typeName the name of the Kotlin native reference type + * @param typeExpr such Kotlin expression that `typeExpr(ptr)` constructs the reference of this type + * to the value located at `ptr` + */ class NativeRefType(val typeName: String, val typeExpr: String = typeName) + /** + * Returns the Kotlin type which describes the reference to value of given (C) type. + */ fun getKotlinTypeForRefTo(type: Type): NativeRefType = when (type) { is Int8Type, is UInt8Type -> NativeRefType("Int8Box") is Int16Type, is UInt16Type -> NativeRefType("Int16Box") @@ -127,7 +153,7 @@ class StubGenerator( is IntPtrType, is UIntPtrType, // TODO: 64-bit specific is Int64Type, is UInt64Type -> NativeRefType("Int64Box") - is RecordType -> NativeRefType("${mangleStructName(type.kotlinName)}") + is RecordType -> NativeRefType("${type.kotlinName}") is EnumType -> NativeRefType("${type.kotlinName}.ref") @@ -153,27 +179,51 @@ class StubGenerator( else -> throw NotImplementedError() } + /** + * Describes how to represent in Kotlin the value to be passed to native code. + * + * @param kotlinType the name of Kotlin type to be used for this value in Kotlin. + * @param kotlinConv the function such that `kotlinConv(name)` converts variable `name` to pass it into JNI stub + * @param convFree the function such that `convFree(name)` is the statement that frees the result of conversion + * @param kotlinJniBridgeType the name of Kotlin type to be used for this value in JNI stub + */ class OutValueBinding(val kotlinType: String, val kotlinConv: ((String) -> String)? = null, val convFree: ((String) -> String)? = null, val kotlinJniBridgeType: String = kotlinType) + /** + * Describes how to represent in Kotlin the value to be received from native code. + * + * @param kotlinJniBridgeType the name of Kotlin type to be used for this value in JNI stub + * @param conv the function such that `conv(name)` converts variable `name` received from JNI stub to `kotlinType`. + * @param kotlinType the name of Kotlin type to be used for this value in Kotlin. + */ class InValueBinding(val kotlinJniBridgeType: String, val conv: ((String) -> String) = { it }, val kotlinType: String = kotlinJniBridgeType) + /** + * Constructs [OutValueBinding] for the value represented by given native reference type in Kotlin. + */ fun outValueRefBinding(refType: NativeRefType) = OutValueBinding( kotlinType = refType.typeName + "?", kotlinConv = { "$it.getNativePtr().asLong()" }, kotlinJniBridgeType = "Long" ) + /** + * Constructs [InValueBinding] for the value represented by given native reference type in Kotlin. + */ fun inValueRefBinding(refType: NativeRefType) = InValueBinding( kotlinJniBridgeType = "Long", conv = { "NativePtr.byValue($it).asRef(${refType.typeExpr})" }, kotlinType = refType.typeName + "?" ) + /** + * Constructs [OutValueBinding] for the value of given C type. + */ fun getOutValueBinding(type: Type): OutValueBinding = when (type) { is PrimitiveType -> OutValueBinding(type.kotlinType) @@ -218,6 +268,9 @@ class StubGenerator( else -> throw NotImplementedError() } + /** + * Constructs [InValueBinding] for the value of given C type. + */ fun getInValueBinding(type: Type): InValueBinding = when (type) { is PrimitiveType -> InValueBinding(type.kotlinType) @@ -257,7 +310,9 @@ class StubGenerator( else -> throw NotImplementedError() } - + /** + * Produces to [out] the definition of Kotlin class representing the reference to given struct. + */ private fun generateKotlinStruct(decl: StructDecl) { val def = decl.def if (def == null) { @@ -265,7 +320,7 @@ class StubGenerator( return } - val className = decl.mangledName + val className = decl.kotlinName out("class $className(ptr: NativePtr) : NativeStruct(ptr) {") indent { out("") @@ -286,13 +341,19 @@ class StubGenerator( out("}") } + /** + * Produces to [out] the definition of Kotlin class representing the reference to given forward (incomplete) struct. + */ private fun generateKotlinForwardStruct(s: StructDecl) { - val className = s.mangledName + val className = s.kotlinName out("class $className(ptr: NativePtr) : NativeRef(ptr) {") out(" companion object : Type<$className>(::$className)") out("}") } + /** + * Produces to [out] the definition of Kotlin class representing the value of given enum. + */ private fun generateKotlinEnum(e: EnumDef) { val baseRefType = getKotlinTypeForRefTo(e.baseType) @@ -318,8 +379,14 @@ class StubGenerator( out("}") } + /** + * Constructs [InValueBinding] for return value of Kotlin binding for given C function. + */ private fun retValBinding(func: FunctionDecl) = getInValueBinding(func.returnType) + /** + * Constructs [OutValueBinding]s for parameters of Kotlin binding for given C function. + */ private fun paramBindings(func: FunctionDecl): Array { val paramBindings = func.parameters.map { param -> getOutValueBinding(param.type) @@ -340,6 +407,9 @@ class StubGenerator( return paramBindings.toTypedArray() } + /** + * Returns names for parameters of Kotlin binding for given C function. + */ private fun paramNames(func: FunctionDecl): Array { val paramNames = func.parameters.mapIndexed { i: Int, parameter: Parameter -> val name = parameter.name @@ -357,6 +427,9 @@ class StubGenerator( return paramNames.toTypedArray() } + /** + * Produces to [out] the definition of Kotlin binding for given C function. + */ private fun generateKotlinBindingMethod(func: FunctionDecl) { val paramNames = paramNames(func) val paramBindings = paramBindings(func) @@ -395,6 +468,9 @@ class StubGenerator( out("}") } + /** + * Produces to [out] the definition of Kotlin JNI function used in binding for given C function. + */ private fun generateKotlinExternalMethod(func: FunctionDecl) { val paramNames = paramNames(func) val paramBindings = paramBindings(func) @@ -407,6 +483,9 @@ class StubGenerator( out("external fun ${func.name}($args): ${retValBinding.kotlinJniBridgeType}") } + /** + * Produces to [out] the contents of file with Kotlin bindings. + */ fun generateKotlinFile() { if (pkgName != "") { out("package $pkgName") @@ -459,6 +538,9 @@ class StubGenerator( out("}") } + /** + * Returns the C type to be used for value of given Kotlin type in JNI function implementation. + */ fun getCJniBridgeType(kotlinJniBridgeType: String) = when (kotlinJniBridgeType) { "Unit" -> "void" "Byte" -> "jbyte" @@ -468,6 +550,9 @@ class StubGenerator( else -> throw NotImplementedError(kotlinJniBridgeType) } + /** + * Produces to [out] the contents of C source file to be compiled into JNI lib used for Kotlin bindings impl. + */ fun generateCFile(headerFiles: List) { out("#include ") out("#include ") @@ -485,6 +570,9 @@ class StubGenerator( } } + /** + * Produces to [out] the implementation of JNI function used in Kotlin binding for given C function. + */ private fun generateCJniFunction(func: FunctionDecl) { val funcReturnType = func.returnType val paramNames = paramNames(func) diff --git a/experiments/buildSrc/src/main/groovy/org/jetbrains/kotlin/NativeInteropPlugin.groovy b/experiments/buildSrc/src/main/groovy/org/jetbrains/kotlin/NativeInteropPlugin.groovy index 8bf825b3b3a..da6e325b5d2 100644 --- a/experiments/buildSrc/src/main/groovy/org/jetbrains/kotlin/NativeInteropPlugin.groovy +++ b/experiments/buildSrc/src/main/groovy/org/jetbrains/kotlin/NativeInteropPlugin.groovy @@ -22,7 +22,9 @@ class NativeInteropPlugin implements Plugin { interopStubGenerator project(path: ":Interop:StubGenerator") } - prj.task("genInteropStubs", type: JavaExec) { + def genStubsTaskName = "genInteropStubs" + + prj.task(genStubsTaskName, type: JavaExec) { classpath = prj.configurations.interopStubGenerator main = "org.jetbrains.kotlin.native.interop.gen.jvm.MainKt" args = [srcDir, generatedSrcDir, nativeLibsDir] @@ -45,12 +47,12 @@ class NativeInteropPlugin implements Plugin { } prj.tasks.getByName("compileKotlin") { - dependsOn "genInteropStubs" + dependsOn genStubsTaskName } // FIXME: choose tasks more wisely prj.tasks.withType(JavaExec) { - if (name != "genInteropStubs") { + if (name != genStubsTaskName) { systemProperties "java.library.path": nativeLibsDir } }