Interop: add minor code improvements

This commit is contained in:
Svyatoslav Scherbina
2016-09-23 16:05:22 +03:00
parent 6572f84b31
commit f9f418c51b
5 changed files with 172 additions and 36 deletions
@@ -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);
}
}
@@ -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
}
@@ -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()
@@ -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)
@@ -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
}
}