diff --git a/Interop/Indexer/build.gradle b/Interop/Indexer/build.gradle index 6b47cbb0029..e5f942750fd 100644 --- a/Interop/Indexer/build.gradle +++ b/Interop/Indexer/build.gradle @@ -17,12 +17,6 @@ def compilerArgsForJniIncludes = ["", "linux", "darwin"].collect { "-I$javaHome/ model { components { - callback(NativeLibrarySpec) { - binaries.all { - cCompiler.args compilerArgsForJniIncludes - } - } - clangbridge(NativeLibrarySpec) { sources { c.source.srcDir 'prebuilt/nativeInteropStubs/c' @@ -59,9 +53,8 @@ dependencies { } task nativelibs(type: Copy) { - dependsOn 'callbackSharedLibrary', 'clangbridgeSharedLibrary' + dependsOn 'clangbridgeSharedLibrary' - from "$buildDir/libs/callback/shared/" from "$buildDir/libs/clangbridge/shared/" into "$buildDir/nativelibs/" } diff --git a/Interop/Indexer/prebuilt/nativeInteropStubs/kotlin/clang/Clang.kt b/Interop/Indexer/prebuilt/nativeInteropStubs/kotlin/clang/Clang.kt index bf5a9d085bb..49cde2d3db9 100644 --- a/Interop/Indexer/prebuilt/nativeInteropStubs/kotlin/clang/Clang.kt +++ b/Interop/Indexer/prebuilt/nativeInteropStubs/kotlin/clang/Clang.kt @@ -1256,9 +1256,9 @@ fun clang_getIBOutletCollectionType(arg0: CXCursor, retValPlacement: Placement): return NativePtr.byValue(res).asRef(CXType)!! } -fun clang_visitChildren(parent: CXCursor, visitor: NativePtr?, client_data: NativePtr?): Int { +fun clang_visitChildren(parent: CXCursor, visitor: ((CXCursor, CXCursor, NativePtr?) -> CXChildVisitResult)?, client_data: NativePtr?): Int { val _parent = parent.getNativePtr().asLong() - val _visitor = visitor.asLong() + val _visitor = visitor?.staticAsNative(NativeFunctionType1).asLong() val _client_data = client_data.asLong() val res = externals.clang_visitChildren(_parent, _visitor, _client_data) return res @@ -1636,8 +1636,8 @@ fun clang_enableStackTraces(): Unit { return res } -fun clang_executeOnThread(fn: NativePtr?, user_data: NativePtr?, stack_size: Int): Unit { - val _fn = fn.asLong() +fun clang_executeOnThread(fn: ((NativePtr?) -> Unit)?, user_data: NativePtr?, stack_size: Int): Unit { + val _fn = fn?.staticAsNative(NativeFunctionType2).asLong() val _user_data = user_data.asLong() val res = externals.clang_executeOnThread(_fn, _user_data, stack_size) return res @@ -1790,9 +1790,9 @@ fun clang_toggleCrashRecovery(isEnabled: Int): Unit { return res } -fun clang_getInclusions(tu: CXTranslationUnitImpl?, visitor: NativePtr?, client_data: NativePtr?): Unit { +fun clang_getInclusions(tu: CXTranslationUnitImpl?, visitor: ((NativePtr?, CXSourceLocation?, Int, NativePtr?) -> Unit)?, client_data: NativePtr?): Unit { val _tu = tu.getNativePtr().asLong() - val _visitor = visitor.asLong() + val _visitor = visitor?.staticAsNative(NativeFunctionType3).asLong() val _client_data = client_data.asLong() val res = externals.clang_getInclusions(_tu, _visitor, _client_data) return res @@ -1986,9 +1986,9 @@ fun clang_indexLoc_getCXSourceLocation(loc: CXIdxLoc, retValPlacement: Placement return NativePtr.byValue(res).asRef(CXSourceLocation)!! } -fun clang_Type_visitFields(T: CXType, visitor: NativePtr?, client_data: NativePtr?): Int { +fun clang_Type_visitFields(T: CXType, visitor: ((CXCursor, NativePtr?) -> CXVisitorResult)?, client_data: NativePtr?): Int { val _T = T.getNativePtr().asLong() - val _visitor = visitor.asLong() + val _visitor = visitor?.staticAsNative(NativeFunctionType4).asLong() val _client_data = client_data.asLong() val res = externals.clang_Type_visitFields(_T, _visitor, _client_data) return res @@ -2006,7 +2006,7 @@ class __darwin_pthread_handler_rec(ptr: NativePtr) : NativeStruct(ptr) { companion object : Type<__darwin_pthread_handler_rec>(24, ::__darwin_pthread_handler_rec) - val __routine by NativePtrBox at 0 + val __routine by NativeFunctionType2.ref at 0 val __arg by NativePtrBox at 8 val __next by __darwin_pthread_handler_rec.ref at 16 } @@ -2266,7 +2266,7 @@ class CXCursorAndRangeVisitor(ptr: NativePtr) : NativeStruct(ptr) { companion object : Type(16, ::CXCursorAndRangeVisitor) val context by NativePtrBox at 0 - val visit by NativePtrBox at 8 + val visit by NativeFunctionType5.ref at 8 } class CXIdxLoc(ptr: NativePtr) : NativeStruct(ptr) { @@ -2446,14 +2446,14 @@ class IndexerCallbacks(ptr: NativePtr) : NativeStruct(ptr) { companion object : Type(64, ::IndexerCallbacks) - val abortQuery by NativePtrBox at 0 - val diagnostic by NativePtrBox at 8 - val enteredMainFile by NativePtrBox at 16 - val ppIncludedFile by NativePtrBox at 24 - val importedASTFile by NativePtrBox at 32 - val startedTranslationUnit by NativePtrBox at 40 - val indexDeclaration by NativePtrBox at 48 - val indexEntityReference by NativePtrBox at 56 + val abortQuery by NativeFunctionType6.ref at 0 + val diagnostic by NativeFunctionType7.ref at 8 + val enteredMainFile by NativeFunctionType8.ref at 16 + val ppIncludedFile by NativeFunctionType9.ref at 24 + val importedASTFile by NativeFunctionType10.ref at 32 + val startedTranslationUnit by NativeFunctionType11.ref at 40 + val indexDeclaration by NativeFunctionType12.ref at 48 + val indexEntityReference by NativeFunctionType13.ref at 56 } enum class CXErrorCode(val value: Int) { @@ -3570,6 +3570,92 @@ enum class CXIndexOptFlags(val value: Int) { } } +object NativeFunctionType1 : NativeFunctionType<(CXCursor, CXCursor, NativePtr?) -> CXChildVisitResult>(UInt32, Struct(UInt32, SInt32, Pointer), Struct(UInt32, SInt32, Pointer), Pointer) { + override fun invoke(function: (CXCursor, CXCursor, NativePtr?) -> CXChildVisitResult, args: NativeArray, ret: NativePtr) { + val res = function(args[0].value.asRef(CXCursor)!!, args[1].value.asRef(CXCursor)!!, args[2].value.asRef(NativePtrBox)!!.value) + CXChildVisitResult.ref.byPtr(ret).value = res + } +} + +object NativeFunctionType2 : NativeFunctionType<(NativePtr?) -> Unit>(Void, Pointer) { + override fun invoke(function: (NativePtr?) -> Unit, args: NativeArray, ret: NativePtr) { + val res = function(args[0].value.asRef(NativePtrBox)!!.value) + } +} + +object NativeFunctionType3 : NativeFunctionType<(NativePtr?, CXSourceLocation?, Int, NativePtr?) -> Unit>(Void, Pointer, Pointer, UInt32, Pointer) { + override fun invoke(function: (NativePtr?, CXSourceLocation?, Int, NativePtr?) -> Unit, args: NativeArray, ret: NativePtr) { + val res = function(args[0].value.asRef(NativePtrBox)!!.value, args[1].value.asRef(CXSourceLocation.ref)!!.value, args[2].value.asRef(Int32Box)!!.value, args[3].value.asRef(NativePtrBox)!!.value) + } +} + +object NativeFunctionType4 : NativeFunctionType<(CXCursor, NativePtr?) -> CXVisitorResult>(UInt32, Struct(UInt32, SInt32, Pointer), Pointer) { + override fun invoke(function: (CXCursor, NativePtr?) -> CXVisitorResult, args: NativeArray, ret: NativePtr) { + val res = function(args[0].value.asRef(CXCursor)!!, args[1].value.asRef(NativePtrBox)!!.value) + CXVisitorResult.ref.byPtr(ret).value = res + } +} + +object NativeFunctionType5 : NativeFunctionType<(NativePtr?, CXCursor, CXSourceRange) -> CXVisitorResult>(UInt32, Pointer, Struct(UInt32, SInt32, Pointer), Struct(Pointer, UInt32, UInt32)) { + override fun invoke(function: (NativePtr?, CXCursor, CXSourceRange) -> CXVisitorResult, args: NativeArray, ret: NativePtr) { + val res = function(args[0].value.asRef(NativePtrBox)!!.value, args[1].value.asRef(CXCursor)!!, args[2].value.asRef(CXSourceRange)!!) + CXVisitorResult.ref.byPtr(ret).value = res + } +} + +object NativeFunctionType6 : NativeFunctionType<(NativePtr?, NativePtr?) -> Int>(SInt32, Pointer, Pointer) { + override fun invoke(function: (NativePtr?, NativePtr?) -> Int, args: NativeArray, ret: NativePtr) { + val res = function(args[0].value.asRef(NativePtrBox)!!.value, args[1].value.asRef(NativePtrBox)!!.value) + Int32Box.byPtr(ret).value = res + } +} + +object NativeFunctionType7 : NativeFunctionType<(NativePtr?, NativePtr?, NativePtr?) -> Unit>(Void, Pointer, Pointer, Pointer) { + override fun invoke(function: (NativePtr?, NativePtr?, NativePtr?) -> Unit, args: NativeArray, ret: NativePtr) { + val res = function(args[0].value.asRef(NativePtrBox)!!.value, args[1].value.asRef(NativePtrBox)!!.value, args[2].value.asRef(NativePtrBox)!!.value) + } +} + +object NativeFunctionType8 : NativeFunctionType<(NativePtr?, NativePtr?, NativePtr?) -> NativePtr?>(Pointer, Pointer, Pointer, Pointer) { + override fun invoke(function: (NativePtr?, NativePtr?, NativePtr?) -> NativePtr?, args: NativeArray, ret: NativePtr) { + val res = function(args[0].value.asRef(NativePtrBox)!!.value, args[1].value.asRef(NativePtrBox)!!.value, args[2].value.asRef(NativePtrBox)!!.value) + NativePtrBox.byPtr(ret).value = res + } +} + +object NativeFunctionType9 : NativeFunctionType<(NativePtr?, CXIdxIncludedFileInfo?) -> NativePtr?>(Pointer, Pointer, Pointer) { + override fun invoke(function: (NativePtr?, CXIdxIncludedFileInfo?) -> NativePtr?, args: NativeArray, ret: NativePtr) { + val res = function(args[0].value.asRef(NativePtrBox)!!.value, args[1].value.asRef(CXIdxIncludedFileInfo.ref)!!.value) + NativePtrBox.byPtr(ret).value = res + } +} + +object NativeFunctionType10 : NativeFunctionType<(NativePtr?, CXIdxImportedASTFileInfo?) -> NativePtr?>(Pointer, Pointer, Pointer) { + override fun invoke(function: (NativePtr?, CXIdxImportedASTFileInfo?) -> NativePtr?, args: NativeArray, ret: NativePtr) { + val res = function(args[0].value.asRef(NativePtrBox)!!.value, args[1].value.asRef(CXIdxImportedASTFileInfo.ref)!!.value) + NativePtrBox.byPtr(ret).value = res + } +} + +object NativeFunctionType11 : NativeFunctionType<(NativePtr?, NativePtr?) -> NativePtr?>(Pointer, Pointer, Pointer) { + override fun invoke(function: (NativePtr?, NativePtr?) -> NativePtr?, args: NativeArray, ret: NativePtr) { + val res = function(args[0].value.asRef(NativePtrBox)!!.value, args[1].value.asRef(NativePtrBox)!!.value) + NativePtrBox.byPtr(ret).value = res + } +} + +object NativeFunctionType12 : NativeFunctionType<(NativePtr?, CXIdxDeclInfo?) -> Unit>(Void, Pointer, Pointer) { + override fun invoke(function: (NativePtr?, CXIdxDeclInfo?) -> Unit, args: NativeArray, ret: NativePtr) { + val res = function(args[0].value.asRef(NativePtrBox)!!.value, args[1].value.asRef(CXIdxDeclInfo.ref)!!.value) + } +} + +object NativeFunctionType13 : NativeFunctionType<(NativePtr?, CXIdxEntityRefInfo?) -> Unit>(Void, Pointer, Pointer) { + override fun invoke(function: (NativePtr?, CXIdxEntityRefInfo?) -> Unit, args: NativeArray, ret: NativePtr) { + val res = function(args[0].value.asRef(NativePtrBox)!!.value, args[1].value.asRef(CXIdxEntityRefInfo.ref)!!.value) + } +} + object externals { init { System.loadLibrary("clangbridge") } external fun asctime(arg0: Long): Long diff --git a/Interop/Indexer/src/callback/c/callback.c b/Interop/Indexer/src/callback/c/callback.c deleted file mode 100644 index c9a521f96b0..00000000000 --- a/Interop/Indexer/src/callback/c/callback.c +++ /dev/null @@ -1,49 +0,0 @@ -#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() { - 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; -} - -// The implementation of native callback. -void indexDeclarationNativeCallback(void* arg1, void* arg2) { - JNIEnv* env = getCurrentEnv(); - - static jclass cls = NULL; - static jmethodID entryFromNative = NULL; - - 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); - } - - (*env)->CallStaticVoidMethod(env, cls, entryFromNative, (jlong) arg2); - if ((*env)->ExceptionCheck(env)) { - (*env)->ExceptionDescribe(env); - assert(0); - } -} - -JNIEXPORT jlong JNICALL Java_org_jetbrains_kotlin_native_interop_indexer_indexDeclarationCallback_nativeCallbackPtr(JNIEnv *env, jobject obj) { - return (jlong) indexDeclarationNativeCallback; -} diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Callbacks.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Callbacks.kt deleted file mode 100644 index 229ae4ef294..00000000000 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Callbacks.kt +++ /dev/null @@ -1,55 +0,0 @@ -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)!!)) - } - - private val nativeCallbackPtr: NativePtr - - init { - System.loadLibrary("callback") - nativeCallbackPtr = NativePtr.byValue(nativeCallbackPtr())!! - } - - /** - * Returns the pointer to native callback. - */ - private external fun nativeCallbackPtr(): Long -} \ No newline at end of file diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt index 362a8841e81..a974f1b5c11 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt @@ -10,7 +10,9 @@ private class StructDeclImpl(spelling: String) : StructDecl(spelling) { override var def: StructDefImpl? = null } -private class StructDefImpl(size: Long, decl: StructDecl) : StructDef(size, decl) { +private class StructDefImpl(size: Long, decl: StructDecl, hasNaturalLayout: Boolean) : + StructDef(size, decl, hasNaturalLayout) { + override val fields = mutableListOf() } @@ -92,6 +94,22 @@ private class NativeIndexImpl : NativeIndex() { return res } + /** + * Computes [StructDef.hasNaturalLayout] property. + */ + fun structHasNaturalLayout(structDefCursor: CXCursor): Boolean { + val hasAttributes = malloc(Int32Box) + hasAttributes.value = 0 + clang_visitChildren(structDefCursor, { cursor, parent, clientData -> + if (clang_isAttribute(cursor.kind.value) != 0) { + clientData.asRef(Int32Box)!!.value = 1 + } + CXChildVisitResult.CXChildVisit_Continue + }, hasAttributes.ptr) + + return hasAttributes.value == 0 + } + fun convertType(type: CXType): Type { val kind = type.kind.value return when (kind) { @@ -104,6 +122,7 @@ private class NativeIndexImpl : NativeIndex() { } } + // TODO: is e.g. CXType_Int guaranteed to be int32_t? CXType_Void -> VoidType CXType_Char_U, CXType_UChar -> UInt8Type @@ -182,7 +201,8 @@ private class NativeIndexImpl : NativeIndex() { val structDecl = getStructDeclAt(cursor) if (clang_isCursorDefinition(cursor) != 0) { val size = clang_Type_getSizeOf(clang_getCursorType(cursor, arena)) - structDecl.def = StructDefImpl(size, structDecl) + val hasNaturalLayout = structHasNaturalLayout(cursor) + structDecl.def = StructDefImpl(size, structDecl, hasNaturalLayout) } } @@ -241,28 +261,32 @@ fun buildNativeIndexImpl(headerFile: File, args: List): NativeIndex { val res = NativeIndexImpl() try { - val indexDeclarationNativeCallback = indexDeclarationCallback.setUp({ res.indexDeclaration(it) }) + val nativeIndexPtr = StableObjPtr.create(res) + val clientData = nativeIndexPtr.value try { with(callbacks) { - abortQuery.value = null - diagnostic.value = null - enteredMainFile.value = null - ppIncludedFile.value = null - importedASTFile.value = null - startedTranslationUnit.value = null - indexDeclaration.value = indexDeclarationNativeCallback - indexEntityReference.value = null + abortQuery.setStatic(null) + diagnostic.setStatic(null) + enteredMainFile.setStatic(null) + ppIncludedFile.setStatic(null) + importedASTFile.setStatic(null) + startedTranslationUnit.setStatic(null) + indexDeclaration.setStatic { clientData, info -> + val index = StableObjPtr.fromValue(clientData!!).get() as NativeIndexImpl + index.indexDeclaration(info!!) + } + indexEntityReference.setStatic(null) } - clang_indexSourceFile(indexAction, null, callbacks, IndexerCallbacks.Companion.size, 0, headerFile.path, + clang_indexSourceFile(indexAction, clientData, callbacks, IndexerCallbacks.size, 0, headerFile.path, mallocNativeArrayOf(Int8Box.Companion, *args1)[0], args1.size, null, 0, null, 0) return res } finally { - indexDeclarationCallback.reset() + nativeIndexPtr.dispose() } } finally { res.clearNativeMem() diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt index c41e5384ea1..6390ddd6d89 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt @@ -31,8 +31,11 @@ abstract class StructDecl(val spelling: String) { /** * C struct definition. + * + * @param hasNaturalLayout must be `false` if the struct has unnatural layout, e.g. it is `packed`. + * May be `false` even if the struct has natural layout. */ -abstract class StructDef(val size: Long, val decl: StructDecl) { +abstract class StructDef(val size: Long, val decl: StructDecl, val hasNaturalLayout: Boolean) { abstract val fields: List } @@ -64,38 +67,40 @@ class FunctionDecl(val name: String, val parameters: List, 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, val returnType: Type) : Type() +data class FunctionType(val parameterTypes: List, 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 \ No newline at end of file diff --git a/Interop/Runtime/build.gradle b/Interop/Runtime/build.gradle index 322cf9e4d63..06fd98a191d 100644 --- a/Interop/Runtime/build.gradle +++ b/Interop/Runtime/build.gradle @@ -9,6 +9,29 @@ 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" + + String libffiPicPath = "$ffiLibPath/libffi_pic.a" + linker.args file(libffiPicPath).exists() ? libffiPicPath : "$ffiLibPath/libffi.a" + } + } + } + +} repositories { mavenCentral() @@ -17,3 +40,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 diff --git a/Interop/Runtime/src/callbacks/c/callbacks.c b/Interop/Runtime/src/callbacks/c/callbacks.c new file mode 100644 index 00000000000..d4a5b30b9f7 --- /dev/null +++ b/Interop/Runtime/src/callbacks/c/callbacks.c @@ -0,0 +1,233 @@ +#include +#include +#include +#include + +/* + * 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); +} diff --git a/Interop/Runtime/src/main/kotlin/kotlin_native/interop/Callbacks.kt b/Interop/Runtime/src/main/kotlin/kotlin_native/interop/Callbacks.kt new file mode 100644 index 00000000000..15389fae432 --- /dev/null +++ b/Interop/Runtime/src/main/kotlin/kotlin_native/interop/Callbacks.kt @@ -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> 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, 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 -> + 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() +} + +/** + * @see NativeFunctionType.fromStatic + */ +fun > F.staticAsNative(type: NativeFunctionType) = type.fromStatic(this) + +/** + * Describes a "struct" with native function pointer field. + */ +class NativeFunctionBox>(ptr: NativePtr, private val type: NativeFunctionType) : 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 > NativeFunctionType.ref: NativeRef.TypeWithSize> + 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) +} + +/** + * Reference to `ffi_cif` struct instance. + */ +internal class ffi_cif (ptr: NativePtr) : NativeRef(ptr) { + companion object : Type(::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 { + 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_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) -> 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, + userData: (NativePtr, NativeArray) -> 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) -> 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) \ No newline at end of file diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt index 5dce6adaf20..72bce02dbfb 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt @@ -66,6 +66,15 @@ class StubGenerator( val functionsToBind = nativeIndex.functions.filter { it.name !in excludedFunctions } + private val usedFunctionTypes = mutableMapOf() + + 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 { val paramBindings = func.parameters.map { param -> - getOutValueBinding(param.type) + getCFunctionParamBinding(param.type) }.toMutableList() val retValType = func.returnType @@ -523,6 +578,68 @@ 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) + is RecordType -> { + val def = type.decl.def!! + if (!def.hasNaturalLayout) { + throw NotImplementedError() // TODO: represent pointer to function as NativePtr instead + } + "Struct(" + 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, 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 +693,11 @@ class StubGenerator( out("") } + usedFunctionTypes.entries.forEach { + generateFunctionType(it.key, it.value) + out("") + } + out("object externals {") indent { out("init { System.loadLibrary(\"$libName\") }") diff --git a/buildSrc/src/main/groovy/org/jetbrains/kotlin/NativeInteropPlugin.groovy b/buildSrc/src/main/groovy/org/jetbrains/kotlin/NativeInteropPlugin.groovy index 1d82682bfa7..88321f4c474 100644 --- a/buildSrc/src/main/groovy/org/jetbrains/kotlin/NativeInteropPlugin.groovy +++ b/buildSrc/src/main/groovy/org/jetbrains/kotlin/NativeInteropPlugin.groovy @@ -8,6 +8,8 @@ class NativeInteropPlugin implements Plugin { @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 { 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 { // 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 } } }