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 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<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
@@ -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<String>) {
out("#include <stdint.h>")
out("#include <jni.h>")
if (platform == KotlinPlatform.JVM) {
out("#include <jni.h>")
}
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;")
@@ -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<String>, compilerOpts: List<String>): NativeIndex {
@@ -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<String> compilerOpts = []
private FileCollection headers;
private List<String> headers;
private String linker
private List<String> 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)
}
}