Interop: generate fewer adapters when targeting Kotlin Native

Also do some refactoring.
This commit is contained in:
Svyatoslav Scherbina
2017-03-13 12:17:54 +07:00
committed by SvyatoslavScherbina
parent a78f38d26c
commit 37b7940544
3 changed files with 170 additions and 56 deletions
@@ -19,7 +19,7 @@ private class EnumDefImpl(spelling: String, type: PrimitiveType) : EnumDef(spell
override val values = mutableListOf<EnumValue>() override val values = mutableListOf<EnumValue>()
} }
internal class NativeIndexImpl : NativeIndex() { internal class NativeIndexImpl(val language: Language) : NativeIndex() {
private data class DeclarationID(val usr: String) private data class DeclarationID(val usr: String)
@@ -260,7 +260,11 @@ internal class NativeIndexImpl : NativeIndex() {
Parameter(argName, type) Parameter(argName, type)
} }
functionByName[name] = FunctionDecl(name, args, returnType) val binaryName = when (language) {
Language.C -> clang_Cursor_getMangling(cursor, memScope).convertAndDispose()
}
functionByName[name] = FunctionDecl(name, args, returnType, binaryName)
} }
CXIdxEntity_Enum -> { CXIdxEntity_Enum -> {
@@ -288,7 +292,7 @@ internal class NativeIndexImpl : NativeIndex() {
} }
fun buildNativeIndexImpl(library: NativeLibrary): NativeIndex { fun buildNativeIndexImpl(library: NativeLibrary): NativeIndex {
val result = NativeIndexImpl() val result = NativeIndexImpl(library.language)
indexDeclarations(library, result) indexDeclarations(library, result)
findMacroConstants(library, result) findMacroConstants(library, result)
return result return result
@@ -69,7 +69,7 @@ class Parameter(val name: String?, val type: Type)
/** /**
* C function declaration. * C function declaration.
*/ */
class FunctionDecl(val name: String, val parameters: List<Parameter>, val returnType: Type) class FunctionDecl(val name: String, val parameters: List<Parameter>, val returnType: Type, val binaryName: String)
/** /**
* C typedef definition. * C typedef definition.
@@ -421,22 +421,20 @@ class StubGenerator(
} }
fun getCFunctionParamBinding(type: Type): OutValueBinding { fun getCFunctionParamBinding(type: Type): OutValueBinding {
when (type) { if (treatCFunctionParameterAsString(type)) {
is PointerType -> { return OutValueBinding(
when (type.pointeeType) { kotlinType = "String?",
is Int8Type -> return OutValueBinding( kotlinConv = { name -> "$name?.toCString(memScope).rawPtr" },
kotlinType = "String?", memScoped = true,
kotlinConv = { name -> "$name?.toCString(memScope).rawPtr" }, kotlinJniBridgeType = "NativePtr"
memScoped = true, )
kotlinJniBridgeType = "NativePtr"
)
}
}
} }
return getOutValueBinding(type) return getOutValueBinding(type)
} }
private fun treatCFunctionParameterAsString(type: Type) = type is PointerType && type.pointeeType is Int8Type
fun getCallbackRetValBinding(type: Type): OutValueBinding { fun getCallbackRetValBinding(type: Type): OutValueBinding {
if (type.unwrapTypedefs() is VoidType) { if (type.unwrapTypedefs() is VoidType) {
return OutValueBinding( return OutValueBinding(
@@ -657,8 +655,15 @@ class StubGenerator(
val header = "fun ${func.name}($args): ${retValBinding.kotlinType}" val header = "fun ${func.name}($args): ${retValBinding.kotlinType}"
if (!func.requiresKotlinAdapter()) {
assert(platform == KotlinPlatform.NATIVE)
out(func.symbolNameAnnotation)
out("external $header")
return
}
fun generateBody(memScoped: Boolean) { fun generateBody(memScoped: Boolean) {
val externalParamNames = paramNames.mapIndexed { i: Int, name: String -> val arguments = paramNames.mapIndexed { i: Int, name: String ->
val binding = paramBindings[i] val binding = paramBindings[i]
val externalParamName: String val externalParamName: String
@@ -672,7 +677,11 @@ class StubGenerator(
externalParamName externalParamName
} }
out("val res = externals.${func.name}(" + externalParamNames.joinToString(", ") + ")") val callee = when (platform) {
KotlinPlatform.JVM -> "externals.${func.name}"
KotlinPlatform.NATIVE -> func.kotlinExternalName
}
out("val res = $callee(" + arguments.joinToString(", ") + ")")
val result = retValBinding.conv("res") val result = retValBinding.conv("res")
if (dumpShims) { if (dumpShims) {
@@ -703,6 +712,28 @@ class StubGenerator(
} }
} }
/**
* Returns the expression to be parsed by Kotlin as string literal with given contents,
* i.e. transforms `foo$bar` to `"foo\$bar"`.
*/
private fun String.quoteAsKotlinLiteral(): String {
val sb = StringBuilder()
sb.append('"')
this.forEach { c ->
val escaped = when (c) {
in 'a' .. 'z', in 'A' .. 'Z', in '0' .. '9', '_' -> c.toString()
'$' -> "\\$"
// TODO: improve result readability by preserving more characters.
else -> "\\u" + "%04X".format(c.toInt())
}
sb.append(escaped)
}
sb.append('"')
return sb.toString()
}
private fun getFfiStructType(elementTypes: List<Type>) = private fun getFfiStructType(elementTypes: List<Type>) =
"Struct(" + "Struct(" +
elementTypes.map { getFfiType(it) }.joinToString(", ") + elementTypes.map { getFfiType(it) }.joinToString(", ") +
@@ -747,13 +778,39 @@ class StubGenerator(
KotlinPlatform.NATIVE -> generateNativeFunctionType(type, name) KotlinPlatform.NATIVE -> generateNativeFunctionType(type, name)
} }
private fun requiresAdapterOnNative(type: FunctionType): Boolean { /**
* Returns `true` iff the function binding for Kotlin Native
* requires non-trivial Kotlin adapter to convert arguments.
*/
private fun FunctionDecl.requiresKotlinAdapter(): Boolean {
if (platform != KotlinPlatform.NATIVE) {
return true
}
return this.parameters.any { treatCFunctionParameterAsString(it.type) } ||
this.returnType.unwrapTypedefs() is RecordType
}
/**
* Returns `true` iff the function binding for Kotlin Native
* requires non-trivial C adapter to convert arguments.
*/
private fun FunctionDecl.requiresCAdapter(): Boolean {
if (platform != KotlinPlatform.NATIVE) {
return true
}
val parameterTypes = this.parameters.map { it.type }
return (parameterTypes + returnType).any { it.unwrapTypedefs() is RecordType }
}
private fun FunctionType.requiresAdapterOnNative(): Boolean {
assert (platform == KotlinPlatform.NATIVE) assert (platform == KotlinPlatform.NATIVE)
return (type.parameterTypes + type.returnType).any { it.unwrapTypedefs() is RecordType } return (parameterTypes + returnType).any { it.unwrapTypedefs() is RecordType }
} }
private fun generateNativeFunctionType(type: FunctionType, name: String) { private fun generateNativeFunctionType(type: FunctionType, name: String) {
if (requiresAdapterOnNative(type)) { if (type.requiresAdapterOnNative()) {
out("object $name : CFunctionType {}") out("object $name : CFunctionType {}")
} else { } else {
val kotlinFunctionType = getKotlinFunctionType(type) val kotlinFunctionType = getKotlinFunctionType(type)
@@ -853,12 +910,41 @@ class StubGenerator(
out("val ${constant.name}: $kotlinType = $literal") out("val ${constant.name}: $kotlinType = $literal")
} }
private val FunctionDecl.stubSymbolName: String /**
* The name of C adapter to be used for the function binding for Kotlin Native.
*/
private val FunctionDecl.cStubName: String
get() { get() {
require(platform == KotlinPlatform.NATIVE) require(platform == KotlinPlatform.NATIVE)
return "kni_" + pkgName.replace('/', '_') + '_' + this.name return "kni_" + pkgName.replace('/', '_') + '_' + this.name
} }
private val FunctionDecl.kotlinExternalName: String
get() {
require(platform == KotlinPlatform.NATIVE)
require(this.requiresKotlinAdapter())
return "kni_$name"
}
/**
* The annotation for an external function on Kotlin Native
* to bind it to either the C function itself or the C adapter.
*/
private val FunctionDecl.symbolNameAnnotation: String
get() {
require(platform == KotlinPlatform.NATIVE)
val symbolName = if (requiresCAdapter()) {
cStubName
} else {
// `@SymbolName` annotation on Kotlin Native defines the LLVM function name
// which is assumed to match the C function name.
// However on some platforms (e.g. macOS) C function names are being mangled.
// Using `0x01` prefix prevents LLVM from mangling the name.
"\u0001${binaryName}"
}
return "@SymbolName(${symbolName.quoteAsKotlinLiteral()})"
}
/** /**
* 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.
*/ */
@@ -871,10 +957,16 @@ class StubGenerator(
"$name: " + paramBindings[i].kotlinJniBridgeType "$name: " + paramBindings[i].kotlinJniBridgeType
}.joinToString(", ") }.joinToString(", ")
if (platform == KotlinPlatform.NATIVE) { when (platform) {
out("@SymbolName(\"${func.stubSymbolName}\")") KotlinPlatform.JVM -> {
out("external fun ${func.name}($args): ${retValBinding.kotlinJniBridgeType}")
}
KotlinPlatform.NATIVE -> {
out(func.symbolNameAnnotation)
out("private external fun ${func.kotlinExternalName}($args): ${retValBinding.kotlinJniBridgeType}")
}
else -> TODO(platform.toString())
} }
out("external fun ${func.name}($args): ${retValBinding.kotlinJniBridgeType}")
} }
/** /**
@@ -936,10 +1028,12 @@ class StubGenerator(
out("") out("")
} }
block("object externals") { generateKotlinExternals()
if (platform == KotlinPlatform.JVM) { }
out("init { System.loadLibrary(\"$libName\") }")
} private fun generateKotlinExternals() = when (platform) {
KotlinPlatform.JVM -> block("object externals") {
out("init { System.loadLibrary(\"$libName\") }")
functionsToBind.forEach { functionsToBind.forEach {
try { try {
transaction { transaction {
@@ -951,6 +1045,19 @@ class StubGenerator(
} }
} }
} }
KotlinPlatform.NATIVE -> functionsToBind.forEach {
try {
transaction {
if (it.requiresKotlinAdapter()) {
generateKotlinExternalMethod(it)
out("")
}
}
} catch (e: Throwable) {
println("Warning: cannot generate external definition for function ${it.name}")
}
}
} }
/** /**
@@ -1004,7 +1111,9 @@ class StubGenerator(
functionsToBind.forEach { func -> functionsToBind.forEach { func ->
try { try {
generateCJniFunction(func) if (func.requiresCAdapter()) {
generateCJniFunction(func)
}
} catch (e: Throwable) { } catch (e: Throwable) {
System.err.println("Warning: cannot generate C JNI function definition ${func.name}") System.err.println("Warning: cannot generate C JNI function definition ${func.name}")
} }
@@ -1026,17 +1135,36 @@ class StubGenerator(
val paramBindings = paramBindings(func) val paramBindings = paramBindings(func)
val retValBinding = retValBinding(func) val retValBinding = retValBinding(func)
val args = val parameters = paramBindings
if (paramBindings.isEmpty()) .map { getCBridgeType(it.kotlinJniBridgeType) }
"" .mapIndexed { i, type -> "$type ${paramNames[i]}" }
else paramBindings
.map { getCBridgeType(it.kotlinJniBridgeType) }
.mapIndexed { i, type -> "$type ${paramNames[i]}" }
.joinToString(separator = ", ", prefix = ", ")
val cReturnType = getCBridgeType(retValBinding.kotlinJniBridgeType) val cReturnType = getCBridgeType(retValBinding.kotlinJniBridgeType)
val params = func.parameters.mapIndexed { i, parameter -> val funcDecl = when (platform) {
KotlinPlatform.JVM -> {
val joinedParameters = if (!parameters.isEmpty()) {
parameters.joinToString(separator = ", ", prefix = ", ")
} else {
""
}
val funcFullName = if (pkgName.isEmpty()) {
"externals.${func.name}"
} else {
"$pkgName.externals.${func.name}"
}
val functionName = "Java_" + funcFullName.replace("_", "_1").replace('.', '_').replace("$", "_00024")
"JNIEXPORT $cReturnType JNICALL $functionName (JNIEnv *env, jobject obj$joinedParameters)"
}
KotlinPlatform.NATIVE -> {
val joinedParameters = parameters.joinToString(", ")
val functionName = func.cStubName
"$cReturnType $functionName ($joinedParameters)"
}
}
val arguments = func.parameters.mapIndexed { i, parameter ->
val cType = parameter.type.getStringRepresentation() val cType = parameter.type.getStringRepresentation()
val name = paramNames[i] val name = paramNames[i]
val unwrappedType = parameter.type.unwrapTypedefs() val unwrappedType = parameter.type.unwrapTypedefs()
@@ -1050,25 +1178,7 @@ class StubGenerator(
} }
}.joinToString(", ") }.joinToString(", ")
val callExpr = "${func.name}($params)" val callExpr = "${func.name}($arguments)"
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 funcDecl = when (platform) {
KotlinPlatform.JVM -> "JNIEXPORT $cReturnType JNICALL $jniFuncName (JNIEnv *env, jobject obj$args)"
KotlinPlatform.NATIVE -> "$cReturnType $jniFuncName (void* externalsObj$args)"
}
block(funcDecl) { block(funcDecl) {