Interop: implement callbacks using libffi

This commit is contained in:
Svyatoslav Scherbina
2016-10-10 10:11:22 +03:00
parent ce10b5fb15
commit af09ce085c
6 changed files with 732 additions and 53 deletions
@@ -64,38 +64,40 @@ class FunctionDecl(val name: String, val parameters: List<Parameter>, val return
/**
* C type.
*/
open class Type
interface Type
open class PrimitiveType : Type()
interface PrimitiveType : Type
object VoidType : Type()
object VoidType : Type
object Int8Type : PrimitiveType()
object UInt8Type : PrimitiveType()
object Int8Type : PrimitiveType
object UInt8Type : PrimitiveType
object Int16Type : PrimitiveType()
object UInt16Type : PrimitiveType()
object Int16Type : PrimitiveType
object UInt16Type : PrimitiveType
object Int32Type : PrimitiveType()
object UInt32Type : PrimitiveType()
object Int32Type : PrimitiveType
object UInt32Type : PrimitiveType
object IntPtrType : PrimitiveType()
object UIntPtrType : PrimitiveType()
object IntPtrType : PrimitiveType
object UIntPtrType : PrimitiveType
object Int64Type : PrimitiveType()
object UInt64Type : PrimitiveType()
object Int64Type : PrimitiveType
object UInt64Type : PrimitiveType
class RecordType(val decl: StructDecl) : Type()
data class RecordType(val decl: StructDecl) : Type
class EnumType(val def: EnumDef) : Type()
data class EnumType(val def: EnumDef) : Type
class PointerType(val pointeeType : Type) : Type()
data class PointerType(val pointeeType : Type) : Type
class FunctionType(val parameterTypes: List<Type>, val returnType: Type) : Type()
data class FunctionType(val parameterTypes: List<Type>, val returnType: Type) : Type
open class ArrayType(val elemType: Type) : Type()
class ConstArrayType(elemType: Type, val length: Long) : ArrayType(elemType)
class IncompleteArrayType(elemType: Type) : ArrayType(elemType)
interface ArrayType : Type {
val elemType: Type
}
object UnsupportedType : Type()
data class ConstArrayType(override val elemType: Type, val length: Long) : ArrayType
data class IncompleteArrayType(override val elemType: Type) : ArrayType
object UnsupportedType : Type
+31
View File
@@ -9,6 +9,27 @@ buildscript {
}
apply plugin: 'kotlin'
apply plugin: 'c'
def javaHome = System.getProperty('java.home')
def compilerArgsForJniIncludes = ["", "linux", "darwin"].collect { "-I$javaHome/../include/$it" } as String[]
model {
components {
callbacks(NativeLibrarySpec) {
sources.c.source {
srcDir 'src/callbacks/c'
include '**/*.c'
}
binaries.all {
cCompiler.args compilerArgsForJniIncludes
cCompiler.args "-I$ffiIncludePath"
linker.args "$ffiLibPath/libffi.a"
}
}
}
}
repositories {
mavenCentral()
@@ -17,3 +38,13 @@ repositories {
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}
task nativelibs(type: Copy) {
dependsOn 'callbacksSharedLibrary'
from "$buildDir/libs/callbacks/shared/"
into "$buildDir/nativelibs/"
}
classes.dependsOn nativelibs
+233
View File
@@ -0,0 +1,233 @@
#include <stdlib.h>
#include <assert.h>
#include <jni.h>
#include <ffi.h>
/*
* Class: kotlin_native_interop_CallbacksKt
* Method: ffiTypeVoid
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlin_1native_interop_CallbacksKt_ffiTypeVoid(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_void;
}
/*
* Class: kotlin_native_interop_CallbacksKt
* Method: ffiTypeUInt8
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlin_1native_interop_CallbacksKt_ffiTypeUInt8(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_uint8;
}
/*
* Class: kotlin_native_interop_CallbacksKt
* Method: ffiTypeSInt8
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlin_1native_interop_CallbacksKt_ffiTypeSInt8(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_sint8;
}
/*
* Class: kotlin_native_interop_CallbacksKt
* Method: ffiTypeUInt16
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlin_1native_interop_CallbacksKt_ffiTypeUInt16(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_uint16;
}
/*
* Class: kotlin_native_interop_CallbacksKt
* Method: ffiTypeSInt16
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlin_1native_interop_CallbacksKt_ffiTypeSInt16(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_sint16;
}
/*
* Class: kotlin_native_interop_CallbacksKt
* Method: ffiTypeUInt32
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlin_1native_interop_CallbacksKt_ffiTypeUInt32(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_uint32;
}
/*
* Class: kotlin_native_interop_CallbacksKt
* Method: ffiTypeSInt32
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlin_1native_interop_CallbacksKt_ffiTypeSInt32(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_sint32;
}
/*
* Class: kotlin_native_interop_CallbacksKt
* Method: ffiTypeUInt64
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlin_1native_interop_CallbacksKt_ffiTypeUInt64(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_uint64;
}
/*
* Class: kotlin_native_interop_CallbacksKt
* Method: ffiTypeSInt64
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlin_1native_interop_CallbacksKt_ffiTypeSInt64(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_sint64;
}
/*
* Class: kotlin_native_interop_CallbacksKt
* Method: ffiTypePointer
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlin_1native_interop_CallbacksKt_ffiTypePointer(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_pointer;
}
/*
* Class: kotlin_native_interop_CallbacksKt
* Method: ffiTypeStruct0
* Signature: (J)J
*/
JNIEXPORT jlong JNICALL Java_kotlin_1native_interop_CallbacksKt_ffiTypeStruct0(JNIEnv *env, jclass cls, jlong elements) {
ffi_type* res = malloc(sizeof(ffi_type));
if (res != NULL) {
res->elements = (ffi_type**) elements;
res->type = FFI_TYPE_STRUCT;
}
return (jlong) res;
}
/*
* Class: kotlin_native_interop_CallbacksKt
* Method: ffiCreateCif0
* Signature: (IJJ)J
*/
JNIEXPORT jlong JNICALL Java_kotlin_1native_interop_CallbacksKt_ffiCreateCif0(JNIEnv *env, jclass cls, jint nArgs, jlong rType, jlong argTypes) {
ffi_cif* res = malloc(sizeof(ffi_cif));
if (res != NULL) {
ffi_status status = ffi_prep_cif(res, FFI_DEFAULT_ABI, nArgs, (ffi_type*)rType, (ffi_type**)argTypes);
if (status != FFI_OK) {
if (status == FFI_BAD_TYPEDEF) {
return -(jlong)1;
} else if (status == FFI_BAD_ABI) {
return -(jlong)2;
} else {
return -(jlong)3;
}
}
}
return (jlong) res;
}
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() {
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);
}
return env;
}
jint JNI_OnLoad(JavaVM *vm_, void *reserved) {
vm = vm_;
return JNI_VERSION_1_1;
}
// Checks for pending exception. If there is one, describes it and terminates the process.
static void checkException(JNIEnv *env) {
if ((*env)->ExceptionCheck(env)) {
(*env)->ExceptionDescribe(env);
abort();
}
}
static void ffi_fun(ffi_cif *cif, void *ret, void **args, void *user_data) {
JNIEnv* env = getCurrentEnv();
static jmethodID ffiFunImpl0 = NULL;
static jclass cls = NULL;
if (ffiFunImpl0 == NULL) {
cls = (*env)->FindClass(env, "kotlin_native/interop/CallbacksKt");
checkException(env);
assert(cls != NULL);
ffiFunImpl0 = (*env)->GetStaticMethodID(env, cls, "ffiFunImpl0", "(JJJLjava/lang/Object;)V");
checkException(env);
assert(ffiFunImpl0 != NULL);
}
(*env)->CallStaticVoidMethod(env, cls, ffiFunImpl0, (jlong) cif, (jlong) ret, (jlong) args, (jobject) user_data);
checkException(env);
}
/*
* Class: kotlin_native_interop_CallbacksKt
* Method: ffiCreateClosure0
* Signature: (JLjava/lang/Object;)J
*/
JNIEXPORT jlong JNICALL Java_kotlin_1native_interop_CallbacksKt_ffiCreateClosure0(JNIEnv *env, jclass cls, jlong ffiCif, jobject userData) {
jobject userDataGlobalRef = (*env)->NewGlobalRef(env, userData);
if (userDataGlobalRef == NULL) {
return (jlong)0;
}
assert(sizeof(jobject) == sizeof(void*)); // TODO: check statically
void* userDataPtr = (void*) userDataGlobalRef;
void* res;
ffi_closure *closure = ffi_closure_alloc(sizeof(ffi_closure), &res);
if (closure == NULL) {
return (jlong)0;
}
ffi_status status = ffi_prep_closure_loc(closure, (ffi_cif*)ffiCif, ffi_fun, userDataPtr, res);
if (status != FFI_OK) {
return -(jlong)1;
}
return (jlong) res;
}
/*
* Class: kotlin_native_interop_CallbacksKt
* Method: newGlobalRef
* Signature: (Ljava/lang/Object;)J
*/
JNIEXPORT jlong JNICALL Java_kotlin_1native_interop_CallbacksKt_newGlobalRef(JNIEnv *env, jclass cls, jobject obj) {
jobject res = (*env)->NewGlobalRef(env, obj);
return (jlong) res;
}
/*
* Class: kotlin_native_interop_CallbacksKt
* Method: derefGlobalRef
* Signature: (J)Ljava/lang/Object;
*/
JNIEXPORT jobject JNICALL Java_kotlin_1native_interop_CallbacksKt_derefGlobalRef(JNIEnv *env, jclass cls, jlong ref) {
return (jobject) ref;
}
/*
* Class: kotlin_native_interop_CallbacksKt
* Method: deleteGlobalRef
* Signature: (J)V
*/
JNIEXPORT void JNICALL Java_kotlin_1native_interop_CallbacksKt_deleteGlobalRef(JNIEnv *env, jclass cls, jlong ref) {
(*env)->DeleteGlobalRef(env, (jobject) ref);
}
@@ -0,0 +1,287 @@
package kotlin_native.interop
/**
* This class provides a way to create a stable handle to any Kotlin object.
* Its [value] can be safely passed to native code e.g. to be received in a Kotlin callback.
*
* Any [StableObjPtr] should be manually [disposed][dispose]
*/
data class StableObjPtr private constructor(val value: NativePtr) {
companion object {
/**
* Creates a handle for given object.
*/
fun create(any: Any) = StableObjPtr(NativePtr.byValue(newGlobalRef(any))!!)
/**
* Creates [StableObjPtr] from given raw value.
*
* @param value must be a [value] of some [StableObjPtr]
*/
fun fromValue(value: NativePtr) = StableObjPtr(value)
init {
loadCallbacksLibrary()
}
}
/**
* Disposes the handle. It must not be [used][get] after that.
*/
fun dispose() {
deleteGlobalRef(value.value)
}
/**
* Returns the object this handle was [created][create] for.
*/
fun get(): Any = derefGlobalRef(value.value)
}
/**
* Describes the type of native function.
*
* The instances of this class are supposed to be Kotlin objects (singletons),
* because creating the instance implies allocating some amount of non-freeable memory for the instance itself
* and for any unique Kotlin function "converted" to this type.
*
* Native function type definition consists in the following:
* - Definitions of native function's parameter and return types to be passed into the constructor
* - Implementation of [invoke] method which describes how to convert between these types and Kotlin types used in [F]
*
* @param F Kotlin function type corresponding to given native function type
*/
abstract class NativeFunctionType<F : Function<*>> protected constructor(returnType: CType, vararg paramTypes: CType) {
/**
* Returns a native function of this type, which calls given Kotlin *static* function.
*
* Given function must be *static*, i.e. an (unbound) reference to a Kotlin function or
* a closure which doesn't capture any variable
*/
fun fromStatic(function: F): NativePtr {
// TODO: optimize synchronization
synchronized(cache) {
return cache.getOrPut(function, { createFromStatic(function) })
}
}
/**
* Describes the C type of a function's parameter or return value.
* It is supposed to be constructed using the primitive types (such as [SInt32]) and the [Struct] combinator.
*
* This description omits the details that are irrelevant for the ABI.
*/
protected open class CType internal constructor(val ffiType: ffi_type) {
internal constructor(ffiTypePtr: Long) : this(NativePtr.byValue(ffiTypePtr).asRef(ffi_type)!!)
}
protected object Void : CType(ffiTypeVoid())
protected object UInt8 : CType(ffiTypeUInt8())
protected object SInt8 : CType(ffiTypeSInt8())
protected object UInt16 : CType(ffiTypeUInt16())
protected object SInt16 : CType(ffiTypeSInt16())
protected object UInt32 : CType(ffiTypeUInt32())
protected object SInt32 : CType(ffiTypeSInt32())
protected object UInt64 : CType(ffiTypeUInt64())
protected object SInt64 : CType(ffiTypeSInt64())
protected object Pointer : CType(ffiTypePointer())
protected class Struct(vararg elementTypes: CType) : CType(
ffiTypeStruct(
elementTypes.map { it.ffiType }
)
)
/**
* This method should invoke given Kotlin function.
*
* @param args array of pointers to arguments to be passed to [function]
* @param ret pointer to memory to be filled with return value of [function]
*/
protected abstract fun invoke(function: F, args: NativeArray<NativePtrBox>, ret: NativePtr)
companion object {
init {
loadCallbacksLibrary()
}
}
private val ffiCif = ffiCreateCif(returnType.ffiType, paramTypes.map { it.ffiType })
/**
* Allocates a native function of this type for given Kotlin function.
*/
private fun createFromStatic(function: F): NativePtr {
if (!isStatic(function)) {
throw IllegalArgumentException()
}
val impl = { ret: NativePtr, args: NativeArray<NativePtrBox> ->
invoke(function, args, ret)
}
return ffiCreateClosure(ffiCif, impl)
}
/**
* Returns `true` if given function is *static* as defined in [fromStatic].
*/
private fun isStatic(function: Function<*>): Boolean {
// TODO: revise
try {
with(function.javaClass.getDeclaredField("INSTANCE")) {
if (!java.lang.reflect.Modifier.isStatic(modifiers) || !java.lang.reflect.Modifier.isFinal(modifiers)) {
return false
}
isAccessible = true // TODO: undo
return get(null) == function
}
} catch (e: NoSuchFieldException) {
return false
}
}
private val cache = mutableMapOf<F, NativePtr>()
}
/**
* @see NativeFunctionType.fromStatic
*/
fun <F : Function<*>> F.staticAsNative(type: NativeFunctionType<F>) = type.fromStatic(this)
/**
* Describes a "struct" with native function pointer field.
*/
class NativeFunctionBox<F : Function<*>>(ptr: NativePtr, private val type: NativeFunctionType<F>) : NativeRef(ptr) {
/**
* Sets the function pointer field to null or native function calling given Kotlin function.
*/
fun setStatic(function: F?) {
val nativeFunPtr = function?.staticAsNative(type)
bridge.putPtr(ptr, nativeFunPtr)
}
}
val <F : Function<*>> NativeFunctionType<F>.ref: NativeRef.TypeWithSize<NativeFunctionBox<F>>
get() = NativeRef.TypeWithSize(8, { NativeFunctionBox(it, this) }) // TODO: 64-bit specific
private fun loadCallbacksLibrary() {
System.loadLibrary("callbacks")
}
/**
* Reference to `ffi_type` struct instance.
*/
internal class ffi_type (ptr: NativePtr) : NativeRef(ptr) {
companion object : Type<ffi_type>(::ffi_type)
}
/**
* Reference to `ffi_cif` struct instance.
*/
internal class ffi_cif (ptr: NativePtr) : NativeRef(ptr) {
companion object : Type<ffi_cif>(::ffi_cif)
}
private external fun ffiTypeVoid(): Long
private external fun ffiTypeUInt8(): Long
private external fun ffiTypeSInt8(): Long
private external fun ffiTypeUInt16(): Long
private external fun ffiTypeSInt16(): Long
private external fun ffiTypeUInt32(): Long
private external fun ffiTypeSInt32(): Long
private external fun ffiTypeUInt64(): Long
private external fun ffiTypeSInt64(): Long
private external fun ffiTypePointer(): Long
private external fun ffiTypeStruct0(elements: Long): Long
/**
* Allocates and initializes `ffi_type` describing the struct.
*
* @param elements types of the struct elements
*/
private fun ffiTypeStruct(elementTypes: List<ffi_type>): ffi_type {
val elements = mallocNativeArrayOf(ffi_type, *elementTypes.toTypedArray(), null).ptr
val res = ffiTypeStruct0(elements.value)
if (res == 0L) {
throw OutOfMemoryError()
}
return NativePtr.byValue(res).asRef(ffi_type)!!
}
private external fun ffiCreateCif0(nArgs: Int, rType: Long, argTypes: Long): Long
/**
* Creates and prepares an `ffi_cif`.
*
* @param returnType native function return value type
* @param paramTypes native function parameter types
*
* @return the initialized `ffi_cif`
*/
private fun ffiCreateCif(returnType: ffi_type, paramTypes: List<ffi_type>): ffi_cif {
val nArgs = paramTypes.size
val rType = returnType.ptr
val argTypes = mallocNativeArrayOf(ffi_type, *paramTypes.toTypedArray(), null).ptr
val res = ffiCreateCif0(nArgs, rType.value, argTypes.value)
when (res) {
0L -> throw OutOfMemoryError()
-1L -> throw Error("FFI_BAD_TYPEDEF")
-2L -> throw Error("FFI_BAD_ABI")
-3L -> throw Error("libffi error occurred")
}
return NativePtr.byValue(res).asRef(ffi_cif)!!
}
private fun ffiFunImpl0(ffiCif: Long, ret: Long, args: Long, userData: Any) {
ffiFunImpl(NativePtr.byValue(ffiCif).asRef(ffi_cif)!!,
NativePtr.byValue(ret)!!,
NativePtr.byValue(args).asRef(array(NativePtrBox))!!,
userData as (ret: NativePtr, args: NativeArray<NativePtrBox>) -> Unit)
}
/**
* This function is called from native code when a native function created with [ffiCreateClosure] is invoked.
*
* @param ret pointer to memory to be filled with return value of the invoked native function
* @param args pointer to array of pointers to arguments passed to the invoked native function
*/
private fun ffiFunImpl(ffiCif: ffi_cif, ret: NativePtr, args: NativeArray<NativePtrBox>,
userData: (NativePtr, NativeArray<NativePtrBox>) -> Unit) {
userData.invoke(ret, args)
}
private external fun ffiCreateClosure0(ffiCif: Long, userData: Any): Long
/**
* Uses libffi to allocate a native function which will call [ffiFunImpl] when invoked.
*
* @param ffiCif describes the type of the function to create
*/
private fun ffiCreateClosure(ffiCif: ffi_cif, userData: (NativePtr, NativeArray<NativePtrBox>) -> Unit): NativePtr {
val res = ffiCreateClosure0(ffiCif.ptr.value, userData)
when (res) {
0L -> throw OutOfMemoryError()
-1L -> throw Error("libffi error occurred")
}
return NativePtr.byValue(res)!!
}
private external fun newGlobalRef(any: Any): Long
private external fun derefGlobalRef(ref: Long): Any
private external fun deleteGlobalRef(ref: Long)
@@ -66,6 +66,15 @@ class StubGenerator(
val functionsToBind = nativeIndex.functions.filter { it.name !in excludedFunctions }
private val usedFunctionTypes = mutableMapOf<FunctionType, String>()
val FunctionType.kotlinName: String
get() {
return usedFunctionTypes.getOrPut(this, {
"NativeFunctionType" + (usedFunctionTypes.size + 1)
})
}
/**
* The output currently used by the generator.
* Should append line separator after any usage.
@@ -181,10 +190,13 @@ class StubGenerator(
}
is PointerType -> {
if (type.pointeeType is VoidType || type.pointeeType is FunctionType) {
val pointeeType = type.pointeeType
if (pointeeType is VoidType) {
NativeRefType("NativePtrBox")
} else if (pointeeType is FunctionType) {
NativeRefType("NativeFunctionBox<${getKotlinFunctionType(pointeeType)}>", "${pointeeType.kotlinName}.ref")
} else {
val pointeeRefType = getKotlinTypeForRefTo(type.pointeeType)
val pointeeRefType = getKotlinTypeForRefTo(pointeeType)
NativeRefType("RefBox<${pointeeRefType.typeName}>", "${pointeeRefType.typeExpr}.ref")
}
}
@@ -258,13 +270,6 @@ class StubGenerator(
kotlinConv = { "$it.asLong()" },
kotlinJniBridgeType = "Long"
)
} else if (type.pointeeType is Int8Type) {
OutValueBinding(
kotlinType = "String?",
kotlinConv = { name -> "CString.fromString($name).getNativePtr().asLong()" },
convFree = { name -> "free(NativePtr.byValue($name))" },
kotlinJniBridgeType = "Long"
)
} else {
outValueRefBinding(getKotlinTypeForRefTo(type.pointeeType))
}
@@ -282,17 +287,51 @@ class StubGenerator(
is ArrayType -> outValueRefBinding(getKotlinTypeForRefTo(type))
is RecordType -> {
val refType = getKotlinTypeForRefTo(type)
// pointer will be converted to value in C code
OutValueBinding(
kotlinType = refType.typeName,
kotlinConv = { "$it.getNativePtr().asLong()" },
kotlinJniBridgeType = "Long"
else -> throw NotImplementedError()
}
fun getCFunctionParamBinding(type: Type): OutValueBinding {
when (type) {
is PointerType -> {
val pointeeType = type.pointeeType
when (pointeeType) {
is FunctionType -> return OutValueBinding(
kotlinType = "(" + getKotlinFunctionType(pointeeType) + ")?",
kotlinConv = { "$it?.staticAsNative(${pointeeType.kotlinName}).asLong()" },
kotlinJniBridgeType = "Long"
)
is Int8Type -> return OutValueBinding(
kotlinType = "String?",
kotlinConv = { name -> "CString.fromString($name).getNativePtr().asLong()" },
convFree = { name -> "free(NativePtr.byValue($name))" },
kotlinJniBridgeType = "Long"
)
}
}
is RecordType -> {
val refType = getKotlinTypeForRefTo(type)
// pointer will be converted to value in C code
return OutValueBinding(
kotlinType = refType.typeName,
kotlinConv = { "$it.getNativePtr().asLong()" },
kotlinJniBridgeType = "Long"
)
}
}
return getOutValueBinding(type)
}
fun getCallbackRetValBinding(type: Type): OutValueBinding {
if (type is VoidType) {
return OutValueBinding(
kotlinType = "Unit",
kotlinConv = { throw UnsupportedOperationException() },
kotlinJniBridgeType = "void"
)
}
else -> throw NotImplementedError()
return getOutValueBinding(type)
}
/**
@@ -302,8 +341,6 @@ class StubGenerator(
is PrimitiveType -> InValueBinding(type.kotlinType)
is VoidType -> InValueBinding("Unit")
is PointerType -> {
if (type.pointeeType is VoidType || type.pointeeType is FunctionType) {
InValueBinding(
@@ -328,17 +365,35 @@ class StubGenerator(
is ArrayType -> inValueRefBinding(getKotlinTypeForRefTo(type))
is RecordType -> {
// TODO: valid only for return values
val refType = getKotlinTypeForRefTo(type)
InValueBinding(
kotlinJniBridgeType = "Long",
conv = { "NativePtr.byValue($it).asRef(${refType.typeExpr})!!" },
kotlinType = refType.typeName
)
else -> throw NotImplementedError()
}
fun getCFunctionRetValBinding(type: Type): InValueBinding {
when (type) {
is VoidType -> return InValueBinding("Unit")
is RecordType -> {
val refType = getKotlinTypeForRefTo(type)
InValueBinding(
kotlinJniBridgeType = "Long",
conv = { "NativePtr.byValue($it).asRef(${refType.typeExpr})!!" },
kotlinType = refType.typeName
)
}
}
else -> throw NotImplementedError()
return getInValueBinding(type)
}
fun getCallbackParamBinding(type: Type): InValueBinding {
return getInValueBinding(type)
}
private fun getKotlinFunctionType(type: FunctionType): String {
return "(" +
type.parameterTypes.map { getCallbackParamBinding(it).kotlinType }.joinToString(", ") +
") -> " +
getCallbackRetValBinding(type.returnType).kotlinType
}
/**
@@ -437,14 +492,14 @@ class StubGenerator(
/**
* Constructs [InValueBinding] for return value of Kotlin binding for given C function.
*/
private fun retValBinding(func: FunctionDecl) = getInValueBinding(func.returnType)
private fun retValBinding(func: FunctionDecl) = getCFunctionRetValBinding(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)
getCFunctionParamBinding(param.type)
}.toMutableList()
val retValType = func.returnType
@@ -523,6 +578,63 @@ class StubGenerator(
out("}")
}
private fun getFfiType(type: Type): String {
return when(type) {
is VoidType -> "Void"
is Int8Type -> "SInt8"
is UInt8Type -> "UInt8"
is Int16Type -> "SInt16"
is UInt16Type -> "UInt16"
is Int32Type -> "SInt32"
is UInt32Type -> "UInt32"
is IntPtrType, is UIntPtrType, // TODO
is PointerType, is ArrayType -> "Pointer"
is EnumType -> getFfiType(type.def.baseType)
// FIXME: check that alignment is natural
is RecordType -> "Struct(" + type.decl.def!!.fields.map {
getFfiType(it.type)
}.joinToString(", ") + ")"
else -> throw NotImplementedError(type.toString())
}
}
private fun generateFunctionType(type: FunctionType, name: String) {
val kotlinFunctionType = getKotlinFunctionType(type)
val constructorArgs = listOf(type.returnType, *type.parameterTypes.toTypedArray()).map {
getFfiType(it)
}.joinToString(", ")
out("object $name : NativeFunctionType<$kotlinFunctionType>($constructorArgs) {")
indent {
out("override fun invoke(function: $kotlinFunctionType, args: NativeArray<NativePtrBox>, ret: NativePtr) {")
indent {
val args = type.parameterTypes.mapIndexed { i, paramType ->
val refType = getKotlinTypeForRefTo(paramType)
val ref = "args[$i].value.asRef(${refType.typeExpr})!!"
when (paramType) {
is RecordType -> "$ref"
else -> "$ref.value"
}
}.joinToString(", ")
out("val res = function($args)")
when (type.returnType) {
is RecordType -> throw NotImplementedError()
is VoidType -> {} // nothing to do
else -> {
val retRefType = getKotlinTypeForRefTo(type.returnType)
out("${retRefType.typeExpr}.byPtr(ret).value = res")
}
}
}
out("}")
}
out("}")
}
/**
* Produces to [out] the definition of Kotlin JNI function used in binding for given C function.
*/
@@ -576,6 +688,11 @@ class StubGenerator(
out("")
}
usedFunctionTypes.entries.forEach {
generateFunctionType(it.key, it.value)
out("")
}
out("object externals {")
indent {
out("init { System.loadLibrary(\"$libName\") }")
@@ -8,6 +8,8 @@ class NativeInteropPlugin implements Plugin<Project> {
@Override
void apply(Project prj) {
def runtimeNativeLibsDir = new File(prj.findProject(':Interop:Runtime').buildDir, 'nativelibs')
def nativeLibsDir = new File(prj.buildDir, "nativelibs")
prj.configurations {
@@ -30,7 +32,11 @@ class NativeInteropPlugin implements Plugin<Project> {
main = "org.jetbrains.kotlin.native.interop.gen.jvm.MainKt"
jvmArgs '-ea'
systemProperties "java.library.path" : new File(prj.findProject(":Interop:Indexer").buildDir, "nativelibs")
systemProperties "java.library.path" : prj.files(
new File(prj.findProject(":Interop:Indexer").buildDir, "nativelibs"),
runtimeNativeLibsDir
).asPath
systemProperties "llvmInstallPath" : prj.llvmInstallPath
environment "LIBCLANG_DISABLE_CRASH_RECOVERY": "1"
environment "DYLD_LIBRARY_PATH": "${prj.llvmInstallPath}/lib"
@@ -60,7 +66,10 @@ class NativeInteropPlugin implements Plugin<Project> {
// FIXME: choose tasks more wisely
prj.tasks.withType(JavaExec) {
if (!name.endsWith("InteropStubs")) {
systemProperties "java.library.path": nativeLibsDir
systemProperties "java.library.path": prj.files(
nativeLibsDir,
runtimeNativeLibsDir
).asPath
}
}
}