From cf3b95fb3d4fe2c3fb8370d0d9c21e4997e45e02 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Tue, 31 Jan 2017 13:29:21 +0700 Subject: [PATCH] Interop/StubGenerator, NativeInteropPlugin: add support for Kotlin Native Also add minor improvements. --- .../native/interop/gen/jvm/StubGenerator.kt | 111 ++++++++++++++---- .../kotlin/native/interop/gen/jvm/main.kt | 62 ++++++---- .../kotlin/NativeInteropPlugin.groovy | 58 +++++---- 3 files changed, 167 insertions(+), 64 deletions(-) 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 02b02fcddc9..8cbd4841acb 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 @@ -3,12 +3,20 @@ package org.jetbrains.kotlin.native.interop.gen.jvm import org.jetbrains.kotlin.native.interop.indexer.* import java.lang.IllegalStateException +enum class KotlinPlatform { + JVM, + NATIVE +} + +// TODO: mostly rename 'jni' to 'c'. + class StubGenerator( val nativeIndex: NativeIndex, val pkgName: String, val libName: String, val excludedFunctions: Set, - val dumpShims: Boolean) { + val dumpShims: Boolean, + val platform: KotlinPlatform = KotlinPlatform.JVM) { /** * The names that should not be used for struct classes to prevent name clashes @@ -253,7 +261,7 @@ class StubGenerator( override fun argFromJni(name: String) = "CPointer.createNullable<$pointee>($name)" override val jniType: String - get() = "Long" + get() = "NativePtr" override fun constructPointedType(valueType: String) = "CPointerVarWithValueMappedTo<$valueType>" } @@ -264,7 +272,7 @@ class StubGenerator( override fun argFromJni(name: String) = "interpretPointed<$pointed>($name)" override val jniType: String - get() = "Long" + get() = "NativePtr" override fun constructPointedType(valueType: String): String { // TODO: this method must not exist @@ -390,7 +398,7 @@ class StubGenerator( kotlinType = "String?", kotlinConv = { name -> "$name?.toCString(memScope).rawPtr" }, memScoped = true, - kotlinJniBridgeType = "Long" + kotlinJniBridgeType = "NativePtr" ) } } @@ -573,7 +581,7 @@ class StubGenerator( paramBindings.add(OutValueBinding( kotlinType = "NativePlacement", kotlinConv = { name -> "$name.alloc<${retValMirror.pointedTypeName}>().rawPtr" }, - kotlinJniBridgeType = "Long" + kotlinJniBridgeType = "NativePtr" )) } @@ -614,7 +622,7 @@ class StubGenerator( val header = "fun ${func.name}($args): ${retValBinding.kotlinType}" - fun generateBody() { + fun generateBody(memScoped: Boolean) { val externalParamNames = paramNames.mapIndexed { i: Int, name: String -> val binding = paramBindings[i] val externalParamName: String @@ -631,8 +639,9 @@ class StubGenerator( } out("val res = externals.${func.name}(" + externalParamNames.joinToString(", ") + ")") + val result = retValBinding.conv("res") if (dumpShims) { - val returnValueRepresentation = retValBinding.conv("res") + val returnValueRepresentation = result out("print(\"\${${returnValueRepresentation}}\\t= \")") out("print(\"${func.name}( \")") val argsRepresentation = paramNames.map{"\${${it}}"}.joinToString(", ") @@ -640,17 +649,21 @@ class StubGenerator( out("println(\")\")") } - out("return " + retValBinding.conv("res")) + if (memScoped) { + out(result) + } else { + out("return " + result) + } } block(header) { val memScoped = paramBindings.any { it.memScoped } if (memScoped) { - block("memScoped") { - generateBody() + block("return memScoped") { + generateBody(true) } } else { - generateBody() + generateBody(false) } } } @@ -696,6 +709,11 @@ class StubGenerator( private fun generateFunctionType(type: FunctionType, name: String) { val kotlinFunctionType = getKotlinFunctionType(type) + if (platform == KotlinPlatform.NATIVE) { + out("object $name : CFunctionType {}") + return + } + val constructorArgs = listOf(getRetValFfiType(type.returnType)) + type.parameterTypes.map { getArgFfiType(it) } @@ -727,6 +745,12 @@ class StubGenerator( } } + private val FunctionDecl.stubSymbolName: String + get() { + require(platform == KotlinPlatform.NATIVE) + return "kni_" + pkgName.replace('/', '_') + '_' + this.name + } + /** * Produces to [out] the definition of Kotlin JNI function used in binding for given C function. */ @@ -739,6 +763,9 @@ class StubGenerator( "$name: " + paramBindings[i].kotlinJniBridgeType }.joinToString(", ") + if (platform == KotlinPlatform.NATIVE) { + out("@SymbolName(\"${func.stubSymbolName}\")") + } out("external fun ${func.name}($args): ${retValBinding.kotlinJniBridgeType}") } @@ -797,7 +824,9 @@ class StubGenerator( } block("object externals") { - out("init { System.loadLibrary(\"$libName\") }") + if (platform == KotlinPlatform.JVM) { + out("init { System.loadLibrary(\"$libName\") }") + } functionsToBind.forEach { try { transaction { @@ -811,6 +840,28 @@ class StubGenerator( } } + /** + * Returns the C type to be used for value of given Kotlin type in JNI function implementation. + */ + fun getCBridgeType(kotlinJniBridgeType: String): String { + return when (platform) { + KotlinPlatform.JVM -> getCJniBridgeType(kotlinJniBridgeType) + KotlinPlatform.NATIVE -> getCNativeBridgeType(kotlinJniBridgeType) + } + } + + fun getCNativeBridgeType(kotlinJniBridgeType: String) = when (kotlinJniBridgeType) { + "Unit" -> "void" + "Byte" -> "int8_t" + "Short" -> "int16_t" + "Int" -> "int32_t" + "Long" -> "int64_t" + "Float" -> "float" + "Double" -> "double" // TODO: float32_t, float64_t? + "NativePtr" -> "void*" + else -> TODO(kotlinJniBridgeType) + } + /** * Returns the C type to be used for value of given Kotlin type in JNI function implementation. */ @@ -819,7 +870,9 @@ class StubGenerator( "Byte" -> "jbyte" "Short" -> "jshort" "Int" -> "jint" - "Long" -> "jlong" + "Long", "NativePtr" -> "jlong" + "Float" -> "jfloat" + "Double" -> "jdouble" else -> throw NotImplementedError(kotlinJniBridgeType) } @@ -828,7 +881,9 @@ class StubGenerator( */ fun generateCFile(headerFiles: List) { out("#include ") - out("#include ") + if (platform == KotlinPlatform.JVM) { + out("#include ") + } headerFiles.forEach { out("#include <$it>") } @@ -856,11 +911,11 @@ class StubGenerator( if (paramBindings.isEmpty()) "" else paramBindings - .map { getCJniBridgeType(it.kotlinJniBridgeType) } + .map { getCBridgeType(it.kotlinJniBridgeType) } .mapIndexed { i, type -> "$type ${paramNames[i]}" } .joinToString(separator = ", ", prefix = ", ") - val cReturnType = getCJniBridgeType(retValBinding.kotlinJniBridgeType) + val cReturnType = getCBridgeType(retValBinding.kotlinJniBridgeType) val params = func.parameters.mapIndexed { i, parameter -> val cType = parameter.type.getStringRepresentation() @@ -872,14 +927,26 @@ class StubGenerator( }.joinToString(", ") val callExpr = "${func.name}($params)" - val funcFullName = if (pkgName.isEmpty()) { - "externals.${func.name}" - } else { - "$pkgName.externals.${func.name}" + val jniFuncName = when (platform) { + KotlinPlatform.JVM -> { + val funcFullName = if (pkgName.isEmpty()) { + "externals.${func.name}" + } else { + "$pkgName.externals.${func.name}" + } + "Java_" + funcFullName.replace("_", "_1").replace('.', '_').replace("$", "_00024") + } + KotlinPlatform.NATIVE -> { + func.stubSymbolName + } } - val jniFuncName = "Java_" + funcFullName.replace("_", "_1").replace('.', '_').replace("$", "_00024") - block("JNIEXPORT $cReturnType JNICALL $jniFuncName (JNIEnv *env, jobject obj$args)") { + val funcDecl = when (platform) { + KotlinPlatform.JVM -> "JNIEXPORT $cReturnType JNICALL $jniFuncName (JNIEnv *env, jobject obj$args)" + KotlinPlatform.NATIVE -> "$cReturnType $jniFuncName (void* obj$args)" + } + + block(funcDecl) { if (cReturnType == "void") { out("$callExpr;") diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt index fb86c6ba379..8d327b953c7 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt @@ -99,6 +99,10 @@ private fun processLib(ktGenRoot: String, val args = additionalArgs.groupBy ({ getArgPrefix(it)!! }, { dropPrefix(it)!! }) // TODO + val platformName = args["-target"]?.single() ?: "jvm" + + val platform = KotlinPlatform.values().single { it.name.equals(platformName, ignoreCase = true) } + val defFile = args["-def"]?.single()?.let { File(it) } val config = Properties() @@ -133,7 +137,7 @@ private fun processLib(ktGenRoot: String, val nativeIndex = buildNativeIndex(headerFiles, compilerOpts) - val gen = StubGenerator(nativeIndex, outKtPkg, libName, excludedFunctions, generateShims) + val gen = StubGenerator(nativeIndex, outKtPkg, libName, excludedFunctions, generateShims, platform) outKtFile.parentFile.mkdirs() outKtFile.bufferedWriter().use { out -> @@ -151,36 +155,50 @@ private fun processLib(ktGenRoot: String, } } - val outOFile = createTempFile(suffix = ".o") - - val javaHome = System.getProperty("java.home") - val compilerArgsForJniIncludes = listOf("", "linux", "darwin").map { "-I$javaHome/../include/$it" }.toTypedArray() - - val compilerCmd = arrayOf("$llvmInstallPath/bin/$compiler", *compilerOpts.toTypedArray(), - *compilerArgsForJniIncludes, - "-c", outCFile.path, "-o", outOFile.path) + File(nativeLibsDir).mkdirs() val workDir = defFile?.parentFile ?: File(System.getProperty("java.io.tmpdir")) - ProcessBuilder(*compilerCmd) - .directory(workDir) - .inheritIO() - .runExpectingSuccess() + if (platform == KotlinPlatform.JVM) { - File(nativeLibsDir).mkdirs() + val outOFile = createTempFile(suffix = ".o") - val outLib = nativeLibsDir + "/" + System.mapLibraryName(libName) + val javaHome = System.getProperty("java.home") + val compilerArgsForJniIncludes = listOf("", "linux", "darwin").map { "-I$javaHome/../include/$it" }.toTypedArray() - val linkerCmd = arrayOf("$llvmInstallPath/bin/$linker", *linkerOpts, outOFile.path, "-shared", "-o", outLib, - "-Wl,-flat_namespace,-undefined,dynamic_lookup") + val compilerCmd = arrayOf("$llvmInstallPath/bin/$compiler", *compilerOpts.toTypedArray(), + *compilerArgsForJniIncludes, + "-c", outCFile.path, "-o", outOFile.path) - ProcessBuilder(*linkerCmd) - .directory(workDir) - .inheritIO() - .runExpectingSuccess() + ProcessBuilder(*compilerCmd) + .directory(workDir) + .inheritIO() + .runExpectingSuccess() + + val outLib = nativeLibsDir + "/" + System.mapLibraryName(libName) + + val linkerCmd = arrayOf("$llvmInstallPath/bin/$linker", *linkerOpts, outOFile.path, "-shared", "-o", outLib, + "-Wl,-flat_namespace,-undefined,dynamic_lookup") + + ProcessBuilder(*linkerCmd) + .directory(workDir) + .inheritIO() + .runExpectingSuccess() + + outOFile.delete() + } else if (platform == KotlinPlatform.NATIVE) { + val outBcName = libName + ".bc" + val outLib = nativeLibsDir + "/" + outBcName + val compilerCmd = arrayOf("$llvmInstallPath/bin/$compiler", *compilerOpts.toTypedArray(), + "-emit-llvm", "-c", outCFile.path, "-o", outLib) + + ProcessBuilder(*compilerCmd) + .directory(workDir) + .inheritIO() + .runExpectingSuccess() + } outCFile.delete() - outOFile.delete() } private fun buildNativeIndex(headerFiles: List, compilerOpts: List): NativeIndex { diff --git a/buildSrc/src/main/groovy/org/jetbrains/kotlin/NativeInteropPlugin.groovy b/buildSrc/src/main/groovy/org/jetbrains/kotlin/NativeInteropPlugin.groovy index 8a0d718cc9d..5e2bcd47ab0 100644 --- a/buildSrc/src/main/groovy/org/jetbrains/kotlin/NativeInteropPlugin.groovy +++ b/buildSrc/src/main/groovy/org/jetbrains/kotlin/NativeInteropPlugin.groovy @@ -18,15 +18,19 @@ class NamedNativeInteropConfig implements Named { private final Project project final String name - private final SourceSet interopStubs - private final JavaExec genTask + + private String interopStubsName + private SourceSet interopStubs + final JavaExec genTask + + private String target = "jvm" private String defFile private String pkg; private List compilerOpts = [] - private FileCollection headers; + private List headers; private String linker private List linkerOpts = [] private FileCollection linkFiles; @@ -34,6 +38,10 @@ class NamedNativeInteropConfig implements Named { Configuration configuration + void target(String value) { + target = value + } + void defFile(String value) { defFile = value genTask.inputs.file(project.file(defFile)) @@ -49,7 +57,11 @@ class NamedNativeInteropConfig implements Named { void headers(FileCollection files) { dependsOnFiles(files) - headers = headers + files + headers = headers + files.toSet().collect { it.absolutePath } + } + + void headers(String... values) { + headers = headers + values.toList() } void linker(String value) { @@ -107,11 +119,11 @@ class NamedNativeInteropConfig implements Named { compilerOpts.addAll(values.collect {"-I$it"}) } - private File getNativeLibsDir() { + File getNativeLibsDir() { return new File(project.buildDir, "nativelibs") } - private File getGeneratedSrcDir() { + File getGeneratedSrcDir() { return new File(project.buildDir, "nativeInteropStubs/$name/kotlin") } @@ -119,25 +131,31 @@ class NamedNativeInteropConfig implements Named { this.name = name this.project = project - this.headers = project.files() + this.headers = [] this.linkFiles = project.files() - interopStubs = project.sourceSets.create(name + "InteropStubs") - genTask = project.task(interopStubs.getTaskName("gen", ""), type: JavaExec) - configuration = project.configurations.create(interopStubs.name) + interopStubsName = name + "InteropStubs" + genTask = project.task("gen" + interopStubsName.capitalize(), type: JavaExec) this.configure() } private void configure() { - project.tasks.getByName(interopStubs.getTaskName("compile", "Kotlin")) { - dependsOn genTask - } + if (project.plugins.hasPlugin("kotlin")) { + interopStubs = project.sourceSets.create(interopStubsName) + configuration = project.configurations.create(interopStubs.name) + project.tasks.getByName(interopStubs.getTaskName("compile", "Kotlin")) { + dependsOn genTask + } - interopStubs.kotlin.srcDirs generatedSrcDir + interopStubs.kotlin.srcDirs generatedSrcDir - project.dependencies { - add interopStubs.getCompileConfigurationName(), project(path: ':Interop:Runtime') + project.dependencies { + add interopStubs.getCompileConfigurationName(), project(path: ':Interop:Runtime') + } + + this.configuration.extendsFrom project.configurations[interopStubs.runtimeConfigurationName] + project.dependencies.add(this.configuration.name, interopStubs.output) } genTask.configure { @@ -166,6 +184,9 @@ class NamedNativeInteropConfig implements Named { linkerOpts += linkFiles.files args = [generatedSrcDir, nativeLibsDir] + + args "-target:" + this.target + if (defFile != null) { args "-def:" + project.file(defFile) } @@ -188,7 +209,7 @@ class NamedNativeInteropConfig implements Named { args compilerOpts.collect { "-copt:$it" } args linkerOpts.collect { "-lopt:$it" } - headers.files.each { + headers.each { args "-h:$it" } @@ -198,9 +219,6 @@ class NamedNativeInteropConfig implements Named { } } - - this.configuration.extendsFrom project.configurations[interopStubs.runtimeConfigurationName] - project.dependencies.add(this.configuration.name, interopStubs.output) } }