Interop: add minor code improvements
This commit is contained in:
@@ -1,31 +1,29 @@
|
||||
#include <pthread.h>
|
||||
#include <jni.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+21
@@ -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
|
||||
}
|
||||
+33
-6
@@ -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<String>): NativeIndex = buildNativeIndexImpl(headerFile, args)
|
||||
|
||||
/**
|
||||
* This class describes the IR of definitions from C header file(s).
|
||||
*/
|
||||
abstract class NativeIndex {
|
||||
abstract val structs: List<StructDecl>
|
||||
abstract val enums: List<EnumDef>
|
||||
abstract val functions: List<FunctionDecl>
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<Field>
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<EnumValue>
|
||||
}
|
||||
|
||||
/**
|
||||
* C function parameter.
|
||||
*/
|
||||
class Parameter(val name: String?, val type: Type)
|
||||
|
||||
/**
|
||||
* C function declaration.
|
||||
*/
|
||||
class FunctionDecl(val name: String, val parameters: List<Parameter>, val returnType: Type)
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* C type.
|
||||
*/
|
||||
open class Type
|
||||
|
||||
open class PrimitiveType : Type()
|
||||
|
||||
+102
-14
@@ -9,19 +9,32 @@ class StubGenerator(
|
||||
val libName: String,
|
||||
val excludedFunctions: Set<String>) {
|
||||
|
||||
/**
|
||||
* 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<OutValueBinding> {
|
||||
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<String> {
|
||||
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<String>) {
|
||||
out("#include <stdint.h>")
|
||||
out("#include <jni.h>")
|
||||
@@ -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)
|
||||
|
||||
+5
-3
@@ -22,7 +22,9 @@ class NativeInteropPlugin implements Plugin<Project> {
|
||||
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<Project> {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user