Interop/StubGenerator, NativeInteropPlugin: add support for Kotlin Native
Also add minor improvements.
This commit is contained in:
committed by
SvyatoslavScherbina
parent
4b694225c1
commit
cf3b95fb3d
+89
-22
@@ -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;")
|
||||
|
||||
+40
-22
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user