Refactor interop stub generation

Explicitly group stub lines related to the single declaration
This commit is contained in:
Svyatoslav Scherbina
2017-04-10 17:30:28 +03:00
committed by SvyatoslavScherbina
parent eadcd98740
commit 05dae83185
3 changed files with 152 additions and 124 deletions
@@ -143,7 +143,7 @@ internal fun List<String>.toNativeStringArray(placement: NativePlacement): CArra
}
}
internal val NativeLibrary.preambleLines: List<String>
val NativeLibrary.preambleLines: List<String>
get() = this.includes.map { "#include <$it>" }
internal fun Appendable.appendPreamble(library: NativeLibrary) = this.apply {
@@ -24,6 +24,8 @@ enum class KotlinPlatform {
NATIVE
}
private class StubsFragment(val kotlinStubLines: List<String>, val nativeStubLines: List<String> = emptyList())
// TODO: mostly rename 'jni' to 'c'.
class StubGenerator(
@@ -43,6 +45,12 @@ class StubGenerator(
val pkgName: String
get() = configuration.pkgName
private val jvmFileClassName = if (pkgName.isEmpty()) {
libName
} else {
pkgName.substringAfterLast('.')
}
val excludedFunctions: Set<String>
get() = configuration.excludedFunctions
@@ -173,12 +181,19 @@ class StubGenerator(
}
}
private fun <R> transaction(action: () -> R): R {
val lines = mutableListOf<String>()
val res = withOutput({ lines.add(it) }, action)
// if action is completed successfully:
lines.forEach(out)
return res
fun generateLinesBy(action: () -> Unit): List<String> {
val result = mutableListOf<String>()
withOutput({ result.add(it) }, action)
return result
}
fun <R> withOutput(appendable: Appendable, action: () -> R): R {
return withOutput({ appendable.appendln(it) }, action)
}
private fun generateKotlinFragmentBy(block: () -> Unit): StubsFragment {
val lines = generateLinesBy(block)
return StubsFragment(kotlinStubLines = lines)
}
private fun <R> indent(action: () -> R): R {
@@ -737,6 +752,22 @@ class StubGenerator(
}
}
private fun generateStubsForFunction(func: FunctionDecl): StubsFragment {
val kotlinStubLines = generateLinesBy {
generateKotlinBindingMethod(func)
if (func.requiresKotlinAdapter()) {
out("")
generateKotlinExternalMethod(func)
}
}
val nativeStubLines = generateLinesBy {
generateCJniFunction(func)
}
return StubsFragment(kotlinStubLines, nativeStubLines)
}
private fun FunctionDecl.generateAsFfiVarargs(): Boolean = (platform == KotlinPlatform.NATIVE && this.isVararg &&
// Neither takes nor returns structs by value:
!this.returnsRecord() && this.parameters.all { it.type.unwrapTypedefs() !is RecordType })
@@ -862,10 +893,7 @@ class StubGenerator(
arguments.add("alloc<${retValMirror.pointedTypeName}>().rawPtr")
}
val callee = when (platform) {
KotlinPlatform.JVM -> "externals.${func.name.asSimpleName()}"
KotlinPlatform.NATIVE -> func.kotlinExternalName
}
val callee = func.kotlinExternalName
out("val res = $callee(" + arguments.joinToString(", ") + ")")
val result = retValBinding.conv("res")
@@ -1144,7 +1172,6 @@ class StubGenerator(
private val FunctionDecl.kotlinExternalName: String
get() {
require(platform == KotlinPlatform.NATIVE)
require(this.requiresKotlinAdapter())
return "kni_$name"
}
@@ -1188,22 +1215,79 @@ class StubGenerator(
"${name.asSimpleName()}: " + paramBindings[i].kotlinJniBridgeType
}.joinToString(", ")
when (platform) {
KotlinPlatform.JVM -> {
out("external fun ${func.name.asSimpleName()}($args): ${retValBinding.kotlinJniBridgeType}")
}
KotlinPlatform.NATIVE -> {
out(func.symbolNameAnnotation)
out("private external fun ${func.kotlinExternalName}($args): ${retValBinding.kotlinJniBridgeType}")
}
else -> TODO(platform.toString())
if (platform == KotlinPlatform.NATIVE) {
out(func.symbolNameAnnotation)
}
out("private external fun ${func.kotlinExternalName}($args): ${retValBinding.kotlinJniBridgeType}")
}
private fun generateStubs(): List<StubsFragment> {
val stubs = mutableListOf<StubsFragment>()
functionsToBind.forEach {
try {
stubs.add(generateStubsForFunction(it))
} catch (e: Throwable) {
log("Warning: cannot generate stubs for function ${it.name}")
}
}
nativeIndex.macroConstants.forEach {
try {
stubs.add(
generateKotlinFragmentBy { generateConstant(it) }
)
} catch (e: Throwable) {
log("Warning: cannot generate stubs for constant ${it.name}")
}
}
nativeIndex.structs.forEach { s ->
try {
stubs.add(
generateKotlinFragmentBy { generateStruct(s) }
)
} catch (e: Throwable) {
log("Warning: cannot generate definition for struct ${s.kotlinName}")
}
}
nativeIndex.enums.forEach {
try {
stubs.add(
generateKotlinFragmentBy { generateEnum(it) }
)
} catch (e: Throwable) {
log("Warning: cannot generate definition for enum ${it.spelling}")
}
}
nativeIndex.typedefs.forEach { t ->
try {
stubs.add(
generateKotlinFragmentBy { generateTypedef(t) }
)
} catch (e: Throwable) {
log("Warning: cannot generate typedef ${t.name}")
}
}
usedFunctionTypes.entries.forEach {
stubs.add(
generateKotlinFragmentBy { generateFunctionType(it.key, it.value) }
)
}
return stubs
}
/**
* Produces to [out] the contents of file with Kotlin bindings.
*/
fun generateKotlinFile() {
private fun generateKotlinFile(stubs: List<StubsFragment>) {
if (platform == KotlinPlatform.JVM) {
out("@file:JvmName(${jvmFileClassName.quoteAsKotlinLiteral()})")
}
out("@file:Suppress(\"UNUSED_EXPRESSION\", \"UNUSED_VARIABLE\")")
if (pkgName != "") {
out("package $pkgName")
@@ -1215,83 +1299,13 @@ class StubGenerator(
out("import kotlinx.cinterop.*")
out("")
functionsToBind.forEach {
try {
transaction {
generateKotlinBindingMethod(it)
out("")
}
} catch (e: Throwable) {
log("Warning: cannot generate binding definition for function ${it.name}")
}
}
nativeIndex.macroConstants.forEach {
generateConstant(it)
}
out("")
nativeIndex.structs.forEach { s ->
try {
transaction {
generateStruct(s)
out("")
}
} catch (e: Throwable) {
log("Warning: cannot generate definition for struct ${s.kotlinName}")
}
}
nativeIndex.enums.forEach { e ->
generateEnum(e)
stubs.forEach {
it.kotlinStubLines.forEach { out(it) }
out("")
}
nativeIndex.typedefs.forEach { t ->
try {
transaction {
generateTypedef(t)
out("")
}
} catch (e: Throwable) {
log("Warning: cannot generate typedef ${t.name}")
}
}
usedFunctionTypes.entries.forEach {
generateFunctionType(it.key, it.value)
out("")
}
generateKotlinExternals()
}
private fun generateKotlinExternals() = when (platform) {
KotlinPlatform.JVM -> block("object externals") {
out("init { System.loadLibrary(\"$libName\") }")
functionsToBind.forEach {
try {
transaction {
generateKotlinExternalMethod(it)
out("")
}
} catch (e: Throwable) {
log("Warning: cannot generate external definition for function ${it.name}")
}
}
}
KotlinPlatform.NATIVE -> functionsToBind.forEach {
try {
transaction {
if (it.requiresKotlinAdapter()) {
generateKotlinExternalMethod(it)
out("")
}
}
} catch (e: Throwable) {
log("Warning: cannot generate external definition for function ${it.name}")
}
if (platform == KotlinPlatform.JVM) {
out("private val loadLibrary = System.loadLibrary(\"$libName\")")
}
}
@@ -1331,27 +1345,30 @@ class StubGenerator(
else -> throw NotImplementedError(kotlinJniBridgeType)
}
private val libraryForCStubs = configuration.library.copy(
includes = mutableListOf<String>().apply {
add("stdint.h")
if (platform == KotlinPlatform.JVM) {
add("jni.h")
}
addAll(configuration.library.includes)
}
)
/**
* Produces to [out] the contents of C source file to be compiled into JNI lib used for Kotlin bindings impl.
*/
fun generateCFile(headerFiles: List<String>, entryPoint: String?) {
out("#include <stdint.h>")
if (platform == KotlinPlatform.JVM) {
out("#include <jni.h>")
}
headerFiles.forEach {
out("#include <$it>")
private fun generateCFile(stubs: List<StubsFragment>, entryPoint: String?) {
libraryForCStubs.preambleLines.forEach {
out(it)
}
out("")
functionsToBind.forEach { func ->
try {
if (func.requiresCAdapter()) {
generateCJniFunction(func)
}
} catch (e: Throwable) {
log("Warning: cannot generate C JNI function definition ${func.name}")
stubs.forEach {
it.nativeStubLines.forEach {
out(it)
}
out("")
}
if (entryPoint != null) {
@@ -1399,13 +1416,18 @@ class StubGenerator(
""
}
val funcFullName = if (pkgName.isEmpty()) {
"externals.${func.name}"
} else {
"$pkgName.externals.${func.name}"
val funcFullName = buildString {
if (pkgName.isNotEmpty()) {
append(pkgName)
append('.')
}
append(jvmFileClassName)
append('.')
append(func.kotlinExternalName)
}
val functionName = "Java_" + funcFullName.replace("_", "_1").replace('.', '_').replace("$", "_00024")
"JNIEXPORT $cReturnType JNICALL $functionName (JNIEnv *jniEnv, jobject externalsObj$joinedParameters)"
"JNIEXPORT $cReturnType JNICALL $functionName (JNIEnv *jniEnv, jclass jclss$joinedParameters)"
}
KotlinPlatform.NATIVE -> {
val joinedParameters = parameters.joinToString(", ")
@@ -1442,4 +1464,16 @@ class StubGenerator(
}
}
}
fun generateFiles(ktFile: Appendable, cFile: Appendable, entryPoint: String?) {
val stubs = generateStubs()
withOutput(cFile) {
generateCFile(stubs, entryPoint)
}
withOutput(ktFile) {
generateKotlinFile(stubs)
}
}
}
@@ -344,19 +344,13 @@ private fun processLib(konanHome: String,
val gen = StubGenerator(nativeIndex, configuration, libName, generateShims, verbose, flavor)
outKtFile.parentFile.mkdirs()
outKtFile.bufferedWriter().use { out ->
gen.withOutput({ out.appendln(it) }) {
gen.generateKotlinFile()
}
}
File(nativeLibsDir).mkdirs()
val outCFile = File("$nativeLibsDir/$libName.c") // TODO: select the better location.
outCFile.bufferedWriter().use { out ->
gen.withOutput({ out.appendln(it) }) {
gen.generateCFile(headerFiles, entryPoint)
outKtFile.bufferedWriter().use { ktFile ->
outCFile.bufferedWriter().use { cFile ->
gen.generateFiles(ktFile = ktFile, cFile = cFile, entryPoint = entryPoint)
}
}