Interop/StubGenerator, NativeInteropPlugin: add support for Kotlin Native

Also add minor improvements.
This commit is contained in:
Svyatoslav Scherbina
2017-01-31 13:29:21 +07:00
committed by SvyatoslavScherbina
parent 4b694225c1
commit cf3b95fb3d
3 changed files with 167 additions and 64 deletions
@@ -3,12 +3,20 @@ package org.jetbrains.kotlin.native.interop.gen.jvm
import org.jetbrains.kotlin.native.interop.indexer.* import org.jetbrains.kotlin.native.interop.indexer.*
import java.lang.IllegalStateException import java.lang.IllegalStateException
enum class KotlinPlatform {
JVM,
NATIVE
}
// TODO: mostly rename 'jni' to 'c'.
class StubGenerator( class StubGenerator(
val nativeIndex: NativeIndex, val nativeIndex: NativeIndex,
val pkgName: String, val pkgName: String,
val libName: String, val libName: String,
val excludedFunctions: Set<String>, val excludedFunctions: Set<String>,
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 * 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 fun argFromJni(name: String) = "CPointer.createNullable<$pointee>($name)"
override val jniType: String override val jniType: String
get() = "Long" get() = "NativePtr"
override fun constructPointedType(valueType: String) = "CPointerVarWithValueMappedTo<$valueType>" override fun constructPointedType(valueType: String) = "CPointerVarWithValueMappedTo<$valueType>"
} }
@@ -264,7 +272,7 @@ class StubGenerator(
override fun argFromJni(name: String) = "interpretPointed<$pointed>($name)" override fun argFromJni(name: String) = "interpretPointed<$pointed>($name)"
override val jniType: String override val jniType: String
get() = "Long" get() = "NativePtr"
override fun constructPointedType(valueType: String): String { override fun constructPointedType(valueType: String): String {
// TODO: this method must not exist // TODO: this method must not exist
@@ -390,7 +398,7 @@ class StubGenerator(
kotlinType = "String?", kotlinType = "String?",
kotlinConv = { name -> "$name?.toCString(memScope).rawPtr" }, kotlinConv = { name -> "$name?.toCString(memScope).rawPtr" },
memScoped = true, memScoped = true,
kotlinJniBridgeType = "Long" kotlinJniBridgeType = "NativePtr"
) )
} }
} }
@@ -573,7 +581,7 @@ class StubGenerator(
paramBindings.add(OutValueBinding( paramBindings.add(OutValueBinding(
kotlinType = "NativePlacement", kotlinType = "NativePlacement",
kotlinConv = { name -> "$name.alloc<${retValMirror.pointedTypeName}>().rawPtr" }, 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}" val header = "fun ${func.name}($args): ${retValBinding.kotlinType}"
fun generateBody() { fun generateBody(memScoped: Boolean) {
val externalParamNames = paramNames.mapIndexed { i: Int, name: String -> val externalParamNames = paramNames.mapIndexed { i: Int, name: String ->
val binding = paramBindings[i] val binding = paramBindings[i]
val externalParamName: String val externalParamName: String
@@ -631,8 +639,9 @@ class StubGenerator(
} }
out("val res = externals.${func.name}(" + externalParamNames.joinToString(", ") + ")") out("val res = externals.${func.name}(" + externalParamNames.joinToString(", ") + ")")
val result = retValBinding.conv("res")
if (dumpShims) { if (dumpShims) {
val returnValueRepresentation = retValBinding.conv("res") val returnValueRepresentation = result
out("print(\"\${${returnValueRepresentation}}\\t= \")") out("print(\"\${${returnValueRepresentation}}\\t= \")")
out("print(\"${func.name}( \")") out("print(\"${func.name}( \")")
val argsRepresentation = paramNames.map{"\${${it}}"}.joinToString(", ") val argsRepresentation = paramNames.map{"\${${it}}"}.joinToString(", ")
@@ -640,17 +649,21 @@ class StubGenerator(
out("println(\")\")") out("println(\")\")")
} }
out("return " + retValBinding.conv("res")) if (memScoped) {
out(result)
} else {
out("return " + result)
}
} }
block(header) { block(header) {
val memScoped = paramBindings.any { it.memScoped } val memScoped = paramBindings.any { it.memScoped }
if (memScoped) { if (memScoped) {
block("memScoped") { block("return memScoped") {
generateBody() generateBody(true)
} }
} else { } else {
generateBody() generateBody(false)
} }
} }
} }
@@ -696,6 +709,11 @@ class StubGenerator(
private fun generateFunctionType(type: FunctionType, name: String) { private fun generateFunctionType(type: FunctionType, name: String) {
val kotlinFunctionType = getKotlinFunctionType(type) val kotlinFunctionType = getKotlinFunctionType(type)
if (platform == KotlinPlatform.NATIVE) {
out("object $name : CFunctionType {}")
return
}
val constructorArgs = listOf(getRetValFfiType(type.returnType)) + val constructorArgs = listOf(getRetValFfiType(type.returnType)) +
type.parameterTypes.map { getArgFfiType(it) } 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. * 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 "$name: " + paramBindings[i].kotlinJniBridgeType
}.joinToString(", ") }.joinToString(", ")
if (platform == KotlinPlatform.NATIVE) {
out("@SymbolName(\"${func.stubSymbolName}\")")
}
out("external fun ${func.name}($args): ${retValBinding.kotlinJniBridgeType}") out("external fun ${func.name}($args): ${retValBinding.kotlinJniBridgeType}")
} }
@@ -797,7 +824,9 @@ class StubGenerator(
} }
block("object externals") { block("object externals") {
out("init { System.loadLibrary(\"$libName\") }") if (platform == KotlinPlatform.JVM) {
out("init { System.loadLibrary(\"$libName\") }")
}
functionsToBind.forEach { functionsToBind.forEach {
try { try {
transaction { 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. * 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" "Byte" -> "jbyte"
"Short" -> "jshort" "Short" -> "jshort"
"Int" -> "jint" "Int" -> "jint"
"Long" -> "jlong" "Long", "NativePtr" -> "jlong"
"Float" -> "jfloat"
"Double" -> "jdouble"
else -> throw NotImplementedError(kotlinJniBridgeType) else -> throw NotImplementedError(kotlinJniBridgeType)
} }
@@ -828,7 +881,9 @@ class StubGenerator(
*/ */
fun generateCFile(headerFiles: List<String>) { fun generateCFile(headerFiles: List<String>) {
out("#include <stdint.h>") out("#include <stdint.h>")
out("#include <jni.h>") if (platform == KotlinPlatform.JVM) {
out("#include <jni.h>")
}
headerFiles.forEach { headerFiles.forEach {
out("#include <$it>") out("#include <$it>")
} }
@@ -856,11 +911,11 @@ class StubGenerator(
if (paramBindings.isEmpty()) if (paramBindings.isEmpty())
"" ""
else paramBindings else paramBindings
.map { getCJniBridgeType(it.kotlinJniBridgeType) } .map { getCBridgeType(it.kotlinJniBridgeType) }
.mapIndexed { i, type -> "$type ${paramNames[i]}" } .mapIndexed { i, type -> "$type ${paramNames[i]}" }
.joinToString(separator = ", ", prefix = ", ") .joinToString(separator = ", ", prefix = ", ")
val cReturnType = getCJniBridgeType(retValBinding.kotlinJniBridgeType) val cReturnType = getCBridgeType(retValBinding.kotlinJniBridgeType)
val params = func.parameters.mapIndexed { i, parameter -> val params = func.parameters.mapIndexed { i, parameter ->
val cType = parameter.type.getStringRepresentation() val cType = parameter.type.getStringRepresentation()
@@ -872,14 +927,26 @@ class StubGenerator(
}.joinToString(", ") }.joinToString(", ")
val callExpr = "${func.name}($params)" val callExpr = "${func.name}($params)"
val funcFullName = if (pkgName.isEmpty()) { val jniFuncName = when (platform) {
"externals.${func.name}" KotlinPlatform.JVM -> {
} else { val funcFullName = if (pkgName.isEmpty()) {
"$pkgName.externals.${func.name}" "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") { if (cReturnType == "void") {
out("$callExpr;") out("$callExpr;")
@@ -99,6 +99,10 @@ private fun processLib(ktGenRoot: String,
val args = additionalArgs.groupBy ({ getArgPrefix(it)!! }, { dropPrefix(it)!! }) // TODO 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 defFile = args["-def"]?.single()?.let { File(it) }
val config = Properties() val config = Properties()
@@ -133,7 +137,7 @@ private fun processLib(ktGenRoot: String,
val nativeIndex = buildNativeIndex(headerFiles, compilerOpts) 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.parentFile.mkdirs()
outKtFile.bufferedWriter().use { out -> outKtFile.bufferedWriter().use { out ->
@@ -151,36 +155,50 @@ private fun processLib(ktGenRoot: String,
} }
} }
val outOFile = createTempFile(suffix = ".o") File(nativeLibsDir).mkdirs()
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)
val workDir = defFile?.parentFile ?: File(System.getProperty("java.io.tmpdir")) val workDir = defFile?.parentFile ?: File(System.getProperty("java.io.tmpdir"))
ProcessBuilder(*compilerCmd) if (platform == KotlinPlatform.JVM) {
.directory(workDir)
.inheritIO()
.runExpectingSuccess()
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, val compilerCmd = arrayOf("$llvmInstallPath/bin/$compiler", *compilerOpts.toTypedArray(),
"-Wl,-flat_namespace,-undefined,dynamic_lookup") *compilerArgsForJniIncludes,
"-c", outCFile.path, "-o", outOFile.path)
ProcessBuilder(*linkerCmd) ProcessBuilder(*compilerCmd)
.directory(workDir) .directory(workDir)
.inheritIO() .inheritIO()
.runExpectingSuccess() .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() outCFile.delete()
outOFile.delete()
} }
private fun buildNativeIndex(headerFiles: List<String>, compilerOpts: List<String>): NativeIndex { private fun buildNativeIndex(headerFiles: List<String>, compilerOpts: List<String>): NativeIndex {
@@ -18,15 +18,19 @@ class NamedNativeInteropConfig implements Named {
private final Project project private final Project project
final String name 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 defFile
private String pkg; private String pkg;
private List<String> compilerOpts = [] private List<String> compilerOpts = []
private FileCollection headers; private List<String> headers;
private String linker private String linker
private List<String> linkerOpts = [] private List<String> linkerOpts = []
private FileCollection linkFiles; private FileCollection linkFiles;
@@ -34,6 +38,10 @@ class NamedNativeInteropConfig implements Named {
Configuration configuration Configuration configuration
void target(String value) {
target = value
}
void defFile(String value) { void defFile(String value) {
defFile = value defFile = value
genTask.inputs.file(project.file(defFile)) genTask.inputs.file(project.file(defFile))
@@ -49,7 +57,11 @@ class NamedNativeInteropConfig implements Named {
void headers(FileCollection files) { void headers(FileCollection files) {
dependsOnFiles(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) { void linker(String value) {
@@ -107,11 +119,11 @@ class NamedNativeInteropConfig implements Named {
compilerOpts.addAll(values.collect {"-I$it"}) compilerOpts.addAll(values.collect {"-I$it"})
} }
private File getNativeLibsDir() { File getNativeLibsDir() {
return new File(project.buildDir, "nativelibs") return new File(project.buildDir, "nativelibs")
} }
private File getGeneratedSrcDir() { File getGeneratedSrcDir() {
return new File(project.buildDir, "nativeInteropStubs/$name/kotlin") return new File(project.buildDir, "nativeInteropStubs/$name/kotlin")
} }
@@ -119,25 +131,31 @@ class NamedNativeInteropConfig implements Named {
this.name = name this.name = name
this.project = project this.project = project
this.headers = project.files() this.headers = []
this.linkFiles = project.files() this.linkFiles = project.files()
interopStubs = project.sourceSets.create(name + "InteropStubs") interopStubsName = name + "InteropStubs"
genTask = project.task(interopStubs.getTaskName("gen", ""), type: JavaExec) genTask = project.task("gen" + interopStubsName.capitalize(), type: JavaExec)
configuration = project.configurations.create(interopStubs.name)
this.configure() this.configure()
} }
private void configure() { private void configure() {
project.tasks.getByName(interopStubs.getTaskName("compile", "Kotlin")) { if (project.plugins.hasPlugin("kotlin")) {
dependsOn genTask 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 { project.dependencies {
add interopStubs.getCompileConfigurationName(), project(path: ':Interop:Runtime') add interopStubs.getCompileConfigurationName(), project(path: ':Interop:Runtime')
}
this.configuration.extendsFrom project.configurations[interopStubs.runtimeConfigurationName]
project.dependencies.add(this.configuration.name, interopStubs.output)
} }
genTask.configure { genTask.configure {
@@ -166,6 +184,9 @@ class NamedNativeInteropConfig implements Named {
linkerOpts += linkFiles.files linkerOpts += linkFiles.files
args = [generatedSrcDir, nativeLibsDir] args = [generatedSrcDir, nativeLibsDir]
args "-target:" + this.target
if (defFile != null) { if (defFile != null) {
args "-def:" + project.file(defFile) args "-def:" + project.file(defFile)
} }
@@ -188,7 +209,7 @@ class NamedNativeInteropConfig implements Named {
args compilerOpts.collect { "-copt:$it" } args compilerOpts.collect { "-copt:$it" }
args linkerOpts.collect { "-lopt:$it" } args linkerOpts.collect { "-lopt:$it" }
headers.files.each { headers.each {
args "-h:$it" 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)
} }
} }